diff options
Diffstat (limited to 'lib/sqlalchemy/ext/mutable.py')
-rw-r--r-- | lib/sqlalchemy/ext/mutable.py | 110 |
1 files changed, 55 insertions, 55 deletions
diff --git a/lib/sqlalchemy/ext/mutable.py b/lib/sqlalchemy/ext/mutable.py index 2bb879322..11a7977f6 100644 --- a/lib/sqlalchemy/ext/mutable.py +++ b/lib/sqlalchemy/ext/mutable.py @@ -22,47 +22,47 @@ import weakref class Mutable(object): """Mixin that defines transparent propagation of change events to a parent object. - + """ - + @memoized_property def _parents(self): """Dictionary of parent object->attribute name on the parent.""" - + return weakref.WeakKeyDictionary() - + def change(self): """Subclasses should call this method whenever change events occur.""" - + for parent, key in self._parents.items(): flag_modified(parent, key) - + @classmethod def coerce(cls, key, value): """Given a value, coerce it into this type. - + By default raises ValueError. """ if value is None: return None raise ValueError("Attribute '%s' accepts objects of type %s" % (key, cls)) - - + + @classmethod def associate_with_attribute(cls, attribute): """Establish this type as a mutation listener for the given mapped descriptor. - + """ key = attribute.key parent_cls = attribute.class_ - + def load(state, *args): - """Listen for objects loaded or refreshed. - + """Listen for objects loaded or refreshed. + Wrap the target data member's value with ``Mutable``. - + """ val = state.dict.get(key, None) if val is not None: @@ -73,20 +73,20 @@ class Mutable(object): def set(target, value, oldvalue, initiator): """Listen for set/replace events on the target data member. - + Establish a weak reference to the parent object on the incoming value, remove it for the one outgoing. - + """ - + if not isinstance(value, cls): value = cls.coerce(key, value) value._parents[target.obj()] = key if isinstance(oldvalue, cls): oldvalue._parents.pop(state.obj(), None) return value - + event.listen(parent_cls, 'load', load, raw=True) event.listen(parent_cls, 'refresh', load, raw=True) event.listen(attribute, 'set', set, raw=True, retval=True) @@ -97,7 +97,7 @@ class Mutable(object): def associate_with(cls, sqltype): """Associate this wrapper with all future mapped columns of the given type. - + This is a convenience method that calls ``associate_with_attribute`` automatically. .. warning:: The listeners established by this method are *global* @@ -105,7 +105,7 @@ class Mutable(object): :meth:`.associate_with` for types that are permanent to an application, not with ad-hoc types else this will cause unbounded growth in memory usage. - + """ def listen_for_type(mapper, class_): @@ -114,39 +114,39 @@ class Mutable(object): if isinstance(prop.columns[0].type, sqltype): cls.associate_with_attribute(getattr(class_, prop.key)) break - + event.listen(mapper, 'mapper_configured', listen_for_type) - + @classmethod def as_mutable(cls, sqltype): """Associate a SQL type with this mutable Python type. - + This establishes listeners that will detect ORM mappings against the given type, adding mutation event trackers to those mappings. - + The type is returned, unconditionally as an instance, so that :meth:`.as_mutable` can be used inline:: - + Table('mytable', metadata, Column('id', Integer, primary_key=True), Column('data', MyMutableType.as_mutable(PickleType)) ) - + Note that the returned type is always an instance, even if a class is given, and that only columns which are declared specifically with that type instance receive additional instrumentation. - + To associate a particular mutable type with all occurences of a particular type, use the :meth:`.Mutable.associate_with` classmethod of the particular :meth:`.Mutable` subclass to establish a global assoiation. - + .. warning:: The listeners established by this method are *global* to all mappers, and are *not* garbage collected. Only use :meth:`.as_mutable` for types that are permanent to an application, not with ad-hoc types else this will cause unbounded growth in memory usage. - + """ sqltype = types.to_instance(sqltype) @@ -156,9 +156,9 @@ class Mutable(object): if prop.columns[0].type is sqltype: cls.associate_with_attribute(getattr(class_, prop.key)) break - + event.listen(mapper, 'mapper_configured', listen_for_type) - + return sqltype @@ -171,14 +171,14 @@ class MutableComposite(object): """Mixin that defines transparent propagation of change events on a SQLAlchemy "composite" object to its owning parent or parents. - + Composite classes, in addition to meeting the usage contract defined in :ref:`mapper_composite`, also define some system of relaying change events to the given :meth:`.change` method, which will notify all parents of the change. Below the special Python method ``__setattr__`` is used to intercept all changes:: - + class Point(MutableComposite): def __init__(self, x, y): self.x = x @@ -187,10 +187,10 @@ class MutableComposite(object): def __setattr__(self, key, value): object.__setattr__(self, key, value) self.change() - + def __composite_values__(self): return self.x, self.y - + def __eq__(self, other): return isinstance(other, Point) and \ other.x == self.x and \ @@ -206,44 +206,44 @@ class MutableComposite(object): :class:`.MutableComposite` for types that are permanent to an application, not with ad-hoc types else this will cause unbounded growth in memory usage. - + """ __metaclass__ = _MutableCompositeMeta @memoized_property def _parents(self): """Dictionary of parent object->attribute name on the parent.""" - + return weakref.WeakKeyDictionary() def change(self): """Subclasses should call this method whenever change events occur.""" - + for parent, key in self._parents.items(): - + prop = object_mapper(parent).get_property(key) for value, attr_name in zip( self.__composite_values__(), prop._attribute_keys): setattr(parent, attr_name, value) - + @classmethod def _listen_on_attribute(cls, attribute): """Establish this type as a mutation listener for the given mapped descriptor. - + """ key = attribute.key parent_cls = attribute.class_ - + def load(state, *args): - """Listen for objects loaded or refreshed. - + """Listen for objects loaded or refreshed. + Wrap the target data member's value with ``Mutable``. - + """ - + val = state.dict.get(key, None) if val is not None: val._parents[state.obj()] = key @@ -251,37 +251,37 @@ class MutableComposite(object): def set(target, value, oldvalue, initiator): """Listen for set/replace events on the target data member. - + Establish a weak reference to the parent object on the incoming value, remove it for the one outgoing. - + """ - + value._parents[target.obj()] = key if isinstance(oldvalue, cls): oldvalue._parents.pop(state.obj(), None) return value - + event.listen(parent_cls, 'load', load, raw=True) event.listen(parent_cls, 'refresh', load, raw=True) event.listen(attribute, 'set', set, raw=True, retval=True) # TODO: need a deserialize hook here - + @classmethod def _setup_listeners(cls): """Associate this wrapper with all future mapped compoistes of the given type. - + This is a convenience method that calls ``associate_with_attribute`` automatically. - + """ - + def listen_for_type(mapper, class_): for prop in mapper.iterate_properties: if hasattr(prop, 'composite_class') and issubclass(prop.composite_class, cls): cls._listen_on_attribute(getattr(class_, prop.key)) - + event.listen(mapper, 'mapper_configured', listen_for_type) |