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
0036from . import events
0037from sqlobject.compat import with_metaclass
0038
0039import itertools
0040counter = itertools.count()
0041
0042__all__ = ('classinstancemethod', 'DeclarativeMeta', 'Declarative')
0043
0044
0045class classinstancemethod(object):
0046 """
0047 Acts like a class method when called from a class, like an
0048 instance method when called by an instance. The method should
0049 take two arguments, 'self' and 'cls'; one of these will be None
0050 depending on how the method was called.
0051 """
0052
0053 def __init__(self, func):
0054 self.func = func
0055
0056 def __get__(self, obj, type=None):
0057 return _methodwrapper(self.func, obj=obj, type=type)
0058
0059
0060class _methodwrapper(object):
0061
0062 def __init__(self, func, obj, type):
0063 self.func = func
0064 self.obj = obj
0065 self.type = type
0066
0067 def __call__(self, *args, **kw):
0068 assert 'self' not in kw and 'cls' not in kw, (
0069 "You cannot use 'self' or 'cls' arguments to a "
0070 "classinstancemethod")
0071 return self.func(*((self.obj, self.type) + args), **kw)
0072
0073 def __repr__(self):
0074 if self.obj is None:
0075 return ('<bound class method %s.%s>'
0076 % (self.type.__name__, self.func.__name__))
0077 else:
0078 return ('<bound method %s.%s of %r>'
0079 % (self.type.__name__, self.func.__name__, self.obj))
0080
0081
0082class DeclarativeMeta(type):
0083
0084 def __new__(meta, class_name, bases, new_attrs):
0085 post_funcs = []
0086 early_funcs = []
0087 events.send(events.ClassCreateSignal,
0088 bases[0], class_name, bases, new_attrs,
0089 post_funcs, early_funcs)
0090 cls = type.__new__(meta, class_name, bases, new_attrs)
0091 for func in early_funcs:
0092 func(cls)
0093 if '__classinit__' in new_attrs:
0094 if hasattr(cls.__classinit__, '__func__'):
0095 cls.__classinit__ = staticmethod(cls.__classinit__.__func__)
0096 else:
0097 cls.__classinit__ = staticmethod(cls.__classinit__)
0098 cls.__classinit__(cls, new_attrs)
0099 for func in post_funcs:
0100 func(cls)
0101 return cls
0102
0103
0104class Declarative(with_metaclass(DeclarativeMeta, object)):
0105
0106 __unpackargs__ = ()
0107
0108 __mutableattributes__ = ()
0109
0110 __restrict_attributes__ = None
0111
0112 def __classinit__(cls, new_attrs):
0113 cls.declarative_count = next(counter)
0114 for name in cls.__mutableattributes__:
0115 if name not in new_attrs:
0116 setattr(cls, copy.copy(getattr(cls, name)))
0117
0118 def __instanceinit__(self, new_attrs):
0119 if self.__restrict_attributes__ is not None:
0120 for name in new_attrs:
0121 if name not in self.__restrict_attributes__:
0122 raise TypeError(
0123 '%s() got an unexpected keyword argument %r'
0124 % (self.__class__.__name__, name))
0125 for name, value in new_attrs.items():
0126 setattr(self, name, value)
0127 if 'declarative_count' not in new_attrs:
0128 self.declarative_count = next(counter)
0129
0130 def __init__(self, *args, **kw):
0131 if self.__unpackargs__ and self.__unpackargs__[0] == '*':
0132 assert len(self.__unpackargs__) == 2, "When using __unpackargs__ = ('*', varname), " "you must only provide a single variable name " "(you gave %r)" % self.__unpackargs__
0136 name = self.__unpackargs__[1]
0137 if name in kw:
0138 raise TypeError(
0139 "keyword parameter '%s' was given by position and name"
0140 % name)
0141 kw[name] = args
0142 else:
0143 if len(args) > len(self.__unpackargs__):
0144 raise TypeError(
0145 '%s() takes at most %i arguments (%i given)'
0146 % (self.__class__.__name__,
0147 len(self.__unpackargs__),
0148 len(args)))
0149 for name, arg in zip(self.__unpackargs__, args):
0150 if name in kw:
0151 raise TypeError(
0152 "keyword parameter '%s' was given by position and name"
0153 % name)
0154 kw[name] = arg
0155 if '__alsocopy' in kw:
0156 for name, value in kw['__alsocopy'].items():
0157 if name not in kw:
0158 if name in self.__mutableattributes__:
0159 value = copy.copy(value)
0160 kw[name] = value
0161 del kw['__alsocopy']
0162 self.__instanceinit__(kw)
0163
0164 def __call__(self, *args, **kw):
0165 kw['__alsocopy'] = self.__dict__
0166 return self.__class__(*args, **kw)
0167
0168 @classinstancemethod
0169 def singleton(self, cls):
0170 if self:
0171 return self
0172 name = '_%s__singleton' % cls.__name__
0173 if not hasattr(cls, name):
0174 setattr(cls, name, cls(declarative_count=cls.declarative_count))
0175 return getattr(cls, name)
0176
0177 @classinstancemethod
0178 def __repr__(self, cls):
0179 if self:
0180 name = '%s object' % self.__class__.__name__
0181 v = self.__dict__.copy()
0182 else:
0183 name = '%s class' % cls.__name__
0184 v = cls.__dict__.copy()
0185 if 'declarative_count' in v:
0186 name = '%s %i' % (name, v['declarative_count'])
0187 del v['declarative_count']
0188
0189
0190 names = v.keys()
0191 args = []
0192 for n in self._repr_vars(names):
0193 args.append('%s=%r' % (n, v[n]))
0194 if not args:
0195 return '<%s>' % name
0196 else:
0197 return '<%s %s>' % (name, ' '.join(args))
0198
0199 @staticmethod
0200 def _repr_vars(dictNames):
0201 names = [n for n in dictNames
0202 if not n.startswith('_') and
0203 n != 'declarative_count']
0204 names.sort()
0205 return names
0206
0207
0208def setup_attributes(cls, new_attrs):
0209 for name, value in new_attrs.items():
0210 if hasattr(value, '__addtoclass__'):
0211 value.__addtoclass__(cls, name)