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