Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/sqlalchemy/orm/events.py: 52%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# orm/events.py
2# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
3# <see AUTHORS file>
4#
5# This module is part of SQLAlchemy and is released under
6# the MIT License: https://www.opensource.org/licenses/mit-license.php
8"""ORM event interfaces.
10"""
11from __future__ import annotations
13from typing import Any
14from typing import Callable
15from typing import Collection
16from typing import Dict
17from typing import Generic
18from typing import Iterable
19from typing import Optional
20from typing import Sequence
21from typing import Set
22from typing import Type
23from typing import TYPE_CHECKING
24from typing import TypeVar
25from typing import Union
26import weakref
28from . import instrumentation
29from . import interfaces
30from . import mapperlib
31from .attributes import QueryableAttribute
32from .base import _mapper_or_none
33from .base import NO_KEY
34from .instrumentation import ClassManager
35from .instrumentation import InstrumentationFactory
36from .query import BulkDelete
37from .query import BulkUpdate
38from .query import Query
39from .scoping import scoped_session
40from .session import Session
41from .session import sessionmaker
42from .. import event
43from .. import exc
44from .. import util
45from ..event import EventTarget
46from ..event.registry import _ET
47from ..util.compat import inspect_getfullargspec
49if TYPE_CHECKING:
50 from weakref import ReferenceType
52 from ._typing import _InstanceDict
53 from ._typing import _InternalEntityType
54 from ._typing import _O
55 from ._typing import _T
56 from .attributes import Event
57 from .base import EventConstants
58 from .session import ORMExecuteState
59 from .session import SessionTransaction
60 from .unitofwork import UOWTransaction
61 from ..engine import Connection
62 from ..event.base import _Dispatch
63 from ..event.base import _HasEventsDispatch
64 from ..event.registry import _EventKey
65 from ..orm.collections import CollectionAdapter
66 from ..orm.context import QueryContext
67 from ..orm.decl_api import DeclarativeAttributeIntercept
68 from ..orm.decl_api import DeclarativeMeta
69 from ..orm.mapper import Mapper
70 from ..orm.state import InstanceState
72_KT = TypeVar("_KT", bound=Any)
73_ET2 = TypeVar("_ET2", bound=EventTarget)
76class InstrumentationEvents(event.Events[InstrumentationFactory]):
77 """Events related to class instrumentation events.
79 The listeners here support being established against
80 any new style class, that is any object that is a subclass
81 of 'type'. Events will then be fired off for events
82 against that class. If the "propagate=True" flag is passed
83 to event.listen(), the event will fire off for subclasses
84 of that class as well.
86 The Python ``type`` builtin is also accepted as a target,
87 which when used has the effect of events being emitted
88 for all classes.
90 Note the "propagate" flag here is defaulted to ``True``,
91 unlike the other class level events where it defaults
92 to ``False``. This means that new subclasses will also
93 be the subject of these events, when a listener
94 is established on a superclass.
96 """
98 _target_class_doc = "SomeBaseClass"
99 _dispatch_target = InstrumentationFactory
101 @classmethod
102 def _accept_with(
103 cls,
104 target: Union[
105 InstrumentationFactory,
106 Type[InstrumentationFactory],
107 ],
108 identifier: str,
109 ) -> Optional[
110 Union[
111 InstrumentationFactory,
112 Type[InstrumentationFactory],
113 ]
114 ]:
115 if isinstance(target, type):
116 return _InstrumentationEventsHold(target) # type: ignore [return-value] # noqa: E501
117 else:
118 return None
120 @classmethod
121 def _listen(
122 cls, event_key: _EventKey[_T], propagate: bool = True, **kw: Any
123 ) -> None:
124 target, identifier, fn = (
125 event_key.dispatch_target,
126 event_key.identifier,
127 event_key._listen_fn,
128 )
130 def listen(target_cls: type, *arg: Any) -> Optional[Any]:
131 listen_cls = target()
133 # if weakref were collected, however this is not something
134 # that normally happens. it was occurring during test teardown
135 # between mapper/registry/instrumentation_manager, however this
136 # interaction was changed to not rely upon the event system.
137 if listen_cls is None:
138 return None
140 if propagate and issubclass(target_cls, listen_cls):
141 return fn(target_cls, *arg)
142 elif not propagate and target_cls is listen_cls:
143 return fn(target_cls, *arg)
144 else:
145 return None
147 def remove(ref: ReferenceType[_T]) -> None:
148 key = event.registry._EventKey( # type: ignore [type-var]
149 None,
150 identifier,
151 listen,
152 instrumentation._instrumentation_factory,
153 )
154 getattr(
155 instrumentation._instrumentation_factory.dispatch, identifier
156 ).remove(key)
158 target = weakref.ref(target.class_, remove)
160 event_key.with_dispatch_target(
161 instrumentation._instrumentation_factory
162 ).with_wrapper(listen).base_listen(**kw)
164 @classmethod
165 def _clear(cls) -> None:
166 super()._clear()
167 instrumentation._instrumentation_factory.dispatch._clear()
169 def class_instrument(self, cls: ClassManager[_O]) -> None:
170 """Called after the given class is instrumented.
172 To get at the :class:`.ClassManager`, use
173 :func:`.manager_of_class`.
175 """
177 def class_uninstrument(self, cls: ClassManager[_O]) -> None:
178 """Called before the given class is uninstrumented.
180 To get at the :class:`.ClassManager`, use
181 :func:`.manager_of_class`.
183 """
185 def attribute_instrument(
186 self, cls: ClassManager[_O], key: _KT, inst: _O
187 ) -> None:
188 """Called when an attribute is instrumented."""
191class _InstrumentationEventsHold:
192 """temporary marker object used to transfer from _accept_with() to
193 _listen() on the InstrumentationEvents class.
195 """
197 def __init__(self, class_: type) -> None:
198 self.class_ = class_
200 dispatch = event.dispatcher(InstrumentationEvents)
203class InstanceEvents(event.Events[ClassManager[Any]]):
204 """Define events specific to object lifecycle.
206 e.g.::
208 from sqlalchemy import event
210 def my_load_listener(target, context):
211 print("on load!")
213 event.listen(SomeClass, 'load', my_load_listener)
215 Available targets include:
217 * mapped classes
218 * unmapped superclasses of mapped or to-be-mapped classes
219 (using the ``propagate=True`` flag)
220 * :class:`_orm.Mapper` objects
221 * the :class:`_orm.Mapper` class itself indicates listening for all
222 mappers.
224 Instance events are closely related to mapper events, but
225 are more specific to the instance and its instrumentation,
226 rather than its system of persistence.
228 When using :class:`.InstanceEvents`, several modifiers are
229 available to the :func:`.event.listen` function.
231 :param propagate=False: When True, the event listener should
232 be applied to all inheriting classes as well as the
233 class which is the target of this listener.
234 :param raw=False: When True, the "target" argument passed
235 to applicable event listener functions will be the
236 instance's :class:`.InstanceState` management
237 object, rather than the mapped instance itself.
238 :param restore_load_context=False: Applies to the
239 :meth:`.InstanceEvents.load` and :meth:`.InstanceEvents.refresh`
240 events. Restores the loader context of the object when the event
241 hook is complete, so that ongoing eager load operations continue
242 to target the object appropriately. A warning is emitted if the
243 object is moved to a new loader context from within one of these
244 events if this flag is not set.
246 .. versionadded:: 1.3.14
249 """
251 _target_class_doc = "SomeClass"
253 _dispatch_target = ClassManager
255 @classmethod
256 def _new_classmanager_instance(
257 cls,
258 class_: Union[DeclarativeAttributeIntercept, DeclarativeMeta, type],
259 classmanager: ClassManager[_O],
260 ) -> None:
261 _InstanceEventsHold.populate(class_, classmanager)
263 @classmethod
264 @util.preload_module("sqlalchemy.orm")
265 def _accept_with(
266 cls,
267 target: Union[
268 ClassManager[Any],
269 Type[ClassManager[Any]],
270 ],
271 identifier: str,
272 ) -> Optional[Union[ClassManager[Any], Type[ClassManager[Any]]]]:
273 orm = util.preloaded.orm
275 if isinstance(target, ClassManager):
276 return target
277 elif isinstance(target, mapperlib.Mapper):
278 return target.class_manager
279 elif target is orm.mapper: # type: ignore [attr-defined]
280 util.warn_deprecated(
281 "The `sqlalchemy.orm.mapper()` symbol is deprecated and "
282 "will be removed in a future release. For the mapper-wide "
283 "event target, use the 'sqlalchemy.orm.Mapper' class.",
284 "2.0",
285 )
286 return ClassManager
287 elif isinstance(target, type):
288 if issubclass(target, mapperlib.Mapper):
289 return ClassManager
290 else:
291 manager = instrumentation.opt_manager_of_class(target)
292 if manager:
293 return manager
294 else:
295 return _InstanceEventsHold(target) # type: ignore [return-value] # noqa: E501
296 return None
298 @classmethod
299 def _listen(
300 cls,
301 event_key: _EventKey[ClassManager[Any]],
302 raw: bool = False,
303 propagate: bool = False,
304 restore_load_context: bool = False,
305 **kw: Any,
306 ) -> None:
307 target, fn = (event_key.dispatch_target, event_key._listen_fn)
309 if not raw or restore_load_context:
311 def wrap(
312 state: InstanceState[_O], *arg: Any, **kw: Any
313 ) -> Optional[Any]:
314 if not raw:
315 target: Any = state.obj()
316 else:
317 target = state
318 if restore_load_context:
319 runid = state.runid
320 try:
321 return fn(target, *arg, **kw)
322 finally:
323 if restore_load_context:
324 state.runid = runid
326 event_key = event_key.with_wrapper(wrap)
328 event_key.base_listen(propagate=propagate, **kw)
330 if propagate:
331 for mgr in target.subclass_managers(True):
332 event_key.with_dispatch_target(mgr).base_listen(propagate=True)
334 @classmethod
335 def _clear(cls) -> None:
336 super()._clear()
337 _InstanceEventsHold._clear()
339 def first_init(self, manager: ClassManager[_O], cls: Type[_O]) -> None:
340 """Called when the first instance of a particular mapping is called.
342 This event is called when the ``__init__`` method of a class
343 is called the first time for that particular class. The event
344 invokes before ``__init__`` actually proceeds as well as before
345 the :meth:`.InstanceEvents.init` event is invoked.
347 """
349 def init(self, target: _O, args: Any, kwargs: Any) -> None:
350 """Receive an instance when its constructor is called.
352 This method is only called during a userland construction of
353 an object, in conjunction with the object's constructor, e.g.
354 its ``__init__`` method. It is not called when an object is
355 loaded from the database; see the :meth:`.InstanceEvents.load`
356 event in order to intercept a database load.
358 The event is called before the actual ``__init__`` constructor
359 of the object is called. The ``kwargs`` dictionary may be
360 modified in-place in order to affect what is passed to
361 ``__init__``.
363 :param target: the mapped instance. If
364 the event is configured with ``raw=True``, this will
365 instead be the :class:`.InstanceState` state-management
366 object associated with the instance.
367 :param args: positional arguments passed to the ``__init__`` method.
368 This is passed as a tuple and is currently immutable.
369 :param kwargs: keyword arguments passed to the ``__init__`` method.
370 This structure *can* be altered in place.
372 .. seealso::
374 :meth:`.InstanceEvents.init_failure`
376 :meth:`.InstanceEvents.load`
378 """
380 def init_failure(self, target: _O, args: Any, kwargs: Any) -> None:
381 """Receive an instance when its constructor has been called,
382 and raised an exception.
384 This method is only called during a userland construction of
385 an object, in conjunction with the object's constructor, e.g.
386 its ``__init__`` method. It is not called when an object is loaded
387 from the database.
389 The event is invoked after an exception raised by the ``__init__``
390 method is caught. After the event
391 is invoked, the original exception is re-raised outwards, so that
392 the construction of the object still raises an exception. The
393 actual exception and stack trace raised should be present in
394 ``sys.exc_info()``.
396 :param target: the mapped instance. If
397 the event is configured with ``raw=True``, this will
398 instead be the :class:`.InstanceState` state-management
399 object associated with the instance.
400 :param args: positional arguments that were passed to the ``__init__``
401 method.
402 :param kwargs: keyword arguments that were passed to the ``__init__``
403 method.
405 .. seealso::
407 :meth:`.InstanceEvents.init`
409 :meth:`.InstanceEvents.load`
411 """
413 def _sa_event_merge_wo_load(
414 self, target: _O, context: QueryContext
415 ) -> None:
416 """receive an object instance after it was the subject of a merge()
417 call, when load=False was passed.
419 The target would be the already-loaded object in the Session which
420 would have had its attributes overwritten by the incoming object. This
421 overwrite operation does not use attribute events, instead just
422 populating dict directly. Therefore the purpose of this event is so
423 that extensions like sqlalchemy.ext.mutable know that object state has
424 changed and incoming state needs to be set up for "parents" etc.
426 This functionality is acceptable to be made public in a later release.
428 .. versionadded:: 1.4.41
430 """
432 def load(self, target: _O, context: QueryContext) -> None:
433 """Receive an object instance after it has been created via
434 ``__new__``, and after initial attribute population has
435 occurred.
437 This typically occurs when the instance is created based on
438 incoming result rows, and is only called once for that
439 instance's lifetime.
441 .. warning::
443 During a result-row load, this event is invoked when the
444 first row received for this instance is processed. When using
445 eager loading with collection-oriented attributes, the additional
446 rows that are to be loaded / processed in order to load subsequent
447 collection items have not occurred yet. This has the effect
448 both that collections will not be fully loaded, as well as that
449 if an operation occurs within this event handler that emits
450 another database load operation for the object, the "loading
451 context" for the object can change and interfere with the
452 existing eager loaders still in progress.
454 Examples of what can cause the "loading context" to change within
455 the event handler include, but are not necessarily limited to:
457 * accessing deferred attributes that weren't part of the row,
458 will trigger an "undefer" operation and refresh the object
460 * accessing attributes on a joined-inheritance subclass that
461 weren't part of the row, will trigger a refresh operation.
463 As of SQLAlchemy 1.3.14, a warning is emitted when this occurs. The
464 :paramref:`.InstanceEvents.restore_load_context` option may be
465 used on the event to prevent this warning; this will ensure that
466 the existing loading context is maintained for the object after the
467 event is called::
469 @event.listens_for(
470 SomeClass, "load", restore_load_context=True)
471 def on_load(instance, context):
472 instance.some_unloaded_attribute
474 .. versionchanged:: 1.3.14 Added
475 :paramref:`.InstanceEvents.restore_load_context`
476 and :paramref:`.SessionEvents.restore_load_context` flags which
477 apply to "on load" events, which will ensure that the loading
478 context for an object is restored when the event hook is
479 complete; a warning is emitted if the load context of the object
480 changes without this flag being set.
483 The :meth:`.InstanceEvents.load` event is also available in a
484 class-method decorator format called :func:`_orm.reconstructor`.
486 :param target: the mapped instance. If
487 the event is configured with ``raw=True``, this will
488 instead be the :class:`.InstanceState` state-management
489 object associated with the instance.
490 :param context: the :class:`.QueryContext` corresponding to the
491 current :class:`_query.Query` in progress. This argument may be
492 ``None`` if the load does not correspond to a :class:`_query.Query`,
493 such as during :meth:`.Session.merge`.
495 .. seealso::
497 :ref:`mapped_class_load_events`
499 :meth:`.InstanceEvents.init`
501 :meth:`.InstanceEvents.refresh`
503 :meth:`.SessionEvents.loaded_as_persistent`
505 """
507 def refresh(
508 self, target: _O, context: QueryContext, attrs: Optional[Iterable[str]]
509 ) -> None:
510 """Receive an object instance after one or more attributes have
511 been refreshed from a query.
513 Contrast this to the :meth:`.InstanceEvents.load` method, which
514 is invoked when the object is first loaded from a query.
516 .. note:: This event is invoked within the loader process before
517 eager loaders may have been completed, and the object's state may
518 not be complete. Additionally, invoking row-level refresh
519 operations on the object will place the object into a new loader
520 context, interfering with the existing load context. See the note
521 on :meth:`.InstanceEvents.load` for background on making use of the
522 :paramref:`.InstanceEvents.restore_load_context` parameter, in
523 order to resolve this scenario.
525 :param target: the mapped instance. If
526 the event is configured with ``raw=True``, this will
527 instead be the :class:`.InstanceState` state-management
528 object associated with the instance.
529 :param context: the :class:`.QueryContext` corresponding to the
530 current :class:`_query.Query` in progress.
531 :param attrs: sequence of attribute names which
532 were populated, or None if all column-mapped, non-deferred
533 attributes were populated.
535 .. seealso::
537 :ref:`mapped_class_load_events`
539 :meth:`.InstanceEvents.load`
541 """
543 def refresh_flush(
544 self,
545 target: _O,
546 flush_context: UOWTransaction,
547 attrs: Optional[Iterable[str]],
548 ) -> None:
549 """Receive an object instance after one or more attributes that
550 contain a column-level default or onupdate handler have been refreshed
551 during persistence of the object's state.
553 This event is the same as :meth:`.InstanceEvents.refresh` except
554 it is invoked within the unit of work flush process, and includes
555 only non-primary-key columns that have column level default or
556 onupdate handlers, including Python callables as well as server side
557 defaults and triggers which may be fetched via the RETURNING clause.
559 .. note::
561 While the :meth:`.InstanceEvents.refresh_flush` event is triggered
562 for an object that was INSERTed as well as for an object that was
563 UPDATEd, the event is geared primarily towards the UPDATE process;
564 it is mostly an internal artifact that INSERT actions can also
565 trigger this event, and note that **primary key columns for an
566 INSERTed row are explicitly omitted** from this event. In order to
567 intercept the newly INSERTed state of an object, the
568 :meth:`.SessionEvents.pending_to_persistent` and
569 :meth:`.MapperEvents.after_insert` are better choices.
571 :param target: the mapped instance. If
572 the event is configured with ``raw=True``, this will
573 instead be the :class:`.InstanceState` state-management
574 object associated with the instance.
575 :param flush_context: Internal :class:`.UOWTransaction` object
576 which handles the details of the flush.
577 :param attrs: sequence of attribute names which
578 were populated.
580 .. seealso::
582 :ref:`mapped_class_load_events`
584 :ref:`orm_server_defaults`
586 :ref:`metadata_defaults_toplevel`
588 """
590 def expire(self, target: _O, attrs: Optional[Iterable[str]]) -> None:
591 """Receive an object instance after its attributes or some subset
592 have been expired.
594 'keys' is a list of attribute names. If None, the entire
595 state was expired.
597 :param target: the mapped instance. If
598 the event is configured with ``raw=True``, this will
599 instead be the :class:`.InstanceState` state-management
600 object associated with the instance.
601 :param attrs: sequence of attribute
602 names which were expired, or None if all attributes were
603 expired.
605 """
607 def pickle(self, target: _O, state_dict: _InstanceDict) -> None:
608 """Receive an object instance when its associated state is
609 being pickled.
611 :param target: the mapped instance. If
612 the event is configured with ``raw=True``, this will
613 instead be the :class:`.InstanceState` state-management
614 object associated with the instance.
615 :param state_dict: the dictionary returned by
616 :class:`.InstanceState.__getstate__`, containing the state
617 to be pickled.
619 """
621 def unpickle(self, target: _O, state_dict: _InstanceDict) -> None:
622 """Receive an object instance after its associated state has
623 been unpickled.
625 :param target: the mapped instance. If
626 the event is configured with ``raw=True``, this will
627 instead be the :class:`.InstanceState` state-management
628 object associated with the instance.
629 :param state_dict: the dictionary sent to
630 :class:`.InstanceState.__setstate__`, containing the state
631 dictionary which was pickled.
633 """
636class _EventsHold(event.RefCollection[_ET]):
637 """Hold onto listeners against unmapped, uninstrumented classes.
639 Establish _listen() for that class' mapper/instrumentation when
640 those objects are created for that class.
642 """
644 all_holds: weakref.WeakKeyDictionary[Any, Any]
646 def __init__(
647 self,
648 class_: Union[DeclarativeAttributeIntercept, DeclarativeMeta, type],
649 ) -> None:
650 self.class_ = class_
652 @classmethod
653 def _clear(cls) -> None:
654 cls.all_holds.clear()
656 class HoldEvents(Generic[_ET2]):
657 _dispatch_target: Optional[Type[_ET2]] = None
659 @classmethod
660 def _listen(
661 cls,
662 event_key: _EventKey[_ET2],
663 raw: bool = False,
664 propagate: bool = False,
665 retval: bool = False,
666 **kw: Any,
667 ) -> None:
668 target = event_key.dispatch_target
670 if target.class_ in target.all_holds:
671 collection = target.all_holds[target.class_]
672 else:
673 collection = target.all_holds[target.class_] = {}
675 event.registry._stored_in_collection(event_key, target)
676 collection[event_key._key] = (
677 event_key,
678 raw,
679 propagate,
680 retval,
681 kw,
682 )
684 if propagate:
685 stack = list(target.class_.__subclasses__())
686 while stack:
687 subclass = stack.pop(0)
688 stack.extend(subclass.__subclasses__())
689 subject = target.resolve(subclass)
690 if subject is not None:
691 # we are already going through __subclasses__()
692 # so leave generic propagate flag False
693 event_key.with_dispatch_target(subject).listen(
694 raw=raw, propagate=False, retval=retval, **kw
695 )
697 def remove(self, event_key: _EventKey[_ET]) -> None:
698 target = event_key.dispatch_target
700 if isinstance(target, _EventsHold):
701 collection = target.all_holds[target.class_]
702 del collection[event_key._key]
704 @classmethod
705 def populate(
706 cls,
707 class_: Union[DeclarativeAttributeIntercept, DeclarativeMeta, type],
708 subject: Union[ClassManager[_O], Mapper[_O]],
709 ) -> None:
710 for subclass in class_.__mro__:
711 if subclass in cls.all_holds:
712 collection = cls.all_holds[subclass]
713 for (
714 event_key,
715 raw,
716 propagate,
717 retval,
718 kw,
719 ) in collection.values():
720 if propagate or subclass is class_:
721 # since we can't be sure in what order different
722 # classes in a hierarchy are triggered with
723 # populate(), we rely upon _EventsHold for all event
724 # assignment, instead of using the generic propagate
725 # flag.
726 event_key.with_dispatch_target(subject).listen(
727 raw=raw, propagate=False, retval=retval, **kw
728 )
731class _InstanceEventsHold(_EventsHold[_ET]):
732 all_holds: weakref.WeakKeyDictionary[Any, Any] = (
733 weakref.WeakKeyDictionary()
734 )
736 def resolve(self, class_: Type[_O]) -> Optional[ClassManager[_O]]:
737 return instrumentation.opt_manager_of_class(class_)
739 class HoldInstanceEvents(_EventsHold.HoldEvents[_ET], InstanceEvents): # type: ignore [misc] # noqa: E501
740 pass
742 dispatch = event.dispatcher(HoldInstanceEvents)
745class MapperEvents(event.Events[mapperlib.Mapper[Any]]):
746 """Define events specific to mappings.
748 e.g.::
750 from sqlalchemy import event
752 def my_before_insert_listener(mapper, connection, target):
753 # execute a stored procedure upon INSERT,
754 # apply the value to the row to be inserted
755 target.calculated_value = connection.execute(
756 text("select my_special_function(%d)" % target.special_number)
757 ).scalar()
759 # associate the listener function with SomeClass,
760 # to execute during the "before_insert" hook
761 event.listen(
762 SomeClass, 'before_insert', my_before_insert_listener)
764 Available targets include:
766 * mapped classes
767 * unmapped superclasses of mapped or to-be-mapped classes
768 (using the ``propagate=True`` flag)
769 * :class:`_orm.Mapper` objects
770 * the :class:`_orm.Mapper` class itself indicates listening for all
771 mappers.
773 Mapper events provide hooks into critical sections of the
774 mapper, including those related to object instrumentation,
775 object loading, and object persistence. In particular, the
776 persistence methods :meth:`~.MapperEvents.before_insert`,
777 and :meth:`~.MapperEvents.before_update` are popular
778 places to augment the state being persisted - however, these
779 methods operate with several significant restrictions. The
780 user is encouraged to evaluate the
781 :meth:`.SessionEvents.before_flush` and
782 :meth:`.SessionEvents.after_flush` methods as more
783 flexible and user-friendly hooks in which to apply
784 additional database state during a flush.
786 When using :class:`.MapperEvents`, several modifiers are
787 available to the :func:`.event.listen` function.
789 :param propagate=False: When True, the event listener should
790 be applied to all inheriting mappers and/or the mappers of
791 inheriting classes, as well as any
792 mapper which is the target of this listener.
793 :param raw=False: When True, the "target" argument passed
794 to applicable event listener functions will be the
795 instance's :class:`.InstanceState` management
796 object, rather than the mapped instance itself.
797 :param retval=False: when True, the user-defined event function
798 must have a return value, the purpose of which is either to
799 control subsequent event propagation, or to otherwise alter
800 the operation in progress by the mapper. Possible return
801 values are:
803 * ``sqlalchemy.orm.interfaces.EXT_CONTINUE`` - continue event
804 processing normally.
805 * ``sqlalchemy.orm.interfaces.EXT_STOP`` - cancel all subsequent
806 event handlers in the chain.
807 * other values - the return value specified by specific listeners.
809 """
811 _target_class_doc = "SomeClass"
812 _dispatch_target = mapperlib.Mapper
814 @classmethod
815 def _new_mapper_instance(
816 cls,
817 class_: Union[DeclarativeAttributeIntercept, DeclarativeMeta, type],
818 mapper: Mapper[_O],
819 ) -> None:
820 _MapperEventsHold.populate(class_, mapper)
822 @classmethod
823 @util.preload_module("sqlalchemy.orm")
824 def _accept_with(
825 cls,
826 target: Union[mapperlib.Mapper[Any], Type[mapperlib.Mapper[Any]]],
827 identifier: str,
828 ) -> Optional[Union[mapperlib.Mapper[Any], Type[mapperlib.Mapper[Any]]]]:
829 orm = util.preloaded.orm
831 if target is orm.mapper: # type: ignore [attr-defined]
832 util.warn_deprecated(
833 "The `sqlalchemy.orm.mapper()` symbol is deprecated and "
834 "will be removed in a future release. For the mapper-wide "
835 "event target, use the 'sqlalchemy.orm.Mapper' class.",
836 "2.0",
837 )
838 return mapperlib.Mapper
839 elif isinstance(target, type):
840 if issubclass(target, mapperlib.Mapper):
841 return target
842 else:
843 mapper = _mapper_or_none(target)
844 if mapper is not None:
845 return mapper
846 else:
847 return _MapperEventsHold(target)
848 else:
849 return target
851 @classmethod
852 def _listen(
853 cls,
854 event_key: _EventKey[_ET],
855 raw: bool = False,
856 retval: bool = False,
857 propagate: bool = False,
858 **kw: Any,
859 ) -> None:
860 target, identifier, fn = (
861 event_key.dispatch_target,
862 event_key.identifier,
863 event_key._listen_fn,
864 )
866 if (
867 identifier in ("before_configured", "after_configured")
868 and target is not mapperlib.Mapper
869 ):
870 util.warn(
871 "'before_configured' and 'after_configured' ORM events "
872 "only invoke with the Mapper class "
873 "as the target."
874 )
876 if not raw or not retval:
877 if not raw:
878 meth = getattr(cls, identifier)
879 try:
880 target_index = (
881 inspect_getfullargspec(meth)[0].index("target") - 1
882 )
883 except ValueError:
884 target_index = None
886 def wrap(*arg: Any, **kw: Any) -> Any:
887 if not raw and target_index is not None:
888 arg = list(arg) # type: ignore [assignment]
889 arg[target_index] = arg[target_index].obj() # type: ignore [index] # noqa: E501
890 if not retval:
891 fn(*arg, **kw)
892 return interfaces.EXT_CONTINUE
893 else:
894 return fn(*arg, **kw)
896 event_key = event_key.with_wrapper(wrap)
898 if propagate:
899 for mapper in target.self_and_descendants:
900 event_key.with_dispatch_target(mapper).base_listen(
901 propagate=True, **kw
902 )
903 else:
904 event_key.base_listen(**kw)
906 @classmethod
907 def _clear(cls) -> None:
908 super()._clear()
909 _MapperEventsHold._clear()
911 def instrument_class(self, mapper: Mapper[_O], class_: Type[_O]) -> None:
912 r"""Receive a class when the mapper is first constructed,
913 before instrumentation is applied to the mapped class.
915 This event is the earliest phase of mapper construction.
916 Most attributes of the mapper are not yet initialized. To
917 receive an event within initial mapper construction where basic
918 state is available such as the :attr:`_orm.Mapper.attrs` collection,
919 the :meth:`_orm.MapperEvents.after_mapper_constructed` event may
920 be a better choice.
922 This listener can either be applied to the :class:`_orm.Mapper`
923 class overall, or to any un-mapped class which serves as a base
924 for classes that will be mapped (using the ``propagate=True`` flag)::
926 Base = declarative_base()
928 @event.listens_for(Base, "instrument_class", propagate=True)
929 def on_new_class(mapper, cls_):
930 " ... "
932 :param mapper: the :class:`_orm.Mapper` which is the target
933 of this event.
934 :param class\_: the mapped class.
936 .. seealso::
938 :meth:`_orm.MapperEvents.after_mapper_constructed`
940 """
942 def after_mapper_constructed(
943 self, mapper: Mapper[_O], class_: Type[_O]
944 ) -> None:
945 """Receive a class and mapper when the :class:`_orm.Mapper` has been
946 fully constructed.
948 This event is called after the initial constructor for
949 :class:`_orm.Mapper` completes. This occurs after the
950 :meth:`_orm.MapperEvents.instrument_class` event and after the
951 :class:`_orm.Mapper` has done an initial pass of its arguments
952 to generate its collection of :class:`_orm.MapperProperty` objects,
953 which are accessible via the :meth:`_orm.Mapper.get_property`
954 method and the :attr:`_orm.Mapper.iterate_properties` attribute.
956 This event differs from the
957 :meth:`_orm.MapperEvents.before_mapper_configured` event in that it
958 is invoked within the constructor for :class:`_orm.Mapper`, rather
959 than within the :meth:`_orm.registry.configure` process. Currently,
960 this event is the only one which is appropriate for handlers that
961 wish to create additional mapped classes in response to the
962 construction of this :class:`_orm.Mapper`, which will be part of the
963 same configure step when :meth:`_orm.registry.configure` next runs.
965 .. versionadded:: 2.0.2
967 .. seealso::
969 :ref:`examples_versioning` - an example which illustrates the use
970 of the :meth:`_orm.MapperEvents.before_mapper_configured`
971 event to create new mappers to record change-audit histories on
972 objects.
974 """
976 def before_mapper_configured(
977 self, mapper: Mapper[_O], class_: Type[_O]
978 ) -> None:
979 """Called right before a specific mapper is to be configured.
981 This event is intended to allow a specific mapper to be skipped during
982 the configure step, by returning the :attr:`.orm.interfaces.EXT_SKIP`
983 symbol which indicates to the :func:`.configure_mappers` call that this
984 particular mapper (or hierarchy of mappers, if ``propagate=True`` is
985 used) should be skipped in the current configuration run. When one or
986 more mappers are skipped, the he "new mappers" flag will remain set,
987 meaning the :func:`.configure_mappers` function will continue to be
988 called when mappers are used, to continue to try to configure all
989 available mappers.
991 In comparison to the other configure-level events,
992 :meth:`.MapperEvents.before_configured`,
993 :meth:`.MapperEvents.after_configured`, and
994 :meth:`.MapperEvents.mapper_configured`, the
995 :meth;`.MapperEvents.before_mapper_configured` event provides for a
996 meaningful return value when it is registered with the ``retval=True``
997 parameter.
999 .. versionadded:: 1.3
1001 e.g.::
1003 from sqlalchemy.orm import EXT_SKIP
1005 Base = declarative_base()
1007 DontConfigureBase = declarative_base()
1009 @event.listens_for(
1010 DontConfigureBase,
1011 "before_mapper_configured", retval=True, propagate=True)
1012 def dont_configure(mapper, cls):
1013 return EXT_SKIP
1016 .. seealso::
1018 :meth:`.MapperEvents.before_configured`
1020 :meth:`.MapperEvents.after_configured`
1022 :meth:`.MapperEvents.mapper_configured`
1024 """
1026 def mapper_configured(self, mapper: Mapper[_O], class_: Type[_O]) -> None:
1027 r"""Called when a specific mapper has completed its own configuration
1028 within the scope of the :func:`.configure_mappers` call.
1030 The :meth:`.MapperEvents.mapper_configured` event is invoked
1031 for each mapper that is encountered when the
1032 :func:`_orm.configure_mappers` function proceeds through the current
1033 list of not-yet-configured mappers.
1034 :func:`_orm.configure_mappers` is typically invoked
1035 automatically as mappings are first used, as well as each time
1036 new mappers have been made available and new mapper use is
1037 detected.
1039 When the event is called, the mapper should be in its final
1040 state, but **not including backrefs** that may be invoked from
1041 other mappers; they might still be pending within the
1042 configuration operation. Bidirectional relationships that
1043 are instead configured via the
1044 :paramref:`.orm.relationship.back_populates` argument
1045 *will* be fully available, since this style of relationship does not
1046 rely upon other possibly-not-configured mappers to know that they
1047 exist.
1049 For an event that is guaranteed to have **all** mappers ready
1050 to go including backrefs that are defined only on other
1051 mappings, use the :meth:`.MapperEvents.after_configured`
1052 event; this event invokes only after all known mappings have been
1053 fully configured.
1055 The :meth:`.MapperEvents.mapper_configured` event, unlike
1056 :meth:`.MapperEvents.before_configured` or
1057 :meth:`.MapperEvents.after_configured`,
1058 is called for each mapper/class individually, and the mapper is
1059 passed to the event itself. It also is called exactly once for
1060 a particular mapper. The event is therefore useful for
1061 configurational steps that benefit from being invoked just once
1062 on a specific mapper basis, which don't require that "backref"
1063 configurations are necessarily ready yet.
1065 :param mapper: the :class:`_orm.Mapper` which is the target
1066 of this event.
1067 :param class\_: the mapped class.
1069 .. seealso::
1071 :meth:`.MapperEvents.before_configured`
1073 :meth:`.MapperEvents.after_configured`
1075 :meth:`.MapperEvents.before_mapper_configured`
1077 """
1078 # TODO: need coverage for this event
1080 def before_configured(self) -> None:
1081 """Called before a series of mappers have been configured.
1083 The :meth:`.MapperEvents.before_configured` event is invoked
1084 each time the :func:`_orm.configure_mappers` function is
1085 invoked, before the function has done any of its work.
1086 :func:`_orm.configure_mappers` is typically invoked
1087 automatically as mappings are first used, as well as each time
1088 new mappers have been made available and new mapper use is
1089 detected.
1091 This event can **only** be applied to the :class:`_orm.Mapper` class,
1092 and not to individual mappings or mapped classes. It is only invoked
1093 for all mappings as a whole::
1095 from sqlalchemy.orm import Mapper
1097 @event.listens_for(Mapper, "before_configured")
1098 def go():
1099 ...
1101 Contrast this event to :meth:`.MapperEvents.after_configured`,
1102 which is invoked after the series of mappers has been configured,
1103 as well as :meth:`.MapperEvents.before_mapper_configured`
1104 and :meth:`.MapperEvents.mapper_configured`, which are both invoked
1105 on a per-mapper basis.
1107 Theoretically this event is called once per
1108 application, but is actually called any time new mappers
1109 are to be affected by a :func:`_orm.configure_mappers`
1110 call. If new mappings are constructed after existing ones have
1111 already been used, this event will likely be called again. To ensure
1112 that a particular event is only called once and no further, the
1113 ``once=True`` argument (new in 0.9.4) can be applied::
1115 from sqlalchemy.orm import mapper
1117 @event.listens_for(mapper, "before_configured", once=True)
1118 def go():
1119 ...
1122 .. seealso::
1124 :meth:`.MapperEvents.before_mapper_configured`
1126 :meth:`.MapperEvents.mapper_configured`
1128 :meth:`.MapperEvents.after_configured`
1130 """
1132 def after_configured(self) -> None:
1133 """Called after a series of mappers have been configured.
1135 The :meth:`.MapperEvents.after_configured` event is invoked
1136 each time the :func:`_orm.configure_mappers` function is
1137 invoked, after the function has completed its work.
1138 :func:`_orm.configure_mappers` is typically invoked
1139 automatically as mappings are first used, as well as each time
1140 new mappers have been made available and new mapper use is
1141 detected.
1143 Contrast this event to the :meth:`.MapperEvents.mapper_configured`
1144 event, which is called on a per-mapper basis while the configuration
1145 operation proceeds; unlike that event, when this event is invoked,
1146 all cross-configurations (e.g. backrefs) will also have been made
1147 available for any mappers that were pending.
1148 Also contrast to :meth:`.MapperEvents.before_configured`,
1149 which is invoked before the series of mappers has been configured.
1151 This event can **only** be applied to the :class:`_orm.Mapper` class,
1152 and not to individual mappings or
1153 mapped classes. It is only invoked for all mappings as a whole::
1155 from sqlalchemy.orm import Mapper
1157 @event.listens_for(Mapper, "after_configured")
1158 def go():
1159 # ...
1161 Theoretically this event is called once per
1162 application, but is actually called any time new mappers
1163 have been affected by a :func:`_orm.configure_mappers`
1164 call. If new mappings are constructed after existing ones have
1165 already been used, this event will likely be called again. To ensure
1166 that a particular event is only called once and no further, the
1167 ``once=True`` argument (new in 0.9.4) can be applied::
1169 from sqlalchemy.orm import mapper
1171 @event.listens_for(mapper, "after_configured", once=True)
1172 def go():
1173 # ...
1175 .. seealso::
1177 :meth:`.MapperEvents.before_mapper_configured`
1179 :meth:`.MapperEvents.mapper_configured`
1181 :meth:`.MapperEvents.before_configured`
1183 """
1185 def before_insert(
1186 self, mapper: Mapper[_O], connection: Connection, target: _O
1187 ) -> None:
1188 """Receive an object instance before an INSERT statement
1189 is emitted corresponding to that instance.
1191 .. note:: this event **only** applies to the
1192 :ref:`session flush operation <session_flushing>`
1193 and does **not** apply to the ORM DML operations described at
1194 :ref:`orm_expression_update_delete`. To intercept ORM
1195 DML events, use :meth:`_orm.SessionEvents.do_orm_execute`.
1197 This event is used to modify local, non-object related
1198 attributes on the instance before an INSERT occurs, as well
1199 as to emit additional SQL statements on the given
1200 connection.
1202 The event is often called for a batch of objects of the
1203 same class before their INSERT statements are emitted at
1204 once in a later step. In the extremely rare case that
1205 this is not desirable, the :class:`_orm.Mapper` object can be
1206 configured with ``batch=False``, which will cause
1207 batches of instances to be broken up into individual
1208 (and more poorly performing) event->persist->event
1209 steps.
1211 .. warning::
1213 Mapper-level flush events only allow **very limited operations**,
1214 on attributes local to the row being operated upon only,
1215 as well as allowing any SQL to be emitted on the given
1216 :class:`_engine.Connection`. **Please read fully** the notes
1217 at :ref:`session_persistence_mapper` for guidelines on using
1218 these methods; generally, the :meth:`.SessionEvents.before_flush`
1219 method should be preferred for general on-flush changes.
1221 :param mapper: the :class:`_orm.Mapper` which is the target
1222 of this event.
1223 :param connection: the :class:`_engine.Connection` being used to
1224 emit INSERT statements for this instance. This
1225 provides a handle into the current transaction on the
1226 target database specific to this instance.
1227 :param target: the mapped instance being persisted. If
1228 the event is configured with ``raw=True``, this will
1229 instead be the :class:`.InstanceState` state-management
1230 object associated with the instance.
1231 :return: No return value is supported by this event.
1233 .. seealso::
1235 :ref:`session_persistence_events`
1237 """
1239 def after_insert(
1240 self, mapper: Mapper[_O], connection: Connection, target: _O
1241 ) -> None:
1242 """Receive an object instance after an INSERT statement
1243 is emitted corresponding to that instance.
1245 .. note:: this event **only** applies to the
1246 :ref:`session flush operation <session_flushing>`
1247 and does **not** apply to the ORM DML operations described at
1248 :ref:`orm_expression_update_delete`. To intercept ORM
1249 DML events, use :meth:`_orm.SessionEvents.do_orm_execute`.
1251 This event is used to modify in-Python-only
1252 state on the instance after an INSERT occurs, as well
1253 as to emit additional SQL statements on the given
1254 connection.
1256 The event is often called for a batch of objects of the
1257 same class after their INSERT statements have been
1258 emitted at once in a previous step. In the extremely
1259 rare case that this is not desirable, the
1260 :class:`_orm.Mapper` object can be configured with ``batch=False``,
1261 which will cause batches of instances to be broken up
1262 into individual (and more poorly performing)
1263 event->persist->event steps.
1265 .. warning::
1267 Mapper-level flush events only allow **very limited operations**,
1268 on attributes local to the row being operated upon only,
1269 as well as allowing any SQL to be emitted on the given
1270 :class:`_engine.Connection`. **Please read fully** the notes
1271 at :ref:`session_persistence_mapper` for guidelines on using
1272 these methods; generally, the :meth:`.SessionEvents.before_flush`
1273 method should be preferred for general on-flush changes.
1275 :param mapper: the :class:`_orm.Mapper` which is the target
1276 of this event.
1277 :param connection: the :class:`_engine.Connection` being used to
1278 emit INSERT statements for this instance. This
1279 provides a handle into the current transaction on the
1280 target database specific to this instance.
1281 :param target: the mapped instance being persisted. If
1282 the event is configured with ``raw=True``, this will
1283 instead be the :class:`.InstanceState` state-management
1284 object associated with the instance.
1285 :return: No return value is supported by this event.
1287 .. seealso::
1289 :ref:`session_persistence_events`
1291 """
1293 def before_update(
1294 self, mapper: Mapper[_O], connection: Connection, target: _O
1295 ) -> None:
1296 """Receive an object instance before an UPDATE statement
1297 is emitted corresponding to that instance.
1299 .. note:: this event **only** applies to the
1300 :ref:`session flush operation <session_flushing>`
1301 and does **not** apply to the ORM DML operations described at
1302 :ref:`orm_expression_update_delete`. To intercept ORM
1303 DML events, use :meth:`_orm.SessionEvents.do_orm_execute`.
1305 This event is used to modify local, non-object related
1306 attributes on the instance before an UPDATE occurs, as well
1307 as to emit additional SQL statements on the given
1308 connection.
1310 This method is called for all instances that are
1311 marked as "dirty", *even those which have no net changes
1312 to their column-based attributes*. An object is marked
1313 as dirty when any of its column-based attributes have a
1314 "set attribute" operation called or when any of its
1315 collections are modified. If, at update time, no
1316 column-based attributes have any net changes, no UPDATE
1317 statement will be issued. This means that an instance
1318 being sent to :meth:`~.MapperEvents.before_update` is
1319 *not* a guarantee that an UPDATE statement will be
1320 issued, although you can affect the outcome here by
1321 modifying attributes so that a net change in value does
1322 exist.
1324 To detect if the column-based attributes on the object have net
1325 changes, and will therefore generate an UPDATE statement, use
1326 ``object_session(instance).is_modified(instance,
1327 include_collections=False)``.
1329 The event is often called for a batch of objects of the
1330 same class before their UPDATE statements are emitted at
1331 once in a later step. In the extremely rare case that
1332 this is not desirable, the :class:`_orm.Mapper` can be
1333 configured with ``batch=False``, which will cause
1334 batches of instances to be broken up into individual
1335 (and more poorly performing) event->persist->event
1336 steps.
1338 .. warning::
1340 Mapper-level flush events only allow **very limited operations**,
1341 on attributes local to the row being operated upon only,
1342 as well as allowing any SQL to be emitted on the given
1343 :class:`_engine.Connection`. **Please read fully** the notes
1344 at :ref:`session_persistence_mapper` for guidelines on using
1345 these methods; generally, the :meth:`.SessionEvents.before_flush`
1346 method should be preferred for general on-flush changes.
1348 :param mapper: the :class:`_orm.Mapper` which is the target
1349 of this event.
1350 :param connection: the :class:`_engine.Connection` being used to
1351 emit UPDATE statements for this instance. This
1352 provides a handle into the current transaction on the
1353 target database specific to this instance.
1354 :param target: the mapped instance being persisted. If
1355 the event is configured with ``raw=True``, this will
1356 instead be the :class:`.InstanceState` state-management
1357 object associated with the instance.
1358 :return: No return value is supported by this event.
1360 .. seealso::
1362 :ref:`session_persistence_events`
1364 """
1366 def after_update(
1367 self, mapper: Mapper[_O], connection: Connection, target: _O
1368 ) -> None:
1369 """Receive an object instance after an UPDATE statement
1370 is emitted corresponding to that instance.
1372 .. note:: this event **only** applies to the
1373 :ref:`session flush operation <session_flushing>`
1374 and does **not** apply to the ORM DML operations described at
1375 :ref:`orm_expression_update_delete`. To intercept ORM
1376 DML events, use :meth:`_orm.SessionEvents.do_orm_execute`.
1378 This event is used to modify in-Python-only
1379 state on the instance after an UPDATE occurs, as well
1380 as to emit additional SQL statements on the given
1381 connection.
1383 This method is called for all instances that are
1384 marked as "dirty", *even those which have no net changes
1385 to their column-based attributes*, and for which
1386 no UPDATE statement has proceeded. An object is marked
1387 as dirty when any of its column-based attributes have a
1388 "set attribute" operation called or when any of its
1389 collections are modified. If, at update time, no
1390 column-based attributes have any net changes, no UPDATE
1391 statement will be issued. This means that an instance
1392 being sent to :meth:`~.MapperEvents.after_update` is
1393 *not* a guarantee that an UPDATE statement has been
1394 issued.
1396 To detect if the column-based attributes on the object have net
1397 changes, and therefore resulted in an UPDATE statement, use
1398 ``object_session(instance).is_modified(instance,
1399 include_collections=False)``.
1401 The event is often called for a batch of objects of the
1402 same class after their UPDATE statements have been emitted at
1403 once in a previous step. In the extremely rare case that
1404 this is not desirable, the :class:`_orm.Mapper` can be
1405 configured with ``batch=False``, which will cause
1406 batches of instances to be broken up into individual
1407 (and more poorly performing) event->persist->event
1408 steps.
1410 .. warning::
1412 Mapper-level flush events only allow **very limited operations**,
1413 on attributes local to the row being operated upon only,
1414 as well as allowing any SQL to be emitted on the given
1415 :class:`_engine.Connection`. **Please read fully** the notes
1416 at :ref:`session_persistence_mapper` for guidelines on using
1417 these methods; generally, the :meth:`.SessionEvents.before_flush`
1418 method should be preferred for general on-flush changes.
1420 :param mapper: the :class:`_orm.Mapper` which is the target
1421 of this event.
1422 :param connection: the :class:`_engine.Connection` being used to
1423 emit UPDATE statements for this instance. This
1424 provides a handle into the current transaction on the
1425 target database specific to this instance.
1426 :param target: the mapped instance being persisted. If
1427 the event is configured with ``raw=True``, this will
1428 instead be the :class:`.InstanceState` state-management
1429 object associated with the instance.
1430 :return: No return value is supported by this event.
1432 .. seealso::
1434 :ref:`session_persistence_events`
1436 """
1438 def before_delete(
1439 self, mapper: Mapper[_O], connection: Connection, target: _O
1440 ) -> None:
1441 """Receive an object instance before a DELETE statement
1442 is emitted corresponding to that instance.
1444 .. note:: this event **only** applies to the
1445 :ref:`session flush operation <session_flushing>`
1446 and does **not** apply to the ORM DML operations described at
1447 :ref:`orm_expression_update_delete`. To intercept ORM
1448 DML events, use :meth:`_orm.SessionEvents.do_orm_execute`.
1450 This event is used to emit additional SQL statements on
1451 the given connection as well as to perform application
1452 specific bookkeeping related to a deletion event.
1454 The event is often called for a batch of objects of the
1455 same class before their DELETE statements are emitted at
1456 once in a later step.
1458 .. warning::
1460 Mapper-level flush events only allow **very limited operations**,
1461 on attributes local to the row being operated upon only,
1462 as well as allowing any SQL to be emitted on the given
1463 :class:`_engine.Connection`. **Please read fully** the notes
1464 at :ref:`session_persistence_mapper` for guidelines on using
1465 these methods; generally, the :meth:`.SessionEvents.before_flush`
1466 method should be preferred for general on-flush changes.
1468 :param mapper: the :class:`_orm.Mapper` which is the target
1469 of this event.
1470 :param connection: the :class:`_engine.Connection` being used to
1471 emit DELETE statements for this instance. This
1472 provides a handle into the current transaction on the
1473 target database specific to this instance.
1474 :param target: the mapped instance being deleted. If
1475 the event is configured with ``raw=True``, this will
1476 instead be the :class:`.InstanceState` state-management
1477 object associated with the instance.
1478 :return: No return value is supported by this event.
1480 .. seealso::
1482 :ref:`session_persistence_events`
1484 """
1486 def after_delete(
1487 self, mapper: Mapper[_O], connection: Connection, target: _O
1488 ) -> None:
1489 """Receive an object instance after a DELETE statement
1490 has been emitted corresponding to that instance.
1492 .. note:: this event **only** applies to the
1493 :ref:`session flush operation <session_flushing>`
1494 and does **not** apply to the ORM DML operations described at
1495 :ref:`orm_expression_update_delete`. To intercept ORM
1496 DML events, use :meth:`_orm.SessionEvents.do_orm_execute`.
1498 This event is used to emit additional SQL statements on
1499 the given connection as well as to perform application
1500 specific bookkeeping related to a deletion event.
1502 The event is often called for a batch of objects of the
1503 same class after their DELETE statements have been emitted at
1504 once in a previous step.
1506 .. warning::
1508 Mapper-level flush events only allow **very limited operations**,
1509 on attributes local to the row being operated upon only,
1510 as well as allowing any SQL to be emitted on the given
1511 :class:`_engine.Connection`. **Please read fully** the notes
1512 at :ref:`session_persistence_mapper` for guidelines on using
1513 these methods; generally, the :meth:`.SessionEvents.before_flush`
1514 method should be preferred for general on-flush changes.
1516 :param mapper: the :class:`_orm.Mapper` which is the target
1517 of this event.
1518 :param connection: the :class:`_engine.Connection` being used to
1519 emit DELETE statements for this instance. This
1520 provides a handle into the current transaction on the
1521 target database specific to this instance.
1522 :param target: the mapped instance being deleted. If
1523 the event is configured with ``raw=True``, this will
1524 instead be the :class:`.InstanceState` state-management
1525 object associated with the instance.
1526 :return: No return value is supported by this event.
1528 .. seealso::
1530 :ref:`session_persistence_events`
1532 """
1535class _MapperEventsHold(_EventsHold[_ET]):
1536 all_holds = weakref.WeakKeyDictionary()
1538 def resolve(
1539 self, class_: Union[Type[_T], _InternalEntityType[_T]]
1540 ) -> Optional[Mapper[_T]]:
1541 return _mapper_or_none(class_)
1543 class HoldMapperEvents(_EventsHold.HoldEvents[_ET], MapperEvents): # type: ignore [misc] # noqa: E501
1544 pass
1546 dispatch = event.dispatcher(HoldMapperEvents)
1549_sessionevents_lifecycle_event_names: Set[str] = set()
1552class SessionEvents(event.Events[Session]):
1553 """Define events specific to :class:`.Session` lifecycle.
1555 e.g.::
1557 from sqlalchemy import event
1558 from sqlalchemy.orm import sessionmaker
1560 def my_before_commit(session):
1561 print("before commit!")
1563 Session = sessionmaker()
1565 event.listen(Session, "before_commit", my_before_commit)
1567 The :func:`~.event.listen` function will accept
1568 :class:`.Session` objects as well as the return result
1569 of :class:`~.sessionmaker()` and :class:`~.scoped_session()`.
1571 Additionally, it accepts the :class:`.Session` class which
1572 will apply listeners to all :class:`.Session` instances
1573 globally.
1575 :param raw=False: When True, the "target" argument passed
1576 to applicable event listener functions that work on individual
1577 objects will be the instance's :class:`.InstanceState` management
1578 object, rather than the mapped instance itself.
1580 .. versionadded:: 1.3.14
1582 :param restore_load_context=False: Applies to the
1583 :meth:`.SessionEvents.loaded_as_persistent` event. Restores the loader
1584 context of the object when the event hook is complete, so that ongoing
1585 eager load operations continue to target the object appropriately. A
1586 warning is emitted if the object is moved to a new loader context from
1587 within this event if this flag is not set.
1589 .. versionadded:: 1.3.14
1591 """
1593 _target_class_doc = "SomeSessionClassOrObject"
1595 _dispatch_target = Session
1597 def _lifecycle_event( # type: ignore [misc]
1598 fn: Callable[[SessionEvents, Session, Any], None]
1599 ) -> Callable[[SessionEvents, Session, Any], None]:
1600 _sessionevents_lifecycle_event_names.add(fn.__name__)
1601 return fn
1603 @classmethod
1604 def _accept_with( # type: ignore [return]
1605 cls, target: Any, identifier: str
1606 ) -> Union[Session, type]:
1607 if isinstance(target, scoped_session):
1608 target = target.session_factory
1609 if not isinstance(target, sessionmaker) and (
1610 not isinstance(target, type) or not issubclass(target, Session)
1611 ):
1612 raise exc.ArgumentError(
1613 "Session event listen on a scoped_session "
1614 "requires that its creation callable "
1615 "is associated with the Session class."
1616 )
1618 if isinstance(target, sessionmaker):
1619 return target.class_
1620 elif isinstance(target, type):
1621 if issubclass(target, scoped_session):
1622 return Session
1623 elif issubclass(target, Session):
1624 return target
1625 elif isinstance(target, Session):
1626 return target
1627 elif hasattr(target, "_no_async_engine_events"):
1628 target._no_async_engine_events()
1629 else:
1630 # allows alternate SessionEvents-like-classes to be consulted
1631 return event.Events._accept_with(target, identifier) # type: ignore [return-value] # noqa: E501
1633 @classmethod
1634 def _listen(
1635 cls,
1636 event_key: Any,
1637 *,
1638 raw: bool = False,
1639 restore_load_context: bool = False,
1640 **kw: Any,
1641 ) -> None:
1642 is_instance_event = (
1643 event_key.identifier in _sessionevents_lifecycle_event_names
1644 )
1646 if is_instance_event:
1647 if not raw or restore_load_context:
1648 fn = event_key._listen_fn
1650 def wrap(
1651 session: Session,
1652 state: InstanceState[_O],
1653 *arg: Any,
1654 **kw: Any,
1655 ) -> Optional[Any]:
1656 if not raw:
1657 target = state.obj()
1658 if target is None:
1659 # existing behavior is that if the object is
1660 # garbage collected, no event is emitted
1661 return None
1662 else:
1663 target = state # type: ignore [assignment]
1664 if restore_load_context:
1665 runid = state.runid
1666 try:
1667 return fn(session, target, *arg, **kw)
1668 finally:
1669 if restore_load_context:
1670 state.runid = runid
1672 event_key = event_key.with_wrapper(wrap)
1674 event_key.base_listen(**kw)
1676 def do_orm_execute(self, orm_execute_state: ORMExecuteState) -> None:
1677 """Intercept statement executions that occur on behalf of an
1678 ORM :class:`.Session` object.
1680 This event is invoked for all top-level SQL statements invoked from the
1681 :meth:`_orm.Session.execute` method, as well as related methods such as
1682 :meth:`_orm.Session.scalars` and :meth:`_orm.Session.scalar`. As of
1683 SQLAlchemy 1.4, all ORM queries that run through the
1684 :meth:`_orm.Session.execute` method as well as related methods
1685 :meth:`_orm.Session.scalars`, :meth:`_orm.Session.scalar` etc.
1686 will participate in this event.
1687 This event hook does **not** apply to the queries that are
1688 emitted internally within the ORM flush process, i.e. the
1689 process described at :ref:`session_flushing`.
1691 .. note:: The :meth:`_orm.SessionEvents.do_orm_execute` event hook
1692 is triggered **for ORM statement executions only**, meaning those
1693 invoked via the :meth:`_orm.Session.execute` and similar methods on
1694 the :class:`_orm.Session` object. It does **not** trigger for
1695 statements that are invoked by SQLAlchemy Core only, i.e. statements
1696 invoked directly using :meth:`_engine.Connection.execute` or
1697 otherwise originating from an :class:`_engine.Engine` object without
1698 any :class:`_orm.Session` involved. To intercept **all** SQL
1699 executions regardless of whether the Core or ORM APIs are in use,
1700 see the event hooks at :class:`.ConnectionEvents`, such as
1701 :meth:`.ConnectionEvents.before_execute` and
1702 :meth:`.ConnectionEvents.before_cursor_execute`.
1704 Also, this event hook does **not** apply to queries that are
1705 emitted internally within the ORM flush process,
1706 i.e. the process described at :ref:`session_flushing`; to
1707 intercept steps within the flush process, see the event
1708 hooks described at :ref:`session_persistence_events` as
1709 well as :ref:`session_persistence_mapper`.
1711 This event is a ``do_`` event, meaning it has the capability to replace
1712 the operation that the :meth:`_orm.Session.execute` method normally
1713 performs. The intended use for this includes sharding and
1714 result-caching schemes which may seek to invoke the same statement
1715 across multiple database connections, returning a result that is
1716 merged from each of them, or which don't invoke the statement at all,
1717 instead returning data from a cache.
1719 The hook intends to replace the use of the
1720 ``Query._execute_and_instances`` method that could be subclassed prior
1721 to SQLAlchemy 1.4.
1723 :param orm_execute_state: an instance of :class:`.ORMExecuteState`
1724 which contains all information about the current execution, as well
1725 as helper functions used to derive other commonly required
1726 information. See that object for details.
1728 .. seealso::
1730 :ref:`session_execute_events` - top level documentation on how
1731 to use :meth:`_orm.SessionEvents.do_orm_execute`
1733 :class:`.ORMExecuteState` - the object passed to the
1734 :meth:`_orm.SessionEvents.do_orm_execute` event which contains
1735 all information about the statement to be invoked. It also
1736 provides an interface to extend the current statement, options,
1737 and parameters as well as an option that allows programmatic
1738 invocation of the statement at any point.
1740 :ref:`examples_session_orm_events` - includes examples of using
1741 :meth:`_orm.SessionEvents.do_orm_execute`
1743 :ref:`examples_caching` - an example of how to integrate
1744 Dogpile caching with the ORM :class:`_orm.Session` making use
1745 of the :meth:`_orm.SessionEvents.do_orm_execute` event hook.
1747 :ref:`examples_sharding` - the Horizontal Sharding example /
1748 extension relies upon the
1749 :meth:`_orm.SessionEvents.do_orm_execute` event hook to invoke a
1750 SQL statement on multiple backends and return a merged result.
1753 .. versionadded:: 1.4
1755 """
1757 def after_transaction_create(
1758 self, session: Session, transaction: SessionTransaction
1759 ) -> None:
1760 """Execute when a new :class:`.SessionTransaction` is created.
1762 This event differs from :meth:`~.SessionEvents.after_begin`
1763 in that it occurs for each :class:`.SessionTransaction`
1764 overall, as opposed to when transactions are begun
1765 on individual database connections. It is also invoked
1766 for nested transactions and subtransactions, and is always
1767 matched by a corresponding
1768 :meth:`~.SessionEvents.after_transaction_end` event
1769 (assuming normal operation of the :class:`.Session`).
1771 :param session: the target :class:`.Session`.
1772 :param transaction: the target :class:`.SessionTransaction`.
1774 To detect if this is the outermost
1775 :class:`.SessionTransaction`, as opposed to a "subtransaction" or a
1776 SAVEPOINT, test that the :attr:`.SessionTransaction.parent` attribute
1777 is ``None``::
1779 @event.listens_for(session, "after_transaction_create")
1780 def after_transaction_create(session, transaction):
1781 if transaction.parent is None:
1782 # work with top-level transaction
1784 To detect if the :class:`.SessionTransaction` is a SAVEPOINT, use the
1785 :attr:`.SessionTransaction.nested` attribute::
1787 @event.listens_for(session, "after_transaction_create")
1788 def after_transaction_create(session, transaction):
1789 if transaction.nested:
1790 # work with SAVEPOINT transaction
1793 .. seealso::
1795 :class:`.SessionTransaction`
1797 :meth:`~.SessionEvents.after_transaction_end`
1799 """
1801 def after_transaction_end(
1802 self, session: Session, transaction: SessionTransaction
1803 ) -> None:
1804 """Execute when the span of a :class:`.SessionTransaction` ends.
1806 This event differs from :meth:`~.SessionEvents.after_commit`
1807 in that it corresponds to all :class:`.SessionTransaction`
1808 objects in use, including those for nested transactions
1809 and subtransactions, and is always matched by a corresponding
1810 :meth:`~.SessionEvents.after_transaction_create` event.
1812 :param session: the target :class:`.Session`.
1813 :param transaction: the target :class:`.SessionTransaction`.
1815 To detect if this is the outermost
1816 :class:`.SessionTransaction`, as opposed to a "subtransaction" or a
1817 SAVEPOINT, test that the :attr:`.SessionTransaction.parent` attribute
1818 is ``None``::
1820 @event.listens_for(session, "after_transaction_create")
1821 def after_transaction_end(session, transaction):
1822 if transaction.parent is None:
1823 # work with top-level transaction
1825 To detect if the :class:`.SessionTransaction` is a SAVEPOINT, use the
1826 :attr:`.SessionTransaction.nested` attribute::
1828 @event.listens_for(session, "after_transaction_create")
1829 def after_transaction_end(session, transaction):
1830 if transaction.nested:
1831 # work with SAVEPOINT transaction
1834 .. seealso::
1836 :class:`.SessionTransaction`
1838 :meth:`~.SessionEvents.after_transaction_create`
1840 """
1842 def before_commit(self, session: Session) -> None:
1843 """Execute before commit is called.
1845 .. note::
1847 The :meth:`~.SessionEvents.before_commit` hook is *not* per-flush,
1848 that is, the :class:`.Session` can emit SQL to the database
1849 many times within the scope of a transaction.
1850 For interception of these events, use the
1851 :meth:`~.SessionEvents.before_flush`,
1852 :meth:`~.SessionEvents.after_flush`, or
1853 :meth:`~.SessionEvents.after_flush_postexec`
1854 events.
1856 :param session: The target :class:`.Session`.
1858 .. seealso::
1860 :meth:`~.SessionEvents.after_commit`
1862 :meth:`~.SessionEvents.after_begin`
1864 :meth:`~.SessionEvents.after_transaction_create`
1866 :meth:`~.SessionEvents.after_transaction_end`
1868 """
1870 def after_commit(self, session: Session) -> None:
1871 """Execute after a commit has occurred.
1873 .. note::
1875 The :meth:`~.SessionEvents.after_commit` hook is *not* per-flush,
1876 that is, the :class:`.Session` can emit SQL to the database
1877 many times within the scope of a transaction.
1878 For interception of these events, use the
1879 :meth:`~.SessionEvents.before_flush`,
1880 :meth:`~.SessionEvents.after_flush`, or
1881 :meth:`~.SessionEvents.after_flush_postexec`
1882 events.
1884 .. note::
1886 The :class:`.Session` is not in an active transaction
1887 when the :meth:`~.SessionEvents.after_commit` event is invoked,
1888 and therefore can not emit SQL. To emit SQL corresponding to
1889 every transaction, use the :meth:`~.SessionEvents.before_commit`
1890 event.
1892 :param session: The target :class:`.Session`.
1894 .. seealso::
1896 :meth:`~.SessionEvents.before_commit`
1898 :meth:`~.SessionEvents.after_begin`
1900 :meth:`~.SessionEvents.after_transaction_create`
1902 :meth:`~.SessionEvents.after_transaction_end`
1904 """
1906 def after_rollback(self, session: Session) -> None:
1907 """Execute after a real DBAPI rollback has occurred.
1909 Note that this event only fires when the *actual* rollback against
1910 the database occurs - it does *not* fire each time the
1911 :meth:`.Session.rollback` method is called, if the underlying
1912 DBAPI transaction has already been rolled back. In many
1913 cases, the :class:`.Session` will not be in
1914 an "active" state during this event, as the current
1915 transaction is not valid. To acquire a :class:`.Session`
1916 which is active after the outermost rollback has proceeded,
1917 use the :meth:`.SessionEvents.after_soft_rollback` event, checking the
1918 :attr:`.Session.is_active` flag.
1920 :param session: The target :class:`.Session`.
1922 """
1924 def after_soft_rollback(
1925 self, session: Session, previous_transaction: SessionTransaction
1926 ) -> None:
1927 """Execute after any rollback has occurred, including "soft"
1928 rollbacks that don't actually emit at the DBAPI level.
1930 This corresponds to both nested and outer rollbacks, i.e.
1931 the innermost rollback that calls the DBAPI's
1932 rollback() method, as well as the enclosing rollback
1933 calls that only pop themselves from the transaction stack.
1935 The given :class:`.Session` can be used to invoke SQL and
1936 :meth:`.Session.query` operations after an outermost rollback
1937 by first checking the :attr:`.Session.is_active` flag::
1939 @event.listens_for(Session, "after_soft_rollback")
1940 def do_something(session, previous_transaction):
1941 if session.is_active:
1942 session.execute(text("select * from some_table"))
1944 :param session: The target :class:`.Session`.
1945 :param previous_transaction: The :class:`.SessionTransaction`
1946 transactional marker object which was just closed. The current
1947 :class:`.SessionTransaction` for the given :class:`.Session` is
1948 available via the :attr:`.Session.transaction` attribute.
1950 """
1952 def before_flush(
1953 self,
1954 session: Session,
1955 flush_context: UOWTransaction,
1956 instances: Optional[Sequence[_O]],
1957 ) -> None:
1958 """Execute before flush process has started.
1960 :param session: The target :class:`.Session`.
1961 :param flush_context: Internal :class:`.UOWTransaction` object
1962 which handles the details of the flush.
1963 :param instances: Usually ``None``, this is the collection of
1964 objects which can be passed to the :meth:`.Session.flush` method
1965 (note this usage is deprecated).
1967 .. seealso::
1969 :meth:`~.SessionEvents.after_flush`
1971 :meth:`~.SessionEvents.after_flush_postexec`
1973 :ref:`session_persistence_events`
1975 """
1977 def after_flush(
1978 self, session: Session, flush_context: UOWTransaction
1979 ) -> None:
1980 """Execute after flush has completed, but before commit has been
1981 called.
1983 Note that the session's state is still in pre-flush, i.e. 'new',
1984 'dirty', and 'deleted' lists still show pre-flush state as well
1985 as the history settings on instance attributes.
1987 .. warning:: This event runs after the :class:`.Session` has emitted
1988 SQL to modify the database, but **before** it has altered its
1989 internal state to reflect those changes, including that newly
1990 inserted objects are placed into the identity map. ORM operations
1991 emitted within this event such as loads of related items
1992 may produce new identity map entries that will immediately
1993 be replaced, sometimes causing confusing results. SQLAlchemy will
1994 emit a warning for this condition as of version 1.3.9.
1996 :param session: The target :class:`.Session`.
1997 :param flush_context: Internal :class:`.UOWTransaction` object
1998 which handles the details of the flush.
2000 .. seealso::
2002 :meth:`~.SessionEvents.before_flush`
2004 :meth:`~.SessionEvents.after_flush_postexec`
2006 :ref:`session_persistence_events`
2008 """
2010 def after_flush_postexec(
2011 self, session: Session, flush_context: UOWTransaction
2012 ) -> None:
2013 """Execute after flush has completed, and after the post-exec
2014 state occurs.
2016 This will be when the 'new', 'dirty', and 'deleted' lists are in
2017 their final state. An actual commit() may or may not have
2018 occurred, depending on whether or not the flush started its own
2019 transaction or participated in a larger transaction.
2021 :param session: The target :class:`.Session`.
2022 :param flush_context: Internal :class:`.UOWTransaction` object
2023 which handles the details of the flush.
2026 .. seealso::
2028 :meth:`~.SessionEvents.before_flush`
2030 :meth:`~.SessionEvents.after_flush`
2032 :ref:`session_persistence_events`
2034 """
2036 def after_begin(
2037 self,
2038 session: Session,
2039 transaction: SessionTransaction,
2040 connection: Connection,
2041 ) -> None:
2042 """Execute after a transaction is begun on a connection.
2044 .. note:: This event is called within the process of the
2045 :class:`_orm.Session` modifying its own internal state.
2046 To invoke SQL operations within this hook, use the
2047 :class:`_engine.Connection` provided to the event;
2048 do not run SQL operations using the :class:`_orm.Session`
2049 directly.
2051 :param session: The target :class:`.Session`.
2052 :param transaction: The :class:`.SessionTransaction`.
2053 :param connection: The :class:`_engine.Connection` object
2054 which will be used for SQL statements.
2056 .. seealso::
2058 :meth:`~.SessionEvents.before_commit`
2060 :meth:`~.SessionEvents.after_commit`
2062 :meth:`~.SessionEvents.after_transaction_create`
2064 :meth:`~.SessionEvents.after_transaction_end`
2066 """
2068 @_lifecycle_event
2069 def before_attach(self, session: Session, instance: _O) -> None:
2070 """Execute before an instance is attached to a session.
2072 This is called before an add, delete or merge causes
2073 the object to be part of the session.
2075 .. seealso::
2077 :meth:`~.SessionEvents.after_attach`
2079 :ref:`session_lifecycle_events`
2081 """
2083 @_lifecycle_event
2084 def after_attach(self, session: Session, instance: _O) -> None:
2085 """Execute after an instance is attached to a session.
2087 This is called after an add, delete or merge.
2089 .. note::
2091 As of 0.8, this event fires off *after* the item
2092 has been fully associated with the session, which is
2093 different than previous releases. For event
2094 handlers that require the object not yet
2095 be part of session state (such as handlers which
2096 may autoflush while the target object is not
2097 yet complete) consider the
2098 new :meth:`.before_attach` event.
2100 .. seealso::
2102 :meth:`~.SessionEvents.before_attach`
2104 :ref:`session_lifecycle_events`
2106 """
2108 def after_bulk_update(self, update_context: _O) -> None:
2109 """Event for after the legacy :meth:`_orm.Query.update` method
2110 has been called.
2112 .. legacy:: The :meth:`_orm.SessionEvents.after_bulk_update` method
2113 is a legacy event hook as of SQLAlchemy 2.0. The event
2114 **does not participate** in :term:`2.0 style` invocations
2115 using :func:`_dml.update` documented at
2116 :ref:`orm_queryguide_update_delete_where`. For 2.0 style use,
2117 the :meth:`_orm.SessionEvents.do_orm_execute` hook will intercept
2118 these calls.
2120 :param update_context: an "update context" object which contains
2121 details about the update, including these attributes:
2123 * ``session`` - the :class:`.Session` involved
2124 * ``query`` -the :class:`_query.Query`
2125 object that this update operation
2126 was called upon.
2127 * ``values`` The "values" dictionary that was passed to
2128 :meth:`_query.Query.update`.
2129 * ``result`` the :class:`_engine.CursorResult`
2130 returned as a result of the
2131 bulk UPDATE operation.
2133 .. versionchanged:: 1.4 the update_context no longer has a
2134 ``QueryContext`` object associated with it.
2136 .. seealso::
2138 :meth:`.QueryEvents.before_compile_update`
2140 :meth:`.SessionEvents.after_bulk_delete`
2142 """
2144 def after_bulk_delete(self, delete_context: _O) -> None:
2145 """Event for after the legacy :meth:`_orm.Query.delete` method
2146 has been called.
2148 .. legacy:: The :meth:`_orm.SessionEvents.after_bulk_delete` method
2149 is a legacy event hook as of SQLAlchemy 2.0. The event
2150 **does not participate** in :term:`2.0 style` invocations
2151 using :func:`_dml.delete` documented at
2152 :ref:`orm_queryguide_update_delete_where`. For 2.0 style use,
2153 the :meth:`_orm.SessionEvents.do_orm_execute` hook will intercept
2154 these calls.
2156 :param delete_context: a "delete context" object which contains
2157 details about the update, including these attributes:
2159 * ``session`` - the :class:`.Session` involved
2160 * ``query`` -the :class:`_query.Query`
2161 object that this update operation
2162 was called upon.
2163 * ``result`` the :class:`_engine.CursorResult`
2164 returned as a result of the
2165 bulk DELETE operation.
2167 .. versionchanged:: 1.4 the update_context no longer has a
2168 ``QueryContext`` object associated with it.
2170 .. seealso::
2172 :meth:`.QueryEvents.before_compile_delete`
2174 :meth:`.SessionEvents.after_bulk_update`
2176 """
2178 @_lifecycle_event
2179 def transient_to_pending(self, session: Session, instance: _O) -> None:
2180 """Intercept the "transient to pending" transition for a specific
2181 object.
2183 This event is a specialization of the
2184 :meth:`.SessionEvents.after_attach` event which is only invoked
2185 for this specific transition. It is invoked typically during the
2186 :meth:`.Session.add` call.
2188 :param session: target :class:`.Session`
2190 :param instance: the ORM-mapped instance being operated upon.
2192 .. seealso::
2194 :ref:`session_lifecycle_events`
2196 """
2198 @_lifecycle_event
2199 def pending_to_transient(self, session: Session, instance: _O) -> None:
2200 """Intercept the "pending to transient" transition for a specific
2201 object.
2203 This less common transition occurs when an pending object that has
2204 not been flushed is evicted from the session; this can occur
2205 when the :meth:`.Session.rollback` method rolls back the transaction,
2206 or when the :meth:`.Session.expunge` method is used.
2208 :param session: target :class:`.Session`
2210 :param instance: the ORM-mapped instance being operated upon.
2212 .. seealso::
2214 :ref:`session_lifecycle_events`
2216 """
2218 @_lifecycle_event
2219 def persistent_to_transient(self, session: Session, instance: _O) -> None:
2220 """Intercept the "persistent to transient" transition for a specific
2221 object.
2223 This less common transition occurs when an pending object that has
2224 has been flushed is evicted from the session; this can occur
2225 when the :meth:`.Session.rollback` method rolls back the transaction.
2227 :param session: target :class:`.Session`
2229 :param instance: the ORM-mapped instance being operated upon.
2231 .. seealso::
2233 :ref:`session_lifecycle_events`
2235 """
2237 @_lifecycle_event
2238 def pending_to_persistent(self, session: Session, instance: _O) -> None:
2239 """Intercept the "pending to persistent"" transition for a specific
2240 object.
2242 This event is invoked within the flush process, and is
2243 similar to scanning the :attr:`.Session.new` collection within
2244 the :meth:`.SessionEvents.after_flush` event. However, in this
2245 case the object has already been moved to the persistent state
2246 when the event is called.
2248 :param session: target :class:`.Session`
2250 :param instance: the ORM-mapped instance being operated upon.
2252 .. seealso::
2254 :ref:`session_lifecycle_events`
2256 """
2258 @_lifecycle_event
2259 def detached_to_persistent(self, session: Session, instance: _O) -> None:
2260 """Intercept the "detached to persistent" transition for a specific
2261 object.
2263 This event is a specialization of the
2264 :meth:`.SessionEvents.after_attach` event which is only invoked
2265 for this specific transition. It is invoked typically during the
2266 :meth:`.Session.add` call, as well as during the
2267 :meth:`.Session.delete` call if the object was not previously
2268 associated with the
2269 :class:`.Session` (note that an object marked as "deleted" remains
2270 in the "persistent" state until the flush proceeds).
2272 .. note::
2274 If the object becomes persistent as part of a call to
2275 :meth:`.Session.delete`, the object is **not** yet marked as
2276 deleted when this event is called. To detect deleted objects,
2277 check the ``deleted`` flag sent to the
2278 :meth:`.SessionEvents.persistent_to_detached` to event after the
2279 flush proceeds, or check the :attr:`.Session.deleted` collection
2280 within the :meth:`.SessionEvents.before_flush` event if deleted
2281 objects need to be intercepted before the flush.
2283 :param session: target :class:`.Session`
2285 :param instance: the ORM-mapped instance being operated upon.
2287 .. seealso::
2289 :ref:`session_lifecycle_events`
2291 """
2293 @_lifecycle_event
2294 def loaded_as_persistent(self, session: Session, instance: _O) -> None:
2295 """Intercept the "loaded as persistent" transition for a specific
2296 object.
2298 This event is invoked within the ORM loading process, and is invoked
2299 very similarly to the :meth:`.InstanceEvents.load` event. However,
2300 the event here is linkable to a :class:`.Session` class or instance,
2301 rather than to a mapper or class hierarchy, and integrates
2302 with the other session lifecycle events smoothly. The object
2303 is guaranteed to be present in the session's identity map when
2304 this event is called.
2306 .. note:: This event is invoked within the loader process before
2307 eager loaders may have been completed, and the object's state may
2308 not be complete. Additionally, invoking row-level refresh
2309 operations on the object will place the object into a new loader
2310 context, interfering with the existing load context. See the note
2311 on :meth:`.InstanceEvents.load` for background on making use of the
2312 :paramref:`.SessionEvents.restore_load_context` parameter, which
2313 works in the same manner as that of
2314 :paramref:`.InstanceEvents.restore_load_context`, in order to
2315 resolve this scenario.
2317 :param session: target :class:`.Session`
2319 :param instance: the ORM-mapped instance being operated upon.
2321 .. seealso::
2323 :ref:`session_lifecycle_events`
2325 """
2327 @_lifecycle_event
2328 def persistent_to_deleted(self, session: Session, instance: _O) -> None:
2329 """Intercept the "persistent to deleted" transition for a specific
2330 object.
2332 This event is invoked when a persistent object's identity
2333 is deleted from the database within a flush, however the object
2334 still remains associated with the :class:`.Session` until the
2335 transaction completes.
2337 If the transaction is rolled back, the object moves again
2338 to the persistent state, and the
2339 :meth:`.SessionEvents.deleted_to_persistent` event is called.
2340 If the transaction is committed, the object becomes detached,
2341 which will emit the :meth:`.SessionEvents.deleted_to_detached`
2342 event.
2344 Note that while the :meth:`.Session.delete` method is the primary
2345 public interface to mark an object as deleted, many objects
2346 get deleted due to cascade rules, which are not always determined
2347 until flush time. Therefore, there's no way to catch
2348 every object that will be deleted until the flush has proceeded.
2349 the :meth:`.SessionEvents.persistent_to_deleted` event is therefore
2350 invoked at the end of a flush.
2352 .. seealso::
2354 :ref:`session_lifecycle_events`
2356 """
2358 @_lifecycle_event
2359 def deleted_to_persistent(self, session: Session, instance: _O) -> None:
2360 """Intercept the "deleted to persistent" transition for a specific
2361 object.
2363 This transition occurs only when an object that's been deleted
2364 successfully in a flush is restored due to a call to
2365 :meth:`.Session.rollback`. The event is not called under
2366 any other circumstances.
2368 .. seealso::
2370 :ref:`session_lifecycle_events`
2372 """
2374 @_lifecycle_event
2375 def deleted_to_detached(self, session: Session, instance: _O) -> None:
2376 """Intercept the "deleted to detached" transition for a specific
2377 object.
2379 This event is invoked when a deleted object is evicted
2380 from the session. The typical case when this occurs is when
2381 the transaction for a :class:`.Session` in which the object
2382 was deleted is committed; the object moves from the deleted
2383 state to the detached state.
2385 It is also invoked for objects that were deleted in a flush
2386 when the :meth:`.Session.expunge_all` or :meth:`.Session.close`
2387 events are called, as well as if the object is individually
2388 expunged from its deleted state via :meth:`.Session.expunge`.
2390 .. seealso::
2392 :ref:`session_lifecycle_events`
2394 """
2396 @_lifecycle_event
2397 def persistent_to_detached(self, session: Session, instance: _O) -> None:
2398 """Intercept the "persistent to detached" transition for a specific
2399 object.
2401 This event is invoked when a persistent object is evicted
2402 from the session. There are many conditions that cause this
2403 to happen, including:
2405 * using a method such as :meth:`.Session.expunge`
2406 or :meth:`.Session.close`
2408 * Calling the :meth:`.Session.rollback` method, when the object
2409 was part of an INSERT statement for that session's transaction
2412 :param session: target :class:`.Session`
2414 :param instance: the ORM-mapped instance being operated upon.
2416 :param deleted: boolean. If True, indicates this object moved
2417 to the detached state because it was marked as deleted and flushed.
2420 .. seealso::
2422 :ref:`session_lifecycle_events`
2424 """
2427class AttributeEvents(event.Events[QueryableAttribute[Any]]):
2428 r"""Define events for object attributes.
2430 These are typically defined on the class-bound descriptor for the
2431 target class.
2433 For example, to register a listener that will receive the
2434 :meth:`_orm.AttributeEvents.append` event::
2436 from sqlalchemy import event
2438 @event.listens_for(MyClass.collection, 'append', propagate=True)
2439 def my_append_listener(target, value, initiator):
2440 print("received append event for target: %s" % target)
2443 Listeners have the option to return a possibly modified version of the
2444 value, when the :paramref:`.AttributeEvents.retval` flag is passed to
2445 :func:`.event.listen` or :func:`.event.listens_for`, such as below,
2446 illustrated using the :meth:`_orm.AttributeEvents.set` event::
2448 def validate_phone(target, value, oldvalue, initiator):
2449 "Strip non-numeric characters from a phone number"
2451 return re.sub(r'\D', '', value)
2453 # setup listener on UserContact.phone attribute, instructing
2454 # it to use the return value
2455 listen(UserContact.phone, 'set', validate_phone, retval=True)
2457 A validation function like the above can also raise an exception
2458 such as :exc:`ValueError` to halt the operation.
2460 The :paramref:`.AttributeEvents.propagate` flag is also important when
2461 applying listeners to mapped classes that also have mapped subclasses,
2462 as when using mapper inheritance patterns::
2465 @event.listens_for(MySuperClass.attr, 'set', propagate=True)
2466 def receive_set(target, value, initiator):
2467 print("value set: %s" % target)
2469 The full list of modifiers available to the :func:`.event.listen`
2470 and :func:`.event.listens_for` functions are below.
2472 :param active_history=False: When True, indicates that the
2473 "set" event would like to receive the "old" value being
2474 replaced unconditionally, even if this requires firing off
2475 database loads. Note that ``active_history`` can also be
2476 set directly via :func:`.column_property` and
2477 :func:`_orm.relationship`.
2479 :param propagate=False: When True, the listener function will
2480 be established not just for the class attribute given, but
2481 for attributes of the same name on all current subclasses
2482 of that class, as well as all future subclasses of that
2483 class, using an additional listener that listens for
2484 instrumentation events.
2485 :param raw=False: When True, the "target" argument to the
2486 event will be the :class:`.InstanceState` management
2487 object, rather than the mapped instance itself.
2488 :param retval=False: when True, the user-defined event
2489 listening must return the "value" argument from the
2490 function. This gives the listening function the opportunity
2491 to change the value that is ultimately used for a "set"
2492 or "append" event.
2494 """
2496 _target_class_doc = "SomeClass.some_attribute"
2497 _dispatch_target = QueryableAttribute
2499 @staticmethod
2500 def _set_dispatch(
2501 cls: Type[_HasEventsDispatch[Any]], dispatch_cls: Type[_Dispatch[Any]]
2502 ) -> _Dispatch[Any]:
2503 dispatch = event.Events._set_dispatch(cls, dispatch_cls)
2504 dispatch_cls._active_history = False
2505 return dispatch
2507 @classmethod
2508 def _accept_with(
2509 cls,
2510 target: Union[QueryableAttribute[Any], Type[QueryableAttribute[Any]]],
2511 identifier: str,
2512 ) -> Union[QueryableAttribute[Any], Type[QueryableAttribute[Any]]]:
2513 # TODO: coverage
2514 if isinstance(target, interfaces.MapperProperty):
2515 return getattr(target.parent.class_, target.key)
2516 else:
2517 return target
2519 @classmethod
2520 def _listen( # type: ignore [override]
2521 cls,
2522 event_key: _EventKey[QueryableAttribute[Any]],
2523 active_history: bool = False,
2524 raw: bool = False,
2525 retval: bool = False,
2526 propagate: bool = False,
2527 include_key: bool = False,
2528 ) -> None:
2529 target, fn = event_key.dispatch_target, event_key._listen_fn
2531 if active_history:
2532 target.dispatch._active_history = True
2534 if not raw or not retval or not include_key:
2536 def wrap(target: InstanceState[_O], *arg: Any, **kw: Any) -> Any:
2537 if not raw:
2538 target = target.obj() # type: ignore [assignment]
2539 if not retval:
2540 if arg:
2541 value = arg[0]
2542 else:
2543 value = None
2544 if include_key:
2545 fn(target, *arg, **kw)
2546 else:
2547 fn(target, *arg)
2548 return value
2549 else:
2550 if include_key:
2551 return fn(target, *arg, **kw)
2552 else:
2553 return fn(target, *arg)
2555 event_key = event_key.with_wrapper(wrap)
2557 event_key.base_listen(propagate=propagate)
2559 if propagate:
2560 manager = instrumentation.manager_of_class(target.class_)
2562 for mgr in manager.subclass_managers(True): # type: ignore [no-untyped-call] # noqa: E501
2563 event_key.with_dispatch_target(mgr[target.key]).base_listen(
2564 propagate=True
2565 )
2566 if active_history:
2567 mgr[target.key].dispatch._active_history = True
2569 def append(
2570 self,
2571 target: _O,
2572 value: _T,
2573 initiator: Event,
2574 *,
2575 key: EventConstants = NO_KEY,
2576 ) -> Optional[_T]:
2577 """Receive a collection append event.
2579 The append event is invoked for each element as it is appended
2580 to the collection. This occurs for single-item appends as well
2581 as for a "bulk replace" operation.
2583 :param target: the object instance receiving the event.
2584 If the listener is registered with ``raw=True``, this will
2585 be the :class:`.InstanceState` object.
2586 :param value: the value being appended. If this listener
2587 is registered with ``retval=True``, the listener
2588 function must return this value, or a new value which
2589 replaces it.
2590 :param initiator: An instance of :class:`.attributes.Event`
2591 representing the initiation of the event. May be modified
2592 from its original value by backref handlers in order to control
2593 chained event propagation, as well as be inspected for information
2594 about the source of the event.
2595 :param key: When the event is established using the
2596 :paramref:`.AttributeEvents.include_key` parameter set to
2597 True, this will be the key used in the operation, such as
2598 ``collection[some_key_or_index] = value``.
2599 The parameter is not passed
2600 to the event at all if the the
2601 :paramref:`.AttributeEvents.include_key`
2602 was not used to set up the event; this is to allow backwards
2603 compatibility with existing event handlers that don't include the
2604 ``key`` parameter.
2606 .. versionadded:: 2.0
2608 :return: if the event was registered with ``retval=True``,
2609 the given value, or a new effective value, should be returned.
2611 .. seealso::
2613 :class:`.AttributeEvents` - background on listener options such
2614 as propagation to subclasses.
2616 :meth:`.AttributeEvents.bulk_replace`
2618 """
2620 def append_wo_mutation(
2621 self,
2622 target: _O,
2623 value: _T,
2624 initiator: Event,
2625 *,
2626 key: EventConstants = NO_KEY,
2627 ) -> None:
2628 """Receive a collection append event where the collection was not
2629 actually mutated.
2631 This event differs from :meth:`_orm.AttributeEvents.append` in that
2632 it is fired off for de-duplicating collections such as sets and
2633 dictionaries, when the object already exists in the target collection.
2634 The event does not have a return value and the identity of the
2635 given object cannot be changed.
2637 The event is used for cascading objects into a :class:`_orm.Session`
2638 when the collection has already been mutated via a backref event.
2640 :param target: the object instance receiving the event.
2641 If the listener is registered with ``raw=True``, this will
2642 be the :class:`.InstanceState` object.
2643 :param value: the value that would be appended if the object did not
2644 already exist in the collection.
2645 :param initiator: An instance of :class:`.attributes.Event`
2646 representing the initiation of the event. May be modified
2647 from its original value by backref handlers in order to control
2648 chained event propagation, as well as be inspected for information
2649 about the source of the event.
2650 :param key: When the event is established using the
2651 :paramref:`.AttributeEvents.include_key` parameter set to
2652 True, this will be the key used in the operation, such as
2653 ``collection[some_key_or_index] = value``.
2654 The parameter is not passed
2655 to the event at all if the the
2656 :paramref:`.AttributeEvents.include_key`
2657 was not used to set up the event; this is to allow backwards
2658 compatibility with existing event handlers that don't include the
2659 ``key`` parameter.
2661 .. versionadded:: 2.0
2663 :return: No return value is defined for this event.
2665 .. versionadded:: 1.4.15
2667 """
2669 def bulk_replace(
2670 self,
2671 target: _O,
2672 values: Iterable[_T],
2673 initiator: Event,
2674 *,
2675 keys: Optional[Iterable[EventConstants]] = None,
2676 ) -> None:
2677 """Receive a collection 'bulk replace' event.
2679 This event is invoked for a sequence of values as they are incoming
2680 to a bulk collection set operation, which can be
2681 modified in place before the values are treated as ORM objects.
2682 This is an "early hook" that runs before the bulk replace routine
2683 attempts to reconcile which objects are already present in the
2684 collection and which are being removed by the net replace operation.
2686 It is typical that this method be combined with use of the
2687 :meth:`.AttributeEvents.append` event. When using both of these
2688 events, note that a bulk replace operation will invoke
2689 the :meth:`.AttributeEvents.append` event for all new items,
2690 even after :meth:`.AttributeEvents.bulk_replace` has been invoked
2691 for the collection as a whole. In order to determine if an
2692 :meth:`.AttributeEvents.append` event is part of a bulk replace,
2693 use the symbol :attr:`~.attributes.OP_BULK_REPLACE` to test the
2694 incoming initiator::
2696 from sqlalchemy.orm.attributes import OP_BULK_REPLACE
2698 @event.listens_for(SomeObject.collection, "bulk_replace")
2699 def process_collection(target, values, initiator):
2700 values[:] = [_make_value(value) for value in values]
2702 @event.listens_for(SomeObject.collection, "append", retval=True)
2703 def process_collection(target, value, initiator):
2704 # make sure bulk_replace didn't already do it
2705 if initiator is None or initiator.op is not OP_BULK_REPLACE:
2706 return _make_value(value)
2707 else:
2708 return value
2710 .. versionadded:: 1.2
2712 :param target: the object instance receiving the event.
2713 If the listener is registered with ``raw=True``, this will
2714 be the :class:`.InstanceState` object.
2715 :param value: a sequence (e.g. a list) of the values being set. The
2716 handler can modify this list in place.
2717 :param initiator: An instance of :class:`.attributes.Event`
2718 representing the initiation of the event.
2719 :param keys: When the event is established using the
2720 :paramref:`.AttributeEvents.include_key` parameter set to
2721 True, this will be the sequence of keys used in the operation,
2722 typically only for a dictionary update. The parameter is not passed
2723 to the event at all if the the
2724 :paramref:`.AttributeEvents.include_key`
2725 was not used to set up the event; this is to allow backwards
2726 compatibility with existing event handlers that don't include the
2727 ``key`` parameter.
2729 .. versionadded:: 2.0
2731 .. seealso::
2733 :class:`.AttributeEvents` - background on listener options such
2734 as propagation to subclasses.
2737 """
2739 def remove(
2740 self,
2741 target: _O,
2742 value: _T,
2743 initiator: Event,
2744 *,
2745 key: EventConstants = NO_KEY,
2746 ) -> None:
2747 """Receive a collection remove event.
2749 :param target: the object instance receiving the event.
2750 If the listener is registered with ``raw=True``, this will
2751 be the :class:`.InstanceState` object.
2752 :param value: the value being removed.
2753 :param initiator: An instance of :class:`.attributes.Event`
2754 representing the initiation of the event. May be modified
2755 from its original value by backref handlers in order to control
2756 chained event propagation.
2758 :param key: When the event is established using the
2759 :paramref:`.AttributeEvents.include_key` parameter set to
2760 True, this will be the key used in the operation, such as
2761 ``del collection[some_key_or_index]``. The parameter is not passed
2762 to the event at all if the the
2763 :paramref:`.AttributeEvents.include_key`
2764 was not used to set up the event; this is to allow backwards
2765 compatibility with existing event handlers that don't include the
2766 ``key`` parameter.
2768 .. versionadded:: 2.0
2770 :return: No return value is defined for this event.
2773 .. seealso::
2775 :class:`.AttributeEvents` - background on listener options such
2776 as propagation to subclasses.
2778 """
2780 def set(
2781 self, target: _O, value: _T, oldvalue: _T, initiator: Event
2782 ) -> None:
2783 """Receive a scalar set event.
2785 :param target: the object instance receiving the event.
2786 If the listener is registered with ``raw=True``, this will
2787 be the :class:`.InstanceState` object.
2788 :param value: the value being set. If this listener
2789 is registered with ``retval=True``, the listener
2790 function must return this value, or a new value which
2791 replaces it.
2792 :param oldvalue: the previous value being replaced. This
2793 may also be the symbol ``NEVER_SET`` or ``NO_VALUE``.
2794 If the listener is registered with ``active_history=True``,
2795 the previous value of the attribute will be loaded from
2796 the database if the existing value is currently unloaded
2797 or expired.
2798 :param initiator: An instance of :class:`.attributes.Event`
2799 representing the initiation of the event. May be modified
2800 from its original value by backref handlers in order to control
2801 chained event propagation.
2803 :return: if the event was registered with ``retval=True``,
2804 the given value, or a new effective value, should be returned.
2806 .. seealso::
2808 :class:`.AttributeEvents` - background on listener options such
2809 as propagation to subclasses.
2811 """
2813 def init_scalar(
2814 self, target: _O, value: _T, dict_: Dict[Any, Any]
2815 ) -> None:
2816 r"""Receive a scalar "init" event.
2818 This event is invoked when an uninitialized, unpersisted scalar
2819 attribute is accessed, e.g. read::
2822 x = my_object.some_attribute
2824 The ORM's default behavior when this occurs for an un-initialized
2825 attribute is to return the value ``None``; note this differs from
2826 Python's usual behavior of raising ``AttributeError``. The
2827 event here can be used to customize what value is actually returned,
2828 with the assumption that the event listener would be mirroring
2829 a default generator that is configured on the Core
2830 :class:`_schema.Column`
2831 object as well.
2833 Since a default generator on a :class:`_schema.Column`
2834 might also produce
2835 a changing value such as a timestamp, the
2836 :meth:`.AttributeEvents.init_scalar`
2837 event handler can also be used to **set** the newly returned value, so
2838 that a Core-level default generation function effectively fires off
2839 only once, but at the moment the attribute is accessed on the
2840 non-persisted object. Normally, no change to the object's state
2841 is made when an uninitialized attribute is accessed (much older
2842 SQLAlchemy versions did in fact change the object's state).
2844 If a default generator on a column returned a particular constant,
2845 a handler might be used as follows::
2847 SOME_CONSTANT = 3.1415926
2849 class MyClass(Base):
2850 # ...
2852 some_attribute = Column(Numeric, default=SOME_CONSTANT)
2854 @event.listens_for(
2855 MyClass.some_attribute, "init_scalar",
2856 retval=True, propagate=True)
2857 def _init_some_attribute(target, dict_, value):
2858 dict_['some_attribute'] = SOME_CONSTANT
2859 return SOME_CONSTANT
2861 Above, we initialize the attribute ``MyClass.some_attribute`` to the
2862 value of ``SOME_CONSTANT``. The above code includes the following
2863 features:
2865 * By setting the value ``SOME_CONSTANT`` in the given ``dict_``,
2866 we indicate that this value is to be persisted to the database.
2867 This supersedes the use of ``SOME_CONSTANT`` in the default generator
2868 for the :class:`_schema.Column`. The ``active_column_defaults.py``
2869 example given at :ref:`examples_instrumentation` illustrates using
2870 the same approach for a changing default, e.g. a timestamp
2871 generator. In this particular example, it is not strictly
2872 necessary to do this since ``SOME_CONSTANT`` would be part of the
2873 INSERT statement in either case.
2875 * By establishing the ``retval=True`` flag, the value we return
2876 from the function will be returned by the attribute getter.
2877 Without this flag, the event is assumed to be a passive observer
2878 and the return value of our function is ignored.
2880 * The ``propagate=True`` flag is significant if the mapped class
2881 includes inheriting subclasses, which would also make use of this
2882 event listener. Without this flag, an inheriting subclass will
2883 not use our event handler.
2885 In the above example, the attribute set event
2886 :meth:`.AttributeEvents.set` as well as the related validation feature
2887 provided by :obj:`_orm.validates` is **not** invoked when we apply our
2888 value to the given ``dict_``. To have these events to invoke in
2889 response to our newly generated value, apply the value to the given
2890 object as a normal attribute set operation::
2892 SOME_CONSTANT = 3.1415926
2894 @event.listens_for(
2895 MyClass.some_attribute, "init_scalar",
2896 retval=True, propagate=True)
2897 def _init_some_attribute(target, dict_, value):
2898 # will also fire off attribute set events
2899 target.some_attribute = SOME_CONSTANT
2900 return SOME_CONSTANT
2902 When multiple listeners are set up, the generation of the value
2903 is "chained" from one listener to the next by passing the value
2904 returned by the previous listener that specifies ``retval=True``
2905 as the ``value`` argument of the next listener.
2907 :param target: the object instance receiving the event.
2908 If the listener is registered with ``raw=True``, this will
2909 be the :class:`.InstanceState` object.
2910 :param value: the value that is to be returned before this event
2911 listener were invoked. This value begins as the value ``None``,
2912 however will be the return value of the previous event handler
2913 function if multiple listeners are present.
2914 :param dict\_: the attribute dictionary of this mapped object.
2915 This is normally the ``__dict__`` of the object, but in all cases
2916 represents the destination that the attribute system uses to get
2917 at the actual value of this attribute. Placing the value in this
2918 dictionary has the effect that the value will be used in the
2919 INSERT statement generated by the unit of work.
2922 .. seealso::
2924 :meth:`.AttributeEvents.init_collection` - collection version
2925 of this event
2927 :class:`.AttributeEvents` - background on listener options such
2928 as propagation to subclasses.
2930 :ref:`examples_instrumentation` - see the
2931 ``active_column_defaults.py`` example.
2933 """
2935 def init_collection(
2936 self,
2937 target: _O,
2938 collection: Type[Collection[Any]],
2939 collection_adapter: CollectionAdapter,
2940 ) -> None:
2941 """Receive a 'collection init' event.
2943 This event is triggered for a collection-based attribute, when
2944 the initial "empty collection" is first generated for a blank
2945 attribute, as well as for when the collection is replaced with
2946 a new one, such as via a set event.
2948 E.g., given that ``User.addresses`` is a relationship-based
2949 collection, the event is triggered here::
2951 u1 = User()
2952 u1.addresses.append(a1) # <- new collection
2954 and also during replace operations::
2956 u1.addresses = [a2, a3] # <- new collection
2958 :param target: the object instance receiving the event.
2959 If the listener is registered with ``raw=True``, this will
2960 be the :class:`.InstanceState` object.
2961 :param collection: the new collection. This will always be generated
2962 from what was specified as
2963 :paramref:`_orm.relationship.collection_class`, and will always
2964 be empty.
2965 :param collection_adapter: the :class:`.CollectionAdapter` that will
2966 mediate internal access to the collection.
2968 .. seealso::
2970 :class:`.AttributeEvents` - background on listener options such
2971 as propagation to subclasses.
2973 :meth:`.AttributeEvents.init_scalar` - "scalar" version of this
2974 event.
2976 """
2978 def dispose_collection(
2979 self,
2980 target: _O,
2981 collection: Collection[Any],
2982 collection_adapter: CollectionAdapter,
2983 ) -> None:
2984 """Receive a 'collection dispose' event.
2986 This event is triggered for a collection-based attribute when
2987 a collection is replaced, that is::
2989 u1.addresses.append(a1)
2991 u1.addresses = [a2, a3] # <- old collection is disposed
2993 The old collection received will contain its previous contents.
2995 .. versionchanged:: 1.2 The collection passed to
2996 :meth:`.AttributeEvents.dispose_collection` will now have its
2997 contents before the dispose intact; previously, the collection
2998 would be empty.
3000 .. seealso::
3002 :class:`.AttributeEvents` - background on listener options such
3003 as propagation to subclasses.
3005 """
3007 def modified(self, target: _O, initiator: Event) -> None:
3008 """Receive a 'modified' event.
3010 This event is triggered when the :func:`.attributes.flag_modified`
3011 function is used to trigger a modify event on an attribute without
3012 any specific value being set.
3014 .. versionadded:: 1.2
3016 :param target: the object instance receiving the event.
3017 If the listener is registered with ``raw=True``, this will
3018 be the :class:`.InstanceState` object.
3020 :param initiator: An instance of :class:`.attributes.Event`
3021 representing the initiation of the event.
3023 .. seealso::
3025 :class:`.AttributeEvents` - background on listener options such
3026 as propagation to subclasses.
3028 """
3031class QueryEvents(event.Events[Query[Any]]):
3032 """Represent events within the construction of a :class:`_query.Query`
3033 object.
3035 .. legacy:: The :class:`_orm.QueryEvents` event methods are legacy
3036 as of SQLAlchemy 2.0, and only apply to direct use of the
3037 :class:`_orm.Query` object. They are not used for :term:`2.0 style`
3038 statements. For events to intercept and modify 2.0 style ORM use,
3039 use the :meth:`_orm.SessionEvents.do_orm_execute` hook.
3042 The :class:`_orm.QueryEvents` hooks are now superseded by the
3043 :meth:`_orm.SessionEvents.do_orm_execute` event hook.
3045 """
3047 _target_class_doc = "SomeQuery"
3048 _dispatch_target = Query
3050 def before_compile(self, query: Query[Any]) -> None:
3051 """Receive the :class:`_query.Query`
3052 object before it is composed into a
3053 core :class:`_expression.Select` object.
3055 .. deprecated:: 1.4 The :meth:`_orm.QueryEvents.before_compile` event
3056 is superseded by the much more capable
3057 :meth:`_orm.SessionEvents.do_orm_execute` hook. In version 1.4,
3058 the :meth:`_orm.QueryEvents.before_compile` event is **no longer
3059 used** for ORM-level attribute loads, such as loads of deferred
3060 or expired attributes as well as relationship loaders. See the
3061 new examples in :ref:`examples_session_orm_events` which
3062 illustrate new ways of intercepting and modifying ORM queries
3063 for the most common purpose of adding arbitrary filter criteria.
3066 This event is intended to allow changes to the query given::
3068 @event.listens_for(Query, "before_compile", retval=True)
3069 def no_deleted(query):
3070 for desc in query.column_descriptions:
3071 if desc['type'] is User:
3072 entity = desc['entity']
3073 query = query.filter(entity.deleted == False)
3074 return query
3076 The event should normally be listened with the ``retval=True``
3077 parameter set, so that the modified query may be returned.
3079 The :meth:`.QueryEvents.before_compile` event by default
3080 will disallow "baked" queries from caching a query, if the event
3081 hook returns a new :class:`_query.Query` object.
3082 This affects both direct
3083 use of the baked query extension as well as its operation within
3084 lazy loaders and eager loaders for relationships. In order to
3085 re-establish the query being cached, apply the event adding the
3086 ``bake_ok`` flag::
3088 @event.listens_for(
3089 Query, "before_compile", retval=True, bake_ok=True)
3090 def my_event(query):
3091 for desc in query.column_descriptions:
3092 if desc['type'] is User:
3093 entity = desc['entity']
3094 query = query.filter(entity.deleted == False)
3095 return query
3097 When ``bake_ok`` is set to True, the event hook will only be invoked
3098 once, and not called for subsequent invocations of a particular query
3099 that is being cached.
3101 .. versionadded:: 1.3.11 - added the "bake_ok" flag to the
3102 :meth:`.QueryEvents.before_compile` event and disallowed caching via
3103 the "baked" extension from occurring for event handlers that
3104 return a new :class:`_query.Query` object if this flag is not set.
3106 .. seealso::
3108 :meth:`.QueryEvents.before_compile_update`
3110 :meth:`.QueryEvents.before_compile_delete`
3112 :ref:`baked_with_before_compile`
3114 """
3116 def before_compile_update(
3117 self, query: Query[Any], update_context: BulkUpdate
3118 ) -> None:
3119 """Allow modifications to the :class:`_query.Query` object within
3120 :meth:`_query.Query.update`.
3122 .. deprecated:: 1.4 The :meth:`_orm.QueryEvents.before_compile_update`
3123 event is superseded by the much more capable
3124 :meth:`_orm.SessionEvents.do_orm_execute` hook.
3126 Like the :meth:`.QueryEvents.before_compile` event, if the event
3127 is to be used to alter the :class:`_query.Query` object, it should
3128 be configured with ``retval=True``, and the modified
3129 :class:`_query.Query` object returned, as in ::
3131 @event.listens_for(Query, "before_compile_update", retval=True)
3132 def no_deleted(query, update_context):
3133 for desc in query.column_descriptions:
3134 if desc['type'] is User:
3135 entity = desc['entity']
3136 query = query.filter(entity.deleted == False)
3138 update_context.values['timestamp'] = datetime.utcnow()
3139 return query
3141 The ``.values`` dictionary of the "update context" object can also
3142 be modified in place as illustrated above.
3144 :param query: a :class:`_query.Query` instance; this is also
3145 the ``.query`` attribute of the given "update context"
3146 object.
3148 :param update_context: an "update context" object which is
3149 the same kind of object as described in
3150 :paramref:`.QueryEvents.after_bulk_update.update_context`.
3151 The object has a ``.values`` attribute in an UPDATE context which is
3152 the dictionary of parameters passed to :meth:`_query.Query.update`.
3153 This
3154 dictionary can be modified to alter the VALUES clause of the
3155 resulting UPDATE statement.
3157 .. versionadded:: 1.2.17
3159 .. seealso::
3161 :meth:`.QueryEvents.before_compile`
3163 :meth:`.QueryEvents.before_compile_delete`
3166 """
3168 def before_compile_delete(
3169 self, query: Query[Any], delete_context: BulkDelete
3170 ) -> None:
3171 """Allow modifications to the :class:`_query.Query` object within
3172 :meth:`_query.Query.delete`.
3174 .. deprecated:: 1.4 The :meth:`_orm.QueryEvents.before_compile_delete`
3175 event is superseded by the much more capable
3176 :meth:`_orm.SessionEvents.do_orm_execute` hook.
3178 Like the :meth:`.QueryEvents.before_compile` event, this event
3179 should be configured with ``retval=True``, and the modified
3180 :class:`_query.Query` object returned, as in ::
3182 @event.listens_for(Query, "before_compile_delete", retval=True)
3183 def no_deleted(query, delete_context):
3184 for desc in query.column_descriptions:
3185 if desc['type'] is User:
3186 entity = desc['entity']
3187 query = query.filter(entity.deleted == False)
3188 return query
3190 :param query: a :class:`_query.Query` instance; this is also
3191 the ``.query`` attribute of the given "delete context"
3192 object.
3194 :param delete_context: a "delete context" object which is
3195 the same kind of object as described in
3196 :paramref:`.QueryEvents.after_bulk_delete.delete_context`.
3198 .. versionadded:: 1.2.17
3200 .. seealso::
3202 :meth:`.QueryEvents.before_compile`
3204 :meth:`.QueryEvents.before_compile_update`
3207 """
3209 @classmethod
3210 def _listen(
3211 cls,
3212 event_key: _EventKey[_ET],
3213 retval: bool = False,
3214 bake_ok: bool = False,
3215 **kw: Any,
3216 ) -> None:
3217 fn = event_key._listen_fn
3219 if not retval:
3221 def wrap(*arg: Any, **kw: Any) -> Any:
3222 if not retval:
3223 query = arg[0]
3224 fn(*arg, **kw)
3225 return query
3226 else:
3227 return fn(*arg, **kw)
3229 event_key = event_key.with_wrapper(wrap)
3230 else:
3231 # don't assume we can apply an attribute to the callable
3232 def wrap(*arg: Any, **kw: Any) -> Any:
3233 return fn(*arg, **kw)
3235 event_key = event_key.with_wrapper(wrap)
3237 wrap._bake_ok = bake_ok # type: ignore [attr-defined]
3239 event_key.base_listen(**kw)