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 class variable ``__unpackargs__`` (a list of
0011strings), and if the constructor is called with non-keyword arguments
0012they will be interpreted as the given keyword arguments.
0013
0014If ``__unpackargs__`` is ``('*', name)``, then all the arguments will
0015be put in a variable by that name.
0016
0017You can define a ``__classinit__(cls, new_attrs)`` method, which will
0018be called when the class is created (including subclasses). Note: you
0019can't use ``super()`` in ``__classinit__`` because the class isn't
0020bound to a name. As an analog to ``__classinit__``, ``Declarative``
0021adds ``__instanceinit__`` which is called with the same argument
0022(``new_attrs``). This is like ``__init__``, but applied after
0023``__unpackargs__`` and other factors have been taken into account.
0024
0025If ``__mutableattributes__`` is defined as a sequence of strings,
0026these attributes 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. So any variables
0030listed there will be copied (shallowly).
0031"""
0032
0033from __future__ import generators
0034import copy
0035from classinst import classinstancemethod
0036from eventhub import EventHub
0037import sys
0038
0039__all__ = ('DeclarativeMeta', 'Declarative')
0040
0041try:
0042 import itertools
0043 counter = itertools.count()
0044except ImportError:
0045 def _counter():
0046 i = 0
0047 while 1:
0048 i += 1
0049 yield i
0050 counter = _counter()
0051
0052class DeclarativeMeta(type):
0053
0054 def __new__(meta, class_name, bases, new_attrs):
0055 post_funcs = []
0056 early_funcs = []
0057
0058
0059
0060 cls = type.__new__(meta, class_name, bases, new_attrs)
0061 for func in early_funcs:
0062 func(cls)
0063 if new_attrs.has_key('__classinit__'):
0064 cls.__classinit__ = staticmethod(cls.__classinit__.im_func)
0065 cls.__classinit__(cls, new_attrs)
0066 for func in post_funcs:
0067 func(cls)
0068 return cls
0069
0070class Declarative(object):
0071
0072 __unpackargs__ = ()
0073
0074 __mutableattributes__ = ()
0075
0076 __metaclass__ = DeclarativeMeta
0077
0078 __restrict_attributes__ = None
0079
0080 def __classinit__(cls, new_attrs):
0081 cls.declarative_count = counter.next()
0082 for name in cls.__mutableattributes__:
0083 if not new_attrs.has_key(name):
0084 setattr(cls, copy.copy(getattr(cls, name)))
0085
0086 def __instanceinit__(self, new_attrs):
0087 if self.__restrict_attributes__ is not None:
0088 for name in new_attrs:
0089 if name not in self.__restrict_attributes__:
0090 raise TypeError(
0091 '%s() got an unexpected keyword argument %r'
0092 % (self.__class__.__name__, name))
0093 for name, value in new_attrs.items():
0094 setattr(self, name, value)
0095 if not new_attrs.has_key('declarative_count'):
0096 self.declarative_count = counter.next()
0097
0098 def __init__(self, *args, **kw):
0099 if self.__unpackargs__ and self.__unpackargs__[0] == '*':
0100 assert len(self.__unpackargs__) == 2, "When using __unpackargs__ = ('*', varname), you must only provide a single variable name (you gave %r)" % self.__unpackargs__
0102 name = self.__unpackargs__[1]
0103 if kw.has_key(name):
0104 raise TypeError(
0105 "keyword parameter '%s' was given by position and name"
0106 % name)
0107 kw[name] = args
0108 else:
0109 if len(args) > len(self.__unpackargs__):
0110 raise TypeError(
0111 '%s() takes at most %i arguments (%i given)'
0112 % (self.__class__.__name__,
0113 len(self.__unpackargs__),
0114 len(args)))
0115 for name, arg in zip(self.__unpackargs__, args):
0116 if kw.has_key(name):
0117 raise TypeError(
0118 "keyword parameter '%s' was given by position and name"
0119 % name)
0120 kw[name] = arg
0121 if kw.has_key('__alsocopy'):
0122 for name, value in kw['__alsocopy'].items():
0123 if not kw.has_key(name):
0124 if name in self.__mutableattributes__:
0125 value = copy.copy(value)
0126 kw[name] = value
0127 del kw['__alsocopy']
0128 self.__instanceinit__(kw)
0129
0130 def __call__(self, *args, **kw):
0131 kw['__alsocopy'] = self.__dict__
0132 return self.__class__(*args, **kw)
0133
0134 def singleton(self, cls):
0135 if self:
0136 return self
0137 name = '_%s__singleton' % cls.__name__
0138 if not hasattr(cls, name):
0139 setattr(cls, name, cls(declarative_count=cls.declarative_count))
0140 return getattr(cls, name)
0141 singleton = classinstancemethod(singleton)
0142
0143 def __repr__(self, cls):
0144 if self:
0145 name = '%s object' % self.__class__.__name__
0146 v = self.__dict__.copy()
0147 else:
0148 name = '%s class' % cls.__name__
0149 v = cls.__dict__.copy()
0150 if v.has_key('declarative_count'):
0151 name = '%s %i' % (name, v['declarative_count'])
0152 del v['declarative_count']
0153
0154
0155 names = v.keys()
0156 args = []
0157 for n in self._repr_vars(names):
0158 args.append('%s=%r' % (n, v[n]))
0159 if not args:
0160 return '<%s>' % name
0161 else:
0162 return '<%s %s>' % (name, ' '.join(args))
0163
0164 def _repr_vars(dictNames):
0165 names = [n for n in dictNames
0166 if not n.startswith('_')
0167 and n != 'declarative_count']
0168 names.sort()
0169 return names
0170 _repr_vars = staticmethod(_repr_vars)
0171
0172 __repr__ = classinstancemethod(__repr__)
0173
0174def setup_attributes(cls, new_attrs):
0175 also_set = getattr(cls, '__addtosubclass__', [])[:]
0176 cls.__addtosubclass__ = []
0177 items = new_attrs.items()
0178 items.sort(declarative_compare)
0179 to_set = []
0180 for name, value in items:
0181 if name in also_set:
0182 also_set.remove(name)
0183 if hasattr(value, '__addtoclass__'):
0184 to_set.append((name, value))
0185 mro = cls.__mro__
0186 early_to_set = []
0187 for name in also_set:
0188 for superclass in mro:
0189 if name in superclass.__dict__:
0190 obj = superclass.__dict__[name]
0191 break
0192 else:
0193 continue
0194 if obj is not None:
0195 early_to_set.append((name, obj))
0196 for name, obj in early_to_set + to_set:
0197 obj.__addtoclass__(cls, name)
0198
0199def declarative_compare(a, b):
0200 return cmp((getattr(a[1], 'declarative_count', sys.maxint),
0201 a[0]),
0202 (getattr(b[1], 'declarative_count', sys.maxint),
0203 b[0]))