]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- add instrumentation events
authorMike Bayer <mike_mp@zzzcomputing.com>
Sat, 2 Oct 2010 17:31:07 +0000 (13:31 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Sat, 2 Oct 2010 17:31:07 +0000 (13:31 -0400)
- simplify listen_for_events example with new system
- add "propagate", "retval", "raw" flags to attribute events.  this solves the "return value"
issue as well as the "subclass" issue.
- begin thinking about event removal.   Each listen() method will have a corresponding remove().
Custom listen() methods will have to package all the info onto the event function that is needed
to remove its state.

examples/custom_attributes/listen_for_events.py
lib/sqlalchemy/event.py
lib/sqlalchemy/orm/attributes.py
lib/sqlalchemy/orm/deprecated_interfaces.py
lib/sqlalchemy/orm/events.py
lib/sqlalchemy/orm/instrumentation.py
lib/sqlalchemy/orm/mapper.py
lib/sqlalchemy/orm/session.py
lib/sqlalchemy/orm/state.py
lib/sqlalchemy/orm/util.py
test/orm/test_instrumentation.py

index 6d467fbbc132d59006e5f7972a1b5283302a37a7..e66ebd090597d9bca858e8b2aad2539999b5f387 100644 (file)
@@ -1,42 +1,25 @@
 """
-Illustrates how to use AttributeExtension to listen for change events 
-across the board.
+Illustrates how to attach events to all instrumented attributes
+and listen for change events.
 
 """
 
-from sqlalchemy.orm.interfaces import AttributeExtension, \
-    InstrumentationManager
+from sqlalchemy import event, orm
 
-class InstallListeners(InstrumentationManager):
-    def post_configure_attribute(self, class_, key, inst):
-        """Add an event listener to an InstrumentedAttribute."""
-        
-        inst.impl.extensions.insert(0, AttributeListener(key))
-        
-class AttributeListener(AttributeExtension):
-    """Generic event listener.  
-    
-    Propagates attribute change events to a 
-    "receive_change_event()" method on the target
-    instance.
-    
-    """
-    def __init__(self, key):
-        self.key = key
-    
-    def append(self, state, value, initiator):
-        self._report(state, value, None, "appended")
-        return value
+def configure_listener(class_, key, inst):
+    def append(instance, value, initiator):
+        instance.receive_change_event("append", key, value, None)
 
-    def remove(self, state, value, initiator):
-        self._report(state, value, None, "removed")
+    def remove(instance, value, initiator):
+        instance.receive_change_event("remove", key, value, None)
+
+    def set_(instance, value, oldvalue, initiator):
+        instance.receive_change_event("set", key, value, oldvalue)
+
+    event.listen(append, 'on_append', inst)
+    event.listen(remove, 'on_remove', inst)
+    event.listen(set_, 'on_set', inst)
 
-    def set(self, state, value, oldvalue, initiator):
-        self._report(state, value, oldvalue, "set")
-        return value
-    
-    def _report(self, state, value, oldvalue, verb):
-        state.obj().receive_change_event(verb, self.key, value, oldvalue)
 
 if __name__ == '__main__':
 
@@ -45,7 +28,6 @@ if __name__ == '__main__':
     from sqlalchemy.ext.declarative import declarative_base
 
     class Base(object):
-        __sa_instrumentation_manager__ = InstallListeners
         
         def receive_change_event(self, verb, key, value, oldvalue):
             s = "Value '%s' %s on attribute '%s', " % (value, verb, key)
@@ -56,6 +38,8 @@ if __name__ == '__main__':
             
     Base = declarative_base(cls=Base)
 
+    event.listen(configure_listener, 'on_attribute_instrument', Base)
+
     class MyMappedClass(Base):
         __tablename__ = "mytable"
     
index c1bc54b177e2f6a323b7933a896f003a2f6f85ea..c7df2bf4859630cc4d6cc072251fe8b2a0a3bb72 100644 (file)
@@ -29,7 +29,20 @@ def listen(fn, identifier, target, *args, **kw):
             return
     raise exc.InvalidRequestError("No such event %s for target %s" %
                                 (identifier,target))
+
+def remove(fn, identifier, target):
+    """Remove an event listener.
+    
+    Note that some event removals, particularly for those event dispatchers
+    which create wrapper functions and secondary even listeners, may not yet
+    be supported.
     
+    """
+    for evt_cls in _registrars[identifier]:
+        for tgt in evt_cls.accept_with(target):
+            tgt.dispatch.remove(fn, identifier, tgt, *args, **kw)
+
+        
 _registrars = util.defaultdict(list)
 
 class _Dispatch(object):
@@ -105,13 +118,11 @@ class Events(object):
     @classmethod
     def listen(cls, fn, identifier, target):
         getattr(target.dispatch, identifier).append(fn, target)
-
-#    def update(self, other):
-#        """Populate from the listeners in another :class:`Events` object."""
-
-#        for ls in other.events:
-#            getattr(self, ls.name).listeners.extend(ls.listeners)
     
+    @classmethod
+    def remove(cls, fn, identifier, target):
+        getattr(target.dispatch, identifier).remove(fn, target)
+        
 class _DispatchDescriptor(object):
     """Class-level attributes on _Dispatch classes."""
     
@@ -127,6 +138,10 @@ class _DispatchDescriptor(object):
         for cls in [target] + target.__subclasses__():
             self._clslevel[cls].append(obj)
     
+    def remove(self, obj, target):
+        for cls in [target] + target.__subclasses__():
+            self._clslevel[cls].remove(obj)
+        
     def __get__(self, obj, cls):
         if obj is None:
             return self
@@ -180,7 +195,11 @@ class _ListenerCollection(object):
     def append(self, obj, target):
         if obj not in self.listeners:
             self.listeners.append(obj)
-
+    
+    def remove(self, obj, target):
+        if obj in self.listeners:
+            self.listeners.remove(obj)
+        
 class dispatcher(object):
     """Descriptor used by target classes to 
     deliver the _Dispatch class at the class level
index 46b5a5dca7a1994327c7de71ab96ce3feba11bfc..6bc15d8f5efdef7462296b8a4f6e8ca075120d93 100644 (file)
@@ -16,7 +16,7 @@ import operator
 from operator import itemgetter
 
 from sqlalchemy import util, event
-from sqlalchemy.orm import interfaces, collections
+from sqlalchemy.orm import interfaces, collections, events
 import sqlalchemy.exceptions as sa_exc
 
 # lazy imports
@@ -52,75 +52,17 @@ PASSIVE_OFF = False #util.symbol('PASSIVE_OFF')
 """Symbol indicating that loader callables should be executed."""
 
 
-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):
-        """Construct an InstrumentedAttribute.
-
-          comparator
-            a sql.Comparator to which class-level compare/math events will be
-            sent
-        """
+    """Base class for class-bound attributes. """
+    
+    def __init__(self, class_, key, impl=None, comparator=None, parententity=None):
+        self.class_ = class_
         self.key = key
         self.impl = impl
         self.comparator = comparator
         self.parententity = parententity
 
-    dispatch = event.dispatcher(AttributeEvents)
+    dispatch = event.dispatcher(events.AttributeEvents)
     dispatch.dispatch_cls.active_history = False
     
     def get_history(self, instance, **kwargs):
@@ -166,7 +108,7 @@ class QueryableAttribute(interfaces.PropComparator):
 
 
 class InstrumentedAttribute(QueryableAttribute):
-    """Public-facing descriptor, placed in the mapped class dictionary."""
+    """Class bound instrumented attribute which adds descriptor methods."""
 
     def __set__(self, instance, value):
         self.impl.set(instance_state(instance), 
@@ -1121,7 +1063,7 @@ def register_attribute_impl(class_, key,
         impl = ScalarAttributeImpl(class_, key, callable_, dispatch, **kw)
 
     manager[key].impl = impl
-    
+
     manager.post_configure_attribute(key)
 
     
@@ -1133,7 +1075,7 @@ def register_descriptor(class_, key, proxy_property=None, comparator=None,
         proxy_type = proxied_attribute_factory(proxy_property)
         descriptor = proxy_type(key, proxy_property, comparator, parententity)
     else:
-        descriptor = InstrumentedAttribute(key, comparator=comparator,
+        descriptor = InstrumentedAttribute(class_, key, comparator=comparator,
                                             parententity=parententity)
     
     descriptor.__doc__ = doc
index fc5a74a32ed6e0be0a870481343dd7a5c146a242..86f6ed74ec533fb7ada90bbe056d93e2bff323dd 100644 (file)
@@ -385,12 +385,14 @@ class AttributeExtension(object):
     @classmethod
     def _adapt_listener(cls, self, listener):
         event.listen(listener.append, 'on_append', self,
-                            active_history=listener.active_history)
+                            active_history=listener.active_history,
+                            raw=True, retval=True)
         event.listen(listener.remove, 'on_remove', self,
-                            active_history=listener.active_history)
+                            active_history=listener.active_history, 
+                            raw=True, retval=True)
         event.listen(listener.set, 'on_set', self,
-                            active_history=listener.active_history)
-        
+                            active_history=listener.active_history
+                            raw=True, retval=True)
     
     def append(self, state, value, initiator):
         """Receive a collection append event.
index d9246c83ebc67348799c01f772135e0785ea55ae..29274ac3b56563e656624c17c13666e83e07e31d 100644 (file)
@@ -1,12 +1,67 @@
 """ORM event interfaces.
 
 """
-from sqlalchemy import event
+from sqlalchemy import event, util, exc
 
-class ClassEvents(event.Events):
+class InstrumentationEvents(event.Events):
+    """Events related to class instrumentation events.
+    
+    The listeners here support being established against
+    any new style class, that is any object that is a subclass
+    of 'type'.  Events will then be fired off for events
+    against that class as well as all subclasses.  
+    'type' itself is also accepted as a target
+    in which case the events fire for all classes.
+    
+    """
+    
     @classmethod
     def accept_with(cls, target):
-        from sqlalchemy.orm.instrumentation import ClassManager
+        from sqlalchemy.orm.instrumentation import instrumentation_registry
+        
+        if isinstance(target, type):
+            return [instrumentation_registry]
+        else:
+            return []
+
+    @classmethod
+    def listen(cls, fn, identifier, target):
+        
+        @util.decorator
+        def adapt_to_target(fn, cls, *arg):
+            if issubclass(cls, target):
+                fn(cls, *arg)
+        event.Events.listen(fn, identifier, target)
+
+    @classmethod
+    def remove(cls, fn, identifier, target):
+        raise NotImplementedError("Removal of instrumentation events not yet implemented")
+
+    def on_class_instrument(self, cls):
+        """Called after the given class is instrumented.
+        
+        To get at the :class:`.ClassManager`, use
+        :func:`.manager_of_class`.
+        
+        """
+
+    def on_class_uninstrument(self, cls):
+        """Called before the given class is uninstrumented.
+        
+        To get at the :class:`.ClassManager`, use
+        :func:`.manager_of_class`.
+        
+        """
+        
+        
+    def on_attribute_instrument(self, cls, key, inst):
+        """Called when an attribute is instrumented."""
+
+class InstanceEvents(event.Events):
+    
+    @classmethod
+    def accept_with(cls, target):
+        from sqlalchemy.orm.instrumentation import ClassManager, manager_of_class
         
         if isinstance(target, ClassManager):
             return [target]
@@ -15,36 +70,147 @@ class ClassEvents(event.Events):
             if manager:
                 return [manager]
         return []
+    
+    @classmethod
+    def listen(cls, fn, identifier, target, raw=False):
+        if not raw:
+            fn = _to_instance(fn)
+        event.Events.listen(fn, identifier, target)
+
+    @classmethod
+    def remove(cls, fn, identifier, target):
+        raise NotImplementedError("Removal of instance events not yet implemented")
         
-    # TODO: change these to accept "target" - 
-    # the target is the state or the instance, depending
-    # on if the listener was registered with "raw=True" -
-    # do the same thing for all the other events here (Mapper, Session, Attributes).  
-    # Not sending raw=True means the listen()  method of the
-    # Events subclass will wrap incoming listeners to marshall each
-    # "target" argument into "instance".  The name "target" can be
-    # used consistently to make it simple.
-    #
-    # this way end users don't have to deal with InstanceState and
-    # the internals can have faster performance.
-    
-    def on_init(self, state, instance, args, kwargs):
+    def on_init(self, target, args, kwargs):
         """"""
         
-    def on_init_failure(self, state, instance, args, kwargs):
+    def on_init_failure(self, target, args, kwargs):
         """"""
     
-    def on_load(self, instance):
+    def on_load(self, target):
         """"""
     
-    def on_resurrect(self, state, instance):
+    def on_resurrect(self, target):
         """"""
 
+        
 class MapperEvents(event.Events):
     """"""
+    @classmethod
+    def remove(cls, fn, identifier, target):
+        raise NotImplementedError("Removal of mapper events not yet implemented")
     
 class SessionEvents(event.Events):
     """"""
-    
+    @classmethod
+    def remove(cls, fn, identifier, target):
+        raise NotImplementedError("Removal of session events not yet implemented")
+
 class AttributeEvents(event.Events):
-    """"""
\ No newline at end of file
+    """Define events for object attributes.
+
+    e.g.::
+    
+        from sqlalchemy import event
+        event.listen(my_append_listener, 'on_append', MyClass.collection)
+        event.listen(my_set_listener, 'on_set', 
+                                MyClass.somescalar, retval=True)
+        
+    Several modifiers are available to the listen() function.
+    
+    :param active_history=False: When True, indicates that the
+      "on_set" event would like to receive the "old" value 
+      being replaced unconditionally, even if this requires
+      firing off database loads.
+    :param propagate=False: When True, the listener function will
+      be established not just for the class attribute given, but
+      for attributes of the same name on all current subclasses 
+      of that class, as well as all future subclasses of that 
+      class, using an additional listener that listens for 
+      instrumentation events.
+    :param raw=False: When True, the "target" argument to the
+      event will be the :class:`.InstanceState` management
+      object, rather than the mapped instance itself.
+    :param retval=False:` when True, the user-defined event 
+      listening must return the "value" argument from the 
+      function.  This gives the listening function the opportunity
+      to change the value that is ultimately used for a "set"
+      or "append" event.   
+    
+    """
+    
+    @classmethod
+    def listen(cls, fn, identifier, target, active_history=False, 
+                                        raw=False, retval=False,
+                                        propagate=False):
+        if active_history:
+            target.dispatch.active_history = True
+        
+        # TODO: for removal, need to package the identity
+        # of the wrapper with the original function.
+        
+        if raw is False or retval is False:
+            @util.decorator
+            def wrap(fn, target, value, *arg):
+                if not raw:
+                    target = target.obj()
+                if not retval:
+                    fn(target, value, *arg)
+                    return value
+                else:
+                    return fn(target, value, *arg)
+            fn = wrap(fn)
+            
+        event.Events.listen(fn, identifier, target)
+        
+        if propagate:
+            # TODO: for removal, need to implement
+            # packaging this info for operation in reverse.
+
+            class_ = target.class_
+            for cls in class_.__subclasses__():
+                impl = getattr(cls, target.key)
+                if impl is not target:
+                    event.Events.listen(fn, identifier, impl)
+            
+            def configure_listener(class_, key, inst):
+                event.Events.listen(fn, identifier, inst)
+            event.listen(configure_listener, 'on_attribute_instrument', class_)
+        
+    @classmethod
+    def remove(cls, fn, identifier, target):
+        raise NotImplementedError("Removal of attribute events not yet implemented")
+        
+    @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.
+
+        """
+
+@util.decorator
+def _to_instance(fn, state, *arg, **kw):
+    """Marshall the :class:`.InstanceState` argument to an instance."""
+    
+    return fn(state.obj(), *arg, **kw)
+    
index 3f134c58abc078f71ea2c6b806ad6f7bfe923557..02ba5e1a22d1a1d05732a304921019f0764ebce3 100644 (file)
@@ -92,7 +92,7 @@ class ClassManager(dict):
         self.manage()
         self._instrument_init()
     
-    dispatch = event.dispatcher(events.ClassEvents)
+    dispatch = event.dispatcher(events.InstanceEvents)
     
     @property
     def is_mapped(self):
@@ -195,7 +195,8 @@ class ClassManager(dict):
             manager.instrument_attribute(key, inst, True)
 
     def post_configure_attribute(self, key):
-        pass
+        instrumentation_registry.dispatch.\
+                on_attribute_instrument(self.class_, key, self[key])
         
     def uninstrument_attribute(self, key, propagated=False):
         if key not in self:
@@ -360,6 +361,7 @@ class _ClassInstrumentationAdapter(ClassManager):
             self._adapted.instrument_attribute(self.class_, key, inst)
 
     def post_configure_attribute(self, key):
+        super(_ClassInstrumentationAdpter, self).post_configure_attribute(key)
         self._adapted.post_configure_attribute(self.class_, key, self[key])
 
     def install_descriptor(self, key, inst):
@@ -470,6 +472,8 @@ class InstrumentationRegistry(object):
     _dict_finders = util.WeakIdentityMapping()
     _extended = False
 
+    dispatch = event.dispatcher(events.InstrumentationEvents)
+
     def create_manager_for_cls(self, class_, **kw):
         assert class_ is not None
         assert manager_of_class(class_) is None
@@ -506,6 +510,9 @@ class InstrumentationRegistry(object):
         self._manager_finders[class_] = manager.manager_getter()
         self._state_finders[class_] = manager.state_getter()
         self._dict_finders[class_] = manager.dict_getter()
+        
+        self.dispatch.on_class_instrument(class_)
+        
         return manager
 
     def _collect_management_factories_for(self, cls):
@@ -572,6 +579,7 @@ class InstrumentationRegistry(object):
     def unregister(self, class_):
         if class_ in self._manager_finders:
             manager = self.manager_of_class(class_)
+            self.dispatch.on_class_uninstrument(class_)
             manager.unregister()
             manager.dispose()
             del self._manager_finders[class_]
index b3aa7fb29497cf9cb07b66603e62dfa0bd82b279..a40021663351b75c5996390345c205a1e9c53f18 100644 (file)
@@ -405,14 +405,14 @@ class Mapper(object):
         if manager.info.get(_INSTRUMENTOR, False):
             return
 
-        event.listen(_event_on_init, 'on_init', manager)
-        event.listen(_event_on_init_failure, 'on_init_failure', manager)
-        event.listen(_event_on_resurrect, 'on_resurrect', manager)
+        event.listen(_event_on_init, 'on_init', manager, raw=True)
+        event.listen(_event_on_init_failure, 'on_init_failure', manager, raw=True)
+        event.listen(_event_on_resurrect, 'on_resurrect', manager, raw=True)
         
         for key, method in util.iterate_attributes(self.class_):
             if isinstance(method, types.FunctionType):
                 if hasattr(method, '__sa_reconstructor__'):
-                    event.listen(method, 'on_load', manager)
+                    event.listen(method, 'on_load', manager, raw=True)
                 elif hasattr(method, '__sa_validators__'):
                     for name in method.__sa_validators__:
                         self._validators[name] = method
@@ -420,7 +420,7 @@ class Mapper(object):
         if 'reconstruct_instance' in self.extension:
             def reconstruct(instance):
                 self.extension.reconstruct_instance(self, instance)
-            event.listen(reconstruct, 'on_load', manager)
+            event.listen(reconstruct, 'on_load', manager, raw=False)
 
         manager.info[_INSTRUMENTOR] = self
 
@@ -2296,7 +2296,7 @@ class Mapper(object):
                         populate_state(state, dict_, row, isnew, attrs)
 
             if loaded_instance:
-                state._run_on_load(instance)
+                state._run_on_load()
 
             if result is not None and \
                         (not append_result or 
@@ -2394,7 +2394,7 @@ def validates(*names):
         return fn
     return wrap
 
-def _event_on_init(state, instance, args, kwargs):
+def _event_on_init(state, args, kwargs):
     """Trigger mapper compilation and run init_instance hooks."""
 
     instrumenting_mapper = state.manager.info[_INSTRUMENTOR]
@@ -2404,9 +2404,9 @@ def _event_on_init(state, instance, args, kwargs):
         instrumenting_mapper.extension.init_instance(
             instrumenting_mapper, instrumenting_mapper.class_,
             state.manager.original_init,
-            instance, args, kwargs)
+            state.obj(), args, kwargs)
 
-def _event_on_init_failure(state, instance, args, kwargs):
+def _event_on_init_failure(state, args, kwargs):
     """Run init_failed hooks."""
 
     instrumenting_mapper = state.manager.info[_INSTRUMENTOR]
@@ -2414,9 +2414,9 @@ def _event_on_init_failure(state, instance, args, kwargs):
         util.warn_exception(
             instrumenting_mapper.extension.init_failed,
             instrumenting_mapper, instrumenting_mapper.class_,
-            state.manager.original_init, instance, args, kwargs)
+            state.manager.original_init, state.obj(), args, kwargs)
 
-def _event_on_resurrect(state, instance):
+def _event_on_resurrect(state):
     # re-populate the primary key elements
     # of the dict based on the mapping.
     instrumenting_mapper = state.manager.info[_INSTRUMENTOR]
index 95d29812e576b4df82989675c92bf94d280b86d5..3444c12ac0c47c585b88ae4ce8d72e4c9e4d5993 100644 (file)
@@ -1243,7 +1243,7 @@ class Session(object):
             merged_state.commit_all(merged_dict, self.identity_map)  
 
         if new_instance:
-            merged_state._run_on_load(merged)
+            merged_state._run_on_load()
         return merged
 
     @classmethod
index a8f03102da7f49c519f9dc7efd123c41ff63f804..42fc5b98ee63db6e2165a26b50a1d89f1bf6c386 100644 (file)
@@ -96,7 +96,7 @@ class InstanceState(object):
         self, instance, args = mixed[0], mixed[1], mixed[2:]
         manager = self.manager
 
-        manager.dispatch.on_init(self, instance, args, kwargs)
+        manager.dispatch.on_init(self, args, kwargs)
             
         # LESSTHANIDEAL:
         # adjust for the case where the InstanceState was created before
@@ -109,7 +109,7 @@ class InstanceState(object):
         try:
             return manager.original_init(*mixed[1:], **kwargs)
         except:
-            manager.dispatch.on_init_failure(self, instance, args, kwargs)
+            manager.dispatch.on_init_failure(self, args, kwargs)
             raise
 
     def get_history(self, key, **kwargs):
@@ -142,8 +142,8 @@ class InstanceState(object):
         else:
             return [x]
 
-    def _run_on_load(self, instance):
-        self.manager.dispatch.on_load(instance)
+    def _run_on_load(self):
+        self.manager.dispatch.on_load(self)
     
     def __getstate__(self):
         d = {'instance':self.obj()}
@@ -511,11 +511,6 @@ class MutableAttrInstanceState(InstanceState):
         # re-establishes identity attributes from the key
         self.manager.dispatch.on_resurrect(self, obj)
         
-        # TODO: don't really think we should run this here.
-        # resurrect is only meant to preserve the minimal state needed to
-        # do an UPDATE, not to produce a fully usable object
-        self._run_on_load(obj)
-        
         return obj
 
 class PendingCollection(object):
index f79a8449fc4a5a179bcc9a78772278193163043e..8ec161e99522642a802786ffaf1db6ebabc52dbb 100644 (file)
@@ -341,7 +341,7 @@ class AliasedClass(object):
         existing = getattr(self.__target, prop.key)
         comparator = existing.comparator.adapted(self.__adapt_element)
 
-        queryattr = attributes.QueryableAttribute(prop.key,
+        queryattr = attributes.QueryableAttribute(self, prop.key,
             impl=existing.impl, parententity=self, comparator=comparator)
         setattr(self, prop.key, queryattr)
         return queryattr
index 0653c15e90fd05daf1790d524b55deb6993d3584..7ffee2a2e5d5aa7be6afb51479f8013ec1037590 100644 (file)
@@ -5,7 +5,7 @@ from sqlalchemy import MetaData, Integer, ForeignKey, util, event
 from sqlalchemy.test.schema import Table
 from sqlalchemy.test.schema import Column
 from sqlalchemy.orm import mapper, relationship, create_session, \
-    attributes, class_mapper, clear_mappers, instrumentation
+    attributes, class_mapper, clear_mappers, instrumentation, events
 from sqlalchemy.test.testing import eq_, ne_
 from sqlalchemy.util import function_named
 from test.orm import _base
@@ -46,9 +46,9 @@ class InitTest(_base.ORMTest):
         instrumentation.register_class(cls)
         ne_(cls.__init__, original_init)
         manager = instrumentation.manager_of_class(cls)
-        def on_init(state, instance, args, kwargs):
-            canary.append((cls, 'on_init', type(instance)))
-        event.listen(on_init, 'on_init', manager)
+        def on_init(state, args, kwargs):
+            canary.append((cls, 'on_init', state.class_))
+        event.listen(on_init, 'on_init', manager, raw=True)
 
     def test_ai(self):
         inits = []
@@ -573,7 +573,7 @@ class ExtendedEventsTest(_base.ORMTest):
 
     @modifies_instrumentation_finders
     def test_subclassed(self):
-        class MyEvents(instrumentation.ClassEvents):
+        class MyEvents(events.InstanceEvents):
             pass
         class MyClassManager(instrumentation.ClassManager):
             dispatch = event.dispatcher(MyEvents)