0001"""
0002Declarative objects.
0003
0004Declarative objects have a simple protocol: you can use classes in
0005lieu of instances and they are equivalent, and any keyword arguments
0006you give to the constructor will override those instance variables.
0007(So if a class is received, we'll simply instantiate an instance with
0008no arguments).
0009
0010You can provide a variable __unpackargs__ (a list of strings), and if
0011the constructor is called with non-keyword arguments they will be
0012interpreted as the given keyword arguments.
0013
0014If __unpackargs__ is ('*', name), then all the arguments will be put
0015in a variable by that name.
0016
0017You can define a __classinit__(cls, new_attrs) method, which will be
0018called when the class is created (including subclasses). Note: you
0019can't use super() in __classinit__ because the class isn't bound to a
0020name. As an analog to __classinit__, Declarative adds
0021__instanceinit__ which is called with the same argument (new_attrs).
0022This is like __init__, but after __unpackargs__ and other factors have
0023been taken into account.
0024
0025If __mutableattributes__ is defined as a sequence of strings, these
0026attributes will not be shared between superclasses and their
0027subclasses. E.g., if you have a class variable that contains a list
0028and you append to that list, changes to subclasses will effect
0029superclasses unless you add the attribute here.
0030
0031Also defines classinstancemethod, which acts as either a class method
0032or an instance method depending on where it is called.
0033"""
0034
0035import copy
0036import events
0037
0038import itertools
0039counter = itertools.count()
0040
0041__all__ = ('classinstancemethod', 'DeclarativeMeta', 'Declarative')
0042
0043
0044class classinstancemethod(object):
0045 """
0046 Acts like a class method when called from a class, like an
0047 instance method when called by an instance. The method should
0048 take two arguments, 'self' and 'cls'; one of these will be None
0049 depending on how the method was called.
0050 """
0051
0052 def __init__(self, func):
0053 self.func = func
0054
0055 def __get__(self, obj, type=None):
0056 return _methodwrapper(self.func, obj=obj, type=type)
0057
0058class _methodwrapper(object):
0059
0060 def __init__(self, func, obj, type):
0061 self.func = func
0062 self.obj = obj
0063 self.type = type
0064
0065 def __call__(self, *args, **kw):
0066 assert not 'self' in kw and not 'cls' in kw, (
0067 "You cannot use 'self' or 'cls' arguments to a "
0068 "classinstancemethod")
0069 return self.func(*((self.obj, self.type) + args), **kw)
0070
0071 def __repr__(self):
0072 if self.obj is None:
0073 return ('<bound class method %s.%s>'
0074 % (self.type.__name__, self.func.func_name))
0075 else:
0076 return ('<bound method %s.%s of %r>'
0077 % (self.type.__name__, self.func.func_name, self.obj))
0078
0079class DeclarativeMeta(type):
0080
0081 def __new__(meta, class_name, bases, new_attrs):
0082 post_funcs = []
0083 early_funcs = []
0084 events.send(events.ClassCreateSignal,
0085 bases[0], class_name, bases, new_attrs,
0086 post_funcs, early_funcs)
0087 cls = type.__new__(meta, class_name, bases, new_attrs)
0088 for func in early_funcs:
0089 func(cls)
0090 if '__classinit__' in new_attrs:
0091 cls.__classinit__ = staticmethod(cls.__classinit__.im_func)
0092 cls.__classinit__(cls, new_attrs)
0093 for func in post_funcs:
0094 func(cls)
0095 return cls
0096
0097class Declarative(object):
0098
0099 __unpackargs__ = ()
0100
0101 __mutableattributes__ = ()
0102
0103 __metaclass__ = DeclarativeMeta
0104
0105 __restrict_attributes__ = None
0106
0107 def __classinit__(cls, new_attrs):
0108 cls.declarative_count = counter.next()
0109 for name in cls.__mutableattributes__:
0110 if name not in new_attrs:
0111 setattr(cls, copy.copy(getattr(cls, name)))
0112
0113 def __instanceinit__(self, new_attrs):
0114 if self.__restrict_attributes__ is not None:
0115 for name in new_attrs:
0116 if name not in self.__restrict_attributes__:
0117 raise TypeError(
0118 '%s() got an unexpected keyword argument %r'
0119 % (self.__class__.__name__, name))
0120 for name, value in new_attrs.items():
0121 setattr(self, name, value)
0122 if 'declarative_count' not in new_attrs:
0123 self.declarative_count = counter.next()
0124
0125 def __init__(self, *args, **kw):
0126 if self.__unpackargs__ and self.__unpackargs__[0] == '*':
0127 assert len(self.__unpackargs__) == 2, "When using __unpackargs__ = ('*', varname), you must only provide a single variable name (you gave %r)" % self.__unpackargs__
0129 name = self.__unpackargs__[1]
0130 if name in kw:
0131 raise TypeError(
0132 "keyword parameter '%s' was given by position and name"
0133 % name)
0134 kw[name] = args
0135 else:
0136 if len(args) > len(self.__unpackargs__):
0137 raise TypeError(
0138 '%s() takes at most %i arguments (%i given)'
0139 % (self.__class__.__name__,
0140 len(self.__unpackargs__),
0141 len(args)))
0142 for name, arg in zip(self.__unpackargs__, args):
0143 if name in kw:
0144 raise TypeError(
0145 "keyword parameter '%s' was given by position and name"
0146 % name)
0147 kw[name] = arg
0148 if '__alsocopy' in kw:
0149 for name, value in kw['__alsocopy'].items():
0150 if name not in kw:
0151 if name in self.__mutableattributes__:
0152 value = copy.copy(value)
0153 kw[name] = value
0154 del kw['__alsocopy']
0155 self.__instanceinit__(kw)
0156
0157 def __call__(self, *args, **kw):
0158 kw['__alsocopy'] = self.__dict__
0159 return self.__class__(*args, **kw)
0160
0161 @classinstancemethod
0162 def singleton(self, cls):
0163 if self:
0164 return self
0165 name = '_%s__singleton' % cls.__name__
0166 if not hasattr(cls, name):
0167 setattr(cls, name, cls(declarative_count=cls.declarative_count))
0168 return getattr(cls, name)
0169
0170 @classinstancemethod
0171 def __repr__(self, cls):
0172 if self:
0173 name = '%s object' % self.__class__.__name__
0174 v = self.__dict__.copy()
0175 else:
0176 name = '%s class' % cls.__name__
0177 v = cls.__dict__.copy()
0178 if 'declarative_count' in v:
0179 name = '%s %i' % (name, v['declarative_count'])
0180 del v['declarative_count']
0181
0182
0183 names = v.keys()
0184 args = []
0185 for n in self._repr_vars(names):
0186 args.append('%s=%r' % (n, v[n]))
0187 if not args:
0188 return '<%s>' % name
0189 else:
0190 return '<%s %s>' % (name, ' '.join(args))
0191
0192 @staticmethod
0193 def _repr_vars(dictNames):
0194 names = [n for n in dictNames
0195 if not n.startswith('_')
0196 and n != 'declarative_count']
0197 names.sort()
0198 return names
0199
0200def setup_attributes(cls, new_attrs):
0201 for name, value in new_attrs.items():
0202 if hasattr(value, '__addtoclass__'):
0203 value.__addtoclass__(cls, name)