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