0001"""
0002Bound attributes are attributes that are bound to a specific class and
0003a specific name.  In SQLObject a typical example is a column object,
0004which knows its name and class.
0005
0006A bound attribute should define a method ``__addtoclass__(added_class,
0007name)`` (attributes without this method will simply be treated as
0008normal).  The return value is ignored; if the attribute wishes to
0009change the value in the class, it must call ``setattr(added_class,
0010name, new_value)``.
0011
0012BoundAttribute is a class that facilitates lazy attribute creation.
0013
0014``bind_attributes(cls, new_attrs)`` is a function that looks for
0015attributes with this special method.  ``new_attrs`` is a dictionary,
0016as typically passed into ``__classinit__`` with declarative (calling
0017``bind_attributes`` in ``__classinit__`` would be typical).
0018
0019Note if you do this that attributes defined in a superclass will not
0020be rebound in subclasses.  If you want to rebind attributes in
0021subclasses, use ``bind_attributes_local``, which adds a
0022``__bound_attributes__`` variable to your class to track these active
0023attributes.
0024"""
0025
0026__all__ = ['BoundAttribute', 'BoundFactory', 'bind_attributes',
0027           'bind_attributes_local']
0028
0029import declarative
0030import events
0031
0032class BoundAttribute(declarative.Declarative):
0033
0034    """
0035    This is a declarative class that passes all the values given to it
0036    to another object.  So you can pass it arguments (via
0037    __init__/__call__) or give it the equivalent of keyword arguments
0038    through subclassing.  Then a bound object will be added in its
0039    place.
0040
0041    To hook this other object in, override ``make_object(added_class,
0042    name, **attrs)`` and maybe ``set_object(added_class, name,
0043    **attrs)`` (the default implementation of ``set_object``
0044    just resets the attribute to whatever ``make_object`` returned).
0045
0046    Also see ``BoundFactory``.
0047    """
0048
0049    _private_variables = (
0050        '_private_variables',
0051        '_all_attributes',
0052        '__classinit__',
0053        '__addtoclass__',
0054        '_add_attrs',
0055        'set_object',
0056        'make_object',
0057        'clone_in_subclass',
0058        )
0059
0060    _all_attrs = ()
0061    clone_for_subclass = True
0062
0063    def __classinit__(cls, new_attrs):
0064        declarative.Declarative.__classinit__(cls, new_attrs)
0065        cls._all_attrs = cls._add_attrs(cls, new_attrs)
0066
0067    def __instanceinit__(self, new_attrs):
0068        declarative.Declarative.__instanceinit__(self, new_attrs)
0069        self.__dict__['_all_attrs'] = self._add_attrs(self, new_attrs)
0070
0071    def _add_attrs(this_object, new_attrs):
0072        private = this_object._private_variables
0073        all_attrs = list(this_object._all_attrs)
0074        for key in new_attrs.keys():
0075            if key.startswith('_') or key in private:
0076                continue
0077            if key not in all_attrs:
0078                all_attrs.append(key)
0079        return tuple(all_attrs)
0080    _add_attrs = staticmethod(_add_attrs)
0081
0082    def __addtoclass__(self, cls, added_class, attr_name):
0083        me = self or cls
0084        attrs = {}
0085        for name in me._all_attrs:
0086            attrs[name] = getattr(me, name)
0087        attrs['added_class'] = added_class
0088        attrs['attr_name'] = attr_name
0089        obj = me.make_object(**attrs)
0090
0091        if self.clone_for_subclass:
0092            def on_rebind(new_class_name, bases, new_attrs,
0093                          post_funcs, early_funcs):
0094                def rebind(new_class):
0095                    me.set_object(
0096                        new_class, attr_name,
0097                        me.make_object(**attrs))
0098                post_funcs.append(rebind)
0099            events.listen(receiver=on_rebind, soClass=added_class,
0100                          signal=events.ClassCreateSignal, weak=False)
0101
0102        me.set_object(added_class, attr_name, obj)
0103
0104    __addtoclass__ = declarative.classinstancemethod(__addtoclass__)
0105
0106    def set_object(cls, added_class, attr_name, obj):
0107        setattr(added_class, attr_name, obj)
0108
0109    set_object = classmethod(set_object)
0110
0111    def make_object(cls, added_class, attr_name, *args, **attrs):
0112        raise NotImplementedError
0113
0114    make_object = classmethod(make_object)
0115
0116    def __setattr__(self, name, value):
0117        self.__dict__['_all_attrs'] = self._add_attrs(self, {name: value})
0118        self.__dict__[name] = value
0119
0120class BoundFactory(BoundAttribute):
0121
0122    """
0123    This will bind the attribute to whatever is given by
0124    ``factory_class``.  This factory should be a callable with the
0125    signature ``factory_class(added_class, attr_name, *args, **kw)``.
0126
0127    The factory will be reinvoked (and the attribute rebound) for
0128    every subclassing.
0129    """
0130
0131    factory_class = None
0132    _private_variables = (
0133        BoundAttribute._private_variables + ('factory_class',))
0134
0135    def make_object(cls, added_class, attr_name, *args, **kw):
0136        return cls.factory_class(added_class, attr_name, *args, **kw)