summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy
diff options
context:
space:
mode:
Diffstat (limited to 'lib/sqlalchemy')
-rw-r--r--lib/sqlalchemy/event.py41
-rw-r--r--lib/sqlalchemy/orm/events.py57
-rw-r--r--lib/sqlalchemy/orm/instrumentation.py2
-rw-r--r--lib/sqlalchemy/orm/state.py2
4 files changed, 67 insertions, 35 deletions
diff --git a/lib/sqlalchemy/event.py b/lib/sqlalchemy/event.py
index c7df2bf48..0f6342e6b 100644
--- a/lib/sqlalchemy/event.py
+++ b/lib/sqlalchemy/event.py
@@ -11,20 +11,13 @@ CANCEL = util.symbol('CANCEL')
NO_RETVAL = util.symbol('NO_RETVAL')
def listen(fn, identifier, target, *args, **kw):
- """Listen for events, accepting an event function that's "raw".
- Only the exact arguments are received in order.
-
- This is used by SQLA internals simply to reduce the overhead
- of creating an event dictionary for each event call.
+ """Register a listener function for the given target.
"""
-
- # rationale - the events on ClassManager, Session, and Mapper
- # will need to accept mapped classes directly as targets and know
- # what to do
for evt_cls in _registrars[identifier]:
- for tgt in evt_cls.accept_with(target):
+ tgt = evt_cls.accept_with(target)
+ if tgt is not None:
tgt.dispatch.listen(fn, identifier, tgt, *args, **kw)
return
raise exc.InvalidRequestError("No such event %s for target %s" %
@@ -41,10 +34,18 @@ def remove(fn, identifier, target):
for evt_cls in _registrars[identifier]:
for tgt in evt_cls.accept_with(target):
tgt.dispatch.remove(fn, identifier, tgt, *args, **kw)
-
+ return
_registrars = util.defaultdict(list)
+class _UnpickleDispatch(object):
+ """Serializable callable that re-generates an instance of :class:`_Dispatch`
+ given a particular :class:`.Events` subclass.
+
+ """
+ def __call__(self, parent_cls):
+ return parent_cls.__dict__['dispatch'].dispatch_cls(parent_cls)
+
class _Dispatch(object):
"""Mirror the event listening definitions of an Events class with
listener collections.
@@ -55,9 +56,8 @@ class _Dispatch(object):
self.parent_cls = parent_cls
def __reduce__(self):
- return dispatcher, (
- self.parent_cls.__dict__['dispatch'].events,
- )
+
+ return _UnpickleDispatch(), (self.parent_cls, )
@property
def descriptors(self):
@@ -81,7 +81,7 @@ class _EventMeta(type):
def __init__(cls, classname, bases, dict_):
_create_dispatcher_class(cls, classname, bases, dict_)
return type.__init__(cls, classname, bases, dict_)
-
+
def _create_dispatcher_class(cls, classname, bases, dict_):
# there's all kinds of ways to do this,
# i.e. make a Dispatch class that shares the 'listen' method
@@ -96,6 +96,13 @@ def _create_dispatcher_class(cls, classname, bases, dict_):
setattr(dispatch_cls, k, _DispatchDescriptor(dict_[k]))
_registrars[k].append(cls)
+def _remove_dispatcher(cls):
+ for k in dir(cls):
+ if k.startswith('on_'):
+ _registrars[k].remove(cls)
+ if not _registrars[k]:
+ del _registrars[k]
+
class Events(object):
"""Define event listening functions for a particular target type."""
@@ -111,9 +118,9 @@ class Events(object):
isinstance(target.dispatch, type) and \
issubclass(target.dispatch, cls.dispatch)
):
- return [target]
+ return target
else:
- return []
+ return None
@classmethod
def listen(cls, fn, identifier, target):
diff --git a/lib/sqlalchemy/orm/events.py b/lib/sqlalchemy/orm/events.py
index 29274ac3b..ff9f7dbc6 100644
--- a/lib/sqlalchemy/orm/events.py
+++ b/lib/sqlalchemy/orm/events.py
@@ -20,9 +20,9 @@ class InstrumentationEvents(event.Events):
from sqlalchemy.orm.instrumentation import instrumentation_registry
if isinstance(target, type):
- return [instrumentation_registry]
+ return instrumentation_registry
else:
- return []
+ return None
@classmethod
def listen(cls, fn, identifier, target):
@@ -64,12 +64,12 @@ class InstanceEvents(event.Events):
from sqlalchemy.orm.instrumentation import ClassManager, manager_of_class
if isinstance(target, ClassManager):
- return [target]
+ return target
elif isinstance(target, type):
manager = manager_of_class(target)
if manager:
- return [manager]
- return []
+ return manager
+ return None
@classmethod
def listen(cls, fn, identifier, target, raw=False):
@@ -185,26 +185,51 @@ class AttributeEvents(event.Events):
def unwrap(cls, identifier, event):
return event['value']
- def on_append(self, state, value, initiator):
+ def on_append(self, target, value, initiator):
"""Receive a collection append event.
- The returned value will be used as the actual value to be
- appended.
+ :param target: the object instance receiving the event.
+ If the listener is registered with ``raw=True``, this will
+ be the :class:`.InstanceState` object.
+ :param value: the value being appended. If this listener
+ is registered with ``retval=True``, the listener
+ function must return this value, or a new value which
+ replaces it.
+ :param initiator: the attribute implementation object
+ which initiated this event.
"""
- def on_remove(self, state, value, initiator):
- """Receive a remove event.
+ def on_remove(self, target, value, initiator):
+ """Receive a collection remove event.
- No return value is defined.
+ :param target: the object instance receiving the event.
+ If the listener is registered with ``raw=True``, this will
+ be the :class:`.InstanceState` object.
+ :param value: the value being removed.
+ :param initiator: the attribute implementation object
+ which initiated this event.
"""
- 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.
+ def on_set(self, target, value, oldvalue, initiator):
+ """Receive a scalar set event.
+
+ :param target: the object instance receiving the event.
+ If the listener is registered with ``raw=True``, this will
+ be the :class:`.InstanceState` object.
+ :param value: the value being set. If this listener
+ is registered with ``retval=True``, the listener
+ function must return this value, or a new value which
+ replaces it.
+ :param oldvalue: the previous value being replaced. This
+ may also be the symbol ``NEVER_SET`` or ``NO_VALUE``.
+ If the listener is registered with ``active_history=True``,
+ the previous value of the attribute will be loaded from
+ the database if the existing value is currently unloaded
+ or expired.
+ :param initiator: the attribute implementation object
+ which initiated this event.
"""
diff --git a/lib/sqlalchemy/orm/instrumentation.py b/lib/sqlalchemy/orm/instrumentation.py
index 02ba5e1a2..52c1c7213 100644
--- a/lib/sqlalchemy/orm/instrumentation.py
+++ b/lib/sqlalchemy/orm/instrumentation.py
@@ -361,7 +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)
+ super(_ClassInstrumentationAdapter, self).post_configure_attribute(key)
self._adapted.post_configure_attribute(self.class_, key, self[key])
def install_descriptor(self, key, inst):
diff --git a/lib/sqlalchemy/orm/state.py b/lib/sqlalchemy/orm/state.py
index 42fc5b98e..dc8a07c17 100644
--- a/lib/sqlalchemy/orm/state.py
+++ b/lib/sqlalchemy/orm/state.py
@@ -509,7 +509,7 @@ class MutableAttrInstanceState(InstanceState):
obj.__dict__.update(self.mutable_dict)
# re-establishes identity attributes from the key
- self.manager.dispatch.on_resurrect(self, obj)
+ self.manager.dispatch.on_resurrect(self)
return obj