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