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