1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848 |
- # orm/events.py
- # Copyright (C) 2005-2022 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.
- """
- import weakref
- from . import instrumentation
- from . import interfaces
- from . import mapperlib
- from .attributes import QueryableAttribute
- from .base import _mapper_or_none
- 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 ..util.compat import inspect_getfullargspec
- class InstrumentationEvents(event.Events):
- """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 = instrumentation.InstrumentationFactory
- @classmethod
- def _accept_with(cls, target):
- if isinstance(target, type):
- return _InstrumentationEventsHold(target)
- else:
- return None
- @classmethod
- def _listen(cls, event_key, propagate=True, **kw):
- target, identifier, fn = (
- event_key.dispatch_target,
- event_key.identifier,
- event_key._listen_fn,
- )
- def listen(target_cls, *arg):
- 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)
- def remove(ref):
- key = event.registry._EventKey(
- 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):
- super(InstrumentationEvents, cls)._clear()
- instrumentation._instrumentation_factory.dispatch._clear()
- def class_instrument(self, cls):
- """Called after the given class is instrumented.
- To get at the :class:`.ClassManager`, use
- :func:`.manager_of_class`.
- """
- def class_uninstrument(self, cls):
- """Called before the given class is uninstrumented.
- To get at the :class:`.ClassManager`, use
- :func:`.manager_of_class`.
- """
- def attribute_instrument(self, cls, key, inst):
- """Called when an attribute is instrumented."""
- class _InstrumentationEventsHold(object):
- """temporary marker object used to transfer from _accept_with() to
- _listen() on the InstrumentationEvents class.
- """
- def __init__(self, class_):
- self.class_ = class_
- dispatch = event.dispatcher(InstrumentationEvents)
- class InstanceEvents(event.Events):
- """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 and the :func:`.mapper`
- function indicate 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 = instrumentation.ClassManager
- @classmethod
- def _new_classmanager_instance(cls, class_, classmanager):
- _InstanceEventsHold.populate(class_, classmanager)
- @classmethod
- @util.preload_module("sqlalchemy.orm")
- def _accept_with(cls, target):
- orm = util.preloaded.orm
- if isinstance(target, instrumentation.ClassManager):
- return target
- elif isinstance(target, mapperlib.Mapper):
- return target.class_manager
- elif target is orm.mapper:
- return instrumentation.ClassManager
- elif isinstance(target, type):
- if issubclass(target, mapperlib.Mapper):
- return instrumentation.ClassManager
- else:
- manager = instrumentation.manager_of_class(target)
- if manager:
- return manager
- else:
- return _InstanceEventsHold(target)
- return None
- @classmethod
- def _listen(
- cls,
- event_key,
- raw=False,
- propagate=False,
- restore_load_context=False,
- **kw
- ):
- target, fn = (event_key.dispatch_target, event_key._listen_fn)
- if not raw or restore_load_context:
- def wrap(state, *arg, **kw):
- if not raw:
- target = 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):
- super(InstanceEvents, cls)._clear()
- _InstanceEventsHold._clear()
- def first_init(self, manager, cls):
- """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, args, kwargs):
- """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, args, kwargs):
- """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 load(self, target, context):
- """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::
- :meth:`.InstanceEvents.init`
- :meth:`.InstanceEvents.refresh`
- :meth:`.SessionEvents.loaded_as_persistent`
- :ref:`mapping_constructors`
- """
- def refresh(self, target, context, attrs):
- """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::
- :meth:`.InstanceEvents.load`
- """
- def refresh_flush(self, target, flush_context, attrs):
- """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.
- .. versionadded:: 1.0.5
- :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:`orm_server_defaults`
- :ref:`metadata_defaults_toplevel`
- """
- def expire(self, target, attrs):
- """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, state_dict):
- """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, state_dict):
- """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):
- """Hold onto listeners against unmapped, uninstrumented classes.
- Establish _listen() for that class' mapper/instrumentation when
- those objects are created for that class.
- """
- def __init__(self, class_):
- self.class_ = class_
- @classmethod
- def _clear(cls):
- cls.all_holds.clear()
- class HoldEvents(object):
- _dispatch_target = None
- @classmethod
- def _listen(
- cls, event_key, raw=False, propagate=False, retval=False, **kw
- ):
- 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):
- 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_, subject):
- 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):
- all_holds = weakref.WeakKeyDictionary()
- def resolve(self, class_):
- return instrumentation.manager_of_class(class_)
- class HoldInstanceEvents(_EventsHold.HoldEvents, InstanceEvents):
- pass
- dispatch = event.dispatcher(HoldInstanceEvents)
- class MapperEvents(event.Events):
- """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 and the :func:`.mapper`
- function indicate 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_, mapper):
- _MapperEventsHold.populate(class_, mapper)
- @classmethod
- @util.preload_module("sqlalchemy.orm")
- def _accept_with(cls, target):
- orm = util.preloaded.orm
- if target is orm.mapper:
- 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, raw=False, retval=False, propagate=False, **kw
- ):
- 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() function or 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, **kw):
- if not raw and target_index is not None:
- arg = list(arg)
- arg[target_index] = arg[target_index].obj()
- 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):
- super(MapperEvents, cls)._clear()
- _MapperEventsHold._clear()
- def instrument_class(self, mapper, class_):
- 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.
- 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.
- """
- def before_mapper_configured(self, mapper, class_):
- """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 he "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, class_):
- 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):
- """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
- or :func:`.mapper` function, 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():
- # ...
- .. versionadded:: 0.9.3
- .. seealso::
- :meth:`.MapperEvents.before_mapper_configured`
- :meth:`.MapperEvents.mapper_configured`
- :meth:`.MapperEvents.after_configured`
- """
- def after_configured(self):
- """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
- or :func:`.mapper` function, 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, connection, target):
- """Receive an object instance before an INSERT statement
- is emitted corresponding to that instance.
- 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 :func:`.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 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, connection, target):
- """Receive an object instance after an INSERT statement
- is emitted corresponding to that instance.
- 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
- :func:`.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 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, connection, target):
- """Receive an object instance before an UPDATE statement
- is emitted corresponding to that instance.
- 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 :func:`.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, connection, target):
- """Receive an object instance after an UPDATE statement
- is emitted corresponding to that instance.
- 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 :func:`.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, connection, target):
- """Receive an object instance before a DELETE statement
- is emitted corresponding to that instance.
- 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, connection, target):
- """Receive an object instance after a DELETE statement
- has been emitted corresponding to that instance.
- 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):
- all_holds = weakref.WeakKeyDictionary()
- def resolve(self, class_):
- return _mapper_or_none(class_)
- class HoldMapperEvents(_EventsHold.HoldEvents, MapperEvents):
- pass
- dispatch = event.dispatcher(HoldMapperEvents)
- _sessionevents_lifecycle_event_names = set()
- class SessionEvents(event.Events):
- """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 = "SomeSessionOrFactory"
- _dispatch_target = Session
- def _lifecycle_event(fn):
- _sessionevents_lifecycle_event_names.add(fn.__name__)
- return fn
- @classmethod
- def _accept_with(cls, target):
- 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
- else:
- # allows alternate SessionEvents-like-classes to be consulted
- return event.Events._accept_with(target)
- @classmethod
- def _listen(cls, event_key, raw=False, restore_load_context=False, **kw):
- 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, state, *arg, **kw):
- 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
- else:
- target = state
- 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):
- """Intercept statement executions that occur in terms of a :class:`.Session`.
- This event is invoked for all top-level SQL statements invoked
- from the :meth:`_orm.Session.execute` method. As of SQLAlchemy 1.4,
- all ORM queries emitted on behalf of a :class:`_orm.Session` will
- flow through this method, so this event hook provides the single
- point at which ORM queries of all types may be intercepted before
- they are invoked, and additionally to replace their execution with
- a different process.
- 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, transaction):
- """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, transaction):
- """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):
- """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):
- """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):
- """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, previous_transaction):
- """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("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, flush_context, instances):
- """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, flush_context):
- """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, flush_context):
- """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, transaction, connection):
- """Execute after a transaction is begun on a connection
- :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, instance):
- """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, instance):
- """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):
- """Execute after an ORM UPDATE against a WHERE expression has been
- invoked.
- This is called as a result of the :meth:`_query.Query.update` method.
- :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):
- """Execute after ORM DELETE against a WHERE expression has been
- invoked.
- This is called as a result of the :meth:`_query.Query.delete` method.
- :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, instance):
- """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.
- .. versionadded:: 1.1
- .. seealso::
- :ref:`session_lifecycle_events`
- """
- @_lifecycle_event
- def pending_to_transient(self, session, instance):
- """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.
- .. versionadded:: 1.1
- .. seealso::
- :ref:`session_lifecycle_events`
- """
- @_lifecycle_event
- def persistent_to_transient(self, session, instance):
- """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.
- .. versionadded:: 1.1
- .. seealso::
- :ref:`session_lifecycle_events`
- """
- @_lifecycle_event
- def pending_to_persistent(self, session, instance):
- """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.
- .. versionadded:: 1.1
- .. seealso::
- :ref:`session_lifecycle_events`
- """
- @_lifecycle_event
- def detached_to_persistent(self, session, instance):
- """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.
- .. versionadded:: 1.1
- .. seealso::
- :ref:`session_lifecycle_events`
- """
- @_lifecycle_event
- def loaded_as_persistent(self, session, instance):
- """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.
- .. versionadded:: 1.1
- .. seealso::
- :ref:`session_lifecycle_events`
- """
- @_lifecycle_event
- def persistent_to_deleted(self, session, instance):
- """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.
- .. versionadded:: 1.1
- .. seealso::
- :ref:`session_lifecycle_events`
- """
- @_lifecycle_event
- def deleted_to_persistent(self, session, instance):
- """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.
- .. versionadded:: 1.1
- .. seealso::
- :ref:`session_lifecycle_events`
- """
- @_lifecycle_event
- def deleted_to_detached(self, session, instance):
- """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`.
- .. versionadded:: 1.1
- .. seealso::
- :ref:`session_lifecycle_events`
- """
- @_lifecycle_event
- def persistent_to_detached(self, session, instance):
- """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.
- .. versionadded:: 1.1
- .. seealso::
- :ref:`session_lifecycle_events`
- """
- class AttributeEvents(event.Events):
- r"""Define events for object attributes.
- These are typically defined on the class-bound descriptor for the
- target class.
- e.g.::
- 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`::
- 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, dispatch_cls):
- dispatch = event.Events._set_dispatch(cls, dispatch_cls)
- dispatch_cls._active_history = False
- return dispatch
- @classmethod
- def _accept_with(cls, target):
- # TODO: coverage
- if isinstance(target, interfaces.MapperProperty):
- return getattr(target.parent.class_, target.key)
- else:
- return target
- @classmethod
- def _listen(
- cls,
- event_key,
- active_history=False,
- raw=False,
- retval=False,
- propagate=False,
- ):
- target, fn = event_key.dispatch_target, event_key._listen_fn
- if active_history:
- target.dispatch._active_history = True
- if not raw or not retval:
- def wrap(target, *arg):
- if not raw:
- target = target.obj()
- if not retval:
- if arg:
- value = arg[0]
- else:
- value = None
- fn(target, *arg)
- return value
- 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):
- 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, value, initiator):
- """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.
- :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, value, initiator):
- """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.
- :return: No return value is defined for this event.
- .. versionadded:: 1.4.15
- """
- def bulk_replace(self, target, values, initiator):
- """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.
- .. seealso::
- :class:`.AttributeEvents` - background on listener options such
- as propagation to subclasses.
- """
- def remove(self, target, value, initiator):
- """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.
- .. versionchanged:: 0.9.0 the ``initiator`` argument is now
- passed as a :class:`.attributes.Event` object, and may be
- modified by backref handlers within a chain of backref-linked
- events.
- :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, value, oldvalue, initiator):
- """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.
- .. versionchanged:: 0.9.0 the ``initiator`` argument is now
- passed as a :class:`.attributes.Event` object, and may be
- modified by backref handlers within a chain of backref-linked
- events.
- :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, value, dict_):
- 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.
- .. versionadded:: 1.1
- :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.
- """
- def init_collection(self, target, collection, collection_adapter):
- """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.
- .. versionadded:: 1.0.0 :meth:`.AttributeEvents.init_collection`
- and :meth:`.AttributeEvents.dispose_collection` events.
- .. 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, collection, collection_adapter):
- """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.
- .. versionadded:: 1.0.0 the :meth:`.AttributeEvents.init_collection`
- and :meth:`.AttributeEvents.dispose_collection` events.
- .. seealso::
- :class:`.AttributeEvents` - background on listener options such
- as propagation to subclasses.
- """
- def modified(self, target, initiator):
- """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):
- """Represent events within the construction of a :class:`_query.Query`
- object.
- 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):
- """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`
- """
- def before_compile_update(self, query, update_context):
- """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.utcnow()
- 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`
- """
- def before_compile_delete(self, query, delete_context):
- """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, retval=False, bake_ok=False, **kw):
- fn = event_key._listen_fn
- if not retval:
- def wrap(*arg, **kw):
- 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, **kw):
- return fn(*arg, **kw)
- event_key = event_key.with_wrapper(wrap)
- wrap._bake_ok = bake_ok
- event_key.base_listen(**kw)
|