summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2010-10-02 13:31:07 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2010-10-02 13:31:07 -0400
commitd46985d699e6ebffe45c94d91cfa842271e06bb0 (patch)
treeb04fbb7dcc96afc2c0c3973e77e53ef2cbcc7643
parent25c08f6def19e1034a887e972ad286ef122473d0 (diff)
downloadsqlalchemy-d46985d699e6ebffe45c94d91cfa842271e06bb0.tar.gz
- add instrumentation events
- 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.
-rw-r--r--examples/custom_attributes/listen_for_events.py50
-rw-r--r--lib/sqlalchemy/event.py33
-rw-r--r--lib/sqlalchemy/orm/attributes.py76
-rw-r--r--lib/sqlalchemy/orm/deprecated_interfaces.py10
-rw-r--r--lib/sqlalchemy/orm/events.py208
-rw-r--r--lib/sqlalchemy/orm/instrumentation.py12
-rw-r--r--lib/sqlalchemy/orm/mapper.py22
-rw-r--r--lib/sqlalchemy/orm/session.py2
-rw-r--r--lib/sqlalchemy/orm/state.py13
-rw-r--r--lib/sqlalchemy/orm/util.py2
-rw-r--r--test/orm/test_instrumentation.py10
11 files changed, 277 insertions, 161 deletions
diff --git a/examples/custom_attributes/listen_for_events.py b/examples/custom_attributes/listen_for_events.py
index 6d467fbbc..e66ebd090 100644
--- a/examples/custom_attributes/listen_for_events.py
+++ b/examples/custom_attributes/listen_for_events.py
@@ -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"
diff --git a/lib/sqlalchemy/event.py b/lib/sqlalchemy/event.py
index c1bc54b17..c7df2bf48 100644
--- a/lib/sqlalchemy/event.py
+++ b/lib/sqlalchemy/event.py
@@ -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
diff --git a/lib/sqlalchemy/orm/attributes.py b/lib/sqlalchemy/orm/attributes.py
index 46b5a5dca..6bc15d8f5 100644
--- a/lib/sqlalchemy/orm/attributes.py
+++ b/lib/sqlalchemy/orm/attributes.py
@@ -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
diff --git a/lib/sqlalchemy/orm/deprecated_interfaces.py b/lib/sqlalchemy/orm/deprecated_interfaces.py
index fc5a74a32..86f6ed74e 100644
--- a/lib/sqlalchemy/orm/deprecated_interfaces.py
+++ b/lib/sqlalchemy/orm/deprecated_interfaces.py
@@ -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.
diff --git a/lib/sqlalchemy/orm/events.py b/lib/sqlalchemy/orm/events.py
index d9246c83e..29274ac3b 100644
--- a/lib/sqlalchemy/orm/events.py
+++ b/lib/sqlalchemy/orm/events.py
@@ -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)
+
diff --git a/lib/sqlalchemy/orm/instrumentation.py b/lib/sqlalchemy/orm/instrumentation.py
index 3f134c58a..02ba5e1a2 100644
--- a/lib/sqlalchemy/orm/instrumentation.py
+++ b/lib/sqlalchemy/orm/instrumentation.py
@@ -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_]
diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py
index b3aa7fb29..a40021663 100644
--- a/lib/sqlalchemy/orm/mapper.py
+++ b/lib/sqlalchemy/orm/mapper.py
@@ -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]
diff --git a/lib/sqlalchemy/orm/session.py b/lib/sqlalchemy/orm/session.py
index 95d29812e..3444c12ac 100644
--- a/lib/sqlalchemy/orm/session.py
+++ b/lib/sqlalchemy/orm/session.py
@@ -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
diff --git a/lib/sqlalchemy/orm/state.py b/lib/sqlalchemy/orm/state.py
index a8f03102d..42fc5b98e 100644
--- a/lib/sqlalchemy/orm/state.py
+++ b/lib/sqlalchemy/orm/state.py
@@ -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):
diff --git a/lib/sqlalchemy/orm/util.py b/lib/sqlalchemy/orm/util.py
index f79a8449f..8ec161e99 100644
--- a/lib/sqlalchemy/orm/util.py
+++ b/lib/sqlalchemy/orm/util.py
@@ -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
diff --git a/test/orm/test_instrumentation.py b/test/orm/test_instrumentation.py
index 0653c15e9..7ffee2a2e 100644
--- a/test/orm/test_instrumentation.py
+++ b/test/orm/test_instrumentation.py
@@ -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)