1"""
2Bound attributes are attributes that are bound to a specific class and
3a specific name.  In SQLObject a typical example is a column object,
4which knows its name and class.
5
6A bound attribute should define a method ``__addtoclass__(added_class,
7name)`` (attributes without this method will simply be treated as
8normal).  The return value is ignored; if the attribute wishes to
9change the value in the class, it must call ``setattr(added_class,
10name, new_value)``.
11
12BoundAttribute is a class that facilitates lazy attribute creation.
13
14``bind_attributes(cls, new_attrs)`` is a function that looks for
15attributes with this special method.  ``new_attrs`` is a dictionary,
16as typically passed into ``__classinit__`` with declarative (calling
17``bind_attributes`` in ``__classinit__`` would be typical).
18
19Note if you do this that attributes defined in a superclass will not
20be rebound in subclasses.  If you want to rebind attributes in
21subclasses, use ``bind_attributes_local``, which adds a
22``__bound_attributes__`` variable to your class to track these active
23attributes.
24"""
25
26__all__ = ['BoundAttribute', 'BoundFactory', 'bind_attributes',
27           'bind_attributes_local']
28
29import declarative
30import events
31
32class BoundAttribute(declarative.Declarative):
33
34    """
35    This is a declarative class that passes all the values given to it
36    to another object.  So you can pass it arguments (via
37    __init__/__call__) or give it the equivalent of keyword arguments
38    through subclassing.  Then a bound object will be added in its
39    place.
40
41    To hook this other object in, override ``make_object(added_class,
42    name, **attrs)`` and maybe ``set_object(added_class, name,
43    **attrs)`` (the default implementation of ``set_object``
44    just resets the attribute to whatever ``make_object`` returned).
45
46    Also see ``BoundFactory``.
47    """
48
49    _private_variables = (
50        '_private_variables',
51        '_all_attributes',
52        '__classinit__',
53        '__addtoclass__',
54        '_add_attrs',
55        'set_object',
56        'make_object',
57        'clone_in_subclass',
58        )
59
60    _all_attrs = ()
61    clone_for_subclass = True
62
63    def __classinit__(cls, new_attrs):
64        declarative.Declarative.__classinit__(cls, new_attrs)
65        cls._all_attrs = cls._add_attrs(cls, new_attrs)
66
67    def __instanceinit__(self, new_attrs):
68        declarative.Declarative.__instanceinit__(self, new_attrs)
69        self.__dict__['_all_attrs'] = self._add_attrs(self, new_attrs)
70
71    @staticmethod
72    def _add_attrs(this_object, new_attrs):
73        private = this_object._private_variables
74        all_attrs = list(this_object._all_attrs)
75        for key in new_attrs.keys():
76            if key.startswith('_') or key in private:
77                continue
78            if key not in all_attrs:
79                all_attrs.append(key)
80        return tuple(all_attrs)
81
82    @declarative.classinstancemethod
83    def __addtoclass__(self, cls, added_class, attr_name):
84        me = self or cls
85        attrs = {}
86        for name in me._all_attrs:
87            attrs[name] = getattr(me, name)
88        attrs['added_class'] = added_class
89        attrs['attr_name'] = attr_name
90        obj = me.make_object(**attrs)
91
92        if self.clone_for_subclass:
93            def on_rebind(new_class_name, bases, new_attrs,
94                          post_funcs, early_funcs):
95                def rebind(new_class):
96                    me.set_object(
97                        new_class, attr_name,
98                        me.make_object(**attrs))
99                post_funcs.append(rebind)
100            events.listen(receiver=on_rebind, soClass=added_class,
101                          signal=events.ClassCreateSignal, weak=False)
102
103        me.set_object(added_class, attr_name, obj)
104
105    @classmethod
106    def set_object(cls, added_class, attr_name, obj):
107        setattr(added_class, attr_name, obj)
108
109    @classmethod
110    def make_object(cls, added_class, attr_name, *args, **attrs):
111        raise NotImplementedError
112
113    def __setattr__(self, name, value):
114        self.__dict__['_all_attrs'] = self._add_attrs(self, {name: value})
115        self.__dict__[name] = value
116
117class BoundFactory(BoundAttribute):
118
119    """
120    This will bind the attribute to whatever is given by
121    ``factory_class``.  This factory should be a callable with the
122    signature ``factory_class(added_class, attr_name, *args, **kw)``.
123
124    The factory will be reinvoked (and the attribute rebound) for
125    every subclassing.
126    """
127
128    factory_class = None
129    _private_variables = (
130        BoundAttribute._private_variables + ('factory_class',))
131
132    def make_object(cls, added_class, attr_name, *args, **kw):
133        return cls.factory_class(added_class, attr_name, *args, **kw)
134