1# orm/events.py
2# Copyright (C) 2005-2026 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
7
8"""ORM event interfaces."""
9
10from __future__ import annotations
11
12from typing import Any
13from typing import Callable
14from typing import Collection
15from typing import Dict
16from typing import Generic
17from typing import Iterable
18from typing import Optional
19from typing import Sequence
20from typing import Set
21from typing import Type
22from typing import TYPE_CHECKING
23from typing import TypeVar
24from typing import Union
25import weakref
26
27from . import instrumentation
28from . import interfaces
29from . import mapperlib
30from .attributes import QueryableAttribute
31from .base import _mapper_or_none
32from .base import NO_KEY
33from .instrumentation import ClassManager
34from .instrumentation import InstrumentationFactory
35from .query import BulkDelete
36from .query import BulkUpdate
37from .query import Query
38from .scoping import scoped_session
39from .session import Session
40from .session import sessionmaker
41from .. import event
42from .. import exc
43from .. import util
44from ..event import EventTarget
45from ..event.registry import _ET
46from ..util.compat import inspect_getfullargspec
47
48if TYPE_CHECKING:
49 from weakref import ReferenceType
50
51 from ._typing import _InstanceDict
52 from ._typing import _InternalEntityType
53 from ._typing import _O
54 from ._typing import _T
55 from .attributes import Event
56 from .base import EventConstants
57 from .session import ORMExecuteState
58 from .session import SessionTransaction
59 from .unitofwork import UOWTransaction
60 from ..engine import Connection
61 from ..event.base import _Dispatch
62 from ..event.base import _HasEventsDispatch
63 from ..event.registry import _EventKey
64 from ..orm.collections import CollectionAdapter
65 from ..orm.context import QueryContext
66 from ..orm.decl_api import DeclarativeAttributeIntercept
67 from ..orm.decl_api import DeclarativeMeta
68 from ..orm.mapper import Mapper
69 from ..orm.state import InstanceState
70
71_KT = TypeVar("_KT", bound=Any)
72_ET2 = TypeVar("_ET2", bound=EventTarget)
73
74
75class InstrumentationEvents(event.Events[InstrumentationFactory]):
76 """Events related to class instrumentation events.
77
78 The listeners here support being established against
79 any new style class, that is any object that is a subclass
80 of 'type'. Events will then be fired off for events
81 against that class. If the "propagate=True" flag is passed
82 to event.listen(), the event will fire off for subclasses
83 of that class as well.
84
85 The Python ``type`` builtin is also accepted as a target,
86 which when used has the effect of events being emitted
87 for all classes.
88
89 Note the "propagate" flag here is defaulted to ``True``,
90 unlike the other class level events where it defaults
91 to ``False``. This means that new subclasses will also
92 be the subject of these events, when a listener
93 is established on a superclass.
94
95 """
96
97 _target_class_doc = "SomeBaseClass"
98 _dispatch_target = InstrumentationFactory
99
100 @classmethod
101 def _accept_with(
102 cls,
103 target: Union[
104 InstrumentationFactory,
105 Type[InstrumentationFactory],
106 ],
107 identifier: str,
108 ) -> Optional[
109 Union[
110 InstrumentationFactory,
111 Type[InstrumentationFactory],
112 ]
113 ]:
114 if isinstance(target, type):
115 return _InstrumentationEventsHold(target) # type: ignore [return-value] # noqa: E501
116 else:
117 return None
118
119 @classmethod
120 def _listen(
121 cls, event_key: _EventKey[_T], propagate: bool = True, **kw: Any
122 ) -> None:
123 target, identifier, fn = (
124 event_key.dispatch_target,
125 event_key.identifier,
126 event_key._listen_fn,
127 )
128
129 def listen(target_cls: type, *arg: Any) -> Optional[Any]:
130 listen_cls = target()
131
132 # if weakref were collected, however this is not something
133 # that normally happens. it was occurring during test teardown
134 # between mapper/registry/instrumentation_manager, however this
135 # interaction was changed to not rely upon the event system.
136 if listen_cls is None:
137 return None
138
139 if propagate and issubclass(target_cls, listen_cls):
140 return fn(target_cls, *arg)
141 elif not propagate and target_cls is listen_cls:
142 return fn(target_cls, *arg)
143 else:
144 return None
145
146 def remove(ref: ReferenceType[_T]) -> None:
147 key = event.registry._EventKey( # type: ignore [type-var]
148 None,
149 identifier,
150 listen,
151 instrumentation._instrumentation_factory,
152 )
153 getattr(
154 instrumentation._instrumentation_factory.dispatch, identifier
155 ).remove(key)
156
157 target = weakref.ref(target.class_, remove)
158
159 event_key.with_dispatch_target(
160 instrumentation._instrumentation_factory
161 ).with_wrapper(listen).base_listen(**kw)
162
163 @classmethod
164 def _clear(cls) -> None:
165 super()._clear()
166 instrumentation._instrumentation_factory.dispatch._clear()
167
168 def class_instrument(self, cls: ClassManager[_O]) -> None:
169 """Called after the given class is instrumented.
170
171 To get at the :class:`.ClassManager`, use
172 :func:`.manager_of_class`.
173
174 """
175
176 def class_uninstrument(self, cls: ClassManager[_O]) -> None:
177 """Called before the given class is uninstrumented.
178
179 To get at the :class:`.ClassManager`, use
180 :func:`.manager_of_class`.
181
182 """
183
184 def attribute_instrument(
185 self, cls: ClassManager[_O], key: _KT, inst: _O
186 ) -> None:
187 """Called when an attribute is instrumented."""
188
189
190class _InstrumentationEventsHold:
191 """temporary marker object used to transfer from _accept_with() to
192 _listen() on the InstrumentationEvents class.
193
194 """
195
196 def __init__(self, class_: type) -> None:
197 self.class_ = class_
198
199 dispatch = event.dispatcher(InstrumentationEvents)
200
201
202class InstanceEvents(event.Events[ClassManager[Any]]):
203 """Define events specific to object lifecycle.
204
205 e.g.::
206
207 from sqlalchemy import event
208
209
210 def my_load_listener(target, context):
211 print("on load!")
212
213
214 event.listen(SomeClass, "load", my_load_listener)
215
216 Available targets include:
217
218 * mapped classes
219 * unmapped superclasses of mapped or to-be-mapped classes
220 (using the ``propagate=True`` flag)
221 * :class:`_orm.Mapper` objects
222 * the :class:`_orm.Mapper` class itself indicates listening for all
223 mappers.
224
225 Instance events are closely related to mapper events, but
226 are more specific to the instance and its instrumentation,
227 rather than its system of persistence.
228
229 When using :class:`.InstanceEvents`, several modifiers are
230 available to the :func:`.event.listen` function.
231
232 :param propagate=False: When True, the event listener should
233 be applied to all inheriting classes as well as the
234 class which is the target of this listener.
235 :param raw=False: When True, the "target" argument passed
236 to applicable event listener functions will be the
237 instance's :class:`.InstanceState` management
238 object, rather than the mapped instance itself.
239 :param restore_load_context=False: Applies to the
240 :meth:`.InstanceEvents.load` and :meth:`.InstanceEvents.refresh`
241 events. Restores the loader context of the object when the event
242 hook is complete, so that ongoing eager load operations continue
243 to target the object appropriately. A warning is emitted if the
244 object is moved to a new loader context from within one of these
245 events if this flag is not set.
246
247 .. versionadded:: 1.3.14
248
249
250 """
251
252 _target_class_doc = "SomeClass"
253
254 _dispatch_target = ClassManager
255
256 @classmethod
257 def _new_classmanager_instance(
258 cls,
259 class_: Union[DeclarativeAttributeIntercept, DeclarativeMeta, type],
260 classmanager: ClassManager[_O],
261 ) -> None:
262 _InstanceEventsHold.populate(class_, classmanager)
263
264 @classmethod
265 @util.preload_module("sqlalchemy.orm")
266 def _accept_with(
267 cls,
268 target: Union[
269 ClassManager[Any],
270 Type[ClassManager[Any]],
271 ],
272 identifier: str,
273 ) -> Optional[Union[ClassManager[Any], Type[ClassManager[Any]]]]:
274 orm = util.preloaded.orm
275
276 if isinstance(target, ClassManager):
277 return target
278 elif isinstance(target, mapperlib.Mapper):
279 return target.class_manager
280 elif target is orm.mapper: # type: ignore [attr-defined]
281 util.warn_deprecated(
282 "The `sqlalchemy.orm.mapper()` symbol is deprecated and "
283 "will be removed in a future release. For the mapper-wide "
284 "event target, use the 'sqlalchemy.orm.Mapper' class.",
285 "2.0",
286 )
287 return ClassManager
288 elif isinstance(target, type):
289 if issubclass(target, mapperlib.Mapper):
290 return ClassManager
291 else:
292 manager = instrumentation.opt_manager_of_class(target)
293 if manager:
294 return manager
295 else:
296 return _InstanceEventsHold(target) # type: ignore [return-value] # noqa: E501
297 return None
298
299 @classmethod
300 def _listen(
301 cls,
302 event_key: _EventKey[ClassManager[Any]],
303 raw: bool = False,
304 propagate: bool = False,
305 restore_load_context: bool = False,
306 **kw: Any,
307 ) -> None:
308 target, fn = (event_key.dispatch_target, event_key._listen_fn)
309
310 if not raw or restore_load_context:
311
312 def wrap(
313 state: InstanceState[_O], *arg: Any, **kw: Any
314 ) -> Optional[Any]:
315 if not raw:
316 target: Any = state.obj()
317 else:
318 target = state
319 if restore_load_context:
320 runid = state.runid
321 try:
322 return fn(target, *arg, **kw)
323 finally:
324 if restore_load_context:
325 state.runid = runid
326
327 event_key = event_key.with_wrapper(wrap)
328
329 event_key.base_listen(propagate=propagate, **kw)
330
331 if propagate:
332 for mgr in target.subclass_managers(True):
333 event_key.with_dispatch_target(mgr).base_listen(propagate=True)
334
335 @classmethod
336 def _clear(cls) -> None:
337 super()._clear()
338 _InstanceEventsHold._clear()
339
340 def first_init(self, manager: ClassManager[_O], cls: Type[_O]) -> None:
341 """Called when the first instance of a particular mapping is called.
342
343 This event is called when the ``__init__`` method of a class
344 is called the first time for that particular class. The event
345 invokes before ``__init__`` actually proceeds as well as before
346 the :meth:`.InstanceEvents.init` event is invoked.
347
348 """
349
350 def init(self, target: _O, args: Any, kwargs: Any) -> None:
351 """Receive an instance when its constructor is called.
352
353 This method is only called during a userland construction of
354 an object, in conjunction with the object's constructor, e.g.
355 its ``__init__`` method. It is not called when an object is
356 loaded from the database; see the :meth:`.InstanceEvents.load`
357 event in order to intercept a database load.
358
359 The event is called before the actual ``__init__`` constructor
360 of the object is called. The ``kwargs`` dictionary may be
361 modified in-place in order to affect what is passed to
362 ``__init__``.
363
364 :param target: the mapped instance. If
365 the event is configured with ``raw=True``, this will
366 instead be the :class:`.InstanceState` state-management
367 object associated with the instance.
368 :param args: positional arguments passed to the ``__init__`` method.
369 This is passed as a tuple and is currently immutable.
370 :param kwargs: keyword arguments passed to the ``__init__`` method.
371 This structure *can* be altered in place.
372
373 .. seealso::
374
375 :meth:`.InstanceEvents.init_failure`
376
377 :meth:`.InstanceEvents.load`
378
379 """
380
381 def init_failure(self, target: _O, args: Any, kwargs: Any) -> None:
382 """Receive an instance when its constructor has been called,
383 and raised an exception.
384
385 This method is only called during a userland construction of
386 an object, in conjunction with the object's constructor, e.g.
387 its ``__init__`` method. It is not called when an object is loaded
388 from the database.
389
390 The event is invoked after an exception raised by the ``__init__``
391 method is caught. After the event
392 is invoked, the original exception is re-raised outwards, so that
393 the construction of the object still raises an exception. The
394 actual exception and stack trace raised should be present in
395 ``sys.exc_info()``.
396
397 :param target: the mapped instance. If
398 the event is configured with ``raw=True``, this will
399 instead be the :class:`.InstanceState` state-management
400 object associated with the instance.
401 :param args: positional arguments that were passed to the ``__init__``
402 method.
403 :param kwargs: keyword arguments that were passed to the ``__init__``
404 method.
405
406 .. seealso::
407
408 :meth:`.InstanceEvents.init`
409
410 :meth:`.InstanceEvents.load`
411
412 """
413
414 def _sa_event_merge_wo_load(
415 self, target: _O, context: QueryContext
416 ) -> None:
417 """receive an object instance after it was the subject of a merge()
418 call, when load=False was passed.
419
420 The target would be the already-loaded object in the Session which
421 would have had its attributes overwritten by the incoming object. This
422 overwrite operation does not use attribute events, instead just
423 populating dict directly. Therefore the purpose of this event is so
424 that extensions like sqlalchemy.ext.mutable know that object state has
425 changed and incoming state needs to be set up for "parents" etc.
426
427 This functionality is acceptable to be made public in a later release.
428
429 .. versionadded:: 1.4.41
430
431 """
432
433 def load(self, target: _O, context: QueryContext) -> None:
434 """Receive an object instance after it has been created via
435 ``__new__``, and after initial attribute population has
436 occurred.
437
438 This typically occurs when the instance is created based on
439 incoming result rows, and is only called once for that
440 instance's lifetime.
441
442 .. warning::
443
444 During a result-row load, this event is invoked when the
445 first row received for this instance is processed. When using
446 eager loading with collection-oriented attributes, the additional
447 rows that are to be loaded / processed in order to load subsequent
448 collection items have not occurred yet. This has the effect
449 both that collections will not be fully loaded, as well as that
450 if an operation occurs within this event handler that emits
451 another database load operation for the object, the "loading
452 context" for the object can change and interfere with the
453 existing eager loaders still in progress.
454
455 Examples of what can cause the "loading context" to change within
456 the event handler include, but are not necessarily limited to:
457
458 * accessing deferred attributes that weren't part of the row,
459 will trigger an "undefer" operation and refresh the object
460
461 * accessing attributes on a joined-inheritance subclass that
462 weren't part of the row, will trigger a refresh operation.
463
464 As of SQLAlchemy 1.3.14, a warning is emitted when this occurs. The
465 :paramref:`.InstanceEvents.restore_load_context` option may be
466 used on the event to prevent this warning; this will ensure that
467 the existing loading context is maintained for the object after the
468 event is called::
469
470 @event.listens_for(SomeClass, "load", restore_load_context=True)
471 def on_load(instance, context):
472 instance.some_unloaded_attribute
473
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.
481
482
483 The :meth:`.InstanceEvents.load` event is also available in a
484 class-method decorator format called :func:`_orm.reconstructor`.
485
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`.
494
495 .. seealso::
496
497 :ref:`mapped_class_load_events`
498
499 :meth:`.InstanceEvents.init`
500
501 :meth:`.InstanceEvents.refresh`
502
503 :meth:`.SessionEvents.loaded_as_persistent`
504
505 """ # noqa: E501
506
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.
512
513 Contrast this to the :meth:`.InstanceEvents.load` method, which
514 is invoked when the object is first loaded from a query.
515
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.
524
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.
534
535 .. seealso::
536
537 :ref:`mapped_class_load_events`
538
539 :meth:`.InstanceEvents.load`
540
541 """
542
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.
552
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.
558
559 .. note::
560
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.
570
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.
579
580 .. seealso::
581
582 :ref:`mapped_class_load_events`
583
584 :ref:`orm_server_defaults`
585
586 :ref:`metadata_defaults_toplevel`
587
588 """
589
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.
593
594 'keys' is a list of attribute names. If None, the entire
595 state was expired.
596
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.
604
605 """
606
607 def pickle(self, target: _O, state_dict: _InstanceDict) -> None:
608 """Receive an object instance when its associated state is
609 being pickled.
610
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.
618
619 """
620
621 def unpickle(self, target: _O, state_dict: _InstanceDict) -> None:
622 """Receive an object instance after its associated state has
623 been unpickled.
624
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.
632
633 """
634
635
636class _EventsHold(event.RefCollection[_ET]):
637 """Hold onto listeners against unmapped, uninstrumented classes.
638
639 Establish _listen() for that class' mapper/instrumentation when
640 those objects are created for that class.
641
642 """
643
644 all_holds: weakref.WeakKeyDictionary[Any, Any]
645
646 def __init__(
647 self,
648 class_: Union[DeclarativeAttributeIntercept, DeclarativeMeta, type],
649 ) -> None:
650 self.class_ = class_
651
652 @classmethod
653 def _clear(cls) -> None:
654 cls.all_holds.clear()
655
656 class HoldEvents(Generic[_ET2]):
657 _dispatch_target: Optional[Type[_ET2]] = None
658
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
669
670 if target.class_ in target.all_holds:
671 collection = target.all_holds[target.class_]
672 else:
673 collection = target.all_holds[target.class_] = {}
674
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 )
683
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 )
696
697 def remove(self, event_key: _EventKey[_ET]) -> None:
698 target = event_key.dispatch_target
699
700 if isinstance(target, _EventsHold):
701 collection = target.all_holds[target.class_]
702 del collection[event_key._key]
703
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 )
729
730
731class _InstanceEventsHold(_EventsHold[_ET]):
732 all_holds: weakref.WeakKeyDictionary[Any, Any] = (
733 weakref.WeakKeyDictionary()
734 )
735
736 def resolve(self, class_: Type[_O]) -> Optional[ClassManager[_O]]:
737 return instrumentation.opt_manager_of_class(class_)
738
739 # this fails on pyright if you use Any. Fails on mypy if you use _ET
740 class HoldInstanceEvents(_EventsHold.HoldEvents[_ET], InstanceEvents): # type: ignore[valid-type,misc] # noqa: E501
741 pass
742
743 dispatch = event.dispatcher(HoldInstanceEvents)
744
745
746class MapperEvents(event.Events[mapperlib.Mapper[Any]]):
747 """Define events specific to mappings.
748
749 e.g.::
750
751 from sqlalchemy import event
752
753
754 def my_before_insert_listener(mapper, connection, target):
755 # execute a stored procedure upon INSERT,
756 # apply the value to the row to be inserted
757 target.calculated_value = connection.execute(
758 text("select my_special_function(%d)" % target.special_number)
759 ).scalar()
760
761
762 # associate the listener function with SomeClass,
763 # to execute during the "before_insert" hook
764 event.listen(SomeClass, "before_insert", my_before_insert_listener)
765
766 Available targets include:
767
768 * mapped classes
769 * unmapped superclasses of mapped or to-be-mapped classes
770 (using the ``propagate=True`` flag)
771 * :class:`_orm.Mapper` objects
772 * the :class:`_orm.Mapper` class itself indicates listening for all
773 mappers.
774
775 Mapper events provide hooks into critical sections of the
776 mapper, including those related to object instrumentation,
777 object loading, and object persistence. In particular, the
778 persistence methods :meth:`~.MapperEvents.before_insert`,
779 and :meth:`~.MapperEvents.before_update` are popular
780 places to augment the state being persisted - however, these
781 methods operate with several significant restrictions. The
782 user is encouraged to evaluate the
783 :meth:`.SessionEvents.before_flush` and
784 :meth:`.SessionEvents.after_flush` methods as more
785 flexible and user-friendly hooks in which to apply
786 additional database state during a flush.
787
788 When using :class:`.MapperEvents`, several modifiers are
789 available to the :func:`.event.listen` function.
790
791 :param propagate=False: When True, the event listener should
792 be applied to all inheriting mappers and/or the mappers of
793 inheriting classes, as well as any
794 mapper which is the target of this listener.
795 :param raw=False: When True, the "target" argument passed
796 to applicable event listener functions will be the
797 instance's :class:`.InstanceState` management
798 object, rather than the mapped instance itself.
799 :param retval=False: when True, the user-defined event function
800 must have a return value, the purpose of which is either to
801 control subsequent event propagation, or to otherwise alter
802 the operation in progress by the mapper. Possible return
803 values are:
804
805 * ``sqlalchemy.orm.interfaces.EXT_CONTINUE`` - continue event
806 processing normally.
807 * ``sqlalchemy.orm.interfaces.EXT_STOP`` - cancel all subsequent
808 event handlers in the chain.
809 * other values - the return value specified by specific listeners.
810
811 """
812
813 _target_class_doc = "SomeClass"
814 _dispatch_target = mapperlib.Mapper
815
816 @classmethod
817 def _new_mapper_instance(
818 cls,
819 class_: Union[DeclarativeAttributeIntercept, DeclarativeMeta, type],
820 mapper: Mapper[_O],
821 ) -> None:
822 _MapperEventsHold.populate(class_, mapper)
823
824 @classmethod
825 @util.preload_module("sqlalchemy.orm")
826 def _accept_with(
827 cls,
828 target: Union[mapperlib.Mapper[Any], Type[mapperlib.Mapper[Any]]],
829 identifier: str,
830 ) -> Optional[Union[mapperlib.Mapper[Any], Type[mapperlib.Mapper[Any]]]]:
831 orm = util.preloaded.orm
832
833 if target is orm.mapper: # type: ignore [attr-defined]
834 util.warn_deprecated(
835 "The `sqlalchemy.orm.mapper()` symbol is deprecated and "
836 "will be removed in a future release. For the mapper-wide "
837 "event target, use the 'sqlalchemy.orm.Mapper' class.",
838 "2.0",
839 )
840 return mapperlib.Mapper
841 elif isinstance(target, type):
842 if issubclass(target, mapperlib.Mapper):
843 return target
844 else:
845 mapper = _mapper_or_none(target)
846 if mapper is not None:
847 return mapper
848 else:
849 return _MapperEventsHold(target)
850 else:
851 return target
852
853 @classmethod
854 def _listen(
855 cls,
856 event_key: _EventKey[_ET],
857 raw: bool = False,
858 retval: bool = False,
859 propagate: bool = False,
860 **kw: Any,
861 ) -> None:
862 target, identifier, fn = (
863 event_key.dispatch_target,
864 event_key.identifier,
865 event_key._listen_fn,
866 )
867
868 if (
869 identifier in ("before_configured", "after_configured")
870 and target is not mapperlib.Mapper
871 ):
872 util.warn(
873 "'before_configured' and 'after_configured' ORM events "
874 "only invoke with the Mapper class "
875 "as the target."
876 )
877
878 if not raw or not retval:
879 if not raw:
880 meth = getattr(cls, identifier)
881 try:
882 target_index = (
883 inspect_getfullargspec(meth)[0].index("target") - 1
884 )
885 except ValueError:
886 target_index = None
887
888 def wrap(*arg: Any, **kw: Any) -> Any:
889 if not raw and target_index is not None:
890 arg = list(arg) # type: ignore [assignment]
891 arg[target_index] = arg[target_index].obj() # type: ignore [index] # noqa: E501
892 if not retval:
893 fn(*arg, **kw)
894 return interfaces.EXT_CONTINUE
895 else:
896 return fn(*arg, **kw)
897
898 event_key = event_key.with_wrapper(wrap)
899
900 if propagate:
901 for mapper in target.self_and_descendants:
902 event_key.with_dispatch_target(mapper).base_listen(
903 propagate=True, **kw
904 )
905 else:
906 event_key.base_listen(**kw)
907
908 @classmethod
909 def _clear(cls) -> None:
910 super()._clear()
911 _MapperEventsHold._clear()
912
913 def instrument_class(self, mapper: Mapper[_O], class_: Type[_O]) -> None:
914 r"""Receive a class when the mapper is first constructed,
915 before instrumentation is applied to the mapped class.
916
917 This event is the earliest phase of mapper construction.
918 Most attributes of the mapper are not yet initialized. To
919 receive an event within initial mapper construction where basic
920 state is available such as the :attr:`_orm.Mapper.attrs` collection,
921 the :meth:`_orm.MapperEvents.after_mapper_constructed` event may
922 be a better choice.
923
924 This listener can either be applied to the :class:`_orm.Mapper`
925 class overall, or to any un-mapped class which serves as a base
926 for classes that will be mapped (using the ``propagate=True`` flag)::
927
928 Base = declarative_base()
929
930
931 @event.listens_for(Base, "instrument_class", propagate=True)
932 def on_new_class(mapper, cls_):
933 "..."
934
935 :param mapper: the :class:`_orm.Mapper` which is the target
936 of this event.
937 :param class\_: the mapped class.
938
939 .. seealso::
940
941 :meth:`_orm.MapperEvents.after_mapper_constructed`
942
943 """
944
945 def after_mapper_constructed(
946 self, mapper: Mapper[_O], class_: Type[_O]
947 ) -> None:
948 """Receive a class and mapper when the :class:`_orm.Mapper` has been
949 fully constructed.
950
951 This event is called after the initial constructor for
952 :class:`_orm.Mapper` completes. This occurs after the
953 :meth:`_orm.MapperEvents.instrument_class` event and after the
954 :class:`_orm.Mapper` has done an initial pass of its arguments
955 to generate its collection of :class:`_orm.MapperProperty` objects,
956 which are accessible via the :meth:`_orm.Mapper.get_property`
957 method and the :attr:`_orm.Mapper.iterate_properties` attribute.
958
959 This event differs from the
960 :meth:`_orm.MapperEvents.before_mapper_configured` event in that it
961 is invoked within the constructor for :class:`_orm.Mapper`, rather
962 than within the :meth:`_orm.registry.configure` process. Currently,
963 this event is the only one which is appropriate for handlers that
964 wish to create additional mapped classes in response to the
965 construction of this :class:`_orm.Mapper`, which will be part of the
966 same configure step when :meth:`_orm.registry.configure` next runs.
967
968 .. versionadded:: 2.0.2
969
970 .. seealso::
971
972 :ref:`examples_versioning` - an example which illustrates the use
973 of the :meth:`_orm.MapperEvents.before_mapper_configured`
974 event to create new mappers to record change-audit histories on
975 objects.
976
977 """
978
979 @event._omit_standard_example
980 def before_mapper_configured(
981 self, mapper: Mapper[_O], class_: Type[_O]
982 ) -> None:
983 """Called right before a specific mapper is to be configured.
984
985 The :meth:`.MapperEvents.before_mapper_configured` event is invoked
986 for each mapper that is encountered when the
987 :func:`_orm.configure_mappers` function proceeds through the current
988 list of not-yet-configured mappers. It is similar to the
989 :meth:`.MapperEvents.mapper_configured` event, except that it's invoked
990 right before the configuration occurs, rather than afterwards.
991
992 The :meth:`.MapperEvents.before_mapper_configured` event includes
993 the special capability where it can force the configure step for a
994 specific mapper to be skipped; to use this feature, establish
995 the event using the ``retval=True`` parameter and return
996 the :attr:`.orm.interfaces.EXT_SKIP` symbol to indicate the mapper
997 should be left unconfigured::
998
999 from sqlalchemy import event
1000 from sqlalchemy.orm import EXT_SKIP
1001 from sqlalchemy.orm import DeclarativeBase
1002
1003
1004 class DontConfigureBase(DeclarativeBase):
1005 pass
1006
1007
1008 @event.listens_for(
1009 DontConfigureBase,
1010 "before_mapper_configured",
1011 # support return values for the event
1012 retval=True,
1013 # propagate the listener to all subclasses of
1014 # DontConfigureBase
1015 propagate=True,
1016 )
1017 def dont_configure(mapper, cls):
1018 return EXT_SKIP
1019
1020 .. seealso::
1021
1022 :meth:`.MapperEvents.before_configured`
1023
1024 :meth:`.MapperEvents.after_configured`
1025
1026 :meth:`.MapperEvents.mapper_configured`
1027
1028 """
1029
1030 def mapper_configured(self, mapper: Mapper[_O], class_: Type[_O]) -> None:
1031 r"""Called when a specific mapper has completed its own configuration
1032 within the scope of the :func:`.configure_mappers` call.
1033
1034 The :meth:`.MapperEvents.mapper_configured` event is invoked
1035 for each mapper that is encountered when the
1036 :func:`_orm.configure_mappers` function proceeds through the current
1037 list of not-yet-configured mappers.
1038 :func:`_orm.configure_mappers` is typically invoked
1039 automatically as mappings are first used, as well as each time
1040 new mappers have been made available and new mapper use is
1041 detected.
1042
1043 When the event is called, the mapper should be in its final
1044 state, but **not including backrefs** that may be invoked from
1045 other mappers; they might still be pending within the
1046 configuration operation. Bidirectional relationships that
1047 are instead configured via the
1048 :paramref:`.orm.relationship.back_populates` argument
1049 *will* be fully available, since this style of relationship does not
1050 rely upon other possibly-not-configured mappers to know that they
1051 exist.
1052
1053 For an event that is guaranteed to have **all** mappers ready
1054 to go including backrefs that are defined only on other
1055 mappings, use the :meth:`.MapperEvents.after_configured`
1056 event; this event invokes only after all known mappings have been
1057 fully configured.
1058
1059 The :meth:`.MapperEvents.mapper_configured` event, unlike the
1060 :meth:`.MapperEvents.before_configured` or
1061 :meth:`.MapperEvents.after_configured` events, is called for each
1062 mapper/class individually, and the mapper is passed to the event
1063 itself. It also is called exactly once for a particular mapper. The
1064 event is therefore useful for configurational steps that benefit from
1065 being invoked just once on a specific mapper basis, which don't require
1066 that "backref" configurations are necessarily ready yet.
1067
1068 :param mapper: the :class:`_orm.Mapper` which is the target
1069 of this event.
1070 :param class\_: the mapped class.
1071
1072 .. seealso::
1073
1074 :meth:`.MapperEvents.before_configured`
1075
1076 :meth:`.MapperEvents.after_configured`
1077
1078 :meth:`.MapperEvents.before_mapper_configured`
1079
1080 """
1081 # TODO: need coverage for this event
1082
1083 @event._omit_standard_example
1084 def before_configured(self) -> None:
1085 """Called before a series of mappers have been configured.
1086
1087 The :meth:`.MapperEvents.before_configured` event is invoked
1088 each time the :func:`_orm.configure_mappers` function is
1089 invoked, before the function has done any of its work.
1090 :func:`_orm.configure_mappers` is typically invoked
1091 automatically as mappings are first used, as well as each time
1092 new mappers have been made available and new mapper use is
1093 detected.
1094
1095 Similar events to this one include
1096 :meth:`.MapperEvents.after_configured`, which is invoked after a series
1097 of mappers has been configured, as well as
1098 :meth:`.MapperEvents.before_mapper_configured` and
1099 :meth:`.MapperEvents.mapper_configured`, which are both invoked on a
1100 per-mapper basis.
1101
1102 This event can **only** be applied to the :class:`_orm.Mapper` class,
1103 and not to individual mappings or mapped classes::
1104
1105 from sqlalchemy.orm import Mapper
1106
1107
1108 @event.listens_for(Mapper, "before_configured")
1109 def go(): ...
1110
1111 Typically, this event is called once per application, but in practice
1112 may be called more than once, any time new mappers are to be affected
1113 by a :func:`_orm.configure_mappers` call. If new mappings are
1114 constructed after existing ones have already been used, this event will
1115 likely be called again.
1116
1117 .. seealso::
1118
1119 :meth:`.MapperEvents.before_mapper_configured`
1120
1121 :meth:`.MapperEvents.mapper_configured`
1122
1123 :meth:`.MapperEvents.after_configured`
1124
1125 """
1126
1127 @event._omit_standard_example
1128 def after_configured(self) -> None:
1129 """Called after a series of mappers have been configured.
1130
1131 The :meth:`.MapperEvents.after_configured` event is invoked
1132 each time the :func:`_orm.configure_mappers` function is
1133 invoked, after the function has completed its work.
1134 :func:`_orm.configure_mappers` is typically invoked
1135 automatically as mappings are first used, as well as each time
1136 new mappers have been made available and new mapper use is
1137 detected.
1138
1139 Similar events to this one include
1140 :meth:`.MapperEvents.before_configured`, which is invoked before a
1141 series of mappers are configured, as well as
1142 :meth:`.MapperEvents.before_mapper_configured` and
1143 :meth:`.MapperEvents.mapper_configured`, which are both invoked on a
1144 per-mapper basis.
1145
1146 This event can **only** be applied to the :class:`_orm.Mapper` class,
1147 and not to individual mappings or mapped classes::
1148
1149 from sqlalchemy.orm import Mapper
1150
1151
1152 @event.listens_for(Mapper, "after_configured")
1153 def go(): ...
1154
1155 Typically, this event is called once per application, but in practice
1156 may be called more than once, any time new mappers are to be affected
1157 by a :func:`_orm.configure_mappers` call. If new mappings are
1158 constructed after existing ones have already been used, this event will
1159 likely be called again.
1160
1161 .. seealso::
1162
1163 :meth:`.MapperEvents.before_mapper_configured`
1164
1165 :meth:`.MapperEvents.mapper_configured`
1166
1167 :meth:`.MapperEvents.before_configured`
1168
1169 """
1170
1171 def before_insert(
1172 self, mapper: Mapper[_O], connection: Connection, target: _O
1173 ) -> None:
1174 """Receive an object instance before an INSERT statement
1175 is emitted corresponding to that instance.
1176
1177 .. note:: this event **only** applies to the
1178 :ref:`session flush operation <session_flushing>`
1179 and does **not** apply to the ORM DML operations described at
1180 :ref:`orm_expression_update_delete`. To intercept ORM
1181 DML events, use :meth:`_orm.SessionEvents.do_orm_execute`.
1182
1183 This event is used to modify local, non-object related
1184 attributes on the instance before an INSERT occurs, as well
1185 as to emit additional SQL statements on the given
1186 connection.
1187
1188 The event is often called for a batch of objects of the
1189 same class before their INSERT statements are emitted at
1190 once in a later step. In the extremely rare case that
1191 this is not desirable, the :class:`_orm.Mapper` object can be
1192 configured with ``batch=False``, which will cause
1193 batches of instances to be broken up into individual
1194 (and more poorly performing) event->persist->event
1195 steps.
1196
1197 .. warning::
1198
1199 Mapper-level flush events only allow **very limited operations**,
1200 on attributes local to the row being operated upon only,
1201 as well as allowing any SQL to be emitted on the given
1202 :class:`_engine.Connection`. **Please read fully** the notes
1203 at :ref:`session_persistence_mapper` for guidelines on using
1204 these methods; generally, the :meth:`.SessionEvents.before_flush`
1205 method should be preferred for general on-flush changes.
1206
1207 :param mapper: the :class:`_orm.Mapper` which is the target
1208 of this event.
1209 :param connection: the :class:`_engine.Connection` being used to
1210 emit INSERT statements for this instance. This
1211 provides a handle into the current transaction on the
1212 target database specific to this instance.
1213 :param target: the mapped instance being persisted. If
1214 the event is configured with ``raw=True``, this will
1215 instead be the :class:`.InstanceState` state-management
1216 object associated with the instance.
1217 :return: No return value is supported by this event.
1218
1219 .. seealso::
1220
1221 :ref:`session_persistence_events`
1222
1223 """
1224
1225 def after_insert(
1226 self, mapper: Mapper[_O], connection: Connection, target: _O
1227 ) -> None:
1228 """Receive an object instance after an INSERT statement
1229 is emitted corresponding to that instance.
1230
1231 .. note:: this event **only** applies to the
1232 :ref:`session flush operation <session_flushing>`
1233 and does **not** apply to the ORM DML operations described at
1234 :ref:`orm_expression_update_delete`. To intercept ORM
1235 DML events, use :meth:`_orm.SessionEvents.do_orm_execute`.
1236
1237 This event is used to modify in-Python-only
1238 state on the instance after an INSERT occurs, as well
1239 as to emit additional SQL statements on the given
1240 connection.
1241
1242 The event is often called for a batch of objects of the
1243 same class after their INSERT statements have been
1244 emitted at once in a previous step. In the extremely
1245 rare case that this is not desirable, the
1246 :class:`_orm.Mapper` object can be configured with ``batch=False``,
1247 which will cause batches of instances to be broken up
1248 into individual (and more poorly performing)
1249 event->persist->event steps.
1250
1251 .. warning::
1252
1253 Mapper-level flush events only allow **very limited operations**,
1254 on attributes local to the row being operated upon only,
1255 as well as allowing any SQL to be emitted on the given
1256 :class:`_engine.Connection`. **Please read fully** the notes
1257 at :ref:`session_persistence_mapper` for guidelines on using
1258 these methods; generally, the :meth:`.SessionEvents.before_flush`
1259 method should be preferred for general on-flush changes.
1260
1261 :param mapper: the :class:`_orm.Mapper` which is the target
1262 of this event.
1263 :param connection: the :class:`_engine.Connection` being used to
1264 emit INSERT statements for this instance. This
1265 provides a handle into the current transaction on the
1266 target database specific to this instance.
1267 :param target: the mapped instance being persisted. If
1268 the event is configured with ``raw=True``, this will
1269 instead be the :class:`.InstanceState` state-management
1270 object associated with the instance.
1271 :return: No return value is supported by this event.
1272
1273 .. seealso::
1274
1275 :ref:`session_persistence_events`
1276
1277 """
1278
1279 def before_update(
1280 self, mapper: Mapper[_O], connection: Connection, target: _O
1281 ) -> None:
1282 """Receive an object instance before an UPDATE statement
1283 is emitted corresponding to that instance.
1284
1285 .. note:: this event **only** applies to the
1286 :ref:`session flush operation <session_flushing>`
1287 and does **not** apply to the ORM DML operations described at
1288 :ref:`orm_expression_update_delete`. To intercept ORM
1289 DML events, use :meth:`_orm.SessionEvents.do_orm_execute`.
1290
1291 This event is used to modify local, non-object related
1292 attributes on the instance before an UPDATE occurs, as well
1293 as to emit additional SQL statements on the given
1294 connection.
1295
1296 This method is called for all instances that are
1297 marked as "dirty", *even those which have no net changes
1298 to their column-based attributes*. An object is marked
1299 as dirty when any of its column-based attributes have a
1300 "set attribute" operation called or when any of its
1301 collections are modified. If, at update time, no
1302 column-based attributes have any net changes, no UPDATE
1303 statement will be issued. This means that an instance
1304 being sent to :meth:`~.MapperEvents.before_update` is
1305 *not* a guarantee that an UPDATE statement will be
1306 issued, although you can affect the outcome here by
1307 modifying attributes so that a net change in value does
1308 exist.
1309
1310 To detect if the column-based attributes on the object have net
1311 changes, and will therefore generate an UPDATE statement, use
1312 ``object_session(instance).is_modified(instance,
1313 include_collections=False)``.
1314
1315 The event is often called for a batch of objects of the
1316 same class before their UPDATE statements are emitted at
1317 once in a later step. In the extremely rare case that
1318 this is not desirable, the :class:`_orm.Mapper` can be
1319 configured with ``batch=False``, which will cause
1320 batches of instances to be broken up into individual
1321 (and more poorly performing) event->persist->event
1322 steps.
1323
1324 .. warning::
1325
1326 Mapper-level flush events only allow **very limited operations**,
1327 on attributes local to the row being operated upon only,
1328 as well as allowing any SQL to be emitted on the given
1329 :class:`_engine.Connection`. **Please read fully** the notes
1330 at :ref:`session_persistence_mapper` for guidelines on using
1331 these methods; generally, the :meth:`.SessionEvents.before_flush`
1332 method should be preferred for general on-flush changes.
1333
1334 :param mapper: the :class:`_orm.Mapper` which is the target
1335 of this event.
1336 :param connection: the :class:`_engine.Connection` being used to
1337 emit UPDATE statements for this instance. This
1338 provides a handle into the current transaction on the
1339 target database specific to this instance.
1340 :param target: the mapped instance being persisted. If
1341 the event is configured with ``raw=True``, this will
1342 instead be the :class:`.InstanceState` state-management
1343 object associated with the instance.
1344 :return: No return value is supported by this event.
1345
1346 .. seealso::
1347
1348 :ref:`session_persistence_events`
1349
1350 """
1351
1352 def after_update(
1353 self, mapper: Mapper[_O], connection: Connection, target: _O
1354 ) -> None:
1355 """Receive an object instance after an UPDATE statement
1356 is emitted corresponding to that instance.
1357
1358 .. note:: this event **only** applies to the
1359 :ref:`session flush operation <session_flushing>`
1360 and does **not** apply to the ORM DML operations described at
1361 :ref:`orm_expression_update_delete`. To intercept ORM
1362 DML events, use :meth:`_orm.SessionEvents.do_orm_execute`.
1363
1364 This event is used to modify in-Python-only
1365 state on the instance after an UPDATE occurs, as well
1366 as to emit additional SQL statements on the given
1367 connection.
1368
1369 This method is called for all instances that are
1370 marked as "dirty", *even those which have no net changes
1371 to their column-based attributes*, and for which
1372 no UPDATE statement has proceeded. An object is marked
1373 as dirty when any of its column-based attributes have a
1374 "set attribute" operation called or when any of its
1375 collections are modified. If, at update time, no
1376 column-based attributes have any net changes, no UPDATE
1377 statement will be issued. This means that an instance
1378 being sent to :meth:`~.MapperEvents.after_update` is
1379 *not* a guarantee that an UPDATE statement has been
1380 issued.
1381
1382 To detect if the column-based attributes on the object have net
1383 changes, and therefore resulted in an UPDATE statement, use
1384 ``object_session(instance).is_modified(instance,
1385 include_collections=False)``.
1386
1387 The event is often called for a batch of objects of the
1388 same class after their UPDATE statements have been emitted at
1389 once in a previous step. In the extremely rare case that
1390 this is not desirable, the :class:`_orm.Mapper` can be
1391 configured with ``batch=False``, which will cause
1392 batches of instances to be broken up into individual
1393 (and more poorly performing) event->persist->event
1394 steps.
1395
1396 .. warning::
1397
1398 Mapper-level flush events only allow **very limited operations**,
1399 on attributes local to the row being operated upon only,
1400 as well as allowing any SQL to be emitted on the given
1401 :class:`_engine.Connection`. **Please read fully** the notes
1402 at :ref:`session_persistence_mapper` for guidelines on using
1403 these methods; generally, the :meth:`.SessionEvents.before_flush`
1404 method should be preferred for general on-flush changes.
1405
1406 :param mapper: the :class:`_orm.Mapper` which is the target
1407 of this event.
1408 :param connection: the :class:`_engine.Connection` being used to
1409 emit UPDATE statements for this instance. This
1410 provides a handle into the current transaction on the
1411 target database specific to this instance.
1412 :param target: the mapped instance being persisted. If
1413 the event is configured with ``raw=True``, this will
1414 instead be the :class:`.InstanceState` state-management
1415 object associated with the instance.
1416 :return: No return value is supported by this event.
1417
1418 .. seealso::
1419
1420 :ref:`session_persistence_events`
1421
1422 """
1423
1424 def before_delete(
1425 self, mapper: Mapper[_O], connection: Connection, target: _O
1426 ) -> None:
1427 """Receive an object instance before a DELETE statement
1428 is emitted corresponding to that instance.
1429
1430 .. note:: this event **only** applies to the
1431 :ref:`session flush operation <session_flushing>`
1432 and does **not** apply to the ORM DML operations described at
1433 :ref:`orm_expression_update_delete`. To intercept ORM
1434 DML events, use :meth:`_orm.SessionEvents.do_orm_execute`.
1435
1436 This event is used to emit additional SQL statements on
1437 the given connection as well as to perform application
1438 specific bookkeeping related to a deletion event.
1439
1440 The event is often called for a batch of objects of the
1441 same class before their DELETE statements are emitted at
1442 once in a later step.
1443
1444 .. warning::
1445
1446 Mapper-level flush events only allow **very limited operations**,
1447 on attributes local to the row being operated upon only,
1448 as well as allowing any SQL to be emitted on the given
1449 :class:`_engine.Connection`. **Please read fully** the notes
1450 at :ref:`session_persistence_mapper` for guidelines on using
1451 these methods; generally, the :meth:`.SessionEvents.before_flush`
1452 method should be preferred for general on-flush changes.
1453
1454 :param mapper: the :class:`_orm.Mapper` which is the target
1455 of this event.
1456 :param connection: the :class:`_engine.Connection` being used to
1457 emit DELETE statements for this instance. This
1458 provides a handle into the current transaction on the
1459 target database specific to this instance.
1460 :param target: the mapped instance being deleted. If
1461 the event is configured with ``raw=True``, this will
1462 instead be the :class:`.InstanceState` state-management
1463 object associated with the instance.
1464 :return: No return value is supported by this event.
1465
1466 .. seealso::
1467
1468 :ref:`session_persistence_events`
1469
1470 """
1471
1472 def after_delete(
1473 self, mapper: Mapper[_O], connection: Connection, target: _O
1474 ) -> None:
1475 """Receive an object instance after a DELETE statement
1476 has been emitted corresponding to that instance.
1477
1478 .. note:: this event **only** applies to the
1479 :ref:`session flush operation <session_flushing>`
1480 and does **not** apply to the ORM DML operations described at
1481 :ref:`orm_expression_update_delete`. To intercept ORM
1482 DML events, use :meth:`_orm.SessionEvents.do_orm_execute`.
1483
1484 This event is used to emit additional SQL statements on
1485 the given connection as well as to perform application
1486 specific bookkeeping related to a deletion event.
1487
1488 The event is often called for a batch of objects of the
1489 same class after their DELETE statements have been emitted at
1490 once in a previous step.
1491
1492 .. warning::
1493
1494 Mapper-level flush events only allow **very limited operations**,
1495 on attributes local to the row being operated upon only,
1496 as well as allowing any SQL to be emitted on the given
1497 :class:`_engine.Connection`. **Please read fully** the notes
1498 at :ref:`session_persistence_mapper` for guidelines on using
1499 these methods; generally, the :meth:`.SessionEvents.before_flush`
1500 method should be preferred for general on-flush changes.
1501
1502 :param mapper: the :class:`_orm.Mapper` which is the target
1503 of this event.
1504 :param connection: the :class:`_engine.Connection` being used to
1505 emit DELETE statements for this instance. This
1506 provides a handle into the current transaction on the
1507 target database specific to this instance.
1508 :param target: the mapped instance being deleted. If
1509 the event is configured with ``raw=True``, this will
1510 instead be the :class:`.InstanceState` state-management
1511 object associated with the instance.
1512 :return: No return value is supported by this event.
1513
1514 .. seealso::
1515
1516 :ref:`session_persistence_events`
1517
1518 """
1519
1520
1521class _MapperEventsHold(_EventsHold[_ET]):
1522 all_holds = weakref.WeakKeyDictionary()
1523
1524 def resolve(
1525 self, class_: Union[Type[_T], _InternalEntityType[_T]]
1526 ) -> Optional[Mapper[_T]]:
1527 return _mapper_or_none(class_)
1528
1529 # this fails on pyright if you use Any. Fails on mypy if you use _ET
1530 class HoldMapperEvents(_EventsHold.HoldEvents[_ET], MapperEvents): # type: ignore[valid-type,misc] # noqa: E501
1531 pass
1532
1533 dispatch = event.dispatcher(HoldMapperEvents)
1534
1535
1536_sessionevents_lifecycle_event_names: Set[str] = set()
1537
1538
1539class SessionEvents(event.Events[Session]):
1540 """Define events specific to :class:`.Session` lifecycle.
1541
1542 e.g.::
1543
1544 from sqlalchemy import event
1545 from sqlalchemy.orm import sessionmaker
1546
1547
1548 def my_before_commit(session):
1549 print("before commit!")
1550
1551
1552 Session = sessionmaker()
1553
1554 event.listen(Session, "before_commit", my_before_commit)
1555
1556 The :func:`~.event.listen` function will accept
1557 :class:`.Session` objects as well as the return result
1558 of :class:`~.sessionmaker()` and :class:`~.scoped_session()`.
1559
1560 Additionally, it accepts the :class:`.Session` class which
1561 will apply listeners to all :class:`.Session` instances
1562 globally.
1563
1564 :param raw=False: When True, the "target" argument passed
1565 to applicable event listener functions that work on individual
1566 objects will be the instance's :class:`.InstanceState` management
1567 object, rather than the mapped instance itself.
1568
1569 .. versionadded:: 1.3.14
1570
1571 :param restore_load_context=False: Applies to the
1572 :meth:`.SessionEvents.loaded_as_persistent` event. Restores the loader
1573 context of the object when the event hook is complete, so that ongoing
1574 eager load operations continue to target the object appropriately. A
1575 warning is emitted if the object is moved to a new loader context from
1576 within this event if this flag is not set.
1577
1578 .. versionadded:: 1.3.14
1579
1580 """
1581
1582 _target_class_doc = "SomeSessionClassOrObject"
1583
1584 _dispatch_target = Session
1585
1586 def _lifecycle_event( # type: ignore [misc]
1587 fn: Callable[[SessionEvents, Session, Any], None],
1588 ) -> Callable[[SessionEvents, Session, Any], None]:
1589 _sessionevents_lifecycle_event_names.add(fn.__name__)
1590 return fn
1591
1592 @classmethod
1593 def _accept_with( # type: ignore [return]
1594 cls, target: Any, identifier: str
1595 ) -> Union[Session, type]:
1596 if isinstance(target, scoped_session):
1597 target = target.session_factory
1598 if not isinstance(target, sessionmaker) and (
1599 not isinstance(target, type) or not issubclass(target, Session)
1600 ):
1601 raise exc.ArgumentError(
1602 "Session event listen on a scoped_session "
1603 "requires that its creation callable "
1604 "is associated with the Session class."
1605 )
1606
1607 if isinstance(target, sessionmaker):
1608 return target.class_
1609 elif isinstance(target, type):
1610 if issubclass(target, scoped_session):
1611 return Session
1612 elif issubclass(target, Session):
1613 return target
1614 elif isinstance(target, Session):
1615 return target
1616 elif hasattr(target, "_no_async_engine_events"):
1617 target._no_async_engine_events()
1618 else:
1619 # allows alternate SessionEvents-like-classes to be consulted
1620 return event.Events._accept_with(target, identifier) # type: ignore [return-value] # noqa: E501
1621
1622 @classmethod
1623 def _listen(
1624 cls,
1625 event_key: Any,
1626 *,
1627 raw: bool = False,
1628 restore_load_context: bool = False,
1629 **kw: Any,
1630 ) -> None:
1631 is_instance_event = (
1632 event_key.identifier in _sessionevents_lifecycle_event_names
1633 )
1634
1635 if is_instance_event:
1636 if not raw or restore_load_context:
1637 fn = event_key._listen_fn
1638
1639 def wrap(
1640 session: Session,
1641 state: InstanceState[_O],
1642 *arg: Any,
1643 **kw: Any,
1644 ) -> Optional[Any]:
1645 if not raw:
1646 target = state.obj()
1647 if target is None:
1648 # existing behavior is that if the object is
1649 # garbage collected, no event is emitted
1650 return None
1651 else:
1652 target = state # type: ignore [assignment]
1653 if restore_load_context:
1654 runid = state.runid
1655 try:
1656 return fn(session, target, *arg, **kw)
1657 finally:
1658 if restore_load_context:
1659 state.runid = runid
1660
1661 event_key = event_key.with_wrapper(wrap)
1662
1663 event_key.base_listen(**kw)
1664
1665 def do_orm_execute(self, orm_execute_state: ORMExecuteState) -> None:
1666 """Intercept statement executions that occur on behalf of an
1667 ORM :class:`.Session` object.
1668
1669 This event is invoked for all top-level SQL statements invoked from the
1670 :meth:`_orm.Session.execute` method, as well as related methods such as
1671 :meth:`_orm.Session.scalars` and :meth:`_orm.Session.scalar`. As of
1672 SQLAlchemy 1.4, all ORM queries that run through the
1673 :meth:`_orm.Session.execute` method as well as related methods
1674 :meth:`_orm.Session.scalars`, :meth:`_orm.Session.scalar` etc.
1675 will participate in this event.
1676 This event hook does **not** apply to the queries that are
1677 emitted internally within the ORM flush process, i.e. the
1678 process described at :ref:`session_flushing`.
1679
1680 .. note:: The :meth:`_orm.SessionEvents.do_orm_execute` event hook
1681 is triggered **for ORM statement executions only**, meaning those
1682 invoked via the :meth:`_orm.Session.execute` and similar methods on
1683 the :class:`_orm.Session` object. It does **not** trigger for
1684 statements that are invoked by SQLAlchemy Core only, i.e. statements
1685 invoked directly using :meth:`_engine.Connection.execute` or
1686 otherwise originating from an :class:`_engine.Engine` object without
1687 any :class:`_orm.Session` involved. To intercept **all** SQL
1688 executions regardless of whether the Core or ORM APIs are in use,
1689 see the event hooks at :class:`.ConnectionEvents`, such as
1690 :meth:`.ConnectionEvents.before_execute` and
1691 :meth:`.ConnectionEvents.before_cursor_execute`.
1692
1693 Also, this event hook does **not** apply to queries that are
1694 emitted internally within the ORM flush process,
1695 i.e. the process described at :ref:`session_flushing`; to
1696 intercept steps within the flush process, see the event
1697 hooks described at :ref:`session_persistence_events` as
1698 well as :ref:`session_persistence_mapper`.
1699
1700 This event is a ``do_`` event, meaning it has the capability to replace
1701 the operation that the :meth:`_orm.Session.execute` method normally
1702 performs. The intended use for this includes sharding and
1703 result-caching schemes which may seek to invoke the same statement
1704 across multiple database connections, returning a result that is
1705 merged from each of them, or which don't invoke the statement at all,
1706 instead returning data from a cache.
1707
1708 The hook intends to replace the use of the
1709 ``Query._execute_and_instances`` method that could be subclassed prior
1710 to SQLAlchemy 1.4.
1711
1712 :param orm_execute_state: an instance of :class:`.ORMExecuteState`
1713 which contains all information about the current execution, as well
1714 as helper functions used to derive other commonly required
1715 information. See that object for details.
1716
1717 .. seealso::
1718
1719 :ref:`session_execute_events` - top level documentation on how
1720 to use :meth:`_orm.SessionEvents.do_orm_execute`
1721
1722 :class:`.ORMExecuteState` - the object passed to the
1723 :meth:`_orm.SessionEvents.do_orm_execute` event which contains
1724 all information about the statement to be invoked. It also
1725 provides an interface to extend the current statement, options,
1726 and parameters as well as an option that allows programmatic
1727 invocation of the statement at any point.
1728
1729 :ref:`examples_session_orm_events` - includes examples of using
1730 :meth:`_orm.SessionEvents.do_orm_execute`
1731
1732 :ref:`examples_caching` - an example of how to integrate
1733 Dogpile caching with the ORM :class:`_orm.Session` making use
1734 of the :meth:`_orm.SessionEvents.do_orm_execute` event hook.
1735
1736 :ref:`examples_sharding` - the Horizontal Sharding example /
1737 extension relies upon the
1738 :meth:`_orm.SessionEvents.do_orm_execute` event hook to invoke a
1739 SQL statement on multiple backends and return a merged result.
1740
1741
1742 .. versionadded:: 1.4
1743
1744 """
1745
1746 def after_transaction_create(
1747 self, session: Session, transaction: SessionTransaction
1748 ) -> None:
1749 """Execute when a new :class:`.SessionTransaction` is created.
1750
1751 This event differs from :meth:`~.SessionEvents.after_begin`
1752 in that it occurs for each :class:`.SessionTransaction`
1753 overall, as opposed to when transactions are begun
1754 on individual database connections. It is also invoked
1755 for nested transactions and subtransactions, and is always
1756 matched by a corresponding
1757 :meth:`~.SessionEvents.after_transaction_end` event
1758 (assuming normal operation of the :class:`.Session`).
1759
1760 :param session: the target :class:`.Session`.
1761 :param transaction: the target :class:`.SessionTransaction`.
1762
1763 To detect if this is the outermost
1764 :class:`.SessionTransaction`, as opposed to a "subtransaction" or a
1765 SAVEPOINT, test that the :attr:`.SessionTransaction.parent` attribute
1766 is ``None``::
1767
1768 @event.listens_for(session, "after_transaction_create")
1769 def after_transaction_create(session, transaction):
1770 if transaction.parent is None:
1771 ... # work with top-level transaction
1772
1773 To detect if the :class:`.SessionTransaction` is a SAVEPOINT, use the
1774 :attr:`.SessionTransaction.nested` attribute::
1775
1776 @event.listens_for(session, "after_transaction_create")
1777 def after_transaction_create(session, transaction):
1778 if transaction.nested:
1779 ... # work with SAVEPOINT transaction
1780
1781 .. seealso::
1782
1783 :class:`.SessionTransaction`
1784
1785 :meth:`~.SessionEvents.after_transaction_end`
1786
1787 """
1788
1789 def after_transaction_end(
1790 self, session: Session, transaction: SessionTransaction
1791 ) -> None:
1792 """Execute when the span of a :class:`.SessionTransaction` ends.
1793
1794 This event differs from :meth:`~.SessionEvents.after_commit`
1795 in that it corresponds to all :class:`.SessionTransaction`
1796 objects in use, including those for nested transactions
1797 and subtransactions, and is always matched by a corresponding
1798 :meth:`~.SessionEvents.after_transaction_create` event.
1799
1800 :param session: the target :class:`.Session`.
1801 :param transaction: the target :class:`.SessionTransaction`.
1802
1803 To detect if this is the outermost
1804 :class:`.SessionTransaction`, as opposed to a "subtransaction" or a
1805 SAVEPOINT, test that the :attr:`.SessionTransaction.parent` attribute
1806 is ``None``::
1807
1808 @event.listens_for(session, "after_transaction_create")
1809 def after_transaction_end(session, transaction):
1810 if transaction.parent is None:
1811 ... # work with top-level transaction
1812
1813 To detect if the :class:`.SessionTransaction` is a SAVEPOINT, use the
1814 :attr:`.SessionTransaction.nested` attribute::
1815
1816 @event.listens_for(session, "after_transaction_create")
1817 def after_transaction_end(session, transaction):
1818 if transaction.nested:
1819 ... # work with SAVEPOINT transaction
1820
1821 .. seealso::
1822
1823 :class:`.SessionTransaction`
1824
1825 :meth:`~.SessionEvents.after_transaction_create`
1826
1827 """
1828
1829 def before_commit(self, session: Session) -> None:
1830 """Execute before commit is called.
1831
1832 .. note::
1833
1834 The :meth:`~.SessionEvents.before_commit` hook is *not* per-flush,
1835 that is, the :class:`.Session` can emit SQL to the database
1836 many times within the scope of a transaction.
1837 For interception of these events, use the
1838 :meth:`~.SessionEvents.before_flush`,
1839 :meth:`~.SessionEvents.after_flush`, or
1840 :meth:`~.SessionEvents.after_flush_postexec`
1841 events.
1842
1843 :param session: The target :class:`.Session`.
1844
1845 .. seealso::
1846
1847 :meth:`~.SessionEvents.after_commit`
1848
1849 :meth:`~.SessionEvents.after_begin`
1850
1851 :meth:`~.SessionEvents.after_transaction_create`
1852
1853 :meth:`~.SessionEvents.after_transaction_end`
1854
1855 """
1856
1857 def after_commit(self, session: Session) -> None:
1858 """Execute after a commit has occurred.
1859
1860 .. note::
1861
1862 The :meth:`~.SessionEvents.after_commit` hook is *not* per-flush,
1863 that is, the :class:`.Session` can emit SQL to the database
1864 many times within the scope of a transaction.
1865 For interception of these events, use the
1866 :meth:`~.SessionEvents.before_flush`,
1867 :meth:`~.SessionEvents.after_flush`, or
1868 :meth:`~.SessionEvents.after_flush_postexec`
1869 events.
1870
1871 .. note::
1872
1873 The :class:`.Session` is not in an active transaction
1874 when the :meth:`~.SessionEvents.after_commit` event is invoked,
1875 and therefore can not emit SQL. To emit SQL corresponding to
1876 every transaction, use the :meth:`~.SessionEvents.before_commit`
1877 event.
1878
1879 :param session: The target :class:`.Session`.
1880
1881 .. seealso::
1882
1883 :meth:`~.SessionEvents.before_commit`
1884
1885 :meth:`~.SessionEvents.after_begin`
1886
1887 :meth:`~.SessionEvents.after_transaction_create`
1888
1889 :meth:`~.SessionEvents.after_transaction_end`
1890
1891 """
1892
1893 def after_rollback(self, session: Session) -> None:
1894 """Execute after a real DBAPI rollback has occurred.
1895
1896 Note that this event only fires when the *actual* rollback against
1897 the database occurs - it does *not* fire each time the
1898 :meth:`.Session.rollback` method is called, if the underlying
1899 DBAPI transaction has already been rolled back. In many
1900 cases, the :class:`.Session` will not be in
1901 an "active" state during this event, as the current
1902 transaction is not valid. To acquire a :class:`.Session`
1903 which is active after the outermost rollback has proceeded,
1904 use the :meth:`.SessionEvents.after_soft_rollback` event, checking the
1905 :attr:`.Session.is_active` flag.
1906
1907 :param session: The target :class:`.Session`.
1908
1909 """
1910
1911 def after_soft_rollback(
1912 self, session: Session, previous_transaction: SessionTransaction
1913 ) -> None:
1914 """Execute after any rollback has occurred, including "soft"
1915 rollbacks that don't actually emit at the DBAPI level.
1916
1917 This corresponds to both nested and outer rollbacks, i.e.
1918 the innermost rollback that calls the DBAPI's
1919 rollback() method, as well as the enclosing rollback
1920 calls that only pop themselves from the transaction stack.
1921
1922 The given :class:`.Session` can be used to invoke SQL and
1923 :meth:`.Session.query` operations after an outermost rollback
1924 by first checking the :attr:`.Session.is_active` flag::
1925
1926 @event.listens_for(Session, "after_soft_rollback")
1927 def do_something(session, previous_transaction):
1928 if session.is_active:
1929 session.execute(text("select * from some_table"))
1930
1931 :param session: The target :class:`.Session`.
1932 :param previous_transaction: The :class:`.SessionTransaction`
1933 transactional marker object which was just closed. The current
1934 :class:`.SessionTransaction` for the given :class:`.Session` is
1935 available via the :attr:`.Session.transaction` attribute.
1936
1937 """
1938
1939 def before_flush(
1940 self,
1941 session: Session,
1942 flush_context: UOWTransaction,
1943 instances: Optional[Sequence[_O]],
1944 ) -> None:
1945 """Execute before flush process has started.
1946
1947 :param session: The target :class:`.Session`.
1948 :param flush_context: Internal :class:`.UOWTransaction` object
1949 which handles the details of the flush.
1950 :param instances: Usually ``None``, this is the collection of
1951 objects which can be passed to the :meth:`.Session.flush` method
1952 (note this usage is deprecated).
1953
1954 .. seealso::
1955
1956 :meth:`~.SessionEvents.after_flush`
1957
1958 :meth:`~.SessionEvents.after_flush_postexec`
1959
1960 :ref:`session_persistence_events`
1961
1962 """
1963
1964 def after_flush(
1965 self, session: Session, flush_context: UOWTransaction
1966 ) -> None:
1967 """Execute after flush has completed, but before commit has been
1968 called.
1969
1970 Note that the session's state is still in pre-flush, i.e. 'new',
1971 'dirty', and 'deleted' lists still show pre-flush state as well
1972 as the history settings on instance attributes.
1973
1974 .. warning:: This event runs after the :class:`.Session` has emitted
1975 SQL to modify the database, but **before** it has altered its
1976 internal state to reflect those changes, including that newly
1977 inserted objects are placed into the identity map. ORM operations
1978 emitted within this event such as loads of related items
1979 may produce new identity map entries that will immediately
1980 be replaced, sometimes causing confusing results. SQLAlchemy will
1981 emit a warning for this condition as of version 1.3.9.
1982
1983 :param session: The target :class:`.Session`.
1984 :param flush_context: Internal :class:`.UOWTransaction` object
1985 which handles the details of the flush.
1986
1987 .. seealso::
1988
1989 :meth:`~.SessionEvents.before_flush`
1990
1991 :meth:`~.SessionEvents.after_flush_postexec`
1992
1993 :ref:`session_persistence_events`
1994
1995 """
1996
1997 def after_flush_postexec(
1998 self, session: Session, flush_context: UOWTransaction
1999 ) -> None:
2000 """Execute after flush has completed, and after the post-exec
2001 state occurs.
2002
2003 This will be when the 'new', 'dirty', and 'deleted' lists are in
2004 their final state. An actual commit() may or may not have
2005 occurred, depending on whether or not the flush started its own
2006 transaction or participated in a larger transaction.
2007
2008 :param session: The target :class:`.Session`.
2009 :param flush_context: Internal :class:`.UOWTransaction` object
2010 which handles the details of the flush.
2011
2012
2013 .. seealso::
2014
2015 :meth:`~.SessionEvents.before_flush`
2016
2017 :meth:`~.SessionEvents.after_flush`
2018
2019 :ref:`session_persistence_events`
2020
2021 """
2022
2023 def after_begin(
2024 self,
2025 session: Session,
2026 transaction: SessionTransaction,
2027 connection: Connection,
2028 ) -> None:
2029 """Execute after a transaction is begun on a connection.
2030
2031 .. note:: This event is called within the process of the
2032 :class:`_orm.Session` modifying its own internal state.
2033 To invoke SQL operations within this hook, use the
2034 :class:`_engine.Connection` provided to the event;
2035 do not run SQL operations using the :class:`_orm.Session`
2036 directly.
2037
2038 :param session: The target :class:`.Session`.
2039 :param transaction: The :class:`.SessionTransaction`.
2040 :param connection: The :class:`_engine.Connection` object
2041 which will be used for SQL statements.
2042
2043 .. seealso::
2044
2045 :meth:`~.SessionEvents.before_commit`
2046
2047 :meth:`~.SessionEvents.after_commit`
2048
2049 :meth:`~.SessionEvents.after_transaction_create`
2050
2051 :meth:`~.SessionEvents.after_transaction_end`
2052
2053 """
2054
2055 @_lifecycle_event
2056 def before_attach(self, session: Session, instance: _O) -> None:
2057 """Execute before an instance is attached to a session.
2058
2059 This is called before an add, delete or merge causes
2060 the object to be part of the session.
2061
2062 .. seealso::
2063
2064 :meth:`~.SessionEvents.after_attach`
2065
2066 :ref:`session_lifecycle_events`
2067
2068 """
2069
2070 @_lifecycle_event
2071 def after_attach(self, session: Session, instance: _O) -> None:
2072 """Execute after an instance is attached to a session.
2073
2074 This is called after an add, delete or merge.
2075
2076 .. note::
2077
2078 As of 0.8, this event fires off *after* the item
2079 has been fully associated with the session, which is
2080 different than previous releases. For event
2081 handlers that require the object not yet
2082 be part of session state (such as handlers which
2083 may autoflush while the target object is not
2084 yet complete) consider the
2085 new :meth:`.before_attach` event.
2086
2087 .. seealso::
2088
2089 :meth:`~.SessionEvents.before_attach`
2090
2091 :ref:`session_lifecycle_events`
2092
2093 """
2094
2095 @event._legacy_signature(
2096 "0.9",
2097 ["session", "query", "query_context", "result"],
2098 lambda update_context: (
2099 update_context.session,
2100 update_context.query,
2101 None,
2102 update_context.result,
2103 ),
2104 )
2105 def after_bulk_update(self, update_context: _O) -> None:
2106 """Event for after the legacy :meth:`_orm.Query.update` method
2107 has been called.
2108
2109 .. legacy:: The :meth:`_orm.SessionEvents.after_bulk_update` method
2110 is a legacy event hook as of SQLAlchemy 2.0. The event
2111 **does not participate** in :term:`2.0 style` invocations
2112 using :func:`_dml.update` documented at
2113 :ref:`orm_queryguide_update_delete_where`. For 2.0 style use,
2114 the :meth:`_orm.SessionEvents.do_orm_execute` hook will intercept
2115 these calls.
2116
2117 :param update_context: an "update context" object which contains
2118 details about the update, including these attributes:
2119
2120 * ``session`` - the :class:`.Session` involved
2121 * ``query`` -the :class:`_query.Query`
2122 object that this update operation
2123 was called upon.
2124 * ``values`` The "values" dictionary that was passed to
2125 :meth:`_query.Query.update`.
2126 * ``result`` the :class:`_engine.CursorResult`
2127 returned as a result of the
2128 bulk UPDATE operation.
2129
2130 .. versionchanged:: 1.4 the update_context no longer has a
2131 ``QueryContext`` object associated with it.
2132
2133 .. seealso::
2134
2135 :meth:`.QueryEvents.before_compile_update`
2136
2137 :meth:`.SessionEvents.after_bulk_delete`
2138
2139 """
2140
2141 @event._legacy_signature(
2142 "0.9",
2143 ["session", "query", "query_context", "result"],
2144 lambda delete_context: (
2145 delete_context.session,
2146 delete_context.query,
2147 None,
2148 delete_context.result,
2149 ),
2150 )
2151 def after_bulk_delete(self, delete_context: _O) -> None:
2152 """Event for after the legacy :meth:`_orm.Query.delete` method
2153 has been called.
2154
2155 .. legacy:: The :meth:`_orm.SessionEvents.after_bulk_delete` method
2156 is a legacy event hook as of SQLAlchemy 2.0. The event
2157 **does not participate** in :term:`2.0 style` invocations
2158 using :func:`_dml.delete` documented at
2159 :ref:`orm_queryguide_update_delete_where`. For 2.0 style use,
2160 the :meth:`_orm.SessionEvents.do_orm_execute` hook will intercept
2161 these calls.
2162
2163 :param delete_context: a "delete context" object which contains
2164 details about the update, including these attributes:
2165
2166 * ``session`` - the :class:`.Session` involved
2167 * ``query`` -the :class:`_query.Query`
2168 object that this update operation
2169 was called upon.
2170 * ``result`` the :class:`_engine.CursorResult`
2171 returned as a result of the
2172 bulk DELETE operation.
2173
2174 .. versionchanged:: 1.4 the update_context no longer has a
2175 ``QueryContext`` object associated with it.
2176
2177 .. seealso::
2178
2179 :meth:`.QueryEvents.before_compile_delete`
2180
2181 :meth:`.SessionEvents.after_bulk_update`
2182
2183 """
2184
2185 @_lifecycle_event
2186 def transient_to_pending(self, session: Session, instance: _O) -> None:
2187 """Intercept the "transient to pending" transition for a specific
2188 object.
2189
2190 This event is a specialization of the
2191 :meth:`.SessionEvents.after_attach` event which is only invoked
2192 for this specific transition. It is invoked typically during the
2193 :meth:`.Session.add` call.
2194
2195 :param session: target :class:`.Session`
2196
2197 :param instance: the ORM-mapped instance being operated upon.
2198
2199 .. seealso::
2200
2201 :ref:`session_lifecycle_events`
2202
2203 """
2204
2205 @_lifecycle_event
2206 def pending_to_transient(self, session: Session, instance: _O) -> None:
2207 """Intercept the "pending to transient" transition for a specific
2208 object.
2209
2210 This less common transition occurs when an pending object that has
2211 not been flushed is evicted from the session; this can occur
2212 when the :meth:`.Session.rollback` method rolls back the transaction,
2213 or when the :meth:`.Session.expunge` method is used.
2214
2215 :param session: target :class:`.Session`
2216
2217 :param instance: the ORM-mapped instance being operated upon.
2218
2219 .. seealso::
2220
2221 :ref:`session_lifecycle_events`
2222
2223 """
2224
2225 @_lifecycle_event
2226 def persistent_to_transient(self, session: Session, instance: _O) -> None:
2227 """Intercept the "persistent to transient" transition for a specific
2228 object.
2229
2230 This less common transition occurs when an pending object that has
2231 has been flushed is evicted from the session; this can occur
2232 when the :meth:`.Session.rollback` method rolls back the transaction.
2233
2234 :param session: target :class:`.Session`
2235
2236 :param instance: the ORM-mapped instance being operated upon.
2237
2238 .. seealso::
2239
2240 :ref:`session_lifecycle_events`
2241
2242 """
2243
2244 @_lifecycle_event
2245 def pending_to_persistent(self, session: Session, instance: _O) -> None:
2246 """Intercept the "pending to persistent"" transition for a specific
2247 object.
2248
2249 This event is invoked within the flush process, and is
2250 similar to scanning the :attr:`.Session.new` collection within
2251 the :meth:`.SessionEvents.after_flush` event. However, in this
2252 case the object has already been moved to the persistent state
2253 when the event is called.
2254
2255 :param session: target :class:`.Session`
2256
2257 :param instance: the ORM-mapped instance being operated upon.
2258
2259 .. seealso::
2260
2261 :ref:`session_lifecycle_events`
2262
2263 """
2264
2265 @_lifecycle_event
2266 def detached_to_persistent(self, session: Session, instance: _O) -> None:
2267 """Intercept the "detached to persistent" transition for a specific
2268 object.
2269
2270 This event is a specialization of the
2271 :meth:`.SessionEvents.after_attach` event which is only invoked
2272 for this specific transition. It is invoked typically during the
2273 :meth:`.Session.add` call, as well as during the
2274 :meth:`.Session.delete` call if the object was not previously
2275 associated with the
2276 :class:`.Session` (note that an object marked as "deleted" remains
2277 in the "persistent" state until the flush proceeds).
2278
2279 .. note::
2280
2281 If the object becomes persistent as part of a call to
2282 :meth:`.Session.delete`, the object is **not** yet marked as
2283 deleted when this event is called. To detect deleted objects,
2284 check the ``deleted`` flag sent to the
2285 :meth:`.SessionEvents.persistent_to_detached` to event after the
2286 flush proceeds, or check the :attr:`.Session.deleted` collection
2287 within the :meth:`.SessionEvents.before_flush` event if deleted
2288 objects need to be intercepted before the flush.
2289
2290 :param session: target :class:`.Session`
2291
2292 :param instance: the ORM-mapped instance being operated upon.
2293
2294 .. seealso::
2295
2296 :ref:`session_lifecycle_events`
2297
2298 """
2299
2300 @_lifecycle_event
2301 def loaded_as_persistent(self, session: Session, instance: _O) -> None:
2302 """Intercept the "loaded as persistent" transition for a specific
2303 object.
2304
2305 This event is invoked within the ORM loading process, and is invoked
2306 very similarly to the :meth:`.InstanceEvents.load` event. However,
2307 the event here is linkable to a :class:`.Session` class or instance,
2308 rather than to a mapper or class hierarchy, and integrates
2309 with the other session lifecycle events smoothly. The object
2310 is guaranteed to be present in the session's identity map when
2311 this event is called.
2312
2313 .. note:: This event is invoked within the loader process before
2314 eager loaders may have been completed, and the object's state may
2315 not be complete. Additionally, invoking row-level refresh
2316 operations on the object will place the object into a new loader
2317 context, interfering with the existing load context. See the note
2318 on :meth:`.InstanceEvents.load` for background on making use of the
2319 :paramref:`.SessionEvents.restore_load_context` parameter, which
2320 works in the same manner as that of
2321 :paramref:`.InstanceEvents.restore_load_context`, in order to
2322 resolve this scenario.
2323
2324 :param session: target :class:`.Session`
2325
2326 :param instance: the ORM-mapped instance being operated upon.
2327
2328 .. seealso::
2329
2330 :ref:`session_lifecycle_events`
2331
2332 """
2333
2334 @_lifecycle_event
2335 def persistent_to_deleted(self, session: Session, instance: _O) -> None:
2336 """Intercept the "persistent to deleted" transition for a specific
2337 object.
2338
2339 This event is invoked when a persistent object's identity
2340 is deleted from the database within a flush, however the object
2341 still remains associated with the :class:`.Session` until the
2342 transaction completes.
2343
2344 If the transaction is rolled back, the object moves again
2345 to the persistent state, and the
2346 :meth:`.SessionEvents.deleted_to_persistent` event is called.
2347 If the transaction is committed, the object becomes detached,
2348 which will emit the :meth:`.SessionEvents.deleted_to_detached`
2349 event.
2350
2351 Note that while the :meth:`.Session.delete` method is the primary
2352 public interface to mark an object as deleted, many objects
2353 get deleted due to cascade rules, which are not always determined
2354 until flush time. Therefore, there's no way to catch
2355 every object that will be deleted until the flush has proceeded.
2356 the :meth:`.SessionEvents.persistent_to_deleted` event is therefore
2357 invoked at the end of a flush.
2358
2359 .. seealso::
2360
2361 :ref:`session_lifecycle_events`
2362
2363 """
2364
2365 @_lifecycle_event
2366 def deleted_to_persistent(self, session: Session, instance: _O) -> None:
2367 """Intercept the "deleted to persistent" transition for a specific
2368 object.
2369
2370 This transition occurs only when an object that's been deleted
2371 successfully in a flush is restored due to a call to
2372 :meth:`.Session.rollback`. The event is not called under
2373 any other circumstances.
2374
2375 .. seealso::
2376
2377 :ref:`session_lifecycle_events`
2378
2379 """
2380
2381 @_lifecycle_event
2382 def deleted_to_detached(self, session: Session, instance: _O) -> None:
2383 """Intercept the "deleted to detached" transition for a specific
2384 object.
2385
2386 This event is invoked when a deleted object is evicted
2387 from the session. The typical case when this occurs is when
2388 the transaction for a :class:`.Session` in which the object
2389 was deleted is committed; the object moves from the deleted
2390 state to the detached state.
2391
2392 It is also invoked for objects that were deleted in a flush
2393 when the :meth:`.Session.expunge_all` or :meth:`.Session.close`
2394 events are called, as well as if the object is individually
2395 expunged from its deleted state via :meth:`.Session.expunge`.
2396
2397 .. seealso::
2398
2399 :ref:`session_lifecycle_events`
2400
2401 """
2402
2403 @_lifecycle_event
2404 def persistent_to_detached(self, session: Session, instance: _O) -> None:
2405 """Intercept the "persistent to detached" transition for a specific
2406 object.
2407
2408 This event is invoked when a persistent object is evicted
2409 from the session. There are many conditions that cause this
2410 to happen, including:
2411
2412 * using a method such as :meth:`.Session.expunge`
2413 or :meth:`.Session.close`
2414
2415 * Calling the :meth:`.Session.rollback` method, when the object
2416 was part of an INSERT statement for that session's transaction
2417
2418
2419 :param session: target :class:`.Session`
2420
2421 :param instance: the ORM-mapped instance being operated upon.
2422
2423 :param deleted: boolean. If True, indicates this object moved
2424 to the detached state because it was marked as deleted and flushed.
2425
2426
2427 .. seealso::
2428
2429 :ref:`session_lifecycle_events`
2430
2431 """
2432
2433
2434class AttributeEvents(event.Events[QueryableAttribute[Any]]):
2435 r"""Define events for object attributes.
2436
2437 These are typically defined on the class-bound descriptor for the
2438 target class.
2439
2440 For example, to register a listener that will receive the
2441 :meth:`_orm.AttributeEvents.append` event::
2442
2443 from sqlalchemy import event
2444
2445
2446 @event.listens_for(MyClass.collection, "append", propagate=True)
2447 def my_append_listener(target, value, initiator):
2448 print("received append event for target: %s" % target)
2449
2450 Listeners have the option to return a possibly modified version of the
2451 value, when the :paramref:`.AttributeEvents.retval` flag is passed to
2452 :func:`.event.listen` or :func:`.event.listens_for`, such as below,
2453 illustrated using the :meth:`_orm.AttributeEvents.set` event::
2454
2455 def validate_phone(target, value, oldvalue, initiator):
2456 "Strip non-numeric characters from a phone number"
2457
2458 return re.sub(r"\D", "", value)
2459
2460
2461 # setup listener on UserContact.phone attribute, instructing
2462 # it to use the return value
2463 listen(UserContact.phone, "set", validate_phone, retval=True)
2464
2465 A validation function like the above can also raise an exception
2466 such as :exc:`ValueError` to halt the operation.
2467
2468 The :paramref:`.AttributeEvents.propagate` flag is also important when
2469 applying listeners to mapped classes that also have mapped subclasses,
2470 as when using mapper inheritance patterns::
2471
2472
2473 @event.listens_for(MySuperClass.attr, "set", propagate=True)
2474 def receive_set(target, value, initiator):
2475 print("value set: %s" % target)
2476
2477 The full list of modifiers available to the :func:`.event.listen`
2478 and :func:`.event.listens_for` functions are below.
2479
2480 :param active_history=False: When True, indicates that the
2481 "set" event would like to receive the "old" value being
2482 replaced unconditionally, even if this requires firing off
2483 database loads. Note that ``active_history`` can also be
2484 set directly via :func:`.column_property` and
2485 :func:`_orm.relationship`.
2486
2487 :param propagate=False: When True, the listener function will
2488 be established not just for the class attribute given, but
2489 for attributes of the same name on all current subclasses
2490 of that class, as well as all future subclasses of that
2491 class, using an additional listener that listens for
2492 instrumentation events.
2493 :param raw=False: When True, the "target" argument to the
2494 event will be the :class:`.InstanceState` management
2495 object, rather than the mapped instance itself.
2496 :param retval=False: when True, the user-defined event
2497 listening must return the "value" argument from the
2498 function. This gives the listening function the opportunity
2499 to change the value that is ultimately used for a "set"
2500 or "append" event.
2501
2502 """
2503
2504 _target_class_doc = "SomeClass.some_attribute"
2505 _dispatch_target = QueryableAttribute
2506
2507 @staticmethod
2508 def _set_dispatch(
2509 cls: Type[_HasEventsDispatch[Any]], dispatch_cls: Type[_Dispatch[Any]]
2510 ) -> _Dispatch[Any]:
2511 dispatch = event.Events._set_dispatch(cls, dispatch_cls)
2512 dispatch_cls._active_history = False
2513 return dispatch
2514
2515 @classmethod
2516 def _accept_with(
2517 cls,
2518 target: Union[QueryableAttribute[Any], Type[QueryableAttribute[Any]]],
2519 identifier: str,
2520 ) -> Union[QueryableAttribute[Any], Type[QueryableAttribute[Any]]]:
2521 # TODO: coverage
2522 if isinstance(target, interfaces.MapperProperty):
2523 return getattr(target.parent.class_, target.key)
2524 else:
2525 return target
2526
2527 @classmethod
2528 def _listen( # type: ignore [override]
2529 cls,
2530 event_key: _EventKey[QueryableAttribute[Any]],
2531 active_history: bool = False,
2532 raw: bool = False,
2533 retval: bool = False,
2534 propagate: bool = False,
2535 include_key: bool = False,
2536 ) -> None:
2537 target, fn = event_key.dispatch_target, event_key._listen_fn
2538
2539 if active_history:
2540 target.dispatch._active_history = True
2541
2542 if not raw or not retval or not include_key:
2543
2544 def wrap(target: InstanceState[_O], *arg: Any, **kw: Any) -> Any:
2545 if not raw:
2546 target = target.obj() # type: ignore [assignment]
2547 if not retval:
2548 if arg:
2549 value = arg[0]
2550 else:
2551 value = None
2552 if include_key:
2553 fn(target, *arg, **kw)
2554 else:
2555 fn(target, *arg)
2556 return value
2557 else:
2558 if include_key:
2559 return fn(target, *arg, **kw)
2560 else:
2561 return fn(target, *arg)
2562
2563 event_key = event_key.with_wrapper(wrap)
2564
2565 event_key.base_listen(propagate=propagate)
2566
2567 if propagate:
2568 manager = instrumentation.manager_of_class(target.class_)
2569
2570 for mgr in manager.subclass_managers(True): # type: ignore [no-untyped-call] # noqa: E501
2571 event_key.with_dispatch_target(mgr[target.key]).base_listen(
2572 propagate=True
2573 )
2574 if active_history:
2575 mgr[target.key].dispatch._active_history = True
2576
2577 def append(
2578 self,
2579 target: _O,
2580 value: _T,
2581 initiator: Event,
2582 *,
2583 key: EventConstants = NO_KEY,
2584 ) -> Optional[_T]:
2585 """Receive a collection append event.
2586
2587 The append event is invoked for each element as it is appended
2588 to the collection. This occurs for single-item appends as well
2589 as for a "bulk replace" operation.
2590
2591 :param target: the object instance receiving the event.
2592 If the listener is registered with ``raw=True``, this will
2593 be the :class:`.InstanceState` object.
2594 :param value: the value being appended. If this listener
2595 is registered with ``retval=True``, the listener
2596 function must return this value, or a new value which
2597 replaces it.
2598 :param initiator: An instance of :class:`.attributes.Event`
2599 representing the initiation of the event. May be modified
2600 from its original value by backref handlers in order to control
2601 chained event propagation, as well as be inspected for information
2602 about the source of the event.
2603 :param key: When the event is established using the
2604 :paramref:`.AttributeEvents.include_key` parameter set to
2605 True, this will be the key used in the operation, such as
2606 ``collection[some_key_or_index] = value``.
2607 The parameter is not passed
2608 to the event at all if the the
2609 :paramref:`.AttributeEvents.include_key`
2610 was not used to set up the event; this is to allow backwards
2611 compatibility with existing event handlers that don't include the
2612 ``key`` parameter.
2613
2614 .. versionadded:: 2.0
2615
2616 :return: if the event was registered with ``retval=True``,
2617 the given value, or a new effective value, should be returned.
2618
2619 .. seealso::
2620
2621 :class:`.AttributeEvents` - background on listener options such
2622 as propagation to subclasses.
2623
2624 :meth:`.AttributeEvents.bulk_replace`
2625
2626 """
2627
2628 def append_wo_mutation(
2629 self,
2630 target: _O,
2631 value: _T,
2632 initiator: Event,
2633 *,
2634 key: EventConstants = NO_KEY,
2635 ) -> None:
2636 """Receive a collection append event where the collection was not
2637 actually mutated.
2638
2639 This event differs from :meth:`_orm.AttributeEvents.append` in that
2640 it is fired off for de-duplicating collections such as sets and
2641 dictionaries, when the object already exists in the target collection.
2642 The event does not have a return value and the identity of the
2643 given object cannot be changed.
2644
2645 The event is used for cascading objects into a :class:`_orm.Session`
2646 when the collection has already been mutated via a backref event.
2647
2648 :param target: the object instance receiving the event.
2649 If the listener is registered with ``raw=True``, this will
2650 be the :class:`.InstanceState` object.
2651 :param value: the value that would be appended if the object did not
2652 already exist in the collection.
2653 :param initiator: An instance of :class:`.attributes.Event`
2654 representing the initiation of the event. May be modified
2655 from its original value by backref handlers in order to control
2656 chained event propagation, as well as be inspected for information
2657 about the source of the event.
2658 :param key: When the event is established using the
2659 :paramref:`.AttributeEvents.include_key` parameter set to
2660 True, this will be the key used in the operation, such as
2661 ``collection[some_key_or_index] = value``.
2662 The parameter is not passed
2663 to the event at all if the the
2664 :paramref:`.AttributeEvents.include_key`
2665 was not used to set up the event; this is to allow backwards
2666 compatibility with existing event handlers that don't include the
2667 ``key`` parameter.
2668
2669 .. versionadded:: 2.0
2670
2671 :return: No return value is defined for this event.
2672
2673 .. versionadded:: 1.4.15
2674
2675 """
2676
2677 def bulk_replace(
2678 self,
2679 target: _O,
2680 values: Iterable[_T],
2681 initiator: Event,
2682 *,
2683 keys: Optional[Iterable[EventConstants]] = None,
2684 ) -> None:
2685 """Receive a collection 'bulk replace' event.
2686
2687 This event is invoked for a sequence of values as they are incoming
2688 to a bulk collection set operation, which can be
2689 modified in place before the values are treated as ORM objects.
2690 This is an "early hook" that runs before the bulk replace routine
2691 attempts to reconcile which objects are already present in the
2692 collection and which are being removed by the net replace operation.
2693
2694 It is typical that this method be combined with use of the
2695 :meth:`.AttributeEvents.append` event. When using both of these
2696 events, note that a bulk replace operation will invoke
2697 the :meth:`.AttributeEvents.append` event for all new items,
2698 even after :meth:`.AttributeEvents.bulk_replace` has been invoked
2699 for the collection as a whole. In order to determine if an
2700 :meth:`.AttributeEvents.append` event is part of a bulk replace,
2701 use the symbol :attr:`~.attributes.OP_BULK_REPLACE` to test the
2702 incoming initiator::
2703
2704 from sqlalchemy.orm.attributes import OP_BULK_REPLACE
2705
2706
2707 @event.listens_for(SomeObject.collection, "bulk_replace")
2708 def process_collection(target, values, initiator):
2709 values[:] = [_make_value(value) for value in values]
2710
2711
2712 @event.listens_for(SomeObject.collection, "append", retval=True)
2713 def process_collection(target, value, initiator):
2714 # make sure bulk_replace didn't already do it
2715 if initiator is None or initiator.op is not OP_BULK_REPLACE:
2716 return _make_value(value)
2717 else:
2718 return value
2719
2720 .. versionadded:: 1.2
2721
2722 :param target: the object instance receiving the event.
2723 If the listener is registered with ``raw=True``, this will
2724 be the :class:`.InstanceState` object.
2725 :param value: a sequence (e.g. a list) of the values being set. The
2726 handler can modify this list in place.
2727 :param initiator: An instance of :class:`.attributes.Event`
2728 representing the initiation of the event.
2729 :param keys: When the event is established using the
2730 :paramref:`.AttributeEvents.include_key` parameter set to
2731 True, this will be the sequence of keys used in the operation,
2732 typically only for a dictionary update. The parameter is not passed
2733 to the event at all if the the
2734 :paramref:`.AttributeEvents.include_key`
2735 was not used to set up the event; this is to allow backwards
2736 compatibility with existing event handlers that don't include the
2737 ``key`` parameter.
2738
2739 .. versionadded:: 2.0
2740
2741 .. seealso::
2742
2743 :class:`.AttributeEvents` - background on listener options such
2744 as propagation to subclasses.
2745
2746
2747 """
2748
2749 def remove(
2750 self,
2751 target: _O,
2752 value: _T,
2753 initiator: Event,
2754 *,
2755 key: EventConstants = NO_KEY,
2756 ) -> None:
2757 """Receive a collection remove event.
2758
2759 :param target: the object instance receiving the event.
2760 If the listener is registered with ``raw=True``, this will
2761 be the :class:`.InstanceState` object.
2762 :param value: the value being removed.
2763 :param initiator: An instance of :class:`.attributes.Event`
2764 representing the initiation of the event. May be modified
2765 from its original value by backref handlers in order to control
2766 chained event propagation.
2767
2768 :param key: When the event is established using the
2769 :paramref:`.AttributeEvents.include_key` parameter set to
2770 True, this will be the key used in the operation, such as
2771 ``del collection[some_key_or_index]``. The parameter is not passed
2772 to the event at all if the the
2773 :paramref:`.AttributeEvents.include_key`
2774 was not used to set up the event; this is to allow backwards
2775 compatibility with existing event handlers that don't include the
2776 ``key`` parameter.
2777
2778 .. versionadded:: 2.0
2779
2780 :return: No return value is defined for this event.
2781
2782
2783 .. seealso::
2784
2785 :class:`.AttributeEvents` - background on listener options such
2786 as propagation to subclasses.
2787
2788 """
2789
2790 def set(
2791 self, target: _O, value: _T, oldvalue: _T, initiator: Event
2792 ) -> None:
2793 """Receive a scalar set event.
2794
2795 :param target: the object instance receiving the event.
2796 If the listener is registered with ``raw=True``, this will
2797 be the :class:`.InstanceState` object.
2798 :param value: the value being set. If this listener
2799 is registered with ``retval=True``, the listener
2800 function must return this value, or a new value which
2801 replaces it.
2802 :param oldvalue: the previous value being replaced. This
2803 may also be the symbol ``NEVER_SET`` or ``NO_VALUE``.
2804 If the listener is registered with ``active_history=True``,
2805 the previous value of the attribute will be loaded from
2806 the database if the existing value is currently unloaded
2807 or expired.
2808 :param initiator: An instance of :class:`.attributes.Event`
2809 representing the initiation of the event. May be modified
2810 from its original value by backref handlers in order to control
2811 chained event propagation.
2812
2813 :return: if the event was registered with ``retval=True``,
2814 the given value, or a new effective value, should be returned.
2815
2816 .. seealso::
2817
2818 :class:`.AttributeEvents` - background on listener options such
2819 as propagation to subclasses.
2820
2821 """
2822
2823 def init_scalar(
2824 self, target: _O, value: _T, dict_: Dict[Any, Any]
2825 ) -> None:
2826 r"""Receive a scalar "init" event.
2827
2828 This event is invoked when an uninitialized, unpersisted scalar
2829 attribute is accessed, e.g. read::
2830
2831
2832 x = my_object.some_attribute
2833
2834 The ORM's default behavior when this occurs for an un-initialized
2835 attribute is to return the value ``None``; note this differs from
2836 Python's usual behavior of raising ``AttributeError``. The
2837 event here can be used to customize what value is actually returned,
2838 with the assumption that the event listener would be mirroring
2839 a default generator that is configured on the Core
2840 :class:`_schema.Column`
2841 object as well.
2842
2843 Since a default generator on a :class:`_schema.Column`
2844 might also produce
2845 a changing value such as a timestamp, the
2846 :meth:`.AttributeEvents.init_scalar`
2847 event handler can also be used to **set** the newly returned value, so
2848 that a Core-level default generation function effectively fires off
2849 only once, but at the moment the attribute is accessed on the
2850 non-persisted object. Normally, no change to the object's state
2851 is made when an uninitialized attribute is accessed (much older
2852 SQLAlchemy versions did in fact change the object's state).
2853
2854 If a default generator on a column returned a particular constant,
2855 a handler might be used as follows::
2856
2857 SOME_CONSTANT = 3.1415926
2858
2859
2860 class MyClass(Base):
2861 # ...
2862
2863 some_attribute = Column(Numeric, default=SOME_CONSTANT)
2864
2865
2866 @event.listens_for(
2867 MyClass.some_attribute, "init_scalar", retval=True, propagate=True
2868 )
2869 def _init_some_attribute(target, dict_, value):
2870 dict_["some_attribute"] = SOME_CONSTANT
2871 return SOME_CONSTANT
2872
2873 Above, we initialize the attribute ``MyClass.some_attribute`` to the
2874 value of ``SOME_CONSTANT``. The above code includes the following
2875 features:
2876
2877 * By setting the value ``SOME_CONSTANT`` in the given ``dict_``,
2878 we indicate that this value is to be persisted to the database.
2879 This supersedes the use of ``SOME_CONSTANT`` in the default generator
2880 for the :class:`_schema.Column`. The ``active_column_defaults.py``
2881 example given at :ref:`examples_instrumentation` illustrates using
2882 the same approach for a changing default, e.g. a timestamp
2883 generator. In this particular example, it is not strictly
2884 necessary to do this since ``SOME_CONSTANT`` would be part of the
2885 INSERT statement in either case.
2886
2887 * By establishing the ``retval=True`` flag, the value we return
2888 from the function will be returned by the attribute getter.
2889 Without this flag, the event is assumed to be a passive observer
2890 and the return value of our function is ignored.
2891
2892 * The ``propagate=True`` flag is significant if the mapped class
2893 includes inheriting subclasses, which would also make use of this
2894 event listener. Without this flag, an inheriting subclass will
2895 not use our event handler.
2896
2897 In the above example, the attribute set event
2898 :meth:`.AttributeEvents.set` as well as the related validation feature
2899 provided by :obj:`_orm.validates` is **not** invoked when we apply our
2900 value to the given ``dict_``. To have these events to invoke in
2901 response to our newly generated value, apply the value to the given
2902 object as a normal attribute set operation::
2903
2904 SOME_CONSTANT = 3.1415926
2905
2906
2907 @event.listens_for(
2908 MyClass.some_attribute, "init_scalar", retval=True, propagate=True
2909 )
2910 def _init_some_attribute(target, dict_, value):
2911 # will also fire off attribute set events
2912 target.some_attribute = SOME_CONSTANT
2913 return SOME_CONSTANT
2914
2915 When multiple listeners are set up, the generation of the value
2916 is "chained" from one listener to the next by passing the value
2917 returned by the previous listener that specifies ``retval=True``
2918 as the ``value`` argument of the next listener.
2919
2920 :param target: the object instance receiving the event.
2921 If the listener is registered with ``raw=True``, this will
2922 be the :class:`.InstanceState` object.
2923 :param value: the value that is to be returned before this event
2924 listener were invoked. This value begins as the value ``None``,
2925 however will be the return value of the previous event handler
2926 function if multiple listeners are present.
2927 :param dict\_: the attribute dictionary of this mapped object.
2928 This is normally the ``__dict__`` of the object, but in all cases
2929 represents the destination that the attribute system uses to get
2930 at the actual value of this attribute. Placing the value in this
2931 dictionary has the effect that the value will be used in the
2932 INSERT statement generated by the unit of work.
2933
2934
2935 .. seealso::
2936
2937 :meth:`.AttributeEvents.init_collection` - collection version
2938 of this event
2939
2940 :class:`.AttributeEvents` - background on listener options such
2941 as propagation to subclasses.
2942
2943 :ref:`examples_instrumentation` - see the
2944 ``active_column_defaults.py`` example.
2945
2946 """ # noqa: E501
2947
2948 def init_collection(
2949 self,
2950 target: _O,
2951 collection: Type[Collection[Any]],
2952 collection_adapter: CollectionAdapter,
2953 ) -> None:
2954 """Receive a 'collection init' event.
2955
2956 This event is triggered for a collection-based attribute, when
2957 the initial "empty collection" is first generated for a blank
2958 attribute, as well as for when the collection is replaced with
2959 a new one, such as via a set event.
2960
2961 E.g., given that ``User.addresses`` is a relationship-based
2962 collection, the event is triggered here::
2963
2964 u1 = User()
2965 u1.addresses.append(a1) # <- new collection
2966
2967 and also during replace operations::
2968
2969 u1.addresses = [a2, a3] # <- new collection
2970
2971 :param target: the object instance receiving the event.
2972 If the listener is registered with ``raw=True``, this will
2973 be the :class:`.InstanceState` object.
2974 :param collection: the new collection. This will always be generated
2975 from what was specified as
2976 :paramref:`_orm.relationship.collection_class`, and will always
2977 be empty.
2978 :param collection_adapter: the :class:`.CollectionAdapter` that will
2979 mediate internal access to the collection.
2980
2981 .. seealso::
2982
2983 :class:`.AttributeEvents` - background on listener options such
2984 as propagation to subclasses.
2985
2986 :meth:`.AttributeEvents.init_scalar` - "scalar" version of this
2987 event.
2988
2989 """
2990
2991 def dispose_collection(
2992 self,
2993 target: _O,
2994 collection: Collection[Any],
2995 collection_adapter: CollectionAdapter,
2996 ) -> None:
2997 """Receive a 'collection dispose' event.
2998
2999 This event is triggered for a collection-based attribute when
3000 a collection is replaced, that is::
3001
3002 u1.addresses.append(a1)
3003
3004 u1.addresses = [a2, a3] # <- old collection is disposed
3005
3006 The old collection received will contain its previous contents.
3007
3008 .. versionchanged:: 1.2 The collection passed to
3009 :meth:`.AttributeEvents.dispose_collection` will now have its
3010 contents before the dispose intact; previously, the collection
3011 would be empty.
3012
3013 .. seealso::
3014
3015 :class:`.AttributeEvents` - background on listener options such
3016 as propagation to subclasses.
3017
3018 """
3019
3020 def modified(self, target: _O, initiator: Event) -> None:
3021 """Receive a 'modified' event.
3022
3023 This event is triggered when the :func:`.attributes.flag_modified`
3024 function is used to trigger a modify event on an attribute without
3025 any specific value being set.
3026
3027 .. versionadded:: 1.2
3028
3029 :param target: the object instance receiving the event.
3030 If the listener is registered with ``raw=True``, this will
3031 be the :class:`.InstanceState` object.
3032
3033 :param initiator: An instance of :class:`.attributes.Event`
3034 representing the initiation of the event.
3035
3036 .. seealso::
3037
3038 :class:`.AttributeEvents` - background on listener options such
3039 as propagation to subclasses.
3040
3041 """
3042
3043
3044class QueryEvents(event.Events[Query[Any]]):
3045 """Represent events within the construction of a :class:`_query.Query`
3046 object.
3047
3048 .. legacy:: The :class:`_orm.QueryEvents` event methods are legacy
3049 as of SQLAlchemy 2.0, and only apply to direct use of the
3050 :class:`_orm.Query` object. They are not used for :term:`2.0 style`
3051 statements. For events to intercept and modify 2.0 style ORM use,
3052 use the :meth:`_orm.SessionEvents.do_orm_execute` hook.
3053
3054
3055 The :class:`_orm.QueryEvents` hooks are now superseded by the
3056 :meth:`_orm.SessionEvents.do_orm_execute` event hook.
3057
3058 """
3059
3060 _target_class_doc = "SomeQuery"
3061 _dispatch_target = Query
3062
3063 def before_compile(self, query: Query[Any]) -> None:
3064 """Receive the :class:`_query.Query`
3065 object before it is composed into a
3066 core :class:`_expression.Select` object.
3067
3068 .. deprecated:: 1.4 The :meth:`_orm.QueryEvents.before_compile` event
3069 is superseded by the much more capable
3070 :meth:`_orm.SessionEvents.do_orm_execute` hook. In version 1.4,
3071 the :meth:`_orm.QueryEvents.before_compile` event is **no longer
3072 used** for ORM-level attribute loads, such as loads of deferred
3073 or expired attributes as well as relationship loaders. See the
3074 new examples in :ref:`examples_session_orm_events` which
3075 illustrate new ways of intercepting and modifying ORM queries
3076 for the most common purpose of adding arbitrary filter criteria.
3077
3078
3079 This event is intended to allow changes to the query given::
3080
3081 @event.listens_for(Query, "before_compile", retval=True)
3082 def no_deleted(query):
3083 for desc in query.column_descriptions:
3084 if desc["type"] is User:
3085 entity = desc["entity"]
3086 query = query.filter(entity.deleted == False)
3087 return query
3088
3089 The event should normally be listened with the ``retval=True``
3090 parameter set, so that the modified query may be returned.
3091
3092 The :meth:`.QueryEvents.before_compile` event by default
3093 will disallow "baked" queries from caching a query, if the event
3094 hook returns a new :class:`_query.Query` object.
3095 This affects both direct
3096 use of the baked query extension as well as its operation within
3097 lazy loaders and eager loaders for relationships. In order to
3098 re-establish the query being cached, apply the event adding the
3099 ``bake_ok`` flag::
3100
3101 @event.listens_for(Query, "before_compile", retval=True, bake_ok=True)
3102 def my_event(query):
3103 for desc in query.column_descriptions:
3104 if desc["type"] is User:
3105 entity = desc["entity"]
3106 query = query.filter(entity.deleted == False)
3107 return query
3108
3109 When ``bake_ok`` is set to True, the event hook will only be invoked
3110 once, and not called for subsequent invocations of a particular query
3111 that is being cached.
3112
3113 .. versionadded:: 1.3.11 - added the "bake_ok" flag to the
3114 :meth:`.QueryEvents.before_compile` event and disallowed caching via
3115 the "baked" extension from occurring for event handlers that
3116 return a new :class:`_query.Query` object if this flag is not set.
3117
3118 .. seealso::
3119
3120 :meth:`.QueryEvents.before_compile_update`
3121
3122 :meth:`.QueryEvents.before_compile_delete`
3123
3124 :ref:`baked_with_before_compile`
3125
3126 """ # noqa: E501
3127
3128 def before_compile_update(
3129 self, query: Query[Any], update_context: BulkUpdate
3130 ) -> None:
3131 """Allow modifications to the :class:`_query.Query` object within
3132 :meth:`_query.Query.update`.
3133
3134 .. deprecated:: 1.4 The :meth:`_orm.QueryEvents.before_compile_update`
3135 event is superseded by the much more capable
3136 :meth:`_orm.SessionEvents.do_orm_execute` hook.
3137
3138 Like the :meth:`.QueryEvents.before_compile` event, if the event
3139 is to be used to alter the :class:`_query.Query` object, it should
3140 be configured with ``retval=True``, and the modified
3141 :class:`_query.Query` object returned, as in ::
3142
3143 @event.listens_for(Query, "before_compile_update", retval=True)
3144 def no_deleted(query, update_context):
3145 for desc in query.column_descriptions:
3146 if desc["type"] is User:
3147 entity = desc["entity"]
3148 query = query.filter(entity.deleted == False)
3149
3150 update_context.values["timestamp"] = datetime.datetime.now(
3151 datetime.UTC
3152 )
3153 return query
3154
3155 The ``.values`` dictionary of the "update context" object can also
3156 be modified in place as illustrated above.
3157
3158 :param query: a :class:`_query.Query` instance; this is also
3159 the ``.query`` attribute of the given "update context"
3160 object.
3161
3162 :param update_context: an "update context" object which is
3163 the same kind of object as described in
3164 :paramref:`.QueryEvents.after_bulk_update.update_context`.
3165 The object has a ``.values`` attribute in an UPDATE context which is
3166 the dictionary of parameters passed to :meth:`_query.Query.update`.
3167 This
3168 dictionary can be modified to alter the VALUES clause of the
3169 resulting UPDATE statement.
3170
3171 .. versionadded:: 1.2.17
3172
3173 .. seealso::
3174
3175 :meth:`.QueryEvents.before_compile`
3176
3177 :meth:`.QueryEvents.before_compile_delete`
3178
3179
3180 """ # noqa: E501
3181
3182 def before_compile_delete(
3183 self, query: Query[Any], delete_context: BulkDelete
3184 ) -> None:
3185 """Allow modifications to the :class:`_query.Query` object within
3186 :meth:`_query.Query.delete`.
3187
3188 .. deprecated:: 1.4 The :meth:`_orm.QueryEvents.before_compile_delete`
3189 event is superseded by the much more capable
3190 :meth:`_orm.SessionEvents.do_orm_execute` hook.
3191
3192 Like the :meth:`.QueryEvents.before_compile` event, this event
3193 should be configured with ``retval=True``, and the modified
3194 :class:`_query.Query` object returned, as in ::
3195
3196 @event.listens_for(Query, "before_compile_delete", retval=True)
3197 def no_deleted(query, delete_context):
3198 for desc in query.column_descriptions:
3199 if desc["type"] is User:
3200 entity = desc["entity"]
3201 query = query.filter(entity.deleted == False)
3202 return query
3203
3204 :param query: a :class:`_query.Query` instance; this is also
3205 the ``.query`` attribute of the given "delete context"
3206 object.
3207
3208 :param delete_context: a "delete context" object which is
3209 the same kind of object as described in
3210 :paramref:`.QueryEvents.after_bulk_delete.delete_context`.
3211
3212 .. versionadded:: 1.2.17
3213
3214 .. seealso::
3215
3216 :meth:`.QueryEvents.before_compile`
3217
3218 :meth:`.QueryEvents.before_compile_update`
3219
3220
3221 """
3222
3223 @classmethod
3224 def _listen(
3225 cls,
3226 event_key: _EventKey[_ET],
3227 retval: bool = False,
3228 bake_ok: bool = False,
3229 **kw: Any,
3230 ) -> None:
3231 fn = event_key._listen_fn
3232
3233 if not retval:
3234
3235 def wrap(*arg: Any, **kw: Any) -> Any:
3236 if not retval:
3237 query = arg[0]
3238 fn(*arg, **kw)
3239 return query
3240 else:
3241 return fn(*arg, **kw)
3242
3243 event_key = event_key.with_wrapper(wrap)
3244 else:
3245 # don't assume we can apply an attribute to the callable
3246 def wrap(*arg: Any, **kw: Any) -> Any:
3247 return fn(*arg, **kw)
3248
3249 event_key = event_key.with_wrapper(wrap)
3250
3251 wrap._bake_ok = bake_ok # type: ignore [attr-defined]
3252
3253 event_key.base_listen(**kw)