]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- get active_history to work, move attribute events into module-level classes
authorMike Bayer <mike_mp@zzzcomputing.com>
Fri, 3 Sep 2010 16:31:16 +0000 (12:31 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Fri, 3 Sep 2010 16:31:16 +0000 (12:31 -0400)
lib/sqlalchemy/event.py
lib/sqlalchemy/orm/attributes.py
lib/sqlalchemy/orm/dynamic.py
lib/sqlalchemy/orm/interfaces.py
lib/sqlalchemy/orm/strategies.py
test/orm/test_backref_mutations.py

index de395575116943abfc984b6864dfbc80ffc94ade..c1bc54b177e2f6a323b7933a896f003a2f6f85ea 100644 (file)
@@ -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 
index f3d74c61274e8b6c26fdae66125e35e27e448010..ddfdb8655a549d72f036a408f52d2126960eca62 100644 (file)
@@ -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)
index d558380114fd4cee7230c1c7b7ccfe0a55084974..918a0aabd7c7e6ac07b7be173746e77db59bd8ec 100644 (file)
@@ -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_):
 
index 77a387f845e613ad9d3d2d1e116bf7cb9985c5bd..ececf4a698a4cdf592967ba6c9d22c91bc2bc102 100644 (file)
@@ -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):
index 1c4571aed8257c5a13a2ea82a39050c7110c51dd..96e6ff6277913747300086088fa01efb1817ac0d 100644 (file)
@@ -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:
index 13b44e1bb600d57806f2c548edab2dbca17d3bdb..42b6054b3717a4372b3e69477172d905d7eb06a6 100644 (file)
@@ -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