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        #events.send(events.ClassCreateSignal,
0058        #            bases[0], class_name, bases, new_attrs,
0059        #            post_funcs, early_funcs)
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        # @@: simplifying repr:
0154        #v = {}
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]))