From 9c6e45ff0157cdd4139cdeb68d38867e2baeaeb5 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Thu, 18 Jul 2013 23:17:33 -0400 Subject: Fixed bug in ORM-level event registration where the "raw" or "propagate" flags could potentially be mis-configured in some "unmapped base class" configurations. Also in 0.8.3. [ticket:2786] --- lib/sqlalchemy/orm/events.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) (limited to 'lib/sqlalchemy/orm/events.py') diff --git a/lib/sqlalchemy/orm/events.py b/lib/sqlalchemy/orm/events.py index 97019bb4e..14b3a770e 100644 --- a/lib/sqlalchemy/orm/events.py +++ b/lib/sqlalchemy/orm/events.py @@ -360,14 +360,17 @@ class _EventsHold(object): def populate(cls, class_, subject): for subclass in class_.__mro__: if subclass in cls.all_holds: - if subclass is class_: - collection = cls.all_holds.pop(subclass) - else: - collection = cls.all_holds[subclass] + collection = cls.all_holds[subclass] for ident, fn, raw, propagate in collection: if propagate or subclass is class_: + # since we can't be sure in what order different classes + # in a hierarchy are triggered with populate(), + # we rely upon _EventsHold for all event + # assignment, instead of using the generic propagate + # flag. subject.dispatch._listen(subject, ident, - fn, raw, propagate) + fn, raw=raw, + propagate=False) class _InstanceEventsHold(_EventsHold): -- cgit v1.2.1 From 550141b14c8e165218cd32c27d91541eeee86d2a Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Fri, 26 Jul 2013 00:01:04 -0400 Subject: - The mechanism by which attribute events pass along an :class:`.AttributeImpl` as an "initiator" token has been changed; the object is now an event-specific object called :class:`.attributes.Event`. Additionally, the attribute system no longer halts events based on a matching "initiator" token; this logic has been moved to be specific to ORM backref event handlers, which are the typical source of the re-propagation of an attribute event onto subsequent append/set/remove operations. End user code which emulates the behavior of backrefs must now ensure that recursive event propagation schemes are halted, if the scheme does not use the backref handlers. Using this new system, backref handlers can now peform a "two-hop" operation when an object is appended to a collection, associated with a new many-to-one, de-associated with the previous many-to-one, and then removed from a previous collection. Before this change, the last step of removal from the previous collection would not occur. [ticket:2789] --- lib/sqlalchemy/orm/events.py | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) (limited to 'lib/sqlalchemy/orm/events.py') diff --git a/lib/sqlalchemy/orm/events.py b/lib/sqlalchemy/orm/events.py index 14b3a770e..9a7190746 100644 --- a/lib/sqlalchemy/orm/events.py +++ b/lib/sqlalchemy/orm/events.py @@ -1561,8 +1561,15 @@ class AttributeEvents(event.Events): 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. + :param initiator: An instance of :class:`.attributes.Event` + representing the initiation of the event. May be modified + from it's original value by backref handlers in order to control + chained event propagation. + + .. versionchanged:: 0.9.0 the ``initiator`` argument is now + passed as a :class:`.attributes.Event` object, and may be modified + by backref handlers within a chain of backref-linked events. + :return: if the event was registered with ``retval=True``, the given value, or a new effective value, should be returned. @@ -1575,8 +1582,15 @@ class AttributeEvents(event.Events): 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. + :param initiator: An instance of :class:`.attributes.Event` + representing the initiation of the event. May be modified + from it's original value by backref handlers in order to control + chained event propagation. + + .. versionchanged:: 0.9.0 the ``initiator`` argument is now + passed as a :class:`.attributes.Event` object, and may be modified + by backref handlers within a chain of backref-linked events. + :return: No return value is defined for this event. """ @@ -1596,8 +1610,15 @@ class AttributeEvents(event.Events): 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. + :param initiator: An instance of :class:`.attributes.Event` + representing the initiation of the event. May be modified + from it's original value by backref handlers in order to control + chained event propagation. + + .. versionchanged:: 0.9.0 the ``initiator`` argument is now + passed as a :class:`.attributes.Event` object, and may be modified + by backref handlers within a chain of backref-linked events. + :return: if the event was registered with ``retval=True``, the given value, or a new effective value, should be returned. -- cgit v1.2.1 From 4505425a38b079a8e2a59fdbe31bc033de25e871 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Fri, 26 Jul 2013 14:21:58 -0400 Subject: - Removal of event listeners is now implemented. The feature is provided via the :func:`.event.remove` function. [ticket:2268] - reorganization of event.py module into a package; with the addition of the docstring work as well as the new registry for removal, there's a lot more code now. the package separates concerns and provides a top-level doc for each subsection of functionality - the remove feature works by providing the EventKey object which associates the user-provided arguments to listen() with a global, weak-referencing registry. This registry stores a collection of _ListenerCollection and _DispatchDescriptor objects associated with each set of arguments, as well as the wrapped function which was applied to that collection. The EventKey can then be recreated for a removal, all the _ListenerCollection and _DispatchDescriptor objects are located, and the correct wrapped function is removed from each one. --- lib/sqlalchemy/orm/events.py | 105 +++++++++++++++++++++---------------------- 1 file changed, 52 insertions(+), 53 deletions(-) (limited to 'lib/sqlalchemy/orm/events.py') diff --git a/lib/sqlalchemy/orm/events.py b/lib/sqlalchemy/orm/events.py index 9a7190746..a846bb832 100644 --- a/lib/sqlalchemy/orm/events.py +++ b/lib/sqlalchemy/orm/events.py @@ -46,14 +46,15 @@ class InstrumentationEvents(event.Events): @classmethod def _accept_with(cls, target): - # TODO: there's no coverage for this if isinstance(target, type): return _InstrumentationEventsHold(target) else: return None @classmethod - def _listen(cls, target, identifier, fn, propagate=True): + def _listen(cls, event_key, propagate=True): + target, identifier, fn = \ + event_key.dispatch_target, event_key.identifier, event_key.fn def listen(target_cls, *arg): listen_cls = target() @@ -63,17 +64,16 @@ class InstrumentationEvents(event.Events): return fn(target_cls, *arg) def remove(ref): - event.Events._remove(orm.instrumentation._instrumentation_factory, - identifier, listen) + key = event.registry._EventKey(None, identifier, listen, + orm.instrumentation._instrumentation_factory) + getattr(orm.instrumentation._instrumentation_factory.dispatch, + identifier).remove(key) target = weakref.ref(target.class_, remove) - event.Events._listen(orm.instrumentation._instrumentation_factory, - identifier, listen) - @classmethod - def _remove(cls, identifier, target, fn): - raise NotImplementedError("Removal of instrumentation events " - "not yet implemented") + event_key.\ + with_dispatch_target(orm.instrumentation._instrumentation_factory).\ + with_wrapper(listen).base_listen() @classmethod def _clear(cls): @@ -176,23 +176,23 @@ class InstanceEvents(event.Events): return None @classmethod - def _listen(cls, target, identifier, fn, raw=False, propagate=False): + def _listen(cls, event_key, raw=False, propagate=False): + target, identifier, fn = \ + event_key.dispatch_target, event_key.identifier, event_key.fn + if not raw: orig_fn = fn def wrap(state, *arg, **kw): return orig_fn(state.obj(), *arg, **kw) fn = wrap + event_key = event_key.with_wrapper(fn) + + event_key.base_listen(propagate=propagate) - event.Events._listen(target, identifier, fn, propagate=propagate) if propagate: for mgr in target.subclass_managers(True): - event.Events._listen(mgr, identifier, fn, True) - - @classmethod - def _remove(cls, identifier, target, fn): - msg = "Removal of instance events not yet implemented" - raise NotImplementedError(msg) + event_key.with_dispatch_target(mgr).base_listen(propagate=True) @classmethod def _clear(cls): @@ -321,8 +321,7 @@ class InstanceEvents(event.Events): """ - -class _EventsHold(object): +class _EventsHold(event.RefCollection): """Hold onto listeners against unmapped, uninstrumented classes. Establish _listen() for that class' mapper/instrumentation when @@ -338,13 +337,17 @@ class _EventsHold(object): class HoldEvents(object): @classmethod - def _listen(cls, target, identifier, fn, raw=False, propagate=False): + def _listen(cls, event_key, raw=False, propagate=False): + target, identifier, fn = \ + event_key.dispatch_target, event_key.identifier, event_key.fn + if target.class_ in target.all_holds: collection = target.all_holds[target.class_] else: - collection = target.all_holds[target.class_] = [] + collection = target.all_holds[target.class_] = {} - collection.append((identifier, fn, raw, propagate)) + event.registry._stored_in_collection(event_key, target) + collection[event_key._key] = (event_key, raw, propagate) if propagate: stack = list(target.class_.__subclasses__()) @@ -353,24 +356,30 @@ class _EventsHold(object): stack.extend(subclass.__subclasses__()) subject = target.resolve(subclass) if subject is not None: - subject.dispatch._listen(subject, identifier, fn, - raw=raw, propagate=propagate) + event_key.with_dispatch_target(subject).\ + listen(raw=raw, propagate=propagate) + + def remove(self, event_key): + target, identifier, fn = \ + event_key.dispatch_target, event_key.identifier, event_key.fn + + collection = target.all_holds[target.class_] + del collection[event_key._key] @classmethod def populate(cls, class_, subject): for subclass in class_.__mro__: if subclass in cls.all_holds: collection = cls.all_holds[subclass] - for ident, fn, raw, propagate in collection: + for event_key, raw, propagate in collection.values(): if propagate or subclass is class_: # since we can't be sure in what order different classes # in a hierarchy are triggered with populate(), # we rely upon _EventsHold for all event # assignment, instead of using the generic propagate # flag. - subject.dispatch._listen(subject, ident, - fn, raw=raw, - propagate=False) + event_key.with_dispatch_target(subject).\ + listen(raw=raw, propagate=False) class _InstanceEventsHold(_EventsHold): @@ -477,8 +486,10 @@ class MapperEvents(event.Events): return target @classmethod - def _listen(cls, target, identifier, fn, + def _listen(cls, event_key, raw=False, retval=False, propagate=False): + target, identifier, fn = \ + event_key.dispatch_target, event_key.identifier, event_key.fn if not raw or not retval: if not raw: @@ -501,12 +512,13 @@ class MapperEvents(event.Events): else: return wrapped_fn(*arg, **kw) fn = wrap + event_key = event_key.with_wrapper(wrap) if propagate: for mapper in target.self_and_descendants: - event.Events._listen(mapper, identifier, fn, propagate=True) + event_key.with_dispatch_target(mapper).base_listen(propagate=True) else: - event.Events._listen(target, identifier, fn) + event_key.base_listen() @classmethod def _clear(cls): @@ -1051,11 +1063,6 @@ class MapperEvents(event.Events): """ - @classmethod - def _remove(cls, identifier, target, fn): - "Removal of mapper events not yet implemented" - raise NotImplementedError(msg) - class _MapperEventsHold(_EventsHold): all_holds = weakref.WeakKeyDictionary() @@ -1123,11 +1130,6 @@ class SessionEvents(event.Events): else: return None - @classmethod - def _remove(cls, identifier, target, fn): - msg = "Removal of session events not yet implemented" - raise NotImplementedError(msg) - def after_transaction_create(self, session, transaction): """Execute when a new :class:`.SessionTransaction` is created. @@ -1516,15 +1518,16 @@ class AttributeEvents(event.Events): return target @classmethod - def _listen(cls, target, identifier, fn, active_history=False, + def _listen(cls, event_key, active_history=False, raw=False, retval=False, propagate=False): + + target, identifier, fn = \ + event_key.dispatch_target, event_key.identifier, event_key.fn + if active_history: target.dispatch._active_history = True - # TODO: for removal, need to package the identity - # of the wrapper with the original function. - if not raw or not retval: orig_fn = fn @@ -1537,19 +1540,15 @@ class AttributeEvents(event.Events): else: return orig_fn(target, value, *arg) fn = wrap + event_key = event_key.with_wrapper(wrap) - event.Events._listen(target, identifier, fn, propagate) + event_key.base_listen(propagate=propagate) if propagate: manager = orm.instrumentation.manager_of_class(target.class_) for mgr in manager.subclass_managers(True): - event.Events._listen(mgr[target.key], identifier, fn, True) - - @classmethod - def _remove(cls, identifier, target, fn): - msg = "Removal of attribute events not yet implemented" - raise NotImplementedError(msg) + event_key.with_dispatch_target(mgr[target.key]).base_listen(propagate=True) def append(self, target, value, initiator): """Receive a collection append event. -- cgit v1.2.1 From c6fdfeed00e25988115254ee5ea8c05253bdbd9f Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Wed, 31 Jul 2013 19:05:58 -0400 Subject: - update ORM event docs to include that you can listen on an unmapped base, [ticket:2777] --- lib/sqlalchemy/orm/events.py | 61 +++++++++++++++++++++++--------------------- 1 file changed, 32 insertions(+), 29 deletions(-) (limited to 'lib/sqlalchemy/orm/events.py') diff --git a/lib/sqlalchemy/orm/events.py b/lib/sqlalchemy/orm/events.py index a846bb832..b38b64f24 100644 --- a/lib/sqlalchemy/orm/events.py +++ b/lib/sqlalchemy/orm/events.py @@ -121,21 +121,19 @@ class InstanceEvents(event.Events): def my_load_listener(target, context): print "on load!" - event.listen(SomeMappedClass, 'load', my_load_listener) + event.listen(SomeClass, 'load', my_load_listener) - Available targets include mapped classes, instances of - :class:`.Mapper` (i.e. returned by :func:`.mapper`, - :func:`.class_mapper` and similar), as well as the - :class:`.Mapper` class and :func:`.mapper` function itself - for global event reception:: + Available targets include: - from sqlalchemy.orm import mapper + * mapped classes + * unmapped superclasses of mapped or to-be-mapped classes + (using the ``propagate=True`` flag) + * :class:`.Mapper` objects + * the :class:`.Mapper` class itself and the :func:`.mapper` + function indicate listening for all mappers. - def some_listener(target, context): - log.debug("Instance %s being loaded" % target) - - # attach to all mappers - event.listen(mapper, 'load', some_listener) + .. versionchanged:: 0.8.0 instance events can be associated with + unmapped superclasses of mapped classes. Instance events are closely related to mapper events, but are more specific to the instance and its instrumentation, @@ -154,7 +152,7 @@ class InstanceEvents(event.Events): """ - _target_class_doc = "SomeMappedClass" + _target_class_doc = "SomeClass" @classmethod def _accept_with(cls, target): @@ -408,24 +406,22 @@ class MapperEvents(event.Events): "select my_special_function(%d)" % target.special_number) - # associate the listener function with SomeMappedClass, + # associate the listener function with SomeClass, # to execute during the "before_insert" hook event.listen( - SomeMappedClass, 'before_insert', my_before_insert_listener) - - Available targets include mapped classes, instances of - :class:`.Mapper` (i.e. returned by :func:`.mapper`, - :func:`.class_mapper` and similar), as well as the - :class:`.Mapper` class and :func:`.mapper` function itself - for global event reception:: + SomeClass, 'before_insert', my_before_insert_listener) - from sqlalchemy.orm import mapper + Available targets include: - def some_listener(mapper, connection, target): - log.debug("Instance %s being inserted" % target) + * mapped classes + * unmapped superclasses of mapped or to-be-mapped classes + (using the ``propagate=True`` flag) + * :class:`.Mapper` objects + * the :class:`.Mapper` class itself and the :func:`.mapper` + function indicate listening for all mappers. - # attach to all mappers - event.listen(mapper, 'before_insert', some_listener) + .. versionchanged:: 0.8.0 mapper events can be associated with + unmapped superclasses of mapped classes. Mapper events provide hooks into critical sections of the mapper, including those related to object instrumentation, @@ -467,7 +463,7 @@ class MapperEvents(event.Events): """ - _target_class_doc = "SomeMappedClass" + _target_class_doc = "SomeClass" @classmethod def _accept_with(cls, target): @@ -532,8 +528,15 @@ class MapperEvents(event.Events): This event is the earliest phase of mapper construction. Most attributes of the mapper are not yet initialized. - This listener can generally only be applied to the :class:`.Mapper` - class overall. + This listener can either be applied to the :class:`.Mapper` + class overall, or to any un-mapped class which serves as a base + for classes that will be mapped (using the ``propagate=True`` flag):: + + Base = declarative_base() + + @event.listens_for(Base, "instrument_class", propagate=True) + def on_new_class(mapper, cls_): + " ... " :param mapper: the :class:`.Mapper` which is the target of this event. -- cgit v1.2.1 From 59141d360e70d1a762719206e3cb0220b4c53fef Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Wed, 14 Aug 2013 19:58:34 -0400 Subject: - apply an import refactoring to the ORM as well - rework the event system so that event modules load after their targets, dependencies are reversed - create an improved strategy lookup system for the ORM - rework the ORM to have very few import cycles - move out "importlater" to just util.dependency - other tricks to cross-populate modules in as clear a way as possible --- lib/sqlalchemy/orm/events.py | 94 +++++++++++++++++++++++++++++--------------- 1 file changed, 62 insertions(+), 32 deletions(-) (limited to 'lib/sqlalchemy/orm/events.py') diff --git a/lib/sqlalchemy/orm/events.py b/lib/sqlalchemy/orm/events.py index b38b64f24..e2ca39137 100644 --- a/lib/sqlalchemy/orm/events.py +++ b/lib/sqlalchemy/orm/events.py @@ -8,10 +8,15 @@ """ from .. import event, exc, util -orm = util.importlater("sqlalchemy", "orm") +from .base import _mapper_or_none import inspect import weakref - +from . import interfaces +from . import mapperlib, instrumentation +from .session import Session, sessionmaker +from .scoping import scoped_session +from .attributes import QueryableAttribute +from . import mapper as mapperfunc class InstrumentationEvents(event.Events): """Events related to class instrumentation events. @@ -43,6 +48,8 @@ class InstrumentationEvents(event.Events): """ _target_class_doc = "SomeBaseClass" + _dispatch_target = instrumentation.InstrumentationFactory + @classmethod def _accept_with(cls, target): @@ -65,20 +72,20 @@ class InstrumentationEvents(event.Events): def remove(ref): key = event.registry._EventKey(None, identifier, listen, - orm.instrumentation._instrumentation_factory) - getattr(orm.instrumentation._instrumentation_factory.dispatch, + instrumentation._instrumentation_factory) + getattr(instrumentation._instrumentation_factory.dispatch, identifier).remove(key) target = weakref.ref(target.class_, remove) event_key.\ - with_dispatch_target(orm.instrumentation._instrumentation_factory).\ + with_dispatch_target(instrumentation._instrumentation_factory).\ with_wrapper(listen).base_listen() @classmethod def _clear(cls): super(InstrumentationEvents, cls)._clear() - orm.instrumentation._instrumentation_factory.dispatch._clear() + instrumentation._instrumentation_factory.dispatch._clear() def class_instrument(self, cls): """Called after the given class is instrumented. @@ -100,6 +107,7 @@ class InstrumentationEvents(event.Events): """Called when an attribute is instrumented.""" + class _InstrumentationEventsHold(object): """temporary marker object used to transfer from _accept_with() to _listen() on the InstrumentationEvents class. @@ -110,7 +118,6 @@ class _InstrumentationEventsHold(object): dispatch = event.dispatcher(InstrumentationEvents) - class InstanceEvents(event.Events): """Define events specific to object lifecycle. @@ -154,19 +161,26 @@ class InstanceEvents(event.Events): _target_class_doc = "SomeClass" + _dispatch_target = instrumentation.ClassManager + @classmethod - def _accept_with(cls, target): - if isinstance(target, orm.instrumentation.ClassManager): + def _new_classmanager_instance(cls, class_, classmanager): + _InstanceEventsHold.populate(class_, classmanager) + + @classmethod + @util.dependencies("sqlalchemy.orm") + def _accept_with(cls, orm, target): + if isinstance(target, instrumentation.ClassManager): return target - elif isinstance(target, orm.Mapper): + elif isinstance(target, mapperlib.Mapper): return target.class_manager elif target is orm.mapper: - return orm.instrumentation.ClassManager + return instrumentation.ClassManager elif isinstance(target, type): - if issubclass(target, orm.Mapper): - return orm.instrumentation.ClassManager + if issubclass(target, mapperlib.Mapper): + return instrumentation.ClassManager else: - manager = orm.instrumentation.manager_of_class(target) + manager = instrumentation.manager_of_class(target) if manager: return manager else: @@ -334,6 +348,8 @@ class _EventsHold(event.RefCollection): cls.all_holds.clear() class HoldEvents(object): + _dispatch_target = None + @classmethod def _listen(cls, event_key, raw=False, propagate=False): target, identifier, fn = \ @@ -384,7 +400,7 @@ class _InstanceEventsHold(_EventsHold): all_holds = weakref.WeakKeyDictionary() def resolve(self, class_): - return orm.instrumentation.manager_of_class(class_) + return instrumentation.manager_of_class(class_) class HoldInstanceEvents(_EventsHold.HoldEvents, InstanceEvents): pass @@ -464,16 +480,22 @@ class MapperEvents(event.Events): """ _target_class_doc = "SomeClass" + _dispatch_target = mapperlib.Mapper @classmethod - def _accept_with(cls, target): + def _new_mapper_instance(cls, class_, mapper): + _MapperEventsHold.populate(class_, mapper) + + @classmethod + @util.dependencies("sqlalchemy.orm") + def _accept_with(cls, orm, target): if target is orm.mapper: - return orm.Mapper + return mapperlib.Mapper elif isinstance(target, type): - if issubclass(target, orm.Mapper): + if issubclass(target, mapperlib.Mapper): return target else: - mapper = orm.util._mapper_or_none(target) + mapper = _mapper_or_none(target) if mapper is not None: return mapper else: @@ -504,7 +526,7 @@ class MapperEvents(event.Events): arg[target_index] = arg[target_index].obj() if not retval: wrapped_fn(*arg, **kw) - return orm.interfaces.EXT_CONTINUE + return interfaces.EXT_CONTINUE else: return wrapped_fn(*arg, **kw) fn = wrap @@ -1066,12 +1088,11 @@ class MapperEvents(event.Events): """ - class _MapperEventsHold(_EventsHold): all_holds = weakref.WeakKeyDictionary() def resolve(self, class_): - return orm.util._mapper_or_none(class_) + return _mapper_or_none(class_) class HoldMapperEvents(_EventsHold.HoldEvents, MapperEvents): pass @@ -1106,29 +1127,31 @@ class SessionEvents(event.Events): _target_class_doc = "SomeSessionOrFactory" + _dispatch_target = Session + @classmethod def _accept_with(cls, target): - if isinstance(target, orm.scoped_session): + if isinstance(target, scoped_session): target = target.session_factory - if not isinstance(target, orm.sessionmaker) and \ + if not isinstance(target, sessionmaker) and \ ( not isinstance(target, type) or - not issubclass(target, orm.Session) + not issubclass(target, Session) ): raise exc.ArgumentError( "Session event listen on a scoped_session " "requires that its creation callable " "is associated with the Session class.") - if isinstance(target, orm.sessionmaker): + if isinstance(target, sessionmaker): return target.class_ elif isinstance(target, type): - if issubclass(target, orm.scoped_session): - return orm.Session - elif issubclass(target, orm.Session): + if issubclass(target, scoped_session): + return Session + elif issubclass(target, Session): return target - elif isinstance(target, orm.Session): + elif isinstance(target, Session): return target else: return None @@ -1511,11 +1534,17 @@ class AttributeEvents(event.Events): """ _target_class_doc = "SomeClass.some_attribute" + _dispatch_target = QueryableAttribute + + @staticmethod + def _set_dispatch(cls, dispatch_cls): + event.Events._set_dispatch(cls, dispatch_cls) + dispatch_cls._active_history = False @classmethod def _accept_with(cls, target): # TODO: coverage - if isinstance(target, orm.interfaces.MapperProperty): + if isinstance(target, interfaces.MapperProperty): return getattr(target.parent.class_, target.key) else: return target @@ -1548,7 +1577,7 @@ class AttributeEvents(event.Events): event_key.base_listen(propagate=propagate) if propagate: - manager = orm.instrumentation.manager_of_class(target.class_) + manager = instrumentation.manager_of_class(target.class_) for mgr in manager.subclass_managers(True): event_key.with_dispatch_target(mgr[target.key]).base_listen(propagate=True) @@ -1625,3 +1654,4 @@ class AttributeEvents(event.Events): the given value, or a new effective value, should be returned. """ + -- cgit v1.2.1 From 2038837fa4c03c6da4b6d4c389fd062abc4e844c Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Sat, 17 Aug 2013 12:14:58 -0400 Subject: - spot checking of imports, obsolete functions --- lib/sqlalchemy/orm/events.py | 1 - 1 file changed, 1 deletion(-) (limited to 'lib/sqlalchemy/orm/events.py') diff --git a/lib/sqlalchemy/orm/events.py b/lib/sqlalchemy/orm/events.py index e2ca39137..3b67f767b 100644 --- a/lib/sqlalchemy/orm/events.py +++ b/lib/sqlalchemy/orm/events.py @@ -16,7 +16,6 @@ from . import mapperlib, instrumentation from .session import Session, sessionmaker from .scoping import scoped_session from .attributes import QueryableAttribute -from . import mapper as mapperfunc class InstrumentationEvents(event.Events): """Events related to class instrumentation events. -- cgit v1.2.1 From fe1d64473896b1e8abeb8ddb966447632c057321 Mon Sep 17 00:00:00 2001 From: Vraj Mohan Date: Tue, 12 Nov 2013 18:18:04 -0500 Subject: Fix indentation issues in docstrings --- lib/sqlalchemy/orm/events.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'lib/sqlalchemy/orm/events.py') diff --git a/lib/sqlalchemy/orm/events.py b/lib/sqlalchemy/orm/events.py index 3b67f767b..855841408 100644 --- a/lib/sqlalchemy/orm/events.py +++ b/lib/sqlalchemy/orm/events.py @@ -1295,9 +1295,9 @@ class SessionEvents(event.Events): :param session: The target :class:`.Session`. :param previous_transaction: The :class:`.SessionTransaction` - transactional marker object which was just closed. The current - :class:`.SessionTransaction` for the given :class:`.Session` is - available via the :attr:`.Session.transaction` attribute. + transactional marker object which was just closed. The current + :class:`.SessionTransaction` for the given :class:`.Session` is + available via the :attr:`.Session.transaction` attribute. .. versionadded:: 0.7.3 -- cgit v1.2.1 From 90d1f98f43fecd50ec215815e99f436748a92136 Mon Sep 17 00:00:00 2001 From: Vraj Mohan Date: Fri, 15 Nov 2013 11:11:13 -0500 Subject: Generate API and resolve cross references --- lib/sqlalchemy/orm/events.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'lib/sqlalchemy/orm/events.py') diff --git a/lib/sqlalchemy/orm/events.py b/lib/sqlalchemy/orm/events.py index 855841408..d34e2395b 100644 --- a/lib/sqlalchemy/orm/events.py +++ b/lib/sqlalchemy/orm/events.py @@ -1116,7 +1116,7 @@ class SessionEvents(event.Events): The :func:`~.event.listen` function will accept :class:`.Session` objects as well as the return result - of :func:`.sessionmaker` and :func:`.scoped_session`. + of :class:`~.sessionmaker()` and :class:`~.scoped_session()`. Additionally, it accepts the :class:`.Session` class which will apply listeners to all :class:`.Session` instances @@ -1203,7 +1203,7 @@ class SessionEvents(event.Events): .. note:: - The :meth:`.before_commit` hook is *not* per-flush, + The :meth:`~.SessionEvents.before_commit` hook is *not* per-flush, that is, the :class:`.Session` can emit SQL to the database many times within the scope of a transaction. For interception of these events, use the :meth:`~.SessionEvents.before_flush`, @@ -1389,7 +1389,7 @@ class SessionEvents(event.Events): This is called before an add, delete or merge causes the object to be part of the session. - .. versionadded:: 0.8. Note that :meth:`.after_attach` now + .. versionadded:: 0.8. Note that :meth:`~.SessionEvents.after_attach` now fires off after the item is part of the session. :meth:`.before_attach` is provided for those cases where the item should not yet be part of the session state. @@ -1504,7 +1504,7 @@ class AttributeEvents(event.Events): listen(UserContact.phone, 'set', validate_phone, retval=True) A validation function like the above can also raise an exception - such as :class:`.ValueError` to halt the operation. + such as :exc:`ValueError` to halt the operation. Several modifiers are available to the :func:`~.event.listen` function. -- cgit v1.2.1 From f89d4d216bd7605c920b7b8a10ecde6bfea2238c Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Sun, 5 Jan 2014 16:57:05 -0500 Subject: - happy new year --- lib/sqlalchemy/orm/events.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/sqlalchemy/orm/events.py') diff --git a/lib/sqlalchemy/orm/events.py b/lib/sqlalchemy/orm/events.py index d34e2395b..a09154dd0 100644 --- a/lib/sqlalchemy/orm/events.py +++ b/lib/sqlalchemy/orm/events.py @@ -1,5 +1,5 @@ # orm/events.py -# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors +# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors # # This module is part of SQLAlchemy and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php -- cgit v1.2.1