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