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