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"""
0014from __future__ import absolute_import
0015
0016from . import declarative
0017from . import events
0018
0019__all__ = ['BoundAttribute', 'BoundFactory']
0020
0021
0022class BoundAttribute(declarative.Declarative):
0023
0024    """
0025    This is a declarative class that passes all the values given to it
0026    to another object.  So you can pass it arguments (via
0027    __init__/__call__) or give it the equivalent of keyword arguments
0028    through subclassing.  Then a bound object will be added in its
0029    place.
0030
0031    To hook this other object in, override ``make_object(added_class,
0032    name, **attrs)`` and maybe ``set_object(added_class, name,
0033    **attrs)`` (the default implementation of ``set_object``
0034    just resets the attribute to whatever ``make_object`` returned).
0035
0036    Also see ``BoundFactory``.
0037    """
0038
0039    _private_variables = (
0040        '_private_variables',
0041        '_all_attributes',
0042        '__classinit__',
0043        '__addtoclass__',
0044        '_add_attrs',
0045        'set_object',
0046        'make_object',
0047        'clone_in_subclass',
0048    )
0049
0050    _all_attrs = ()
0051    clone_for_subclass = True
0052
0053    def __classinit__(cls, new_attrs):
0054        declarative.Declarative.__classinit__(cls, new_attrs)
0055        cls._all_attrs = cls._add_attrs(cls, new_attrs)
0056
0057    def __instanceinit__(self, new_attrs):
0058        declarative.Declarative.__instanceinit__(self, new_attrs)
0059        self.__dict__['_all_attrs'] = self._add_attrs(self, new_attrs)
0060
0061    @staticmethod
0062    def _add_attrs(this_object, new_attrs):
0063        private = this_object._private_variables
0064        all_attrs = list(this_object._all_attrs)
0065        for key in new_attrs.keys():
0066            if key.startswith('_') or key in private:
0067                continue
0068            if key not in all_attrs:
0069                all_attrs.append(key)
0070        return tuple(all_attrs)
0071
0072    @declarative.classinstancemethod
0073    def __addtoclass__(self, cls, added_class, attr_name):
0074        me = self or cls
0075        attrs = {}
0076        for name in me._all_attrs:
0077            attrs[name] = getattr(me, name)
0078        attrs['added_class'] = added_class
0079        attrs['attr_name'] = attr_name
0080        obj = me.make_object(**attrs)
0081
0082        if self.clone_for_subclass:
0083            def on_rebind(new_class_name, bases, new_attrs,
0084                          post_funcs, early_funcs):
0085                def rebind(new_class):
0086                    me.set_object(
0087                        new_class, attr_name,
0088                        me.make_object(**attrs))
0089                post_funcs.append(rebind)
0090            events.listen(receiver=on_rebind, soClass=added_class,
0091                          signal=events.ClassCreateSignal, weak=False)
0092
0093        me.set_object(added_class, attr_name, obj)
0094
0095    @classmethod
0096    def set_object(cls, added_class, attr_name, obj):
0097        setattr(added_class, attr_name, obj)
0098
0099    @classmethod
0100    def make_object(cls, added_class, attr_name, *args, **attrs):
0101        raise NotImplementedError
0102
0103    def __setattr__(self, name, value):
0104        self.__dict__['_all_attrs'] = self._add_attrs(self, {name: value})
0105        self.__dict__[name] = value
0106
0107
0108class BoundFactory(BoundAttribute):
0109
0110    """
0111    This will bind the attribute to whatever is given by
0112    ``factory_class``.  This factory should be a callable with the
0113    signature ``factory_class(added_class, attr_name, *args, **kw)``.
0114
0115    The factory will be reinvoked (and the attribute rebound) for
0116    every subclassing.
0117    """
0118
0119    factory_class = None
0120    _private_variables = (
0121        BoundAttribute._private_variables + ('factory_class',))
0122
0123    def make_object(cls, added_class, attr_name, *args, **kw):
0124        return cls.factory_class(added_class, attr_name, *args, **kw)