diff options
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/sqlalchemy/event.py | 41 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/events.py | 57 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/instrumentation.py | 2 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/state.py | 2 |
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 |
