| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269 |
- # orm/events.py
- # Copyright (C) 2005-2025 the SQLAlchemy authors and contributors
- # <see AUTHORS file>
- #
- # This module is part of SQLAlchemy and is released under
- # the MIT License: https://www.opensource.org/licenses/mit-license.php
- """ORM event interfaces."""
- from __future__ import annotations
- from typing import Any
- from typing import Callable
- from typing import Collection
- from typing import Dict
- from typing import Generic
- from typing import Iterable
- from typing import Optional
- from typing import Sequence
- from typing import Set
- from typing import Type
- from typing import TYPE_CHECKING
- from typing import TypeVar
- from typing import Union
- import weakref
- from . import instrumentation
- from . import interfaces
- from . import mapperlib
- from .attributes import QueryableAttribute
- from .base import _mapper_or_none
- from .base import NO_KEY
- from .instrumentation import ClassManager
- from .instrumentation import InstrumentationFactory
- from .query import BulkDelete
- from .query import BulkUpdate
- from .query import Query
- from .scoping import scoped_session
- from .session import Session
- from .session import sessionmaker
- from .. import event
- from .. import exc
- from .. import util
- from ..event import EventTarget
- from ..event.registry import _ET
- from ..util.compat import inspect_getfullargspec
- if TYPE_CHECKING:
- from weakref import ReferenceType
- from ._typing import _InstanceDict
- from ._typing import _InternalEntityType
- from ._typing import _O
- from ._typing import _T
- from .attributes import Event
- from .base import EventConstants
- from .session import ORMExecuteState
- from .session import SessionTransaction
- from .unitofwork import UOWTransaction
- from ..engine import Connection
- from ..event.base import _Dispatch
- from ..event.base import _HasEventsDispatch
- from ..event.registry import _EventKey
- from ..orm.collections import CollectionAdapter
- from ..orm.context import QueryContext
- from ..orm.decl_api import DeclarativeAttributeIntercept
- from ..orm.decl_api import DeclarativeMeta
- from ..orm.mapper import Mapper
- from ..orm.state import InstanceState
- _KT = TypeVar("_KT", bound=Any)
- _ET2 = TypeVar("_ET2", bound=EventTarget)
- class InstrumentationEvents(event.Events[InstrumentationFactory]):
- """Events related to class instrumentation events.
- The listeners here support being established against
- any new style class, that is any object that is a subclass
- of 'type'. Events will then be fired off for events
- against that class. If the "propagate=True" flag is passed
- to event.listen(), the event will fire off for subclasses
- of that class as well.
- The Python ``type`` builtin is also accepted as a target,
- which when used has the effect of events being emitted
- for all classes.
- Note the "propagate" flag here is defaulted to ``True``,
- unlike the other class level events where it defaults
- to ``False``. This means that new subclasses will also
- be the subject of these events, when a listener
- is established on a superclass.
- """
- _target_class_doc = "SomeBaseClass"
- _dispatch_target = InstrumentationFactory
- @classmethod
- def _accept_with(
- cls,
- target: Union[
- InstrumentationFactory,
- Type[InstrumentationFactory],
- ],
- identifier: str,
- ) -> Optional[
- Union[
- InstrumentationFactory,
- Type[InstrumentationFactory],
- ]
- ]:
- if isinstance(target, type):
- return _InstrumentationEventsHold(target) # type: ignore [return-value] # noqa: E501
- else:
- return None
- @classmethod
- def _listen(
- cls, event_key: _EventKey[_T], propagate: bool = True, **kw: Any
- ) -> None:
- target, identifier, fn = (
- event_key.dispatch_target,
- event_key.identifier,
- event_key._listen_fn,
- )
- def listen(target_cls: type, *arg: Any) -> Optional[Any]:
- listen_cls = target()
- # if weakref were collected, however this is not something
- # that normally happens. it was occurring during test teardown
- # between mapper/registry/instrumentation_manager, however this
- # interaction was changed to not rely upon the event system.
- if listen_cls is None:
- return None
- if propagate and issubclass(target_cls, listen_cls):
- return fn(target_cls, *arg)
- elif not propagate and target_cls is listen_cls:
- return fn(target_cls, *arg)
- else:
- return None
- def remove(ref: ReferenceType[_T]) -> None:
- key = event.registry._EventKey( # type: ignore [type-var]
- None,
- identifier,
- listen,
- instrumentation._instrumentation_factory,
- )
- getattr(
- instrumentation._instrumentation_factory.dispatch, identifier
- ).remove(key)
- target = weakref.ref(target.class_, remove)
- event_key.with_dispatch_target(
- instrumentation._instrumentation_factory
- ).with_wrapper(listen).base_listen(**kw)
- @classmethod
- def _clear(cls) -> None:
- super()._clear()
- instrumentation._instrumentation_factory.dispatch._clear()
- def class_instrument(self, cls: ClassManager[_O]) -> None:
- """Called after the given class is instrumented.
- To get at the :class:`.ClassManager`, use
- :func:`.manager_of_class`.
- """
- def class_uninstrument(self, cls: ClassManager[_O]) -> None:
- """Called before the given class is uninstrumented.
- To get at the :class:`.ClassManager`, use
- :func:`.manager_of_class`.
- """
- def attribute_instrument(
- self, cls: ClassManager[_O], key: _KT, inst: _O
- ) -> None:
- """Called when an attribute is instrumented."""
- class _InstrumentationEventsHold:
- """temporary marker object used to transfer from _accept_with() to
- _listen() on the InstrumentationEvents class.
- """
- def __init__(self, class_: type) -> None:
- self.class_ = class_
- dispatch = event.dispatcher(InstrumentationEvents)
- class InstanceEvents(event.Events[ClassManager[Any]]):
- """Define events specific to object lifecycle.
- e.g.::
- from sqlalchemy import event
- def my_load_listener(target, context):
- print("on load!")
- event.listen(SomeClass, "load", my_load_listener)
- Available targets include:
- * mapped classes
- * unmapped superclasses of mapped or to-be-mapped classes
- (using the ``propagate=True`` flag)
- * :class:`_orm.Mapper` objects
- * the :class:`_orm.Mapper` class itself indicates listening for all
- mappers.
- Instance events are closely related to mapper events, but
- are more specific to the instance and its instrumentation,
- rather than its system of persistence.
- When using :class:`.InstanceEvents`, several modifiers are
- available to the :func:`.event.listen` function.
- :param propagate=False: When True, the event listener should
- be applied to all inheriting classes as well as the
- class which is the target of this listener.
- :param raw=False: When True, the "target" argument passed
- to applicable event listener functions will be the
- instance's :class:`.InstanceState` management
- object, rather than the mapped instance itself.
- :param restore_load_context=False: Applies to the
- :meth:`.InstanceEvents.load` and :meth:`.InstanceEvents.refresh`
- events. Restores the loader context of the object when the event
- hook is complete, so that ongoing eager load operations continue
- to target the object appropriately. A warning is emitted if the
- object is moved to a new loader context from within one of these
- events if this flag is not set.
- .. versionadded:: 1.3.14
- """
- _target_class_doc = "SomeClass"
- _dispatch_target = ClassManager
- @classmethod
- def _new_classmanager_instance(
- cls,
- class_: Union[DeclarativeAttributeIntercept, DeclarativeMeta, type],
- classmanager: ClassManager[_O],
- ) -> None:
- _InstanceEventsHold.populate(class_, classmanager)
- @classmethod
- @util.preload_module("sqlalchemy.orm")
- def _accept_with(
- cls,
- target: Union[
- ClassManager[Any],
- Type[ClassManager[Any]],
- ],
- identifier: str,
- ) -> Optional[Union[ClassManager[Any], Type[ClassManager[Any]]]]:
- orm = util.preloaded.orm
- if isinstance(target, ClassManager):
- return target
- elif isinstance(target, mapperlib.Mapper):
- return target.class_manager
- elif target is orm.mapper: # type: ignore [attr-defined]
- util.warn_deprecated(
- "The `sqlalchemy.orm.mapper()` symbol is deprecated and "
- "will be removed in a future release. For the mapper-wide "
- "event target, use the 'sqlalchemy.orm.Mapper' class.",
- "2.0",
- )
- return ClassManager
- elif isinstance(target, type):
- if issubclass(target, mapperlib.Mapper):
- return ClassManager
- else:
- manager = instrumentation.opt_manager_of_class(target)
- if manager:
- return manager
- else:
- return _InstanceEventsHold(target) # type: ignore [return-value] # noqa: E501
- return None
- @classmethod
- def _listen(
- cls,
- event_key: _EventKey[ClassManager[Any]],
- raw: bool = False,
- propagate: bool = False,
- restore_load_context: bool = False,
- **kw: Any,
- ) -> None:
- target, fn = (event_key.dispatch_target, event_key._listen_fn)
- if not raw or restore_load_context:
- def wrap(
- state: InstanceState[_O], *arg: Any, **kw: Any
- ) -> Optional[Any]:
- if not raw:
- target: Any = state.obj()
- else:
- target = state
- if restore_load_context:
- runid = state.runid
- try:
- return fn(target, *arg, **kw)
- finally:
- if restore_load_context:
- state.runid = runid
- event_key = event_key.with_wrapper(wrap)
- event_key.base_listen(propagate=propagate, **kw)
- if propagate:
- for mgr in target.subclass_managers(True):
- event_key.with_dispatch_target(mgr).base_listen(propagate=True)
- @classmethod
- def _clear(cls) -> None:
- super()._clear()
- _InstanceEventsHold._clear()
- def first_init(self, manager: ClassManager[_O], cls: Type[_O]) -> None:
- """Called when the first instance of a particular mapping is called.
- This event is called when the ``__init__`` method of a class
- is called the first time for that particular class. The event
- invokes before ``__init__`` actually proceeds as well as before
- the :meth:`.InstanceEvents.init` event is invoked.
- """
- def init(self, target: _O, args: Any, kwargs: Any) -> None:
- """Receive an instance when its constructor is called.
- This method is only called during a userland construction of
- an object, in conjunction with the object's constructor, e.g.
- its ``__init__`` method. It is not called when an object is
- loaded from the database; see the :meth:`.InstanceEvents.load`
- event in order to intercept a database load.
- The event is called before the actual ``__init__`` constructor
- of the object is called. The ``kwargs`` dictionary may be
- modified in-place in order to affect what is passed to
- ``__init__``.
- :param target: the mapped instance. If
- the event is configured with ``raw=True``, this will
- instead be the :class:`.InstanceState` state-management
- object associated with the instance.
- :param args: positional arguments passed to the ``__init__`` method.
- This is passed as a tuple and is currently immutable.
- :param kwargs: keyword arguments passed to the ``__init__`` method.
- This structure *can* be altered in place.
- .. seealso::
- :meth:`.InstanceEvents.init_failure`
- :meth:`.InstanceEvents.load`
- """
- def init_failure(self, target: _O, args: Any, kwargs: Any) -> None:
- """Receive an instance when its constructor has been called,
- and raised an exception.
- This method is only called during a userland construction of
- an object, in conjunction with the object's constructor, e.g.
- its ``__init__`` method. It is not called when an object is loaded
- from the database.
- The event is invoked after an exception raised by the ``__init__``
- method is caught. After the event
- is invoked, the original exception is re-raised outwards, so that
- the construction of the object still raises an exception. The
- actual exception and stack trace raised should be present in
- ``sys.exc_info()``.
- :param target: the mapped instance. If
- the event is configured with ``raw=True``, this will
- instead be the :class:`.InstanceState` state-management
- object associated with the instance.
- :param args: positional arguments that were passed to the ``__init__``
- method.
- :param kwargs: keyword arguments that were passed to the ``__init__``
- method.
- .. seealso::
- :meth:`.InstanceEvents.init`
- :meth:`.InstanceEvents.load`
- """
- def _sa_event_merge_wo_load(
- self, target: _O, context: QueryContext
- ) -> None:
- """receive an object instance after it was the subject of a merge()
- call, when load=False was passed.
- The target would be the already-loaded object in the Session which
- would have had its attributes overwritten by the incoming object. This
- overwrite operation does not use attribute events, instead just
- populating dict directly. Therefore the purpose of this event is so
- that extensions like sqlalchemy.ext.mutable know that object state has
- changed and incoming state needs to be set up for "parents" etc.
- This functionality is acceptable to be made public in a later release.
- .. versionadded:: 1.4.41
- """
- def load(self, target: _O, context: QueryContext) -> None:
- """Receive an object instance after it has been created via
- ``__new__``, and after initial attribute population has
- occurred.
- This typically occurs when the instance is created based on
- incoming result rows, and is only called once for that
- instance's lifetime.
- .. warning::
- During a result-row load, this event is invoked when the
- first row received for this instance is processed. When using
- eager loading with collection-oriented attributes, the additional
- rows that are to be loaded / processed in order to load subsequent
- collection items have not occurred yet. This has the effect
- both that collections will not be fully loaded, as well as that
- if an operation occurs within this event handler that emits
- another database load operation for the object, the "loading
- context" for the object can change and interfere with the
- existing eager loaders still in progress.
- Examples of what can cause the "loading context" to change within
- the event handler include, but are not necessarily limited to:
- * accessing deferred attributes that weren't part of the row,
- will trigger an "undefer" operation and refresh the object
- * accessing attributes on a joined-inheritance subclass that
- weren't part of the row, will trigger a refresh operation.
- As of SQLAlchemy 1.3.14, a warning is emitted when this occurs. The
- :paramref:`.InstanceEvents.restore_load_context` option may be
- used on the event to prevent this warning; this will ensure that
- the existing loading context is maintained for the object after the
- event is called::
- @event.listens_for(SomeClass, "load", restore_load_context=True)
- def on_load(instance, context):
- instance.some_unloaded_attribute
- .. versionchanged:: 1.3.14 Added
- :paramref:`.InstanceEvents.restore_load_context`
- and :paramref:`.SessionEvents.restore_load_context` flags which
- apply to "on load" events, which will ensure that the loading
- context for an object is restored when the event hook is
- complete; a warning is emitted if the load context of the object
- changes without this flag being set.
- The :meth:`.InstanceEvents.load` event is also available in a
- class-method decorator format called :func:`_orm.reconstructor`.
- :param target: the mapped instance. If
- the event is configured with ``raw=True``, this will
- instead be the :class:`.InstanceState` state-management
- object associated with the instance.
- :param context: the :class:`.QueryContext` corresponding to the
- current :class:`_query.Query` in progress. This argument may be
- ``None`` if the load does not correspond to a :class:`_query.Query`,
- such as during :meth:`.Session.merge`.
- .. seealso::
- :ref:`mapped_class_load_events`
- :meth:`.InstanceEvents.init`
- :meth:`.InstanceEvents.refresh`
- :meth:`.SessionEvents.loaded_as_persistent`
- """ # noqa: E501
- def refresh(
- self, target: _O, context: QueryContext, attrs: Optional[Iterable[str]]
- ) -> None:
- """Receive an object instance after one or more attributes have
- been refreshed from a query.
- Contrast this to the :meth:`.InstanceEvents.load` method, which
- is invoked when the object is first loaded from a query.
- .. note:: This event is invoked within the loader process before
- eager loaders may have been completed, and the object's state may
- not be complete. Additionally, invoking row-level refresh
- operations on the object will place the object into a new loader
- context, interfering with the existing load context. See the note
- on :meth:`.InstanceEvents.load` for background on making use of the
- :paramref:`.InstanceEvents.restore_load_context` parameter, in
- order to resolve this scenario.
- :param target: the mapped instance. If
- the event is configured with ``raw=True``, this will
- instead be the :class:`.InstanceState` state-management
- object associated with the instance.
- :param context: the :class:`.QueryContext` corresponding to the
- current :class:`_query.Query` in progress.
- :param attrs: sequence of attribute names which
- were populated, or None if all column-mapped, non-deferred
- attributes were populated.
- .. seealso::
- :ref:`mapped_class_load_events`
- :meth:`.InstanceEvents.load`
- """
- def refresh_flush(
- self,
- target: _O,
- flush_context: UOWTransaction,
- attrs: Optional[Iterable[str]],
- ) -> None:
- """Receive an object instance after one or more attributes that
- contain a column-level default or onupdate handler have been refreshed
- during persistence of the object's state.
- This event is the same as :meth:`.InstanceEvents.refresh` except
- it is invoked within the unit of work flush process, and includes
- only non-primary-key columns that have column level default or
- onupdate handlers, including Python callables as well as server side
- defaults and triggers which may be fetched via the RETURNING clause.
- .. note::
- While the :meth:`.InstanceEvents.refresh_flush` event is triggered
- for an object that was INSERTed as well as for an object that was
- UPDATEd, the event is geared primarily towards the UPDATE process;
- it is mostly an internal artifact that INSERT actions can also
- trigger this event, and note that **primary key columns for an
- INSERTed row are explicitly omitted** from this event. In order to
- intercept the newly INSERTed state of an object, the
- :meth:`.SessionEvents.pending_to_persistent` and
- :meth:`.MapperEvents.after_insert` are better choices.
- :param target: the mapped instance. If
- the event is configured with ``raw=True``, this will
- instead be the :class:`.InstanceState` state-management
- object associated with the instance.
- :param flush_context: Internal :class:`.UOWTransaction` object
- which handles the details of the flush.
- :param attrs: sequence of attribute names which
- were populated.
- .. seealso::
- :ref:`mapped_class_load_events`
- :ref:`orm_server_defaults`
- :ref:`metadata_defaults_toplevel`
- """
- def expire(self, target: _O, attrs: Optional[Iterable[str]]) -> None:
- """Receive an object instance after its attributes or some subset
- have been expired.
- 'keys' is a list of attribute names. If None, the entire
- state was expired.
- :param target: the mapped instance. If
- the event is configured with ``raw=True``, this will
- instead be the :class:`.InstanceState` state-management
- object associated with the instance.
- :param attrs: sequence of attribute
- names which were expired, or None if all attributes were
- expired.
- """
- def pickle(self, target: _O, state_dict: _InstanceDict) -> None:
- """Receive an object instance when its associated state is
- being pickled.
- :param target: the mapped instance. If
- the event is configured with ``raw=True``, this will
- instead be the :class:`.InstanceState` state-management
- object associated with the instance.
- :param state_dict: the dictionary returned by
- :class:`.InstanceState.__getstate__`, containing the state
- to be pickled.
- """
- def unpickle(self, target: _O, state_dict: _InstanceDict) -> None:
- """Receive an object instance after its associated state has
- been unpickled.
- :param target: the mapped instance. If
- the event is configured with ``raw=True``, this will
- instead be the :class:`.InstanceState` state-management
- object associated with the instance.
- :param state_dict: the dictionary sent to
- :class:`.InstanceState.__setstate__`, containing the state
- dictionary which was pickled.
- """
- class _EventsHold(event.RefCollection[_ET]):
- """Hold onto listeners against unmapped, uninstrumented classes.
- Establish _listen() for that class' mapper/instrumentation when
- those objects are created for that class.
- """
- all_holds: weakref.WeakKeyDictionary[Any, Any]
- def __init__(
- self,
- class_: Union[DeclarativeAttributeIntercept, DeclarativeMeta, type],
- ) -> None:
- self.class_ = class_
- @classmethod
- def _clear(cls) -> None:
- cls.all_holds.clear()
- class HoldEvents(Generic[_ET2]):
- _dispatch_target: Optional[Type[_ET2]] = None
- @classmethod
- def _listen(
- cls,
- event_key: _EventKey[_ET2],
- raw: bool = False,
- propagate: bool = False,
- retval: bool = False,
- **kw: Any,
- ) -> None:
- target = event_key.dispatch_target
- if target.class_ in target.all_holds:
- collection = target.all_holds[target.class_]
- else:
- collection = target.all_holds[target.class_] = {}
- event.registry._stored_in_collection(event_key, target)
- collection[event_key._key] = (
- event_key,
- raw,
- propagate,
- retval,
- kw,
- )
- if propagate:
- stack = list(target.class_.__subclasses__())
- while stack:
- subclass = stack.pop(0)
- stack.extend(subclass.__subclasses__())
- subject = target.resolve(subclass)
- if subject is not None:
- # we are already going through __subclasses__()
- # so leave generic propagate flag False
- event_key.with_dispatch_target(subject).listen(
- raw=raw, propagate=False, retval=retval, **kw
- )
- def remove(self, event_key: _EventKey[_ET]) -> None:
- target = event_key.dispatch_target
- if isinstance(target, _EventsHold):
- collection = target.all_holds[target.class_]
- del collection[event_key._key]
- @classmethod
- def populate(
- cls,
- class_: Union[DeclarativeAttributeIntercept, DeclarativeMeta, type],
- subject: Union[ClassManager[_O], Mapper[_O]],
- ) -> None:
- for subclass in class_.__mro__:
- if subclass in cls.all_holds:
- collection = cls.all_holds[subclass]
- for (
- event_key,
- raw,
- propagate,
- retval,
- kw,
- ) in collection.values():
- if propagate or subclass is class_:
- # since we can't be sure in what order different
- # classes in a hierarchy are triggered with
- # populate(), we rely upon _EventsHold for all event
- # assignment, instead of using the generic propagate
- # flag.
- event_key.with_dispatch_target(subject).listen(
- raw=raw, propagate=False, retval=retval, **kw
- )
- class _InstanceEventsHold(_EventsHold[_ET]):
- all_holds: weakref.WeakKeyDictionary[Any, Any] = (
- weakref.WeakKeyDictionary()
- )
- def resolve(self, class_: Type[_O]) -> Optional[ClassManager[_O]]:
- return instrumentation.opt_manager_of_class(class_)
- class HoldInstanceEvents(_EventsHold.HoldEvents[_ET], InstanceEvents): # type: ignore [misc] # noqa: E501
- pass
- dispatch = event.dispatcher(HoldInstanceEvents)
- class MapperEvents(event.Events[mapperlib.Mapper[Any]]):
- """Define events specific to mappings.
- e.g.::
- from sqlalchemy import event
- def my_before_insert_listener(mapper, connection, target):
- # execute a stored procedure upon INSERT,
- # apply the value to the row to be inserted
- target.calculated_value = connection.execute(
- text("select my_special_function(%d)" % target.special_number)
- ).scalar()
- # associate the listener function with SomeClass,
- # to execute during the "before_insert" hook
- event.listen(SomeClass, "before_insert", my_before_insert_listener)
- Available targets include:
- * mapped classes
- * unmapped superclasses of mapped or to-be-mapped classes
- (using the ``propagate=True`` flag)
- * :class:`_orm.Mapper` objects
- * the :class:`_orm.Mapper` class itself indicates listening for all
- mappers.
- Mapper events provide hooks into critical sections of the
- mapper, including those related to object instrumentation,
- object loading, and object persistence. In particular, the
- persistence methods :meth:`~.MapperEvents.before_insert`,
- and :meth:`~.MapperEvents.before_update` are popular
- places to augment the state being persisted - however, these
- methods operate with several significant restrictions. The
- user is encouraged to evaluate the
- :meth:`.SessionEvents.before_flush` and
- :meth:`.SessionEvents.after_flush` methods as more
- flexible and user-friendly hooks in which to apply
- additional database state during a flush.
- When using :class:`.MapperEvents`, several modifiers are
- available to the :func:`.event.listen` function.
- :param propagate=False: When True, the event listener should
- be applied to all inheriting mappers and/or the mappers of
- inheriting classes, as well as any
- mapper which is the target of this listener.
- :param raw=False: When True, the "target" argument passed
- to applicable event listener functions will be the
- instance's :class:`.InstanceState` management
- object, rather than the mapped instance itself.
- :param retval=False: when True, the user-defined event function
- must have a return value, the purpose of which is either to
- control subsequent event propagation, or to otherwise alter
- the operation in progress by the mapper. Possible return
- values are:
- * ``sqlalchemy.orm.interfaces.EXT_CONTINUE`` - continue event
- processing normally.
- * ``sqlalchemy.orm.interfaces.EXT_STOP`` - cancel all subsequent
- event handlers in the chain.
- * other values - the return value specified by specific listeners.
- """
- _target_class_doc = "SomeClass"
- _dispatch_target = mapperlib.Mapper
- @classmethod
- def _new_mapper_instance(
- cls,
- class_: Union[DeclarativeAttributeIntercept, DeclarativeMeta, type],
- mapper: Mapper[_O],
- ) -> None:
- _MapperEventsHold.populate(class_, mapper)
- @classmethod
- @util.preload_module("sqlalchemy.orm")
- def _accept_with(
- cls,
- target: Union[mapperlib.Mapper[Any], Type[mapperlib.Mapper[Any]]],
- identifier: str,
- ) -> Optional[Union[mapperlib.Mapper[Any], Type[mapperlib.Mapper[Any]]]]:
- orm = util.preloaded.orm
- if target is orm.mapper: # type: ignore [attr-defined]
- util.warn_deprecated(
- "The `sqlalchemy.orm.mapper()` symbol is deprecated and "
- "will be removed in a future release. For the mapper-wide "
- "event target, use the 'sqlalchemy.orm.Mapper' class.",
- "2.0",
- )
- return mapperlib.Mapper
- elif isinstance(target, type):
- if issubclass(target, mapperlib.Mapper):
- return target
- else:
- mapper = _mapper_or_none(target)
- if mapper is not None:
- return mapper
- else:
- return _MapperEventsHold(target)
- else:
- return target
- @classmethod
- def _listen(
- cls,
- event_key: _EventKey[_ET],
- raw: bool = False,
- retval: bool = False,
- propagate: bool = False,
- **kw: Any,
- ) -> None:
- target, identifier, fn = (
- event_key.dispatch_target,
- event_key.identifier,
- event_key._listen_fn,
- )
- if (
- identifier in ("before_configured", "after_configured")
- and target is not mapperlib.Mapper
- ):
- util.warn(
- "'before_configured' and 'after_configured' ORM events "
- "only invoke with the Mapper class "
- "as the target."
- )
- if not raw or not retval:
- if not raw:
- meth = getattr(cls, identifier)
- try:
- target_index = (
- inspect_getfullargspec(meth)[0].index("target") - 1
- )
- except ValueError:
- target_index = None
- def wrap(*arg: Any, **kw: Any) -> Any:
- if not raw and target_index is not None:
- arg = list(arg) # type: ignore [assignment]
- arg[target_index] = arg[target_index].obj() # type: ignore [index] # noqa: E501
- if not retval:
- fn(*arg, **kw)
- return interfaces.EXT_CONTINUE
- else:
- return fn(*arg, **kw)
- event_key = event_key.with_wrapper(wrap)
- if propagate:
- for mapper in target.self_and_descendants:
- event_key.with_dispatch_target(mapper).base_listen(
- propagate=True, **kw
- )
- else:
- event_key.base_listen(**kw)
- @classmethod
- def _clear(cls) -> None:
- super()._clear()
- _MapperEventsHold._clear()
- def instrument_class(self, mapper: Mapper[_O], class_: Type[_O]) -> None:
- r"""Receive a class when the mapper is first constructed,
- before instrumentation is applied to the mapped class.
- This event is the earliest phase of mapper construction.
- Most attributes of the mapper are not yet initialized. To
- receive an event within initial mapper construction where basic
- state is available such as the :attr:`_orm.Mapper.attrs` collection,
- the :meth:`_orm.MapperEvents.after_mapper_constructed` event may
- be a better choice.
- This listener can either be applied to the :class:`_orm.Mapper`
- class overall, or to any un-mapped class which serves as a base
- for classes that will be mapped (using the ``propagate=True`` flag)::
- Base = declarative_base()
- @event.listens_for(Base, "instrument_class", propagate=True)
- def on_new_class(mapper, cls_):
- "..."
- :param mapper: the :class:`_orm.Mapper` which is the target
- of this event.
- :param class\_: the mapped class.
- .. seealso::
- :meth:`_orm.MapperEvents.after_mapper_constructed`
- """
- def after_mapper_constructed(
- self, mapper: Mapper[_O], class_: Type[_O]
- ) -> None:
- """Receive a class and mapper when the :class:`_orm.Mapper` has been
- fully constructed.
- This event is called after the initial constructor for
- :class:`_orm.Mapper` completes. This occurs after the
- :meth:`_orm.MapperEvents.instrument_class` event and after the
- :class:`_orm.Mapper` has done an initial pass of its arguments
- to generate its collection of :class:`_orm.MapperProperty` objects,
- which are accessible via the :meth:`_orm.Mapper.get_property`
- method and the :attr:`_orm.Mapper.iterate_properties` attribute.
- This event differs from the
- :meth:`_orm.MapperEvents.before_mapper_configured` event in that it
- is invoked within the constructor for :class:`_orm.Mapper`, rather
- than within the :meth:`_orm.registry.configure` process. Currently,
- this event is the only one which is appropriate for handlers that
- wish to create additional mapped classes in response to the
- construction of this :class:`_orm.Mapper`, which will be part of the
- same configure step when :meth:`_orm.registry.configure` next runs.
- .. versionadded:: 2.0.2
- .. seealso::
- :ref:`examples_versioning` - an example which illustrates the use
- of the :meth:`_orm.MapperEvents.before_mapper_configured`
- event to create new mappers to record change-audit histories on
- objects.
- """
- def before_mapper_configured(
- self, mapper: Mapper[_O], class_: Type[_O]
- ) -> None:
- """Called right before a specific mapper is to be configured.
- This event is intended to allow a specific mapper to be skipped during
- the configure step, by returning the :attr:`.orm.interfaces.EXT_SKIP`
- symbol which indicates to the :func:`.configure_mappers` call that this
- particular mapper (or hierarchy of mappers, if ``propagate=True`` is
- used) should be skipped in the current configuration run. When one or
- more mappers are skipped, the "new mappers" flag will remain set,
- meaning the :func:`.configure_mappers` function will continue to be
- called when mappers are used, to continue to try to configure all
- available mappers.
- In comparison to the other configure-level events,
- :meth:`.MapperEvents.before_configured`,
- :meth:`.MapperEvents.after_configured`, and
- :meth:`.MapperEvents.mapper_configured`, the
- :meth:`.MapperEvents.before_mapper_configured` event provides for a
- meaningful return value when it is registered with the ``retval=True``
- parameter.
- .. versionadded:: 1.3
- e.g.::
- from sqlalchemy.orm import EXT_SKIP
- Base = declarative_base()
- DontConfigureBase = declarative_base()
- @event.listens_for(
- DontConfigureBase,
- "before_mapper_configured",
- retval=True,
- propagate=True,
- )
- def dont_configure(mapper, cls):
- return EXT_SKIP
- .. seealso::
- :meth:`.MapperEvents.before_configured`
- :meth:`.MapperEvents.after_configured`
- :meth:`.MapperEvents.mapper_configured`
- """
- def mapper_configured(self, mapper: Mapper[_O], class_: Type[_O]) -> None:
- r"""Called when a specific mapper has completed its own configuration
- within the scope of the :func:`.configure_mappers` call.
- The :meth:`.MapperEvents.mapper_configured` event is invoked
- for each mapper that is encountered when the
- :func:`_orm.configure_mappers` function proceeds through the current
- list of not-yet-configured mappers.
- :func:`_orm.configure_mappers` is typically invoked
- automatically as mappings are first used, as well as each time
- new mappers have been made available and new mapper use is
- detected.
- When the event is called, the mapper should be in its final
- state, but **not including backrefs** that may be invoked from
- other mappers; they might still be pending within the
- configuration operation. Bidirectional relationships that
- are instead configured via the
- :paramref:`.orm.relationship.back_populates` argument
- *will* be fully available, since this style of relationship does not
- rely upon other possibly-not-configured mappers to know that they
- exist.
- For an event that is guaranteed to have **all** mappers ready
- to go including backrefs that are defined only on other
- mappings, use the :meth:`.MapperEvents.after_configured`
- event; this event invokes only after all known mappings have been
- fully configured.
- The :meth:`.MapperEvents.mapper_configured` event, unlike
- :meth:`.MapperEvents.before_configured` or
- :meth:`.MapperEvents.after_configured`,
- is called for each mapper/class individually, and the mapper is
- passed to the event itself. It also is called exactly once for
- a particular mapper. The event is therefore useful for
- configurational steps that benefit from being invoked just once
- on a specific mapper basis, which don't require that "backref"
- configurations are necessarily ready yet.
- :param mapper: the :class:`_orm.Mapper` which is the target
- of this event.
- :param class\_: the mapped class.
- .. seealso::
- :meth:`.MapperEvents.before_configured`
- :meth:`.MapperEvents.after_configured`
- :meth:`.MapperEvents.before_mapper_configured`
- """
- # TODO: need coverage for this event
- def before_configured(self) -> None:
- """Called before a series of mappers have been configured.
- The :meth:`.MapperEvents.before_configured` event is invoked
- each time the :func:`_orm.configure_mappers` function is
- invoked, before the function has done any of its work.
- :func:`_orm.configure_mappers` is typically invoked
- automatically as mappings are first used, as well as each time
- new mappers have been made available and new mapper use is
- detected.
- This event can **only** be applied to the :class:`_orm.Mapper` class,
- and not to individual mappings or mapped classes. It is only invoked
- for all mappings as a whole::
- from sqlalchemy.orm import Mapper
- @event.listens_for(Mapper, "before_configured")
- def go(): ...
- Contrast this event to :meth:`.MapperEvents.after_configured`,
- which is invoked after the series of mappers has been configured,
- as well as :meth:`.MapperEvents.before_mapper_configured`
- and :meth:`.MapperEvents.mapper_configured`, which are both invoked
- on a per-mapper basis.
- Theoretically this event is called once per
- application, but is actually called any time new mappers
- are to be affected by a :func:`_orm.configure_mappers`
- call. If new mappings are constructed after existing ones have
- already been used, this event will likely be called again. To ensure
- that a particular event is only called once and no further, the
- ``once=True`` argument (new in 0.9.4) can be applied::
- from sqlalchemy.orm import mapper
- @event.listens_for(mapper, "before_configured", once=True)
- def go(): ...
- .. seealso::
- :meth:`.MapperEvents.before_mapper_configured`
- :meth:`.MapperEvents.mapper_configured`
- :meth:`.MapperEvents.after_configured`
- """
- def after_configured(self) -> None:
- """Called after a series of mappers have been configured.
- The :meth:`.MapperEvents.after_configured` event is invoked
- each time the :func:`_orm.configure_mappers` function is
- invoked, after the function has completed its work.
- :func:`_orm.configure_mappers` is typically invoked
- automatically as mappings are first used, as well as each time
- new mappers have been made available and new mapper use is
- detected.
- Contrast this event to the :meth:`.MapperEvents.mapper_configured`
- event, which is called on a per-mapper basis while the configuration
- operation proceeds; unlike that event, when this event is invoked,
- all cross-configurations (e.g. backrefs) will also have been made
- available for any mappers that were pending.
- Also contrast to :meth:`.MapperEvents.before_configured`,
- which is invoked before the series of mappers has been configured.
- This event can **only** be applied to the :class:`_orm.Mapper` class,
- and not to individual mappings or
- mapped classes. It is only invoked for all mappings as a whole::
- from sqlalchemy.orm import Mapper
- @event.listens_for(Mapper, "after_configured")
- def go(): ...
- Theoretically this event is called once per
- application, but is actually called any time new mappers
- have been affected by a :func:`_orm.configure_mappers`
- call. If new mappings are constructed after existing ones have
- already been used, this event will likely be called again. To ensure
- that a particular event is only called once and no further, the
- ``once=True`` argument (new in 0.9.4) can be applied::
- from sqlalchemy.orm import mapper
- @event.listens_for(mapper, "after_configured", once=True)
- def go(): ...
- .. seealso::
- :meth:`.MapperEvents.before_mapper_configured`
- :meth:`.MapperEvents.mapper_configured`
- :meth:`.MapperEvents.before_configured`
- """
- def before_insert(
- self, mapper: Mapper[_O], connection: Connection, target: _O
- ) -> None:
- """Receive an object instance before an INSERT statement
- is emitted corresponding to that instance.
- .. note:: this event **only** applies to the
- :ref:`session flush operation <session_flushing>`
- and does **not** apply to the ORM DML operations described at
- :ref:`orm_expression_update_delete`. To intercept ORM
- DML events, use :meth:`_orm.SessionEvents.do_orm_execute`.
- This event is used to modify local, non-object related
- attributes on the instance before an INSERT occurs, as well
- as to emit additional SQL statements on the given
- connection.
- The event is often called for a batch of objects of the
- same class before their INSERT statements are emitted at
- once in a later step. In the extremely rare case that
- this is not desirable, the :class:`_orm.Mapper` object can be
- configured with ``batch=False``, which will cause
- batches of instances to be broken up into individual
- (and more poorly performing) event->persist->event
- steps.
- .. warning::
- Mapper-level flush events only allow **very limited operations**,
- on attributes local to the row being operated upon only,
- as well as allowing any SQL to be emitted on the given
- :class:`_engine.Connection`. **Please read fully** the notes
- at :ref:`session_persistence_mapper` for guidelines on using
- these methods; generally, the :meth:`.SessionEvents.before_flush`
- method should be preferred for general on-flush changes.
- :param mapper: the :class:`_orm.Mapper` which is the target
- of this event.
- :param connection: the :class:`_engine.Connection` being used to
- emit INSERT statements for this instance. This
- provides a handle into the current transaction on the
- target database specific to this instance.
- :param target: the mapped instance being persisted. If
- the event is configured with ``raw=True``, this will
- instead be the :class:`.InstanceState` state-management
- object associated with the instance.
- :return: No return value is supported by this event.
- .. seealso::
- :ref:`session_persistence_events`
- """
- def after_insert(
- self, mapper: Mapper[_O], connection: Connection, target: _O
- ) -> None:
- """Receive an object instance after an INSERT statement
- is emitted corresponding to that instance.
- .. note:: this event **only** applies to the
- :ref:`session flush operation <session_flushing>`
- and does **not** apply to the ORM DML operations described at
- :ref:`orm_expression_update_delete`. To intercept ORM
- DML events, use :meth:`_orm.SessionEvents.do_orm_execute`.
- This event is used to modify in-Python-only
- state on the instance after an INSERT occurs, as well
- as to emit additional SQL statements on the given
- connection.
- The event is often called for a batch of objects of the
- same class after their INSERT statements have been
- emitted at once in a previous step. In the extremely
- rare case that this is not desirable, the
- :class:`_orm.Mapper` object can be configured with ``batch=False``,
- which will cause batches of instances to be broken up
- into individual (and more poorly performing)
- event->persist->event steps.
- .. warning::
- Mapper-level flush events only allow **very limited operations**,
- on attributes local to the row being operated upon only,
- as well as allowing any SQL to be emitted on the given
- :class:`_engine.Connection`. **Please read fully** the notes
- at :ref:`session_persistence_mapper` for guidelines on using
- these methods; generally, the :meth:`.SessionEvents.before_flush`
- method should be preferred for general on-flush changes.
- :param mapper: the :class:`_orm.Mapper` which is the target
- of this event.
- :param connection: the :class:`_engine.Connection` being used to
- emit INSERT statements for this instance. This
- provides a handle into the current transaction on the
- target database specific to this instance.
- :param target: the mapped instance being persisted. If
- the event is configured with ``raw=True``, this will
- instead be the :class:`.InstanceState` state-management
- object associated with the instance.
- :return: No return value is supported by this event.
- .. seealso::
- :ref:`session_persistence_events`
- """
- def before_update(
- self, mapper: Mapper[_O], connection: Connection, target: _O
- ) -> None:
- """Receive an object instance before an UPDATE statement
- is emitted corresponding to that instance.
- .. note:: this event **only** applies to the
- :ref:`session flush operation <session_flushing>`
- and does **not** apply to the ORM DML operations described at
- :ref:`orm_expression_update_delete`. To intercept ORM
- DML events, use :meth:`_orm.SessionEvents.do_orm_execute`.
- This event is used to modify local, non-object related
- attributes on the instance before an UPDATE occurs, as well
- as to emit additional SQL statements on the given
- connection.
- This method is called for all instances that are
- marked as "dirty", *even those which have no net changes
- to their column-based attributes*. An object is marked
- as dirty when any of its column-based attributes have a
- "set attribute" operation called or when any of its
- collections are modified. If, at update time, no
- column-based attributes have any net changes, no UPDATE
- statement will be issued. This means that an instance
- being sent to :meth:`~.MapperEvents.before_update` is
- *not* a guarantee that an UPDATE statement will be
- issued, although you can affect the outcome here by
- modifying attributes so that a net change in value does
- exist.
- To detect if the column-based attributes on the object have net
- changes, and will therefore generate an UPDATE statement, use
- ``object_session(instance).is_modified(instance,
- include_collections=False)``.
- The event is often called for a batch of objects of the
- same class before their UPDATE statements are emitted at
- once in a later step. In the extremely rare case that
- this is not desirable, the :class:`_orm.Mapper` can be
- configured with ``batch=False``, which will cause
- batches of instances to be broken up into individual
- (and more poorly performing) event->persist->event
- steps.
- .. warning::
- Mapper-level flush events only allow **very limited operations**,
- on attributes local to the row being operated upon only,
- as well as allowing any SQL to be emitted on the given
- :class:`_engine.Connection`. **Please read fully** the notes
- at :ref:`session_persistence_mapper` for guidelines on using
- these methods; generally, the :meth:`.SessionEvents.before_flush`
- method should be preferred for general on-flush changes.
- :param mapper: the :class:`_orm.Mapper` which is the target
- of this event.
- :param connection: the :class:`_engine.Connection` being used to
- emit UPDATE statements for this instance. This
- provides a handle into the current transaction on the
- target database specific to this instance.
- :param target: the mapped instance being persisted. If
- the event is configured with ``raw=True``, this will
- instead be the :class:`.InstanceState` state-management
- object associated with the instance.
- :return: No return value is supported by this event.
- .. seealso::
- :ref:`session_persistence_events`
- """
- def after_update(
- self, mapper: Mapper[_O], connection: Connection, target: _O
- ) -> None:
- """Receive an object instance after an UPDATE statement
- is emitted corresponding to that instance.
- .. note:: this event **only** applies to the
- :ref:`session flush operation <session_flushing>`
- and does **not** apply to the ORM DML operations described at
- :ref:`orm_expression_update_delete`. To intercept ORM
- DML events, use :meth:`_orm.SessionEvents.do_orm_execute`.
- This event is used to modify in-Python-only
- state on the instance after an UPDATE occurs, as well
- as to emit additional SQL statements on the given
- connection.
- This method is called for all instances that are
- marked as "dirty", *even those which have no net changes
- to their column-based attributes*, and for which
- no UPDATE statement has proceeded. An object is marked
- as dirty when any of its column-based attributes have a
- "set attribute" operation called or when any of its
- collections are modified. If, at update time, no
- column-based attributes have any net changes, no UPDATE
- statement will be issued. This means that an instance
- being sent to :meth:`~.MapperEvents.after_update` is
- *not* a guarantee that an UPDATE statement has been
- issued.
- To detect if the column-based attributes on the object have net
- changes, and therefore resulted in an UPDATE statement, use
- ``object_session(instance).is_modified(instance,
- include_collections=False)``.
- The event is often called for a batch of objects of the
- same class after their UPDATE statements have been emitted at
- once in a previous step. In the extremely rare case that
- this is not desirable, the :class:`_orm.Mapper` can be
- configured with ``batch=False``, which will cause
- batches of instances to be broken up into individual
- (and more poorly performing) event->persist->event
- steps.
- .. warning::
- Mapper-level flush events only allow **very limited operations**,
- on attributes local to the row being operated upon only,
- as well as allowing any SQL to be emitted on the given
- :class:`_engine.Connection`. **Please read fully** the notes
- at :ref:`session_persistence_mapper` for guidelines on using
- these methods; generally, the :meth:`.SessionEvents.before_flush`
- method should be preferred for general on-flush changes.
- :param mapper: the :class:`_orm.Mapper` which is the target
- of this event.
- :param connection: the :class:`_engine.Connection` being used to
- emit UPDATE statements for this instance. This
- provides a handle into the current transaction on the
- target database specific to this instance.
- :param target: the mapped instance being persisted. If
- the event is configured with ``raw=True``, this will
- instead be the :class:`.InstanceState` state-management
- object associated with the instance.
- :return: No return value is supported by this event.
- .. seealso::
- :ref:`session_persistence_events`
- """
- def before_delete(
- self, mapper: Mapper[_O], connection: Connection, target: _O
- ) -> None:
- """Receive an object instance before a DELETE statement
- is emitted corresponding to that instance.
- .. note:: this event **only** applies to the
- :ref:`session flush operation <session_flushing>`
- and does **not** apply to the ORM DML operations described at
- :ref:`orm_expression_update_delete`. To intercept ORM
- DML events, use :meth:`_orm.SessionEvents.do_orm_execute`.
- This event is used to emit additional SQL statements on
- the given connection as well as to perform application
- specific bookkeeping related to a deletion event.
- The event is often called for a batch of objects of the
- same class before their DELETE statements are emitted at
- once in a later step.
- .. warning::
- Mapper-level flush events only allow **very limited operations**,
- on attributes local to the row being operated upon only,
- as well as allowing any SQL to be emitted on the given
- :class:`_engine.Connection`. **Please read fully** the notes
- at :ref:`session_persistence_mapper` for guidelines on using
- these methods; generally, the :meth:`.SessionEvents.before_flush`
- method should be preferred for general on-flush changes.
- :param mapper: the :class:`_orm.Mapper` which is the target
- of this event.
- :param connection: the :class:`_engine.Connection` being used to
- emit DELETE statements for this instance. This
- provides a handle into the current transaction on the
- target database specific to this instance.
- :param target: the mapped instance being deleted. If
- the event is configured with ``raw=True``, this will
- instead be the :class:`.InstanceState` state-management
- object associated with the instance.
- :return: No return value is supported by this event.
- .. seealso::
- :ref:`session_persistence_events`
- """
- def after_delete(
- self, mapper: Mapper[_O], connection: Connection, target: _O
- ) -> None:
- """Receive an object instance after a DELETE statement
- has been emitted corresponding to that instance.
- .. note:: this event **only** applies to the
- :ref:`session flush operation <session_flushing>`
- and does **not** apply to the ORM DML operations described at
- :ref:`orm_expression_update_delete`. To intercept ORM
- DML events, use :meth:`_orm.SessionEvents.do_orm_execute`.
- This event is used to emit additional SQL statements on
- the given connection as well as to perform application
- specific bookkeeping related to a deletion event.
- The event is often called for a batch of objects of the
- same class after their DELETE statements have been emitted at
- once in a previous step.
- .. warning::
- Mapper-level flush events only allow **very limited operations**,
- on attributes local to the row being operated upon only,
- as well as allowing any SQL to be emitted on the given
- :class:`_engine.Connection`. **Please read fully** the notes
- at :ref:`session_persistence_mapper` for guidelines on using
- these methods; generally, the :meth:`.SessionEvents.before_flush`
- method should be preferred for general on-flush changes.
- :param mapper: the :class:`_orm.Mapper` which is the target
- of this event.
- :param connection: the :class:`_engine.Connection` being used to
- emit DELETE statements for this instance. This
- provides a handle into the current transaction on the
- target database specific to this instance.
- :param target: the mapped instance being deleted. If
- the event is configured with ``raw=True``, this will
- instead be the :class:`.InstanceState` state-management
- object associated with the instance.
- :return: No return value is supported by this event.
- .. seealso::
- :ref:`session_persistence_events`
- """
- class _MapperEventsHold(_EventsHold[_ET]):
- all_holds = weakref.WeakKeyDictionary()
- def resolve(
- self, class_: Union[Type[_T], _InternalEntityType[_T]]
- ) -> Optional[Mapper[_T]]:
- return _mapper_or_none(class_)
- class HoldMapperEvents(_EventsHold.HoldEvents[_ET], MapperEvents): # type: ignore [misc] # noqa: E501
- pass
- dispatch = event.dispatcher(HoldMapperEvents)
- _sessionevents_lifecycle_event_names: Set[str] = set()
- class SessionEvents(event.Events[Session]):
- """Define events specific to :class:`.Session` lifecycle.
- e.g.::
- from sqlalchemy import event
- from sqlalchemy.orm import sessionmaker
- def my_before_commit(session):
- print("before commit!")
- Session = sessionmaker()
- event.listen(Session, "before_commit", my_before_commit)
- The :func:`~.event.listen` function will accept
- :class:`.Session` objects as well as the return result
- of :class:`~.sessionmaker()` and :class:`~.scoped_session()`.
- Additionally, it accepts the :class:`.Session` class which
- will apply listeners to all :class:`.Session` instances
- globally.
- :param raw=False: When True, the "target" argument passed
- to applicable event listener functions that work on individual
- objects will be the instance's :class:`.InstanceState` management
- object, rather than the mapped instance itself.
- .. versionadded:: 1.3.14
- :param restore_load_context=False: Applies to the
- :meth:`.SessionEvents.loaded_as_persistent` event. Restores the loader
- context of the object when the event hook is complete, so that ongoing
- eager load operations continue to target the object appropriately. A
- warning is emitted if the object is moved to a new loader context from
- within this event if this flag is not set.
- .. versionadded:: 1.3.14
- """
- _target_class_doc = "SomeSessionClassOrObject"
- _dispatch_target = Session
- def _lifecycle_event( # type: ignore [misc]
- fn: Callable[[SessionEvents, Session, Any], None],
- ) -> Callable[[SessionEvents, Session, Any], None]:
- _sessionevents_lifecycle_event_names.add(fn.__name__)
- return fn
- @classmethod
- def _accept_with( # type: ignore [return]
- cls, target: Any, identifier: str
- ) -> Union[Session, type]:
- if isinstance(target, scoped_session):
- target = target.session_factory
- if not isinstance(target, sessionmaker) and (
- not isinstance(target, type) or not issubclass(target, Session)
- ):
- raise exc.ArgumentError(
- "Session event listen on a scoped_session "
- "requires that its creation callable "
- "is associated with the Session class."
- )
- if isinstance(target, sessionmaker):
- return target.class_
- elif isinstance(target, type):
- if issubclass(target, scoped_session):
- return Session
- elif issubclass(target, Session):
- return target
- elif isinstance(target, Session):
- return target
- elif hasattr(target, "_no_async_engine_events"):
- target._no_async_engine_events()
- else:
- # allows alternate SessionEvents-like-classes to be consulted
- return event.Events._accept_with(target, identifier) # type: ignore [return-value] # noqa: E501
- @classmethod
- def _listen(
- cls,
- event_key: Any,
- *,
- raw: bool = False,
- restore_load_context: bool = False,
- **kw: Any,
- ) -> None:
- is_instance_event = (
- event_key.identifier in _sessionevents_lifecycle_event_names
- )
- if is_instance_event:
- if not raw or restore_load_context:
- fn = event_key._listen_fn
- def wrap(
- session: Session,
- state: InstanceState[_O],
- *arg: Any,
- **kw: Any,
- ) -> Optional[Any]:
- if not raw:
- target = state.obj()
- if target is None:
- # existing behavior is that if the object is
- # garbage collected, no event is emitted
- return None
- else:
- target = state # type: ignore [assignment]
- if restore_load_context:
- runid = state.runid
- try:
- return fn(session, target, *arg, **kw)
- finally:
- if restore_load_context:
- state.runid = runid
- event_key = event_key.with_wrapper(wrap)
- event_key.base_listen(**kw)
- def do_orm_execute(self, orm_execute_state: ORMExecuteState) -> None:
- """Intercept statement executions that occur on behalf of an
- ORM :class:`.Session` object.
- This event is invoked for all top-level SQL statements invoked from the
- :meth:`_orm.Session.execute` method, as well as related methods such as
- :meth:`_orm.Session.scalars` and :meth:`_orm.Session.scalar`. As of
- SQLAlchemy 1.4, all ORM queries that run through the
- :meth:`_orm.Session.execute` method as well as related methods
- :meth:`_orm.Session.scalars`, :meth:`_orm.Session.scalar` etc.
- will participate in this event.
- This event hook does **not** apply to the queries that are
- emitted internally within the ORM flush process, i.e. the
- process described at :ref:`session_flushing`.
- .. note:: The :meth:`_orm.SessionEvents.do_orm_execute` event hook
- is triggered **for ORM statement executions only**, meaning those
- invoked via the :meth:`_orm.Session.execute` and similar methods on
- the :class:`_orm.Session` object. It does **not** trigger for
- statements that are invoked by SQLAlchemy Core only, i.e. statements
- invoked directly using :meth:`_engine.Connection.execute` or
- otherwise originating from an :class:`_engine.Engine` object without
- any :class:`_orm.Session` involved. To intercept **all** SQL
- executions regardless of whether the Core or ORM APIs are in use,
- see the event hooks at :class:`.ConnectionEvents`, such as
- :meth:`.ConnectionEvents.before_execute` and
- :meth:`.ConnectionEvents.before_cursor_execute`.
- Also, this event hook does **not** apply to queries that are
- emitted internally within the ORM flush process,
- i.e. the process described at :ref:`session_flushing`; to
- intercept steps within the flush process, see the event
- hooks described at :ref:`session_persistence_events` as
- well as :ref:`session_persistence_mapper`.
- This event is a ``do_`` event, meaning it has the capability to replace
- the operation that the :meth:`_orm.Session.execute` method normally
- performs. The intended use for this includes sharding and
- result-caching schemes which may seek to invoke the same statement
- across multiple database connections, returning a result that is
- merged from each of them, or which don't invoke the statement at all,
- instead returning data from a cache.
- The hook intends to replace the use of the
- ``Query._execute_and_instances`` method that could be subclassed prior
- to SQLAlchemy 1.4.
- :param orm_execute_state: an instance of :class:`.ORMExecuteState`
- which contains all information about the current execution, as well
- as helper functions used to derive other commonly required
- information. See that object for details.
- .. seealso::
- :ref:`session_execute_events` - top level documentation on how
- to use :meth:`_orm.SessionEvents.do_orm_execute`
- :class:`.ORMExecuteState` - the object passed to the
- :meth:`_orm.SessionEvents.do_orm_execute` event which contains
- all information about the statement to be invoked. It also
- provides an interface to extend the current statement, options,
- and parameters as well as an option that allows programmatic
- invocation of the statement at any point.
- :ref:`examples_session_orm_events` - includes examples of using
- :meth:`_orm.SessionEvents.do_orm_execute`
- :ref:`examples_caching` - an example of how to integrate
- Dogpile caching with the ORM :class:`_orm.Session` making use
- of the :meth:`_orm.SessionEvents.do_orm_execute` event hook.
- :ref:`examples_sharding` - the Horizontal Sharding example /
- extension relies upon the
- :meth:`_orm.SessionEvents.do_orm_execute` event hook to invoke a
- SQL statement on multiple backends and return a merged result.
- .. versionadded:: 1.4
- """
- def after_transaction_create(
- self, session: Session, transaction: SessionTransaction
- ) -> None:
- """Execute when a new :class:`.SessionTransaction` is created.
- This event differs from :meth:`~.SessionEvents.after_begin`
- in that it occurs for each :class:`.SessionTransaction`
- overall, as opposed to when transactions are begun
- on individual database connections. It is also invoked
- for nested transactions and subtransactions, and is always
- matched by a corresponding
- :meth:`~.SessionEvents.after_transaction_end` event
- (assuming normal operation of the :class:`.Session`).
- :param session: the target :class:`.Session`.
- :param transaction: the target :class:`.SessionTransaction`.
- To detect if this is the outermost
- :class:`.SessionTransaction`, as opposed to a "subtransaction" or a
- SAVEPOINT, test that the :attr:`.SessionTransaction.parent` attribute
- is ``None``::
- @event.listens_for(session, "after_transaction_create")
- def after_transaction_create(session, transaction):
- if transaction.parent is None:
- ... # work with top-level transaction
- To detect if the :class:`.SessionTransaction` is a SAVEPOINT, use the
- :attr:`.SessionTransaction.nested` attribute::
- @event.listens_for(session, "after_transaction_create")
- def after_transaction_create(session, transaction):
- if transaction.nested:
- ... # work with SAVEPOINT transaction
- .. seealso::
- :class:`.SessionTransaction`
- :meth:`~.SessionEvents.after_transaction_end`
- """
- def after_transaction_end(
- self, session: Session, transaction: SessionTransaction
- ) -> None:
- """Execute when the span of a :class:`.SessionTransaction` ends.
- This event differs from :meth:`~.SessionEvents.after_commit`
- in that it corresponds to all :class:`.SessionTransaction`
- objects in use, including those for nested transactions
- and subtransactions, and is always matched by a corresponding
- :meth:`~.SessionEvents.after_transaction_create` event.
- :param session: the target :class:`.Session`.
- :param transaction: the target :class:`.SessionTransaction`.
- To detect if this is the outermost
- :class:`.SessionTransaction`, as opposed to a "subtransaction" or a
- SAVEPOINT, test that the :attr:`.SessionTransaction.parent` attribute
- is ``None``::
- @event.listens_for(session, "after_transaction_create")
- def after_transaction_end(session, transaction):
- if transaction.parent is None:
- ... # work with top-level transaction
- To detect if the :class:`.SessionTransaction` is a SAVEPOINT, use the
- :attr:`.SessionTransaction.nested` attribute::
- @event.listens_for(session, "after_transaction_create")
- def after_transaction_end(session, transaction):
- if transaction.nested:
- ... # work with SAVEPOINT transaction
- .. seealso::
- :class:`.SessionTransaction`
- :meth:`~.SessionEvents.after_transaction_create`
- """
- def before_commit(self, session: Session) -> None:
- """Execute before commit is called.
- .. note::
- The :meth:`~.SessionEvents.before_commit` hook is *not* per-flush,
- that is, the :class:`.Session` can emit SQL to the database
- many times within the scope of a transaction.
- For interception of these events, use the
- :meth:`~.SessionEvents.before_flush`,
- :meth:`~.SessionEvents.after_flush`, or
- :meth:`~.SessionEvents.after_flush_postexec`
- events.
- :param session: The target :class:`.Session`.
- .. seealso::
- :meth:`~.SessionEvents.after_commit`
- :meth:`~.SessionEvents.after_begin`
- :meth:`~.SessionEvents.after_transaction_create`
- :meth:`~.SessionEvents.after_transaction_end`
- """
- def after_commit(self, session: Session) -> None:
- """Execute after a commit has occurred.
- .. note::
- The :meth:`~.SessionEvents.after_commit` hook is *not* per-flush,
- that is, the :class:`.Session` can emit SQL to the database
- many times within the scope of a transaction.
- For interception of these events, use the
- :meth:`~.SessionEvents.before_flush`,
- :meth:`~.SessionEvents.after_flush`, or
- :meth:`~.SessionEvents.after_flush_postexec`
- events.
- .. note::
- The :class:`.Session` is not in an active transaction
- when the :meth:`~.SessionEvents.after_commit` event is invoked,
- and therefore can not emit SQL. To emit SQL corresponding to
- every transaction, use the :meth:`~.SessionEvents.before_commit`
- event.
- :param session: The target :class:`.Session`.
- .. seealso::
- :meth:`~.SessionEvents.before_commit`
- :meth:`~.SessionEvents.after_begin`
- :meth:`~.SessionEvents.after_transaction_create`
- :meth:`~.SessionEvents.after_transaction_end`
- """
- def after_rollback(self, session: Session) -> None:
- """Execute after a real DBAPI rollback has occurred.
- Note that this event only fires when the *actual* rollback against
- the database occurs - it does *not* fire each time the
- :meth:`.Session.rollback` method is called, if the underlying
- DBAPI transaction has already been rolled back. In many
- cases, the :class:`.Session` will not be in
- an "active" state during this event, as the current
- transaction is not valid. To acquire a :class:`.Session`
- which is active after the outermost rollback has proceeded,
- use the :meth:`.SessionEvents.after_soft_rollback` event, checking the
- :attr:`.Session.is_active` flag.
- :param session: The target :class:`.Session`.
- """
- def after_soft_rollback(
- self, session: Session, previous_transaction: SessionTransaction
- ) -> None:
- """Execute after any rollback has occurred, including "soft"
- rollbacks that don't actually emit at the DBAPI level.
- This corresponds to both nested and outer rollbacks, i.e.
- the innermost rollback that calls the DBAPI's
- rollback() method, as well as the enclosing rollback
- calls that only pop themselves from the transaction stack.
- The given :class:`.Session` can be used to invoke SQL and
- :meth:`.Session.query` operations after an outermost rollback
- by first checking the :attr:`.Session.is_active` flag::
- @event.listens_for(Session, "after_soft_rollback")
- def do_something(session, previous_transaction):
- if session.is_active:
- session.execute(text("select * from some_table"))
- :param session: The target :class:`.Session`.
- :param previous_transaction: The :class:`.SessionTransaction`
- transactional marker object which was just closed. The current
- :class:`.SessionTransaction` for the given :class:`.Session` is
- available via the :attr:`.Session.transaction` attribute.
- """
- def before_flush(
- self,
- session: Session,
- flush_context: UOWTransaction,
- instances: Optional[Sequence[_O]],
- ) -> None:
- """Execute before flush process has started.
- :param session: The target :class:`.Session`.
- :param flush_context: Internal :class:`.UOWTransaction` object
- which handles the details of the flush.
- :param instances: Usually ``None``, this is the collection of
- objects which can be passed to the :meth:`.Session.flush` method
- (note this usage is deprecated).
- .. seealso::
- :meth:`~.SessionEvents.after_flush`
- :meth:`~.SessionEvents.after_flush_postexec`
- :ref:`session_persistence_events`
- """
- def after_flush(
- self, session: Session, flush_context: UOWTransaction
- ) -> None:
- """Execute after flush has completed, but before commit has been
- called.
- Note that the session's state is still in pre-flush, i.e. 'new',
- 'dirty', and 'deleted' lists still show pre-flush state as well
- as the history settings on instance attributes.
- .. warning:: This event runs after the :class:`.Session` has emitted
- SQL to modify the database, but **before** it has altered its
- internal state to reflect those changes, including that newly
- inserted objects are placed into the identity map. ORM operations
- emitted within this event such as loads of related items
- may produce new identity map entries that will immediately
- be replaced, sometimes causing confusing results. SQLAlchemy will
- emit a warning for this condition as of version 1.3.9.
- :param session: The target :class:`.Session`.
- :param flush_context: Internal :class:`.UOWTransaction` object
- which handles the details of the flush.
- .. seealso::
- :meth:`~.SessionEvents.before_flush`
- :meth:`~.SessionEvents.after_flush_postexec`
- :ref:`session_persistence_events`
- """
- def after_flush_postexec(
- self, session: Session, flush_context: UOWTransaction
- ) -> None:
- """Execute after flush has completed, and after the post-exec
- state occurs.
- This will be when the 'new', 'dirty', and 'deleted' lists are in
- their final state. An actual commit() may or may not have
- occurred, depending on whether or not the flush started its own
- transaction or participated in a larger transaction.
- :param session: The target :class:`.Session`.
- :param flush_context: Internal :class:`.UOWTransaction` object
- which handles the details of the flush.
- .. seealso::
- :meth:`~.SessionEvents.before_flush`
- :meth:`~.SessionEvents.after_flush`
- :ref:`session_persistence_events`
- """
- def after_begin(
- self,
- session: Session,
- transaction: SessionTransaction,
- connection: Connection,
- ) -> None:
- """Execute after a transaction is begun on a connection.
- .. note:: This event is called within the process of the
- :class:`_orm.Session` modifying its own internal state.
- To invoke SQL operations within this hook, use the
- :class:`_engine.Connection` provided to the event;
- do not run SQL operations using the :class:`_orm.Session`
- directly.
- :param session: The target :class:`.Session`.
- :param transaction: The :class:`.SessionTransaction`.
- :param connection: The :class:`_engine.Connection` object
- which will be used for SQL statements.
- .. seealso::
- :meth:`~.SessionEvents.before_commit`
- :meth:`~.SessionEvents.after_commit`
- :meth:`~.SessionEvents.after_transaction_create`
- :meth:`~.SessionEvents.after_transaction_end`
- """
- @_lifecycle_event
- def before_attach(self, session: Session, instance: _O) -> None:
- """Execute before an instance is attached to a session.
- This is called before an add, delete or merge causes
- the object to be part of the session.
- .. seealso::
- :meth:`~.SessionEvents.after_attach`
- :ref:`session_lifecycle_events`
- """
- @_lifecycle_event
- def after_attach(self, session: Session, instance: _O) -> None:
- """Execute after an instance is attached to a session.
- This is called after an add, delete or merge.
- .. note::
- As of 0.8, this event fires off *after* the item
- has been fully associated with the session, which is
- different than previous releases. For event
- handlers that require the object not yet
- be part of session state (such as handlers which
- may autoflush while the target object is not
- yet complete) consider the
- new :meth:`.before_attach` event.
- .. seealso::
- :meth:`~.SessionEvents.before_attach`
- :ref:`session_lifecycle_events`
- """
- @event._legacy_signature(
- "0.9",
- ["session", "query", "query_context", "result"],
- lambda update_context: (
- update_context.session,
- update_context.query,
- None,
- update_context.result,
- ),
- )
- def after_bulk_update(self, update_context: _O) -> None:
- """Event for after the legacy :meth:`_orm.Query.update` method
- has been called.
- .. legacy:: The :meth:`_orm.SessionEvents.after_bulk_update` method
- is a legacy event hook as of SQLAlchemy 2.0. The event
- **does not participate** in :term:`2.0 style` invocations
- using :func:`_dml.update` documented at
- :ref:`orm_queryguide_update_delete_where`. For 2.0 style use,
- the :meth:`_orm.SessionEvents.do_orm_execute` hook will intercept
- these calls.
- :param update_context: an "update context" object which contains
- details about the update, including these attributes:
- * ``session`` - the :class:`.Session` involved
- * ``query`` -the :class:`_query.Query`
- object that this update operation
- was called upon.
- * ``values`` The "values" dictionary that was passed to
- :meth:`_query.Query.update`.
- * ``result`` the :class:`_engine.CursorResult`
- returned as a result of the
- bulk UPDATE operation.
- .. versionchanged:: 1.4 the update_context no longer has a
- ``QueryContext`` object associated with it.
- .. seealso::
- :meth:`.QueryEvents.before_compile_update`
- :meth:`.SessionEvents.after_bulk_delete`
- """
- @event._legacy_signature(
- "0.9",
- ["session", "query", "query_context", "result"],
- lambda delete_context: (
- delete_context.session,
- delete_context.query,
- None,
- delete_context.result,
- ),
- )
- def after_bulk_delete(self, delete_context: _O) -> None:
- """Event for after the legacy :meth:`_orm.Query.delete` method
- has been called.
- .. legacy:: The :meth:`_orm.SessionEvents.after_bulk_delete` method
- is a legacy event hook as of SQLAlchemy 2.0. The event
- **does not participate** in :term:`2.0 style` invocations
- using :func:`_dml.delete` documented at
- :ref:`orm_queryguide_update_delete_where`. For 2.0 style use,
- the :meth:`_orm.SessionEvents.do_orm_execute` hook will intercept
- these calls.
- :param delete_context: a "delete context" object which contains
- details about the update, including these attributes:
- * ``session`` - the :class:`.Session` involved
- * ``query`` -the :class:`_query.Query`
- object that this update operation
- was called upon.
- * ``result`` the :class:`_engine.CursorResult`
- returned as a result of the
- bulk DELETE operation.
- .. versionchanged:: 1.4 the update_context no longer has a
- ``QueryContext`` object associated with it.
- .. seealso::
- :meth:`.QueryEvents.before_compile_delete`
- :meth:`.SessionEvents.after_bulk_update`
- """
- @_lifecycle_event
- def transient_to_pending(self, session: Session, instance: _O) -> None:
- """Intercept the "transient to pending" transition for a specific
- object.
- This event is a specialization of the
- :meth:`.SessionEvents.after_attach` event which is only invoked
- for this specific transition. It is invoked typically during the
- :meth:`.Session.add` call.
- :param session: target :class:`.Session`
- :param instance: the ORM-mapped instance being operated upon.
- .. seealso::
- :ref:`session_lifecycle_events`
- """
- @_lifecycle_event
- def pending_to_transient(self, session: Session, instance: _O) -> None:
- """Intercept the "pending to transient" transition for a specific
- object.
- This less common transition occurs when an pending object that has
- not been flushed is evicted from the session; this can occur
- when the :meth:`.Session.rollback` method rolls back the transaction,
- or when the :meth:`.Session.expunge` method is used.
- :param session: target :class:`.Session`
- :param instance: the ORM-mapped instance being operated upon.
- .. seealso::
- :ref:`session_lifecycle_events`
- """
- @_lifecycle_event
- def persistent_to_transient(self, session: Session, instance: _O) -> None:
- """Intercept the "persistent to transient" transition for a specific
- object.
- This less common transition occurs when an pending object that has
- has been flushed is evicted from the session; this can occur
- when the :meth:`.Session.rollback` method rolls back the transaction.
- :param session: target :class:`.Session`
- :param instance: the ORM-mapped instance being operated upon.
- .. seealso::
- :ref:`session_lifecycle_events`
- """
- @_lifecycle_event
- def pending_to_persistent(self, session: Session, instance: _O) -> None:
- """Intercept the "pending to persistent"" transition for a specific
- object.
- This event is invoked within the flush process, and is
- similar to scanning the :attr:`.Session.new` collection within
- the :meth:`.SessionEvents.after_flush` event. However, in this
- case the object has already been moved to the persistent state
- when the event is called.
- :param session: target :class:`.Session`
- :param instance: the ORM-mapped instance being operated upon.
- .. seealso::
- :ref:`session_lifecycle_events`
- """
- @_lifecycle_event
- def detached_to_persistent(self, session: Session, instance: _O) -> None:
- """Intercept the "detached to persistent" transition for a specific
- object.
- This event is a specialization of the
- :meth:`.SessionEvents.after_attach` event which is only invoked
- for this specific transition. It is invoked typically during the
- :meth:`.Session.add` call, as well as during the
- :meth:`.Session.delete` call if the object was not previously
- associated with the
- :class:`.Session` (note that an object marked as "deleted" remains
- in the "persistent" state until the flush proceeds).
- .. note::
- If the object becomes persistent as part of a call to
- :meth:`.Session.delete`, the object is **not** yet marked as
- deleted when this event is called. To detect deleted objects,
- check the ``deleted`` flag sent to the
- :meth:`.SessionEvents.persistent_to_detached` to event after the
- flush proceeds, or check the :attr:`.Session.deleted` collection
- within the :meth:`.SessionEvents.before_flush` event if deleted
- objects need to be intercepted before the flush.
- :param session: target :class:`.Session`
- :param instance: the ORM-mapped instance being operated upon.
- .. seealso::
- :ref:`session_lifecycle_events`
- """
- @_lifecycle_event
- def loaded_as_persistent(self, session: Session, instance: _O) -> None:
- """Intercept the "loaded as persistent" transition for a specific
- object.
- This event is invoked within the ORM loading process, and is invoked
- very similarly to the :meth:`.InstanceEvents.load` event. However,
- the event here is linkable to a :class:`.Session` class or instance,
- rather than to a mapper or class hierarchy, and integrates
- with the other session lifecycle events smoothly. The object
- is guaranteed to be present in the session's identity map when
- this event is called.
- .. note:: This event is invoked within the loader process before
- eager loaders may have been completed, and the object's state may
- not be complete. Additionally, invoking row-level refresh
- operations on the object will place the object into a new loader
- context, interfering with the existing load context. See the note
- on :meth:`.InstanceEvents.load` for background on making use of the
- :paramref:`.SessionEvents.restore_load_context` parameter, which
- works in the same manner as that of
- :paramref:`.InstanceEvents.restore_load_context`, in order to
- resolve this scenario.
- :param session: target :class:`.Session`
- :param instance: the ORM-mapped instance being operated upon.
- .. seealso::
- :ref:`session_lifecycle_events`
- """
- @_lifecycle_event
- def persistent_to_deleted(self, session: Session, instance: _O) -> None:
- """Intercept the "persistent to deleted" transition for a specific
- object.
- This event is invoked when a persistent object's identity
- is deleted from the database within a flush, however the object
- still remains associated with the :class:`.Session` until the
- transaction completes.
- If the transaction is rolled back, the object moves again
- to the persistent state, and the
- :meth:`.SessionEvents.deleted_to_persistent` event is called.
- If the transaction is committed, the object becomes detached,
- which will emit the :meth:`.SessionEvents.deleted_to_detached`
- event.
- Note that while the :meth:`.Session.delete` method is the primary
- public interface to mark an object as deleted, many objects
- get deleted due to cascade rules, which are not always determined
- until flush time. Therefore, there's no way to catch
- every object that will be deleted until the flush has proceeded.
- the :meth:`.SessionEvents.persistent_to_deleted` event is therefore
- invoked at the end of a flush.
- .. seealso::
- :ref:`session_lifecycle_events`
- """
- @_lifecycle_event
- def deleted_to_persistent(self, session: Session, instance: _O) -> None:
- """Intercept the "deleted to persistent" transition for a specific
- object.
- This transition occurs only when an object that's been deleted
- successfully in a flush is restored due to a call to
- :meth:`.Session.rollback`. The event is not called under
- any other circumstances.
- .. seealso::
- :ref:`session_lifecycle_events`
- """
- @_lifecycle_event
- def deleted_to_detached(self, session: Session, instance: _O) -> None:
- """Intercept the "deleted to detached" transition for a specific
- object.
- This event is invoked when a deleted object is evicted
- from the session. The typical case when this occurs is when
- the transaction for a :class:`.Session` in which the object
- was deleted is committed; the object moves from the deleted
- state to the detached state.
- It is also invoked for objects that were deleted in a flush
- when the :meth:`.Session.expunge_all` or :meth:`.Session.close`
- events are called, as well as if the object is individually
- expunged from its deleted state via :meth:`.Session.expunge`.
- .. seealso::
- :ref:`session_lifecycle_events`
- """
- @_lifecycle_event
- def persistent_to_detached(self, session: Session, instance: _O) -> None:
- """Intercept the "persistent to detached" transition for a specific
- object.
- This event is invoked when a persistent object is evicted
- from the session. There are many conditions that cause this
- to happen, including:
- * using a method such as :meth:`.Session.expunge`
- or :meth:`.Session.close`
- * Calling the :meth:`.Session.rollback` method, when the object
- was part of an INSERT statement for that session's transaction
- :param session: target :class:`.Session`
- :param instance: the ORM-mapped instance being operated upon.
- :param deleted: boolean. If True, indicates this object moved
- to the detached state because it was marked as deleted and flushed.
- .. seealso::
- :ref:`session_lifecycle_events`
- """
- class AttributeEvents(event.Events[QueryableAttribute[Any]]):
- r"""Define events for object attributes.
- These are typically defined on the class-bound descriptor for the
- target class.
- For example, to register a listener that will receive the
- :meth:`_orm.AttributeEvents.append` event::
- from sqlalchemy import event
- @event.listens_for(MyClass.collection, "append", propagate=True)
- def my_append_listener(target, value, initiator):
- print("received append event for target: %s" % target)
- Listeners have the option to return a possibly modified version of the
- value, when the :paramref:`.AttributeEvents.retval` flag is passed to
- :func:`.event.listen` or :func:`.event.listens_for`, such as below,
- illustrated using the :meth:`_orm.AttributeEvents.set` event::
- def validate_phone(target, value, oldvalue, initiator):
- "Strip non-numeric characters from a phone number"
- return re.sub(r"\D", "", value)
- # setup listener on UserContact.phone attribute, instructing
- # it to use the return value
- listen(UserContact.phone, "set", validate_phone, retval=True)
- A validation function like the above can also raise an exception
- such as :exc:`ValueError` to halt the operation.
- The :paramref:`.AttributeEvents.propagate` flag is also important when
- applying listeners to mapped classes that also have mapped subclasses,
- as when using mapper inheritance patterns::
- @event.listens_for(MySuperClass.attr, "set", propagate=True)
- def receive_set(target, value, initiator):
- print("value set: %s" % target)
- The full list of modifiers available to the :func:`.event.listen`
- and :func:`.event.listens_for` functions are below.
- :param active_history=False: When True, indicates that the
- "set" event would like to receive the "old" value being
- replaced unconditionally, even if this requires firing off
- database loads. Note that ``active_history`` can also be
- set directly via :func:`.column_property` and
- :func:`_orm.relationship`.
- :param propagate=False: When True, the listener function will
- be established not just for the class attribute given, but
- for attributes of the same name on all current subclasses
- of that class, as well as all future subclasses of that
- class, using an additional listener that listens for
- instrumentation events.
- :param raw=False: When True, the "target" argument to the
- event will be the :class:`.InstanceState` management
- object, rather than the mapped instance itself.
- :param retval=False: when True, the user-defined event
- listening must return the "value" argument from the
- function. This gives the listening function the opportunity
- to change the value that is ultimately used for a "set"
- or "append" event.
- """
- _target_class_doc = "SomeClass.some_attribute"
- _dispatch_target = QueryableAttribute
- @staticmethod
- def _set_dispatch(
- cls: Type[_HasEventsDispatch[Any]], dispatch_cls: Type[_Dispatch[Any]]
- ) -> _Dispatch[Any]:
- dispatch = event.Events._set_dispatch(cls, dispatch_cls)
- dispatch_cls._active_history = False
- return dispatch
- @classmethod
- def _accept_with(
- cls,
- target: Union[QueryableAttribute[Any], Type[QueryableAttribute[Any]]],
- identifier: str,
- ) -> Union[QueryableAttribute[Any], Type[QueryableAttribute[Any]]]:
- # TODO: coverage
- if isinstance(target, interfaces.MapperProperty):
- return getattr(target.parent.class_, target.key)
- else:
- return target
- @classmethod
- def _listen( # type: ignore [override]
- cls,
- event_key: _EventKey[QueryableAttribute[Any]],
- active_history: bool = False,
- raw: bool = False,
- retval: bool = False,
- propagate: bool = False,
- include_key: bool = False,
- ) -> None:
- target, fn = event_key.dispatch_target, event_key._listen_fn
- if active_history:
- target.dispatch._active_history = True
- if not raw or not retval or not include_key:
- def wrap(target: InstanceState[_O], *arg: Any, **kw: Any) -> Any:
- if not raw:
- target = target.obj() # type: ignore [assignment]
- if not retval:
- if arg:
- value = arg[0]
- else:
- value = None
- if include_key:
- fn(target, *arg, **kw)
- else:
- fn(target, *arg)
- return value
- else:
- if include_key:
- return fn(target, *arg, **kw)
- else:
- return fn(target, *arg)
- event_key = event_key.with_wrapper(wrap)
- event_key.base_listen(propagate=propagate)
- if propagate:
- manager = instrumentation.manager_of_class(target.class_)
- for mgr in manager.subclass_managers(True): # type: ignore [no-untyped-call] # noqa: E501
- event_key.with_dispatch_target(mgr[target.key]).base_listen(
- propagate=True
- )
- if active_history:
- mgr[target.key].dispatch._active_history = True
- def append(
- self,
- target: _O,
- value: _T,
- initiator: Event,
- *,
- key: EventConstants = NO_KEY,
- ) -> Optional[_T]:
- """Receive a collection append event.
- The append event is invoked for each element as it is appended
- to the collection. This occurs for single-item appends as well
- as for a "bulk replace" operation.
- :param target: the object instance receiving the event.
- If the listener is registered with ``raw=True``, this will
- be the :class:`.InstanceState` object.
- :param value: the value being appended. If this listener
- is registered with ``retval=True``, the listener
- function must return this value, or a new value which
- replaces it.
- :param initiator: An instance of :class:`.attributes.Event`
- representing the initiation of the event. May be modified
- from its original value by backref handlers in order to control
- chained event propagation, as well as be inspected for information
- about the source of the event.
- :param key: When the event is established using the
- :paramref:`.AttributeEvents.include_key` parameter set to
- True, this will be the key used in the operation, such as
- ``collection[some_key_or_index] = value``.
- The parameter is not passed
- to the event at all if the the
- :paramref:`.AttributeEvents.include_key`
- was not used to set up the event; this is to allow backwards
- compatibility with existing event handlers that don't include the
- ``key`` parameter.
- .. versionadded:: 2.0
- :return: if the event was registered with ``retval=True``,
- the given value, or a new effective value, should be returned.
- .. seealso::
- :class:`.AttributeEvents` - background on listener options such
- as propagation to subclasses.
- :meth:`.AttributeEvents.bulk_replace`
- """
- def append_wo_mutation(
- self,
- target: _O,
- value: _T,
- initiator: Event,
- *,
- key: EventConstants = NO_KEY,
- ) -> None:
- """Receive a collection append event where the collection was not
- actually mutated.
- This event differs from :meth:`_orm.AttributeEvents.append` in that
- it is fired off for de-duplicating collections such as sets and
- dictionaries, when the object already exists in the target collection.
- The event does not have a return value and the identity of the
- given object cannot be changed.
- The event is used for cascading objects into a :class:`_orm.Session`
- when the collection has already been mutated via a backref event.
- :param target: the object instance receiving the event.
- If the listener is registered with ``raw=True``, this will
- be the :class:`.InstanceState` object.
- :param value: the value that would be appended if the object did not
- already exist in the collection.
- :param initiator: An instance of :class:`.attributes.Event`
- representing the initiation of the event. May be modified
- from its original value by backref handlers in order to control
- chained event propagation, as well as be inspected for information
- about the source of the event.
- :param key: When the event is established using the
- :paramref:`.AttributeEvents.include_key` parameter set to
- True, this will be the key used in the operation, such as
- ``collection[some_key_or_index] = value``.
- The parameter is not passed
- to the event at all if the the
- :paramref:`.AttributeEvents.include_key`
- was not used to set up the event; this is to allow backwards
- compatibility with existing event handlers that don't include the
- ``key`` parameter.
- .. versionadded:: 2.0
- :return: No return value is defined for this event.
- .. versionadded:: 1.4.15
- """
- def bulk_replace(
- self,
- target: _O,
- values: Iterable[_T],
- initiator: Event,
- *,
- keys: Optional[Iterable[EventConstants]] = None,
- ) -> None:
- """Receive a collection 'bulk replace' event.
- This event is invoked for a sequence of values as they are incoming
- to a bulk collection set operation, which can be
- modified in place before the values are treated as ORM objects.
- This is an "early hook" that runs before the bulk replace routine
- attempts to reconcile which objects are already present in the
- collection and which are being removed by the net replace operation.
- It is typical that this method be combined with use of the
- :meth:`.AttributeEvents.append` event. When using both of these
- events, note that a bulk replace operation will invoke
- the :meth:`.AttributeEvents.append` event for all new items,
- even after :meth:`.AttributeEvents.bulk_replace` has been invoked
- for the collection as a whole. In order to determine if an
- :meth:`.AttributeEvents.append` event is part of a bulk replace,
- use the symbol :attr:`~.attributes.OP_BULK_REPLACE` to test the
- incoming initiator::
- from sqlalchemy.orm.attributes import OP_BULK_REPLACE
- @event.listens_for(SomeObject.collection, "bulk_replace")
- def process_collection(target, values, initiator):
- values[:] = [_make_value(value) for value in values]
- @event.listens_for(SomeObject.collection, "append", retval=True)
- def process_collection(target, value, initiator):
- # make sure bulk_replace didn't already do it
- if initiator is None or initiator.op is not OP_BULK_REPLACE:
- return _make_value(value)
- else:
- return value
- .. versionadded:: 1.2
- :param target: the object instance receiving the event.
- If the listener is registered with ``raw=True``, this will
- be the :class:`.InstanceState` object.
- :param value: a sequence (e.g. a list) of the values being set. The
- handler can modify this list in place.
- :param initiator: An instance of :class:`.attributes.Event`
- representing the initiation of the event.
- :param keys: When the event is established using the
- :paramref:`.AttributeEvents.include_key` parameter set to
- True, this will be the sequence of keys used in the operation,
- typically only for a dictionary update. The parameter is not passed
- to the event at all if the the
- :paramref:`.AttributeEvents.include_key`
- was not used to set up the event; this is to allow backwards
- compatibility with existing event handlers that don't include the
- ``key`` parameter.
- .. versionadded:: 2.0
- .. seealso::
- :class:`.AttributeEvents` - background on listener options such
- as propagation to subclasses.
- """
- def remove(
- self,
- target: _O,
- value: _T,
- initiator: Event,
- *,
- key: EventConstants = NO_KEY,
- ) -> None:
- """Receive a collection remove event.
- :param target: the object instance receiving the event.
- If the listener is registered with ``raw=True``, this will
- be the :class:`.InstanceState` object.
- :param value: the value being removed.
- :param initiator: An instance of :class:`.attributes.Event`
- representing the initiation of the event. May be modified
- from its original value by backref handlers in order to control
- chained event propagation.
- :param key: When the event is established using the
- :paramref:`.AttributeEvents.include_key` parameter set to
- True, this will be the key used in the operation, such as
- ``del collection[some_key_or_index]``. The parameter is not passed
- to the event at all if the the
- :paramref:`.AttributeEvents.include_key`
- was not used to set up the event; this is to allow backwards
- compatibility with existing event handlers that don't include the
- ``key`` parameter.
- .. versionadded:: 2.0
- :return: No return value is defined for this event.
- .. seealso::
- :class:`.AttributeEvents` - background on listener options such
- as propagation to subclasses.
- """
- def set(
- self, target: _O, value: _T, oldvalue: _T, initiator: Event
- ) -> None:
- """Receive a scalar set event.
- :param target: the object instance receiving the event.
- If the listener is registered with ``raw=True``, this will
- be the :class:`.InstanceState` object.
- :param value: the value being set. If this listener
- is registered with ``retval=True``, the listener
- function must return this value, or a new value which
- replaces it.
- :param oldvalue: the previous value being replaced. This
- may also be the symbol ``NEVER_SET`` or ``NO_VALUE``.
- If the listener is registered with ``active_history=True``,
- the previous value of the attribute will be loaded from
- the database if the existing value is currently unloaded
- or expired.
- :param initiator: An instance of :class:`.attributes.Event`
- representing the initiation of the event. May be modified
- from its original value by backref handlers in order to control
- chained event propagation.
- :return: if the event was registered with ``retval=True``,
- the given value, or a new effective value, should be returned.
- .. seealso::
- :class:`.AttributeEvents` - background on listener options such
- as propagation to subclasses.
- """
- def init_scalar(
- self, target: _O, value: _T, dict_: Dict[Any, Any]
- ) -> None:
- r"""Receive a scalar "init" event.
- This event is invoked when an uninitialized, unpersisted scalar
- attribute is accessed, e.g. read::
- x = my_object.some_attribute
- The ORM's default behavior when this occurs for an un-initialized
- attribute is to return the value ``None``; note this differs from
- Python's usual behavior of raising ``AttributeError``. The
- event here can be used to customize what value is actually returned,
- with the assumption that the event listener would be mirroring
- a default generator that is configured on the Core
- :class:`_schema.Column`
- object as well.
- Since a default generator on a :class:`_schema.Column`
- might also produce
- a changing value such as a timestamp, the
- :meth:`.AttributeEvents.init_scalar`
- event handler can also be used to **set** the newly returned value, so
- that a Core-level default generation function effectively fires off
- only once, but at the moment the attribute is accessed on the
- non-persisted object. Normally, no change to the object's state
- is made when an uninitialized attribute is accessed (much older
- SQLAlchemy versions did in fact change the object's state).
- If a default generator on a column returned a particular constant,
- a handler might be used as follows::
- SOME_CONSTANT = 3.1415926
- class MyClass(Base):
- # ...
- some_attribute = Column(Numeric, default=SOME_CONSTANT)
- @event.listens_for(
- MyClass.some_attribute, "init_scalar", retval=True, propagate=True
- )
- def _init_some_attribute(target, dict_, value):
- dict_["some_attribute"] = SOME_CONSTANT
- return SOME_CONSTANT
- Above, we initialize the attribute ``MyClass.some_attribute`` to the
- value of ``SOME_CONSTANT``. The above code includes the following
- features:
- * By setting the value ``SOME_CONSTANT`` in the given ``dict_``,
- we indicate that this value is to be persisted to the database.
- This supersedes the use of ``SOME_CONSTANT`` in the default generator
- for the :class:`_schema.Column`. The ``active_column_defaults.py``
- example given at :ref:`examples_instrumentation` illustrates using
- the same approach for a changing default, e.g. a timestamp
- generator. In this particular example, it is not strictly
- necessary to do this since ``SOME_CONSTANT`` would be part of the
- INSERT statement in either case.
- * By establishing the ``retval=True`` flag, the value we return
- from the function will be returned by the attribute getter.
- Without this flag, the event is assumed to be a passive observer
- and the return value of our function is ignored.
- * The ``propagate=True`` flag is significant if the mapped class
- includes inheriting subclasses, which would also make use of this
- event listener. Without this flag, an inheriting subclass will
- not use our event handler.
- In the above example, the attribute set event
- :meth:`.AttributeEvents.set` as well as the related validation feature
- provided by :obj:`_orm.validates` is **not** invoked when we apply our
- value to the given ``dict_``. To have these events to invoke in
- response to our newly generated value, apply the value to the given
- object as a normal attribute set operation::
- SOME_CONSTANT = 3.1415926
- @event.listens_for(
- MyClass.some_attribute, "init_scalar", retval=True, propagate=True
- )
- def _init_some_attribute(target, dict_, value):
- # will also fire off attribute set events
- target.some_attribute = SOME_CONSTANT
- return SOME_CONSTANT
- When multiple listeners are set up, the generation of the value
- is "chained" from one listener to the next by passing the value
- returned by the previous listener that specifies ``retval=True``
- as the ``value`` argument of the next listener.
- :param target: the object instance receiving the event.
- If the listener is registered with ``raw=True``, this will
- be the :class:`.InstanceState` object.
- :param value: the value that is to be returned before this event
- listener were invoked. This value begins as the value ``None``,
- however will be the return value of the previous event handler
- function if multiple listeners are present.
- :param dict\_: the attribute dictionary of this mapped object.
- This is normally the ``__dict__`` of the object, but in all cases
- represents the destination that the attribute system uses to get
- at the actual value of this attribute. Placing the value in this
- dictionary has the effect that the value will be used in the
- INSERT statement generated by the unit of work.
- .. seealso::
- :meth:`.AttributeEvents.init_collection` - collection version
- of this event
- :class:`.AttributeEvents` - background on listener options such
- as propagation to subclasses.
- :ref:`examples_instrumentation` - see the
- ``active_column_defaults.py`` example.
- """ # noqa: E501
- def init_collection(
- self,
- target: _O,
- collection: Type[Collection[Any]],
- collection_adapter: CollectionAdapter,
- ) -> None:
- """Receive a 'collection init' event.
- This event is triggered for a collection-based attribute, when
- the initial "empty collection" is first generated for a blank
- attribute, as well as for when the collection is replaced with
- a new one, such as via a set event.
- E.g., given that ``User.addresses`` is a relationship-based
- collection, the event is triggered here::
- u1 = User()
- u1.addresses.append(a1) # <- new collection
- and also during replace operations::
- u1.addresses = [a2, a3] # <- new collection
- :param target: the object instance receiving the event.
- If the listener is registered with ``raw=True``, this will
- be the :class:`.InstanceState` object.
- :param collection: the new collection. This will always be generated
- from what was specified as
- :paramref:`_orm.relationship.collection_class`, and will always
- be empty.
- :param collection_adapter: the :class:`.CollectionAdapter` that will
- mediate internal access to the collection.
- .. seealso::
- :class:`.AttributeEvents` - background on listener options such
- as propagation to subclasses.
- :meth:`.AttributeEvents.init_scalar` - "scalar" version of this
- event.
- """
- def dispose_collection(
- self,
- target: _O,
- collection: Collection[Any],
- collection_adapter: CollectionAdapter,
- ) -> None:
- """Receive a 'collection dispose' event.
- This event is triggered for a collection-based attribute when
- a collection is replaced, that is::
- u1.addresses.append(a1)
- u1.addresses = [a2, a3] # <- old collection is disposed
- The old collection received will contain its previous contents.
- .. versionchanged:: 1.2 The collection passed to
- :meth:`.AttributeEvents.dispose_collection` will now have its
- contents before the dispose intact; previously, the collection
- would be empty.
- .. seealso::
- :class:`.AttributeEvents` - background on listener options such
- as propagation to subclasses.
- """
- def modified(self, target: _O, initiator: Event) -> None:
- """Receive a 'modified' event.
- This event is triggered when the :func:`.attributes.flag_modified`
- function is used to trigger a modify event on an attribute without
- any specific value being set.
- .. versionadded:: 1.2
- :param target: the object instance receiving the event.
- If the listener is registered with ``raw=True``, this will
- be the :class:`.InstanceState` object.
- :param initiator: An instance of :class:`.attributes.Event`
- representing the initiation of the event.
- .. seealso::
- :class:`.AttributeEvents` - background on listener options such
- as propagation to subclasses.
- """
- class QueryEvents(event.Events[Query[Any]]):
- """Represent events within the construction of a :class:`_query.Query`
- object.
- .. legacy:: The :class:`_orm.QueryEvents` event methods are legacy
- as of SQLAlchemy 2.0, and only apply to direct use of the
- :class:`_orm.Query` object. They are not used for :term:`2.0 style`
- statements. For events to intercept and modify 2.0 style ORM use,
- use the :meth:`_orm.SessionEvents.do_orm_execute` hook.
- The :class:`_orm.QueryEvents` hooks are now superseded by the
- :meth:`_orm.SessionEvents.do_orm_execute` event hook.
- """
- _target_class_doc = "SomeQuery"
- _dispatch_target = Query
- def before_compile(self, query: Query[Any]) -> None:
- """Receive the :class:`_query.Query`
- object before it is composed into a
- core :class:`_expression.Select` object.
- .. deprecated:: 1.4 The :meth:`_orm.QueryEvents.before_compile` event
- is superseded by the much more capable
- :meth:`_orm.SessionEvents.do_orm_execute` hook. In version 1.4,
- the :meth:`_orm.QueryEvents.before_compile` event is **no longer
- used** for ORM-level attribute loads, such as loads of deferred
- or expired attributes as well as relationship loaders. See the
- new examples in :ref:`examples_session_orm_events` which
- illustrate new ways of intercepting and modifying ORM queries
- for the most common purpose of adding arbitrary filter criteria.
- This event is intended to allow changes to the query given::
- @event.listens_for(Query, "before_compile", retval=True)
- def no_deleted(query):
- for desc in query.column_descriptions:
- if desc["type"] is User:
- entity = desc["entity"]
- query = query.filter(entity.deleted == False)
- return query
- The event should normally be listened with the ``retval=True``
- parameter set, so that the modified query may be returned.
- The :meth:`.QueryEvents.before_compile` event by default
- will disallow "baked" queries from caching a query, if the event
- hook returns a new :class:`_query.Query` object.
- This affects both direct
- use of the baked query extension as well as its operation within
- lazy loaders and eager loaders for relationships. In order to
- re-establish the query being cached, apply the event adding the
- ``bake_ok`` flag::
- @event.listens_for(Query, "before_compile", retval=True, bake_ok=True)
- def my_event(query):
- for desc in query.column_descriptions:
- if desc["type"] is User:
- entity = desc["entity"]
- query = query.filter(entity.deleted == False)
- return query
- When ``bake_ok`` is set to True, the event hook will only be invoked
- once, and not called for subsequent invocations of a particular query
- that is being cached.
- .. versionadded:: 1.3.11 - added the "bake_ok" flag to the
- :meth:`.QueryEvents.before_compile` event and disallowed caching via
- the "baked" extension from occurring for event handlers that
- return a new :class:`_query.Query` object if this flag is not set.
- .. seealso::
- :meth:`.QueryEvents.before_compile_update`
- :meth:`.QueryEvents.before_compile_delete`
- :ref:`baked_with_before_compile`
- """ # noqa: E501
- def before_compile_update(
- self, query: Query[Any], update_context: BulkUpdate
- ) -> None:
- """Allow modifications to the :class:`_query.Query` object within
- :meth:`_query.Query.update`.
- .. deprecated:: 1.4 The :meth:`_orm.QueryEvents.before_compile_update`
- event is superseded by the much more capable
- :meth:`_orm.SessionEvents.do_orm_execute` hook.
- Like the :meth:`.QueryEvents.before_compile` event, if the event
- is to be used to alter the :class:`_query.Query` object, it should
- be configured with ``retval=True``, and the modified
- :class:`_query.Query` object returned, as in ::
- @event.listens_for(Query, "before_compile_update", retval=True)
- def no_deleted(query, update_context):
- for desc in query.column_descriptions:
- if desc["type"] is User:
- entity = desc["entity"]
- query = query.filter(entity.deleted == False)
- update_context.values["timestamp"] = datetime.datetime.now(
- datetime.UTC
- )
- return query
- The ``.values`` dictionary of the "update context" object can also
- be modified in place as illustrated above.
- :param query: a :class:`_query.Query` instance; this is also
- the ``.query`` attribute of the given "update context"
- object.
- :param update_context: an "update context" object which is
- the same kind of object as described in
- :paramref:`.QueryEvents.after_bulk_update.update_context`.
- The object has a ``.values`` attribute in an UPDATE context which is
- the dictionary of parameters passed to :meth:`_query.Query.update`.
- This
- dictionary can be modified to alter the VALUES clause of the
- resulting UPDATE statement.
- .. versionadded:: 1.2.17
- .. seealso::
- :meth:`.QueryEvents.before_compile`
- :meth:`.QueryEvents.before_compile_delete`
- """ # noqa: E501
- def before_compile_delete(
- self, query: Query[Any], delete_context: BulkDelete
- ) -> None:
- """Allow modifications to the :class:`_query.Query` object within
- :meth:`_query.Query.delete`.
- .. deprecated:: 1.4 The :meth:`_orm.QueryEvents.before_compile_delete`
- event is superseded by the much more capable
- :meth:`_orm.SessionEvents.do_orm_execute` hook.
- Like the :meth:`.QueryEvents.before_compile` event, this event
- should be configured with ``retval=True``, and the modified
- :class:`_query.Query` object returned, as in ::
- @event.listens_for(Query, "before_compile_delete", retval=True)
- def no_deleted(query, delete_context):
- for desc in query.column_descriptions:
- if desc["type"] is User:
- entity = desc["entity"]
- query = query.filter(entity.deleted == False)
- return query
- :param query: a :class:`_query.Query` instance; this is also
- the ``.query`` attribute of the given "delete context"
- object.
- :param delete_context: a "delete context" object which is
- the same kind of object as described in
- :paramref:`.QueryEvents.after_bulk_delete.delete_context`.
- .. versionadded:: 1.2.17
- .. seealso::
- :meth:`.QueryEvents.before_compile`
- :meth:`.QueryEvents.before_compile_update`
- """
- @classmethod
- def _listen(
- cls,
- event_key: _EventKey[_ET],
- retval: bool = False,
- bake_ok: bool = False,
- **kw: Any,
- ) -> None:
- fn = event_key._listen_fn
- if not retval:
- def wrap(*arg: Any, **kw: Any) -> Any:
- if not retval:
- query = arg[0]
- fn(*arg, **kw)
- return query
- else:
- return fn(*arg, **kw)
- event_key = event_key.with_wrapper(wrap)
- else:
- # don't assume we can apply an attribute to the callable
- def wrap(*arg: Any, **kw: Any) -> Any:
- return fn(*arg, **kw)
- event_key = event_key.with_wrapper(wrap)
- wrap._bake_ok = bake_ok # type: ignore [attr-defined]
- event_key.base_listen(**kw)
|