From: Mike Bayer Date: Fri, 3 Sep 2010 16:31:16 +0000 (-0400) Subject: - get active_history to work, move attribute events into module-level classes X-Git-Tag: rel_0_7b1~253^2~25 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=47f56ac4990c7c3e6a020a837e91e39f41adf39e;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - get active_history to work, move attribute events into module-level classes --- diff --git a/lib/sqlalchemy/event.py b/lib/sqlalchemy/event.py index de39557511..c1bc54b177 100644 --- a/lib/sqlalchemy/event.py +++ b/lib/sqlalchemy/event.py @@ -51,10 +51,15 @@ class _Dispatch(object): return (getattr(self, k) for k in dir(self) if k.startswith("on_")) def update(self, other): - """Populate from the listeners in another :class:`Events` object.""" + """Populate from the listeners in another :class:`_Dispatch` + object.""" for ls in other.descriptors: - getattr(self, ls.name).listeners.extend(ls.listeners) + existing_listeners = getattr(self, ls.name).listeners + existing_listener_set = set(existing_listeners) + existing_listeners.extend([l for l + in ls.listeners + if l not in existing_listener_set]) class _EventMeta(type): """Intercept new Event subclasses and create diff --git a/lib/sqlalchemy/orm/attributes.py b/lib/sqlalchemy/orm/attributes.py index f3d74c6127..ddfdb8655a 100644 --- a/lib/sqlalchemy/orm/attributes.py +++ b/lib/sqlalchemy/orm/attributes.py @@ -90,6 +90,60 @@ ClassManager instrumentation is used. """ +class AttributeEvents(event.Events): + """Events for ORM attributes. + + e.g.:: + + from sqlalchemy import event + event.listen(listener, 'on_append', MyClass.collection) + event.listen(listener, 'on_set', MyClass.some_scalar, + active_history=True) + + active_history = True indicates that the "on_set" event would like + to receive the 'old' value, even if it means firing lazy callables. + + """ + + # TODO: what to do about subclasses !! + # a shared approach will be needed. listeners can be placed + # before subclasses are created. new attrs on subclasses + # can pull them from the superclass attr. listeners + # should be auto-propagated to existing subclasses. + + @classmethod + def listen(cls, fn, identifier, target, active_history=False): + if active_history: + target.dispatch.active_history = True + event.Events.listen(fn, identifier, target) + + @classmethod + def unwrap(cls, identifier, event): + return event['value'] + + def on_append(self, state, value, initiator): + """Receive a collection append event. + + The returned value will be used as the actual value to be + appended. + + """ + + def on_remove(self, state, value, initiator): + """Receive a remove event. + + No return value is defined. + + """ + + def on_set(self, state, value, oldvalue, initiator): + """Receive a set event. + + The returned value will be used as the actual value to be + set. + + """ + class QueryableAttribute(interfaces.PropComparator): def __init__(self, key, impl=None, comparator=None, parententity=None): @@ -104,63 +158,9 @@ class QueryableAttribute(interfaces.PropComparator): self.comparator = comparator self.parententity = parententity - class events(event.Events): - """Events for ORM attributes. - - e.g.:: - - from sqlalchemy import event - event.listen(listener, 'on_append', MyClass.collection) - event.listen(listener, 'on_set', MyClass.some_scalar, active_history=True) - - active_history = True indicates that the "on_set" event would like - to receive the 'old' value, even if it means firing lazy callables. - - """ - - active_history = False - - # TODO: what to do about subclasses !! - # a shared approach will be needed. listeners can be placed - # before subclasses are created. new attrs on subclasses - # can pull them from the superclass attr. listeners - # should be auto-propagated to existing subclasses. - - @classmethod - def listen(cls, fn, identifier, target, active_history=False): - if active_history: - target.active_history = True - event.Events.listen(fn, identifier, target) - - @classmethod - def unwrap(cls, identifier, event): - return event['value'] - - def on_append(self, state, value, initiator): - """Receive a collection append event. - - The returned value will be used as the actual value to be - appended. - - """ - - def on_remove(self, state, value, initiator): - """Receive a remove event. - - No return value is defined. - - """ - - def on_set(self, state, value, oldvalue, initiator): - """Receive a set event. - - The returned value will be used as the actual value to be - set. - - """ - dispatch = event.dispatcher(events) + dispatch = event.dispatcher(AttributeEvents) + dispatch.dispatch_cls.active_history = False - def get_history(self, instance, **kwargs): return self.impl.get_history(instance_state(instance), instance_dict(instance), **kwargs) @@ -343,7 +343,6 @@ class AttributeImpl(object): self.class_ = class_ self.key = key self.callable_ = callable_ - self.active_history = False self.dispatch = dispatch self.trackparent = trackparent self.parent_token = parent_token or self @@ -360,10 +359,11 @@ class AttributeImpl(object): ext._adapt_listener(attr, ext) if active_history: - self.active_history = True - - self.expire_missing = expire_missing + self.dispatch.active_history = True + self.expire_missing = expire_missing + + def hasparent(self, state, optimistic=False): """Return the boolean value of a `hasparent` flag attached to the given state. @@ -497,7 +497,7 @@ class ScalarAttributeImpl(AttributeImpl): def delete(self, state, dict_): # TODO: catch key errors, convert to attributeerror? - if self.active_history: + if self.dispatch.active_history: old = self.get(state, dict_) else: old = dict_.get(self.key, NO_VALUE) @@ -515,7 +515,7 @@ class ScalarAttributeImpl(AttributeImpl): if initiator is self: return - if self.active_history: + if self.dispatch.active_history: old = self.get(state, dict_) else: old = dict_.get(self.key, NO_VALUE) @@ -656,7 +656,7 @@ class ScalarObjectAttributeImpl(ScalarAttributeImpl): if initiator is self: return - if self.active_history: + if self.dispatch.active_history: old = self.get(state, dict_) else: old = self.get(state, dict_, passive=PASSIVE_NO_FETCH) @@ -967,6 +967,18 @@ class GenericBackrefExtension(interfaces.AttributeExtension): initiator, passive=PASSIVE_NO_FETCH) +class ClassEvents(event.Events): + def on_init(self, state, instance, args, kwargs): + """""" + + def on_init_failure(self, state, instance, args, kwargs): + """""" + + def on_load(self, instance): + """""" + + def on_resurrect(self, state, instance): + """""" class ClassManager(dict): """tracks state information at the class level.""" @@ -995,20 +1007,7 @@ class ClassManager(dict): self.manage() self._instrument_init() - class events(event.Events): - def on_init(self, state, instance, args, kwargs): - """""" - - def on_init_failure(self, state, instance, args, kwargs): - """""" - - def on_load(self, instance): - """""" - - def on_resurrect(self, state, instance): - """""" - - dispatch = event.dispatcher(events) + dispatch = event.dispatcher(ClassEvents) @property def is_mapped(self): @@ -1504,12 +1503,13 @@ def register_attribute_impl(class_, key, dispatch = manager[key].dispatch if impl_class: - impl = impl_class(class_, key, typecallable, **kw) + impl = impl_class(class_, key, typecallable, dispatch, **kw) elif uselist: impl = CollectionAttributeImpl(class_, key, callable_, dispatch, typecallable=typecallable, **kw) elif useobject: - impl = ScalarObjectAttributeImpl(class_, key, callable_, dispatch,**kw) + impl = ScalarObjectAttributeImpl(class_, key, callable_, + dispatch,**kw) elif mutable_scalars: impl = MutableScalarAttributeImpl(class_, key, callable_, dispatch, class_manager=manager, **kw) diff --git a/lib/sqlalchemy/orm/dynamic.py b/lib/sqlalchemy/orm/dynamic.py index d558380114..918a0aabd7 100644 --- a/lib/sqlalchemy/orm/dynamic.py +++ b/lib/sqlalchemy/orm/dynamic.py @@ -46,9 +46,10 @@ class DynamicAttributeImpl(attributes.AttributeImpl): supports_population = False def __init__(self, class_, key, typecallable, - target_mapper, order_by, query_class=None, **kwargs): + dispatch, + target_mapper, order_by, query_class=None, **kw): super(DynamicAttributeImpl, self).\ - __init__(class_, key, typecallable, **kwargs) + __init__(class_, key, typecallable, dispatch, **kw) self.target_mapper = target_mapper self.order_by = order_by if not query_class: @@ -78,8 +79,8 @@ class DynamicAttributeImpl(attributes.AttributeImpl): collection_history = self._modified_event(state, dict_) collection_history.added_items.append(value) - for ext in self.extensions: - ext.append(state, value, initiator or self) + for fn in self.dispatch.on_append: + value = fn(state, value, initiator or self) if self.trackparent and value is not None: self.sethasparent(attributes.instance_state(value), True) @@ -91,8 +92,8 @@ class DynamicAttributeImpl(attributes.AttributeImpl): if self.trackparent and value is not None: self.sethasparent(attributes.instance_state(value), False) - for ext in self.extensions: - ext.remove(state, value, initiator or self) + for fn in self.dispatch.on_remove: + fn(state, value, initiator or self) def _modified_event(self, state, dict_): diff --git a/lib/sqlalchemy/orm/interfaces.py b/lib/sqlalchemy/orm/interfaces.py index 77a387f845..ececf4a698 100644 --- a/lib/sqlalchemy/orm/interfaces.py +++ b/lib/sqlalchemy/orm/interfaces.py @@ -895,9 +895,12 @@ class AttributeExtension(object): @classmethod def _adapt_listener(cls, self, listener): - event.listen(listener.append, 'on_append', self, active_history=listener.active_history) - event.listen(listener.remove, 'on_remove', self, active_history=listener.active_history) - event.listen(listener.set, 'on_set', self, active_history=listener.active_history) + event.listen(listener.append, 'on_append', self, + active_history=listener.active_history) + event.listen(listener.remove, 'on_remove', self, + active_history=listener.active_history) + event.listen(listener.set, 'on_set', self, + active_history=listener.active_history) def append(self, state, value, initiator): diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py index 1c4571aed8..96e6ff6277 100644 --- a/lib/sqlalchemy/orm/strategies.py +++ b/lib/sqlalchemy/orm/strategies.py @@ -34,6 +34,7 @@ def _register_attribute(strategy, mapper, useobject, ): prop = strategy.parent_property + attribute_ext = list(util.to_list(prop.extension, default=[])) if useobject and prop.single_parent: diff --git a/test/orm/test_backref_mutations.py b/test/orm/test_backref_mutations.py index 13b44e1bb6..42b6054b37 100644 --- a/test/orm/test_backref_mutations.py +++ b/test/orm/test_backref_mutations.py @@ -420,7 +420,8 @@ class O2OScalarOrphanTest(_fixtures.FixtureTest): mapper(Address, addresses) mapper(User, users, properties = { 'address':relationship(Address, uselist=False, - backref=backref('user', single_parent=True, cascade="all, delete-orphan")) + backref=backref('user', single_parent=True, + cascade="all, delete-orphan")) }) @testing.resolve_artifact_names