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