summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/sqlalchemy/event.py9
-rw-r--r--lib/sqlalchemy/orm/attributes.py158
-rw-r--r--lib/sqlalchemy/orm/dynamic.py13
-rw-r--r--lib/sqlalchemy/orm/interfaces.py9
-rw-r--r--lib/sqlalchemy/orm/strategies.py1
-rw-r--r--test/orm/test_backref_mutations.py3
6 files changed, 102 insertions, 91 deletions
diff --git a/lib/sqlalchemy/event.py b/lib/sqlalchemy/event.py
index de3955751..c1bc54b17 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 f3d74c612..ddfdb8655 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 d55838011..918a0aabd 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 77a387f84..ececf4a69 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 1c4571aed..96e6ff627 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 13b44e1bb..42b6054b3 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