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