codekingpro/portable-devtools
115k
1# orm/events.py
2# Copyright (C) 2005-2026 the SQLAlchemy authors and contributors
3# <see AUTHORS file>
4#
5# This module is part of SQLAlchemy and is released under
6# the MIT License: https://www.opensource.org/licenses/mit-license.php
7
8"""ORM event interfaces."""
9from __future__ import annotations
10
11from typing import Any
12from typing import Callable
13from typing import Collection
14from typing import Dict
15from typing import Generic
16from typing import Iterable
17from typing import Optional
18from typing import Sequence
19from typing import Set
20from typing import Type
21from typing import TYPE_CHECKING
22from typing import TypeVar
23from typing import Union
24import weakref
25
26from . import instrumentation
27from . import interfaces
28from . import mapperlib
29from .attributes import QueryableAttribute
30from .base import _mapper_or_none
31from .base import NO_KEY
32from .instrumentation import ClassManager
33from .instrumentation import InstrumentationFactory
34from .query import BulkDelete
35from .query import BulkUpdate
36from .query import Query
37from .scoping import scoped_session
38from .session import Session
39from .session import sessionmaker
40from .. import event
41from .. import exc
42from .. import util
43from ..event import EventTarget
44from ..event.registry import _ET
45from ..util.compat import inspect_getfullargspec
46
47if TYPE_CHECKING:
48 from weakref import ReferenceType
49
50 from ._typing import _InstanceDict
51 from ._typing import _InternalEntityType
52 from ._typing import _O
53 from ._typing import _T
54 from .attributes import Event
55 from .base import EventConstants
56 from .session import ORMExecuteState
57 from .session import SessionTransaction
58 from .unitofwork import UOWTransaction
59 from ..engine import Connection
60 from ..event.base import _Dispatch
61 from ..event.base import _HasEventsDispatch
62 from ..event.registry import _EventKey
63 from ..orm.collections import CollectionAdapter
64 from ..orm.context import QueryContext
65 from ..orm.decl_api import DeclarativeAttributeIntercept
66 from ..orm.decl_api import DeclarativeMeta
67 from ..orm.mapper import Mapper
68 from ..orm.state import InstanceState
69
70_KT = TypeVar("_KT", bound=Any)
71_ET2 = TypeVar("_ET2", bound=EventTarget)
72
73
74class InstrumentationEvents(event.Events[InstrumentationFactory]):
75 """Events related to class instrumentation events.
76
77 The listeners here support being established against
78 any new style class, that is any object that is a subclass
79 of 'type'. Events will then be fired off for events
80 against that class. If the "propagate=True" flag is passed
81 to event.listen(), the event will fire off for subclasses
82 of that class as well.
83
84 The Python ``type`` builtin is also accepted as a target,
85 which when used has the effect of events being emitted
86 for all classes.
87
88 Note the "propagate" flag here is defaulted to ``True``,
89 unlike the other class level events where it defaults
90 to ``False``. This means that new subclasses will also
91 be the subject of these events, when a listener
92 is established on a superclass.
93
94 """
95
96 _target_class_doc = "SomeBaseClass"
97 _dispatch_target = InstrumentationFactory
98
99 @classmethod
100 def _accept_with(
101 cls,
102 target: Union[
103 InstrumentationFactory,
104 Type[InstrumentationFactory],
105 ],
106 identifier: str,
107 ) -> Optional[
108 Union[
109 InstrumentationFactory,
110 Type[InstrumentationFactory],
111 ]
112 ]:
113 if isinstance(target, type):
114 return _InstrumentationEventsHold(target) # type: ignore [return-value] # noqa: E501
115 else:
116 return None
117
118 @classmethod
119 def _listen(
120 cls, event_key: _EventKey[_T], propagate: bool = True, **kw: Any
121 ) -> None:
122 target, identifier, fn = (
123 event_key.dispatch_target,
124 event_key.identifier,
125 event_key._listen_fn,
126 )
127
128 def listen(target_cls: type, *arg: Any) -> Optional[Any]:
129 listen_cls = target()
130
131 # if weakref were collected, however this is not something
132 # that normally happens. it was occurring during test teardown
133 # between mapper/registry/instrumentation_manager, however this
134 # interaction was changed to not rely upon the event system.
135 if listen_cls is None:
136 return None
137
138 if propagate and issubclass(target_cls, listen_cls):
139 return fn(target_cls, *arg)
140 elif not propagate and target_cls is listen_cls:
141 return fn(target_cls, *arg)
142 else:
143 return None
144
145 def remove(ref: ReferenceType[_T]) -> None:
146 key = event.registry._EventKey( # type: ignore [type-var]
147 None,
148 identifier,
149 listen,
150 instrumentation._instrumentation_factory,
151 )
152 getattr(
153 instrumentation._instrumentation_factory.dispatch, identifier
154 ).remove(key)
155
156 target = weakref.ref(target.class_, remove)
157
158 event_key.with_dispatch_target(
159 instrumentation._instrumentation_factory
160 ).with_wrapper(listen).base_listen(**kw)
161
162 @classmethod
163 def _clear(cls) -> None:
164 super()._clear()
165 instrumentation._instrumentation_factory.dispatch._clear()
166
167 def class_instrument(self, cls: ClassManager[_O]) -> None:
168 """Called after the given class is instrumented.
169
170 To get at the :class:`.ClassManager`, use
171 :func:`.manager_of_class`.
172
173 """
174
175 def class_uninstrument(self, cls: ClassManager[_O]) -> None:
176 """Called before the given class is uninstrumented.
177
178 To get at the :class:`.ClassManager`, use
179 :func:`.manager_of_class`.
180
181 """
182
183 def attribute_instrument(
184 self, cls: ClassManager[_O], key: _KT, inst: _O
185 ) -> None:
186 """Called when an attribute is instrumented."""
187
188
189class _InstrumentationEventsHold:
190 """temporary marker object used to transfer from _accept_with() to
191 _listen() on the InstrumentationEvents class.
192
193 """
194
195 def __init__(self, class_: type) -> None:
196 self.class_ = class_
197
198 dispatch = event.dispatcher(InstrumentationEvents)
199
200
201class InstanceEvents(event.Events[ClassManager[Any]]):
202 """Define events specific to object lifecycle.
203
204 e.g.::
205
206 from sqlalchemy import event
207
208
209 def my_load_listener(target, context):
210 print("on load!")
211
212
213 event.listen(SomeClass, "load", my_load_listener)
214
215 Available targets include:
216
217 * mapped classes
218 * unmapped superclasses of mapped or to-be-mapped classes
219 (using the ``propagate=True`` flag)
220 * :class:`_orm.Mapper` objects
221 * the :class:`_orm.Mapper` class itself indicates listening for all
222 mappers.
223
224 Instance events are closely related to mapper events, but
225 are more specific to the instance and its instrumentation,
226 rather than its system of persistence.
227
228 When using :class:`.InstanceEvents`, several modifiers are
229 available to the :func:`.event.listen` function.
230
231 :param propagate=False: When True, the event listener should
232 be applied to all inheriting classes as well as the
233 class which is the target of this listener.
234 :param raw=False: When True, the "target" argument passed
235 to applicable event listener functions will be the
236 instance's :class:`.InstanceState` management
237 object, rather than the mapped instance itself.
238 :param restore_load_context=False: Applies to the
239 :meth:`.InstanceEvents.load` and :meth:`.InstanceEvents.refresh`
240 events. Restores the loader context of the object when the event
241 hook is complete, so that ongoing eager load operations continue
242 to target the object appropriately. A warning is emitted if the
243 object is moved to a new loader context from within one of these
244 events if this flag is not set.
245
246 .. versionadded:: 1.3.14
247
248
249 """
250
251 _target_class_doc = "SomeClass"
252
253 _dispatch_target = ClassManager
254
255 @classmethod
256 def _new_classmanager_instance(
257 cls,
258 class_: Union[DeclarativeAttributeIntercept, DeclarativeMeta, type],
259 classmanager: ClassManager[_O],
260 ) -> None:
261 _InstanceEventsHold.populate(class_, classmanager)
262
263 @classmethod
264 @util.preload_module("sqlalchemy.orm")
265 def _accept_with(
266 cls,
267 target: Union[
268 ClassManager[Any],
269 Type[ClassManager[Any]],
270 ],
271 identifier: str,
272 ) -> Optional[Union[ClassManager[Any], Type[ClassManager[Any]]]]:
273 orm = util.preloaded.orm
274
275 if isinstance(target, ClassManager):
276 return target
277 elif isinstance(target, mapperlib.Mapper):
278 return target.class_manager
279 elif target is orm.mapper: # type: ignore [attr-defined]
280 util.warn_deprecated(
281 "The `sqlalchemy.orm.mapper()` symbol is deprecated and "
282 "will be removed in a future release. For the mapper-wide "
283 "event target, use the 'sqlalchemy.orm.Mapper' class.",
284 "2.0",
285 )
286 return ClassManager
287 elif isinstance(target, type):
288 if issubclass(target, mapperlib.Mapper):
289 return ClassManager
290 else:
291 manager = instrumentation.opt_manager_of_class(target)
292 if manager:
293 return manager
294 else:
295 return _InstanceEventsHold(target) # type: ignore [return-value] # noqa: E501
296 return None
297
298 @classmethod
299 def _listen(
300 cls,
301 event_key: _EventKey[ClassManager[Any]],
302 raw: bool = False,
303 propagate: bool = False,
304 restore_load_context: bool = False,
305 **kw: Any,
306 ) -> None:
307 target, fn = (event_key.dispatch_target, event_key._listen_fn)
308
309 if not raw or restore_load_context:
310
311 def wrap(
312 state: InstanceState[_O], *arg: Any, **kw: Any
313 ) -> Optional[Any]:
314 if not raw:
315 target: Any = state.obj()
316 else:
317 target = state
318 if restore_load_context:
319 runid = state.runid
320 try:
321 return fn(target, *arg, **kw)
322 finally:
323 if restore_load_context:
324 state.runid = runid
325
326 event_key = event_key.with_wrapper(wrap)
327
328 event_key.base_listen(propagate=propagate, **kw)
329
330 if propagate:
331 for mgr in target.subclass_managers(True):
332 event_key.with_dispatch_target(mgr).base_listen(propagate=True)
333
334 @classmethod
335 def _clear(cls) -> None:
336 super()._clear()
337 _InstanceEventsHold._clear()
338
339 def first_init(self, manager: ClassManager[_O], cls: Type[_O]) -> None:
340 """Called when the first instance of a particular mapping is called.
341
342 This event is called when the ``__init__`` method of a class
343 is called the first time for that particular class. The event
344 invokes before ``__init__`` actually proceeds as well as before
345 the :meth:`.InstanceEvents.init` event is invoked.
346
347 """
348
349 def init(self, target: _O, args: Any, kwargs: Any) -> None:
350 """Receive an instance when its constructor is called.
351
352 This method is only called during a userland construction of
353 an object, in conjunction with the object's constructor, e.g.
354 its ``__init__`` method. It is not called when an object is
355 loaded from the database; see the :meth:`.InstanceEvents.load`
356 event in order to intercept a database load.
357
358 The event is called before the actual ``__init__`` constructor
359 of the object is called. The ``kwargs`` dictionary may be
360 modified in-place in order to affect what is passed to
361 ``__init__``.
362
363 :param target: the mapped instance. If
364 the event is configured with ``raw=True``, this will
365 instead be the :class:`.InstanceState` state-management
366 object associated with the instance.
367 :param args: positional arguments passed to the ``__init__`` method.
368 This is passed as a tuple and is currently immutable.
369 :param kwargs: keyword arguments passed to the ``__init__`` method.
370 This structure *can* be altered in place.
371
372 .. seealso::
373
374 :meth:`.InstanceEvents.init_failure`
375
376 :meth:`.InstanceEvents.load`
377
378 """
379
380 def init_failure(self, target: _O, args: Any, kwargs: Any) -> None:
381 """Receive an instance when its constructor has been called,
382 and raised an exception.
383
384 This method is only called during a userland construction of
385 an object, in conjunction with the object's constructor, e.g.
386 its ``__init__`` method. It is not called when an object is loaded
387 from the database.
388
389 The event is invoked after an exception raised by the ``__init__``
390 method is caught. After the event
391 is invoked, the original exception is re-raised outwards, so that
392 the construction of the object still raises an exception. The
393 actual exception and stack trace raised should be present in
394 ``sys.exc_info()``.
395
396 :param target: the mapped instance. If
397 the event is configured with ``raw=True``, this will
398 instead be the :class:`.InstanceState` state-management
399 object associated with the instance.
400 :param args: positional arguments that were passed to the ``__init__``
401 method.
402 :param kwargs: keyword arguments that were passed to the ``__init__``
403 method.
404
405 .. seealso::
406
407 :meth:`.InstanceEvents.init`
408
409 :meth:`.InstanceEvents.load`
410
411 """
412
413 def _sa_event_merge_wo_load(
414 self, target: _O, context: QueryContext
415 ) -> None:
416 """receive an object instance after it was the subject of a merge()
417 call, when load=False was passed.
418
419 The target would be the already-loaded object in the Session which
420 would have had its attributes overwritten by the incoming object. This
421 overwrite operation does not use attribute events, instead just
422 populating dict directly. Therefore the purpose of this event is so
423 that extensions like sqlalchemy.ext.mutable know that object state has
424 changed and incoming state needs to be set up for "parents" etc.
425
426 This functionality is acceptable to be made public in a later release.
427
428 .. versionadded:: 1.4.41
429
430 """
431
432 def load(self, target: _O, context: QueryContext) -> None:
433 """Receive an object instance after it has been created via
434 ``__new__``, and after initial attribute population has
435 occurred.
436
437 This typically occurs when the instance is created based on
438 incoming result rows, and is only called once for that
439 instance's lifetime.
440
441 .. warning::
442
443 During a result-row load, this event is invoked when the
444 first row received for this instance is processed. When using
445 eager loading with collection-oriented attributes, the additional
446 rows that are to be loaded / processed in order to load subsequent
447 collection items have not occurred yet. This has the effect
448 both that collections will not be fully loaded, as well as that
449 if an operation occurs within this event handler that emits
450 another database load operation for the object, the "loading
451 context" for the object can change and interfere with the
452 existing eager loaders still in progress.
453
454 Examples of what can cause the "loading context" to change within
455 the event handler include, but are not necessarily limited to:
456
457 * accessing deferred attributes that weren't part of the row,
458 will trigger an "undefer" operation and refresh the object
459
460 * accessing attributes on a joined-inheritance subclass that
461 weren't part of the row, will trigger a refresh operation.
462
463 As of SQLAlchemy 1.3.14, a warning is emitted when this occurs. The
464 :paramref:`.InstanceEvents.restore_load_context` option may be
465 used on the event to prevent this warning; this will ensure that
466 the existing loading context is maintained for the object after the
467 event is called::
468
469 @event.listens_for(SomeClass, "load", restore_load_context=True)
470 def on_load(instance, context):
471 instance.some_unloaded_attribute
472
473 .. versionchanged:: 1.3.14 Added
474 :paramref:`.InstanceEvents.restore_load_context`
475 and :paramref:`.SessionEvents.restore_load_context` flags which
476 apply to "on load" events, which will ensure that the loading
477 context for an object is restored when the event hook is
478 complete; a warning is emitted if the load context of the object
479 changes without this flag being set.
480
481
482 The :meth:`.InstanceEvents.load` event is also available in a
483 class-method decorator format called :func:`_orm.reconstructor`.
484
485 :param target: the mapped instance. If
486 the event is configured with ``raw=True``, this will
487 instead be the :class:`.InstanceState` state-management
488 object associated with the instance.
489 :param context: the :class:`.QueryContext` corresponding to the
490 current :class:`_query.Query` in progress. This argument may be
491 ``None`` if the load does not correspond to a :class:`_query.Query`,
492 such as during :meth:`.Session.merge`.
493
494 .. seealso::
495
496 :ref:`mapped_class_load_events`
497
498 :meth:`.InstanceEvents.init`
499
500 :meth:`.InstanceEvents.refresh`
501
502 :meth:`.SessionEvents.loaded_as_persistent`
503
504 """ # noqa: E501
505
506 def refresh(
507 self, target: _O, context: QueryContext, attrs: Optional[Iterable[str]]
508 ) -> None:
509 """Receive an object instance after one or more attributes have
510 been refreshed from a query.
511
512 Contrast this to the :meth:`.InstanceEvents.load` method, which
513 is invoked when the object is first loaded from a query.
514
515 .. note:: This event is invoked within the loader process before
516 eager loaders may have been completed, and the object's state may
517 not be complete. Additionally, invoking row-level refresh
518 operations on the object will place the object into a new loader
519 context, interfering with the existing load context. See the note
520 on :meth:`.InstanceEvents.load` for background on making use of the
521 :paramref:`.InstanceEvents.restore_load_context` parameter, in
522 order to resolve this scenario.
523
524 :param target: the mapped instance. If
525 the event is configured with ``raw=True``, this will
526 instead be the :class:`.InstanceState` state-management
527 object associated with the instance.
528 :param context: the :class:`.QueryContext` corresponding to the
529 current :class:`_query.Query` in progress.
530 :param attrs: sequence of attribute names which
531 were populated, or None if all column-mapped, non-deferred
532 attributes were populated.
533
534 .. seealso::
535
536 :ref:`mapped_class_load_events`
537
538 :meth:`.InstanceEvents.load`
539
540 """
541
542 def refresh_flush(
543 self,
544 target: _O,
545 flush_context: UOWTransaction,
546 attrs: Optional[Iterable[str]],
547 ) -> None:
548 """Receive an object instance after one or more attributes that
549 contain a column-level default or onupdate handler have been refreshed
550 during persistence of the object's state.
551
552 This event is the same as :meth:`.InstanceEvents.refresh` except
553 it is invoked within the unit of work flush process, and includes
554 only non-primary-key columns that have column level default or
555 onupdate handlers, including Python callables as well as server side
556 defaults and triggers which may be fetched via the RETURNING clause.
557
558 .. note::
559
560 While the :meth:`.InstanceEvents.refresh_flush` event is triggered
561 for an object that was INSERTed as well as for an object that was
562 UPDATEd, the event is geared primarily towards the UPDATE process;
563 it is mostly an internal artifact that INSERT actions can also
564 trigger this event, and note that **primary key columns for an
565 INSERTed row are explicitly omitted** from this event. In order to
566 intercept the newly INSERTed state of an object, the
567 :meth:`.SessionEvents.pending_to_persistent` and
568 :meth:`.MapperEvents.after_insert` are better choices.
569
570 :param target: the mapped instance. If
571 the event is configured with ``raw=True``, this will
572 instead be the :class:`.InstanceState` state-management
573 object associated with the instance.
574 :param flush_context: Internal :class:`.UOWTransaction` object
575 which handles the details of the flush.
576 :param attrs: sequence of attribute names which
577 were populated.
578
579 .. seealso::
580
581 :ref:`mapped_class_load_events`
582
583 :ref:`orm_server_defaults`
584
585 :ref:`metadata_defaults_toplevel`
586
587 """
588
589 def expire(self, target: _O, attrs: Optional[Iterable[str]]) -> None:
590 """Receive an object instance after its attributes or some subset
591 have been expired.
592
593 'keys' is a list of attribute names. If None, the entire
594 state was expired.
595
596 :param target: the mapped instance. If
597 the event is configured with ``raw=True``, this will
598 instead be the :class:`.InstanceState` state-management
599 object associated with the instance.
600 :param attrs: sequence of attribute
601 names which were expired, or None if all attributes were
602 expired.
603
604 """
605
606 def pickle(self, target: _O, state_dict: _InstanceDict) -> None:
607 """Receive an object instance when its associated state is
608 being pickled.
609
610 :param target: the mapped instance. If
611 the event is configured with ``raw=True``, this will
612 instead be the :class:`.InstanceState` state-management
613 object associated with the instance.
614 :param state_dict: the dictionary returned by
615 :class:`.InstanceState.__getstate__`, containing the state
616 to be pickled.
617
618 """
619
620 def unpickle(self, target: _O, state_dict: _InstanceDict) -> None:
621 """Receive an object instance after its associated state has
622 been unpickled.
623
624 :param target: the mapped instance. If
625 the event is configured with ``raw=True``, this will
626 instead be the :class:`.InstanceState` state-management
627 object associated with the instance.
628 :param state_dict: the dictionary sent to
629 :class:`.InstanceState.__setstate__`, containing the state
630 dictionary which was pickled.
631
632 """
633
634
635class _EventsHold(event.RefCollection[_ET]):
636 """Hold onto listeners against unmapped, uninstrumented classes.
637
638 Establish _listen() for that class' mapper/instrumentation when
639 those objects are created for that class.
640
641 """
642
643 all_holds: weakref.WeakKeyDictionary[Any, Any]
644
645 def __init__(
646 self,
647 class_: Union[DeclarativeAttributeIntercept, DeclarativeMeta, type],
648 ) -> None:
649 self.class_ = class_
650
651 @classmethod
652 def _clear(cls) -> None:
653 cls.all_holds.clear()
654
655 class HoldEvents(Generic[_ET2]):
656 _dispatch_target: Optional[Type[_ET2]] = None
657
658 @classmethod
659 def _listen(
660 cls,
661 event_key: _EventKey[_ET2],
662 raw: bool = False,
663 propagate: bool = False,
664 retval: bool = False,
665 **kw: Any,
666 ) -> None:
667 target = event_key.dispatch_target
668
669 if target.class_ in target.all_holds:
670 collection = target.all_holds[target.class_]
671 else:
672 collection = target.all_holds[target.class_] = {}
673
674 event.registry._stored_in_collection(event_key, target)
675 collection[event_key._key] = (
676 event_key,
677 raw,
678 propagate,
679 retval,
680 kw,
681 )
682
683 if propagate:
684 stack = list(target.class_.__subclasses__())
685 while stack:
686 subclass = stack.pop(0)
687 stack.extend(subclass.__subclasses__())
688 subject = target.resolve(subclass)
689 if subject is not None:
690 # we are already going through __subclasses__()
691 # so leave generic propagate flag False
692 event_key.with_dispatch_target(subject).listen(
693 raw=raw, propagate=False, retval=retval, **kw
694 )
695
696 def remove(self, event_key: _EventKey[_ET]) -> None:
697 target = event_key.dispatch_target
698
699 if isinstance(target, _EventsHold):
700 collection = target.all_holds[target.class_]
701 del collection[event_key._key]
702
703 @classmethod
704 def populate(
705 cls,
706 class_: Union[DeclarativeAttributeIntercept, DeclarativeMeta, type],
707 subject: Union[ClassManager[_O], Mapper[_O]],
708 ) -> None:
709 for subclass in class_.__mro__:
710 if subclass in cls.all_holds:
711 collection = cls.all_holds[subclass]
712 for (
713 event_key,
714 raw,
715 propagate,
716 retval,
717 kw,
718 ) in collection.values():
719 if propagate or subclass is class_:
720 # since we can't be sure in what order different
721 # classes in a hierarchy are triggered with
722 # populate(), we rely upon _EventsHold for all event
723 # assignment, instead of using the generic propagate
724 # flag.
725 event_key.with_dispatch_target(subject).listen(
726 raw=raw, propagate=False, retval=retval, **kw
727 )
728
729
730class _InstanceEventsHold(_EventsHold[_ET]):
731 all_holds: weakref.WeakKeyDictionary[Any, Any] = (
732 weakref.WeakKeyDictionary()
733 )
734
735 def resolve(self, class_: Type[_O]) -> Optional[ClassManager[_O]]:
736 return instrumentation.opt_manager_of_class(class_)
737
738 # this fails on pyright if you use Any. Fails on mypy if you use _ET
739 class HoldInstanceEvents(_EventsHold.HoldEvents[_ET], InstanceEvents): # type: ignore[valid-type,misc] # noqa: E501
740 pass
741
742 dispatch = event.dispatcher(HoldInstanceEvents)
743
744
745class MapperEvents(event.Events[mapperlib.Mapper[Any]]):
746 """Define events specific to mappings.
747
748 e.g.::
749
750 from sqlalchemy import event
751
752
753 def my_before_insert_listener(mapper, connection, target):
754 # execute a stored procedure upon INSERT,
755 # apply the value to the row to be inserted
756 target.calculated_value = connection.execute(
757 text("select my_special_function(%d)" % target.special_number)
758 ).scalar()
759
760
761 # associate the listener function with SomeClass,
762 # to execute during the "before_insert" hook
763 event.listen(SomeClass, "before_insert", my_before_insert_listener)
764
765 Available targets include:
766
767 * mapped classes
768 * unmapped superclasses of mapped or to-be-mapped classes
769 (using the ``propagate=True`` flag)
770 * :class:`_orm.Mapper` objects
771 * the :class:`_orm.Mapper` class itself indicates listening for all
772 mappers.
773
774 Mapper events provide hooks into critical sections of the
775 mapper, including those related to object instrumentation,
776 object loading, and object persistence. In particular, the
777 persistence methods :meth:`~.MapperEvents.before_insert`,
778 and :meth:`~.MapperEvents.before_update` are popular
779 places to augment the state being persisted - however, these
780 methods operate with several significant restrictions. The
781 user is encouraged to evaluate the
782 :meth:`.SessionEvents.before_flush` and
783 :meth:`.SessionEvents.after_flush` methods as more
784 flexible and user-friendly hooks in which to apply
785 additional database state during a flush.
786
787 When using :class:`.MapperEvents`, several modifiers are
788 available to the :func:`.event.listen` function.
789
790 :param propagate=False: When True, the event listener should
791 be applied to all inheriting mappers and/or the mappers of
792 inheriting classes, as well as any
793 mapper which is the target of this listener.
794 :param raw=False: When True, the "target" argument passed
795 to applicable event listener functions will be the
796 instance's :class:`.InstanceState` management
797 object, rather than the mapped instance itself.
798 :param retval=False: when True, the user-defined event function
799 must have a return value, the purpose of which is either to
800 control subsequent event propagation, or to otherwise alter
801 the operation in progress by the mapper. Possible return
802 values are:
803
804 * ``sqlalchemy.orm.interfaces.EXT_CONTINUE`` - continue event
805 processing normally.
806 * ``sqlalchemy.orm.interfaces.EXT_STOP`` - cancel all subsequent
807 event handlers in the chain.
808 * other values - the return value specified by specific listeners.
809
810 """
811
812 _target_class_doc = "SomeClass"
813 _dispatch_target = mapperlib.Mapper
814
815 @classmethod
816 def _new_mapper_instance(
817 cls,
818 class_: Union[DeclarativeAttributeIntercept, DeclarativeMeta, type],
819 mapper: Mapper[_O],
820 ) -> None:
821 _MapperEventsHold.populate(class_, mapper)
822
823 @classmethod
824 @util.preload_module("sqlalchemy.orm")
825 def _accept_with(
826 cls,
827 target: Union[mapperlib.Mapper[Any], Type[mapperlib.Mapper[Any]]],
828 identifier: str,
829 ) -> Optional[Union[mapperlib.Mapper[Any], Type[mapperlib.Mapper[Any]]]]:
830 orm = util.preloaded.orm
831
832 if target is orm.mapper: # type: ignore [attr-defined]
833 util.warn_deprecated(
834 "The `sqlalchemy.orm.mapper()` symbol is deprecated and "
835 "will be removed in a future release. For the mapper-wide "
836 "event target, use the 'sqlalchemy.orm.Mapper' class.",
837 "2.0",
838 )
839 return mapperlib.Mapper
840 elif isinstance(target, type):
841 if issubclass(target, mapperlib.Mapper):
842 return target
843 else:
844 mapper = _mapper_or_none(target)
845 if mapper is not None:
846 return mapper
847 else:
848 return _MapperEventsHold(target)
849 else:
850 return target
851
852 @classmethod
853 def _listen(
854 cls,
855 event_key: _EventKey[_ET],
856 raw: bool = False,
857 retval: bool = False,
858 propagate: bool = False,
859 **kw: Any,
860 ) -> None:
861 target, identifier, fn = (
862 event_key.dispatch_target,
863 event_key.identifier,
864 event_key._listen_fn,
865 )
866
867 if (
868 identifier in ("before_configured", "after_configured")
869 and target is not mapperlib.Mapper
870 ):
871 util.warn(
872 "'before_configured' and 'after_configured' ORM events "
873 "only invoke with the Mapper class "
874 "as the target."
875 )
876
877 if not raw or not retval:
878 if not raw:
879 meth = getattr(cls, identifier)
880 try:
881 target_index = (
882 inspect_getfullargspec(meth)[0].index("target") - 1
883 )
884 except ValueError:
885 target_index = None
886
887 def wrap(*arg: Any, **kw: Any) -> Any:
888 if not raw and target_index is not None:
889 arg = list(arg) # type: ignore [assignment]
890 arg[target_index] = arg[target_index].obj() # type: ignore [index] # noqa: E501
891 if not retval:
892 fn(*arg, **kw)
893 return interfaces.EXT_CONTINUE
894 else:
895 return fn(*arg, **kw)
896
897 event_key = event_key.with_wrapper(wrap)
898
899 if propagate:
900 for mapper in target.self_and_descendants:
901 event_key.with_dispatch_target(mapper).base_listen(
902 propagate=True, **kw
903 )
904 else:
905 event_key.base_listen(**kw)
906
907 @classmethod
908 def _clear(cls) -> None:
909 super()._clear()
910 _MapperEventsHold._clear()
911
912 def instrument_class(self, mapper: Mapper[_O], class_: Type[_O]) -> None:
913 r"""Receive a class when the mapper is first constructed,
914 before instrumentation is applied to the mapped class.
915
916 This event is the earliest phase of mapper construction.
917 Most attributes of the mapper are not yet initialized. To
918 receive an event within initial mapper construction where basic
919 state is available such as the :attr:`_orm.Mapper.attrs` collection,
920 the :meth:`_orm.MapperEvents.after_mapper_constructed` event may
921 be a better choice.
922
923 This listener can either be applied to the :class:`_orm.Mapper`
924 class overall, or to any un-mapped class which serves as a base
925 for classes that will be mapped (using the ``propagate=True`` flag)::
926
927 Base = declarative_base()
928
929
930 @event.listens_for(Base, "instrument_class", propagate=True)
931 def on_new_class(mapper, cls_):
932 "..."
933
934 :param mapper: the :class:`_orm.Mapper` which is the target
935 of this event.
936 :param class\_: the mapped class.
937
938 .. seealso::
939
940 :meth:`_orm.MapperEvents.after_mapper_constructed`
941
942 """
943
944 def after_mapper_constructed(
945 self, mapper: Mapper[_O], class_: Type[_O]
946 ) -> None:
947 """Receive a class and mapper when the :class:`_orm.Mapper` has been
948 fully constructed.
949
950 This event is called after the initial constructor for
951 :class:`_orm.Mapper` completes. This occurs after the
952 :meth:`_orm.MapperEvents.instrument_class` event and after the
953 :class:`_orm.Mapper` has done an initial pass of its arguments
954 to generate its collection of :class:`_orm.MapperProperty` objects,
955 which are accessible via the :meth:`_orm.Mapper.get_property`
956 method and the :attr:`_orm.Mapper.iterate_properties` attribute.
957
958 This event differs from the
959 :meth:`_orm.MapperEvents.before_mapper_configured` event in that it
960 is invoked within the constructor for :class:`_orm.Mapper`, rather
961 than within the :meth:`_orm.registry.configure` process. Currently,
962 this event is the only one which is appropriate for handlers that
963 wish to create additional mapped classes in response to the
964 construction of this :class:`_orm.Mapper`, which will be part of the
965 same configure step when :meth:`_orm.registry.configure` next runs.
966
967 .. versionadded:: 2.0.2
968
969 .. seealso::
970
971 :ref:`examples_versioning` - an example which illustrates the use
972 of the :meth:`_orm.MapperEvents.before_mapper_configured`
973 event to create new mappers to record change-audit histories on
974 objects.
975
976 """
977
978 @event._omit_standard_example
979 def before_mapper_configured(
980 self, mapper: Mapper[_O], class_: Type[_O]
981 ) -> None:
982 """Called right before a specific mapper is to be configured.
983
984 The :meth:`.MapperEvents.before_mapper_configured` event is invoked
985 for each mapper that is encountered when the
986 :func:`_orm.configure_mappers` function proceeds through the current
987 list of not-yet-configured mappers. It is similar to the
988 :meth:`.MapperEvents.mapper_configured` event, except that it's invoked
989 right before the configuration occurs, rather than afterwards.
990
991 The :meth:`.MapperEvents.before_mapper_configured` event includes
992 the special capability where it can force the configure step for a
993 specific mapper to be skipped; to use this feature, establish
994 the event using the ``retval=True`` parameter and return
995 the :attr:`.orm.interfaces.EXT_SKIP` symbol to indicate the mapper
996 should be left unconfigured::
997
998 from sqlalchemy import event
999 from sqlalchemy.orm import EXT_SKIP
1000 from sqlalchemy.orm import DeclarativeBase
1001
1002
1003 class DontConfigureBase(DeclarativeBase):
1004 pass
1005
1006
1007 @event.listens_for(
1008 DontConfigureBase,
1009 "before_mapper_configured",
1010 # support return values for the event
1011 retval=True,
1012 # propagate the listener to all subclasses of
1013 # DontConfigureBase
1014 propagate=True,
1015 )
1016 def dont_configure(mapper, cls):
1017 return EXT_SKIP
1018
1019 .. seealso::
1020
1021 :meth:`.MapperEvents.before_configured`
1022
1023 :meth:`.MapperEvents.after_configured`
1024
1025 :meth:`.MapperEvents.mapper_configured`
1026
1027 """
1028
1029 def mapper_configured(self, mapper: Mapper[_O], class_: Type[_O]) -> None:
1030 r"""Called when a specific mapper has completed its own configuration
1031 within the scope of the :func:`.configure_mappers` call.
1032
1033 The :meth:`.MapperEvents.mapper_configured` event is invoked
1034 for each mapper that is encountered when the
1035 :func:`_orm.configure_mappers` function proceeds through the current
1036 list of not-yet-configured mappers.
1037 :func:`_orm.configure_mappers` is typically invoked
1038 automatically as mappings are first used, as well as each time
1039 new mappers have been made available and new mapper use is
1040 detected.
1041
1042 When the event is called, the mapper should be in its final
1043 state, but **not including backrefs** that may be invoked from
1044 other mappers; they might still be pending within the
1045 configuration operation. Bidirectional relationships that
1046 are instead configured via the
1047 :paramref:`.orm.relationship.back_populates` argument
1048 *will* be fully available, since this style of relationship does not
1049 rely upon other possibly-not-configured mappers to know that they
1050 exist.
1051
1052 For an event that is guaranteed to have **all** mappers ready
1053 to go including backrefs that are defined only on other
1054 mappings, use the :meth:`.MapperEvents.after_configured`
1055 event; this event invokes only after all known mappings have been
1056 fully configured.
1057
1058 The :meth:`.MapperEvents.mapper_configured` event, unlike the
1059 :meth:`.MapperEvents.before_configured` or
1060 :meth:`.MapperEvents.after_configured` events, is called for each
1061 mapper/class individually, and the mapper is passed to the event
1062 itself. It also is called exactly once for a particular mapper. The
1063 event is therefore useful for configurational steps that benefit from
1064 being invoked just once on a specific mapper basis, which don't require
1065 that "backref" configurations are necessarily ready yet.
1066
1067 :param mapper: the :class:`_orm.Mapper` which is the target
1068 of this event.
1069 :param class\_: the mapped class.
1070
1071 .. seealso::
1072
1073 :meth:`.MapperEvents.before_configured`
1074
1075 :meth:`.MapperEvents.after_configured`
1076
1077 :meth:`.MapperEvents.before_mapper_configured`
1078
1079 """
1080 # TODO: need coverage for this event
1081
1082 @event._omit_standard_example
1083 def before_configured(self) -> None:
1084 """Called before a series of mappers have been configured.
1085
1086 The :meth:`.MapperEvents.before_configured` event is invoked
1087 each time the :func:`_orm.configure_mappers` function is
1088 invoked, before the function has done any of its work.
1089 :func:`_orm.configure_mappers` is typically invoked
1090 automatically as mappings are first used, as well as each time
1091 new mappers have been made available and new mapper use is
1092 detected.
1093
1094 Similar events to this one include
1095 :meth:`.MapperEvents.after_configured`, which is invoked after a series
1096 of mappers has been configured, as well as
1097 :meth:`.MapperEvents.before_mapper_configured` and
1098 :meth:`.MapperEvents.mapper_configured`, which are both invoked on a
1099 per-mapper basis.
1100
1101 This event can **only** be applied to the :class:`_orm.Mapper` class,
1102 and not to individual mappings or mapped classes::
1103
1104 from sqlalchemy.orm import Mapper
1105
1106
1107 @event.listens_for(Mapper, "before_configured")
1108 def go(): ...
1109
1110 Typically, this event is called once per application, but in practice
1111 may be called more than once, any time new mappers are to be affected
1112 by a :func:`_orm.configure_mappers` call. If new mappings are
1113 constructed after existing ones have already been used, this event will
1114 likely be called again.
1115
1116 .. seealso::
1117
1118 :meth:`.MapperEvents.before_mapper_configured`
1119
1120 :meth:`.MapperEvents.mapper_configured`
1121
1122 :meth:`.MapperEvents.after_configured`
1123
1124 """
1125
1126 @event._omit_standard_example
1127 def after_configured(self) -> None:
1128 """Called after a series of mappers have been configured.
1129
1130 The :meth:`.MapperEvents.after_configured` event is invoked
1131 each time the :func:`_orm.configure_mappers` function is
1132 invoked, after the function has completed its work.
1133 :func:`_orm.configure_mappers` is typically invoked
1134 automatically as mappings are first used, as well as each time
1135 new mappers have been made available and new mapper use is
1136 detected.
1137
1138 Similar events to this one include
1139 :meth:`.MapperEvents.before_configured`, which is invoked before a
1140 series of mappers are configured, as well as
1141 :meth:`.MapperEvents.before_mapper_configured` and
1142 :meth:`.MapperEvents.mapper_configured`, which are both invoked on a
1143 per-mapper basis.
1144
1145 This event can **only** be applied to the :class:`_orm.Mapper` class,
1146 and not to individual mappings or mapped classes::
1147
1148 from sqlalchemy.orm import Mapper
1149
1150
1151 @event.listens_for(Mapper, "after_configured")
1152 def go(): ...
1153
1154 Typically, this event is called once per application, but in practice
1155 may be called more than once, any time new mappers are to be affected
1156 by a :func:`_orm.configure_mappers` call. If new mappings are
1157 constructed after existing ones have already been used, this event will
1158 likely be called again.
1159
1160 .. seealso::
1161
1162 :meth:`.MapperEvents.before_mapper_configured`
1163
1164 :meth:`.MapperEvents.mapper_configured`
1165
1166 :meth:`.MapperEvents.before_configured`
1167
1168 """
1169
1170 def before_insert(
1171 self, mapper: Mapper[_O], connection: Connection, target: _O
1172 ) -> None:
1173 """Receive an object instance before an INSERT statement
1174 is emitted corresponding to that instance.
1175
1176 .. note:: this event **only** applies to the
1177 :ref:`session flush operation <session_flushing>`
1178 and does **not** apply to the ORM DML operations described at
1179 :ref:`orm_expression_update_delete`. To intercept ORM
1180 DML events, use :meth:`_orm.SessionEvents.do_orm_execute`.
1181
1182 This event is used to modify local, non-object related
1183 attributes on the instance before an INSERT occurs, as well
1184 as to emit additional SQL statements on the given
1185 connection.
1186
1187 The event is often called for a batch of objects of the
1188 same class before their INSERT statements are emitted at
1189 once in a later step. In the extremely rare case that
1190 this is not desirable, the :class:`_orm.Mapper` object can be
1191 configured with ``batch=False``, which will cause
1192 batches of instances to be broken up into individual
1193 (and more poorly performing) event->persist->event
1194 steps.
1195
1196 .. warning::
1197
1198 Mapper-level flush events only allow **very limited operations**,
1199 on attributes local to the row being operated upon only,
1200 as well as allowing any SQL to be emitted on the given
