123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440 |
- # orm/session.py
- # Copyright (C) 2005-2024 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
- """Provides the Session class and related utilities."""
- import itertools
- import sys
- import weakref
- from . import attributes
- from . import context
- from . import exc
- from . import identity
- from . import loading
- from . import persistence
- from . import query
- from . import state as statelib
- from .base import _class_to_mapper
- from .base import _none_set
- from .base import _state_mapper
- from .base import instance_str
- from .base import object_mapper
- from .base import object_state
- from .base import state_str
- from .unitofwork import UOWTransaction
- from .. import engine
- from .. import exc as sa_exc
- from .. import sql
- from .. import util
- from ..engine.util import TransactionalContext
- from ..inspection import inspect
- from ..sql import coercions
- from ..sql import dml
- from ..sql import roles
- from ..sql import visitors
- from ..sql.base import CompileState
- from ..sql.selectable import LABEL_STYLE_TABLENAME_PLUS_COL
- __all__ = [
- "Session",
- "SessionTransaction",
- "sessionmaker",
- "ORMExecuteState",
- "close_all_sessions",
- "make_transient",
- "make_transient_to_detached",
- "object_session",
- ]
- _sessions = weakref.WeakValueDictionary()
- """Weak-referencing dictionary of :class:`.Session` objects.
- """
- statelib._sessions = _sessions
- def _state_session(state):
- """Given an :class:`.InstanceState`, return the :class:`.Session`
- associated, if any.
- """
- return state.session
- class _SessionClassMethods(object):
- """Class-level methods for :class:`.Session`, :class:`.sessionmaker`."""
- @classmethod
- @util.deprecated(
- "1.3",
- "The :meth:`.Session.close_all` method is deprecated and will be "
- "removed in a future release. Please refer to "
- ":func:`.session.close_all_sessions`.",
- )
- def close_all(cls):
- """Close *all* sessions in memory."""
- close_all_sessions()
- @classmethod
- @util.preload_module("sqlalchemy.orm.util")
- def identity_key(cls, *args, **kwargs):
- """Return an identity key.
- This is an alias of :func:`.util.identity_key`.
- """
- return util.preloaded.orm_util.identity_key(*args, **kwargs)
- @classmethod
- def object_session(cls, instance):
- """Return the :class:`.Session` to which an object belongs.
- This is an alias of :func:`.object_session`.
- """
- return object_session(instance)
- ACTIVE = util.symbol("ACTIVE")
- PREPARED = util.symbol("PREPARED")
- COMMITTED = util.symbol("COMMITTED")
- DEACTIVE = util.symbol("DEACTIVE")
- CLOSED = util.symbol("CLOSED")
- class ORMExecuteState(util.MemoizedSlots):
- """Represents a call to the :meth:`_orm.Session.execute` method, as passed
- to the :meth:`.SessionEvents.do_orm_execute` event hook.
- .. versionadded:: 1.4
- .. seealso::
- :ref:`session_execute_events` - top level documentation on how
- to use :meth:`_orm.SessionEvents.do_orm_execute`
- """
- __slots__ = (
- "session",
- "statement",
- "parameters",
- "execution_options",
- "local_execution_options",
- "bind_arguments",
- "_compile_state_cls",
- "_starting_event_idx",
- "_events_todo",
- "_update_execution_options",
- )
- def __init__(
- self,
- session,
- statement,
- parameters,
- execution_options,
- bind_arguments,
- compile_state_cls,
- events_todo,
- ):
- self.session = session
- self.statement = statement
- self.parameters = parameters
- self.local_execution_options = execution_options
- self.execution_options = statement._execution_options.union(
- execution_options
- )
- self.bind_arguments = bind_arguments
- self._compile_state_cls = compile_state_cls
- self._events_todo = list(events_todo)
- def _remaining_events(self):
- return self._events_todo[self._starting_event_idx + 1 :]
- def invoke_statement(
- self,
- statement=None,
- params=None,
- execution_options=None,
- bind_arguments=None,
- ):
- """Execute the statement represented by this
- :class:`.ORMExecuteState`, without re-invoking events that have
- already proceeded.
- This method essentially performs a re-entrant execution of the current
- statement for which the :meth:`.SessionEvents.do_orm_execute` event is
- being currently invoked. The use case for this is for event handlers
- that want to override how the ultimate
- :class:`_engine.Result` object is returned, such as for schemes that
- retrieve results from an offline cache or which concatenate results
- from multiple executions.
- When the :class:`_engine.Result` object is returned by the actual
- handler function within :meth:`_orm.SessionEvents.do_orm_execute` and
- is propagated to the calling
- :meth:`_orm.Session.execute` method, the remainder of the
- :meth:`_orm.Session.execute` method is preempted and the
- :class:`_engine.Result` object is returned to the caller of
- :meth:`_orm.Session.execute` immediately.
- :param statement: optional statement to be invoked, in place of the
- statement currently represented by :attr:`.ORMExecuteState.statement`.
- :param params: optional dictionary of parameters which will be merged
- into the existing :attr:`.ORMExecuteState.parameters` of this
- :class:`.ORMExecuteState`.
- :param execution_options: optional dictionary of execution options
- will be merged into the existing
- :attr:`.ORMExecuteState.execution_options` of this
- :class:`.ORMExecuteState`.
- :param bind_arguments: optional dictionary of bind_arguments
- which will be merged amongst the current
- :attr:`.ORMExecuteState.bind_arguments`
- of this :class:`.ORMExecuteState`.
- :return: a :class:`_engine.Result` object with ORM-level results.
- .. seealso::
- :ref:`do_orm_execute_re_executing` - background and examples on the
- appropriate usage of :meth:`_orm.ORMExecuteState.invoke_statement`.
- """
- if statement is None:
- statement = self.statement
- _bind_arguments = dict(self.bind_arguments)
- if bind_arguments:
- _bind_arguments.update(bind_arguments)
- _bind_arguments["_sa_skip_events"] = True
- if params:
- _params = dict(self.parameters)
- _params.update(params)
- else:
- _params = self.parameters
- _execution_options = self.local_execution_options
- if execution_options:
- _execution_options = _execution_options.union(execution_options)
- return self.session.execute(
- statement,
- _params,
- _execution_options,
- _bind_arguments,
- _parent_execute_state=self,
- )
- @property
- def bind_mapper(self):
- """Return the :class:`_orm.Mapper` that is the primary "bind" mapper.
- For an :class:`_orm.ORMExecuteState` object invoking an ORM
- statement, that is, the :attr:`_orm.ORMExecuteState.is_orm_statement`
- attribute is ``True``, this attribute will return the
- :class:`_orm.Mapper` that is considered to be the "primary" mapper
- of the statement. The term "bind mapper" refers to the fact that
- a :class:`_orm.Session` object may be "bound" to multiple
- :class:`_engine.Engine` objects keyed to mapped classes, and the
- "bind mapper" determines which of those :class:`_engine.Engine` objects
- would be selected.
- For a statement that is invoked against a single mapped class,
- :attr:`_orm.ORMExecuteState.bind_mapper` is intended to be a reliable
- way of getting this mapper.
- .. versionadded:: 1.4.0b2
- .. seealso::
- :attr:`_orm.ORMExecuteState.all_mappers`
- """
- return self.bind_arguments.get("mapper", None)
- @property
- def all_mappers(self):
- """Return a sequence of all :class:`_orm.Mapper` objects that are
- involved at the top level of this statement.
- By "top level" we mean those :class:`_orm.Mapper` objects that would
- be represented in the result set rows for a :func:`_sql.select`
- query, or for a :func:`_dml.update` or :func:`_dml.delete` query,
- the mapper that is the main subject of the UPDATE or DELETE.
- .. versionadded:: 1.4.0b2
- .. seealso::
- :attr:`_orm.ORMExecuteState.bind_mapper`
- """
- if not self.is_orm_statement:
- return []
- elif self.is_select:
- result = []
- seen = set()
- for d in self.statement.column_descriptions:
- ent = d["entity"]
- if ent:
- insp = inspect(ent, raiseerr=False)
- if insp and insp.mapper and insp.mapper not in seen:
- seen.add(insp.mapper)
- result.append(insp.mapper)
- return result
- elif self.is_update or self.is_delete:
- return [self.bind_mapper]
- else:
- return []
- @property
- def is_orm_statement(self):
- """return True if the operation is an ORM statement.
- This indicates that the select(), update(), or delete() being
- invoked contains ORM entities as subjects. For a statement
- that does not have ORM entities and instead refers only to
- :class:`.Table` metadata, it is invoked as a Core SQL statement
- and no ORM-level automation takes place.
- """
- return self._compile_state_cls is not None
- @property
- def is_select(self):
- """return True if this is a SELECT operation."""
- return self.statement.is_select
- @property
- def is_insert(self):
- """return True if this is an INSERT operation."""
- return self.statement.is_dml and self.statement.is_insert
- @property
- def is_update(self):
- """return True if this is an UPDATE operation."""
- return self.statement.is_dml and self.statement.is_update
- @property
- def is_delete(self):
- """return True if this is a DELETE operation."""
- return self.statement.is_dml and self.statement.is_delete
- @property
- def _is_crud(self):
- return isinstance(self.statement, (dml.Update, dml.Delete))
- def update_execution_options(self, **opts):
- # TODO: no coverage
- self.local_execution_options = self.local_execution_options.union(opts)
- def _orm_compile_options(self):
- if not self.is_select:
- return None
- try:
- opts = self.statement._compile_options
- except AttributeError:
- return None
- if opts.isinstance(context.ORMCompileState.default_compile_options):
- return opts
- else:
- return None
- @property
- def lazy_loaded_from(self):
- """An :class:`.InstanceState` that is using this statement execution
- for a lazy load operation.
- The primary rationale for this attribute is to support the horizontal
- sharding extension, where it is available within specific query
- execution time hooks created by this extension. To that end, the
- attribute is only intended to be meaningful at **query execution
- time**, and importantly not any time prior to that, including query
- compilation time.
- """
- return self.load_options._lazy_loaded_from
- @property
- def loader_strategy_path(self):
- """Return the :class:`.PathRegistry` for the current load path.
- This object represents the "path" in a query along relationships
- when a particular object or collection is being loaded.
- """
- opts = self._orm_compile_options()
- if opts is not None:
- return opts._current_path
- else:
- return None
- @property
- def is_column_load(self):
- """Return True if the operation is refreshing column-oriented
- attributes on an existing ORM object.
- This occurs during operations such as :meth:`_orm.Session.refresh`,
- as well as when an attribute deferred by :func:`_orm.defer` is
- being loaded, or an attribute that was expired either directly
- by :meth:`_orm.Session.expire` or via a commit operation is being
- loaded.
- Handlers will very likely not want to add any options to queries
- when such an operation is occurring as the query should be a straight
- primary key fetch which should not have any additional WHERE criteria,
- and loader options travelling with the instance
- will have already been added to the query.
- .. versionadded:: 1.4.0b2
- .. seealso::
- :attr:`_orm.ORMExecuteState.is_relationship_load`
- """
- opts = self._orm_compile_options()
- return opts is not None and opts._for_refresh_state
- @property
- def is_relationship_load(self):
- """Return True if this load is loading objects on behalf of a
- relationship.
- This means, the loader in effect is either a LazyLoader,
- SelectInLoader, SubqueryLoader, or similar, and the entire
- SELECT statement being emitted is on behalf of a relationship
- load.
- Handlers will very likely not want to add any options to queries
- when such an operation is occurring, as loader options are already
- capable of being propagated to relationship loaders and should
- be already present.
- .. seealso::
- :attr:`_orm.ORMExecuteState.is_column_load`
- """
- opts = self._orm_compile_options()
- if opts is None:
- return False
- path = self.loader_strategy_path
- return path is not None and not path.is_root
- @property
- def load_options(self):
- """Return the load_options that will be used for this execution."""
- if not self.is_select:
- raise sa_exc.InvalidRequestError(
- "This ORM execution is not against a SELECT statement "
- "so there are no load options."
- )
- return self.execution_options.get(
- "_sa_orm_load_options", context.QueryContext.default_load_options
- )
- @property
- def update_delete_options(self):
- """Return the update_delete_options that will be used for this
- execution."""
- if not self._is_crud:
- raise sa_exc.InvalidRequestError(
- "This ORM execution is not against an UPDATE or DELETE "
- "statement so there are no update options."
- )
- return self.execution_options.get(
- "_sa_orm_update_options",
- persistence.BulkUDCompileState.default_update_options,
- )
- @property
- def user_defined_options(self):
- """The sequence of :class:`.UserDefinedOptions` that have been
- associated with the statement being invoked.
- """
- return [
- opt
- for opt in self.statement._with_options
- if not opt._is_compile_state and not opt._is_legacy_option
- ]
- class SessionTransaction(TransactionalContext):
- """A :class:`.Session`-level transaction.
- :class:`.SessionTransaction` is produced from the
- :meth:`_orm.Session.begin`
- and :meth:`_orm.Session.begin_nested` methods. It's largely an internal
- object that in modern use provides a context manager for session
- transactions.
- Documentation on interacting with :class:`_orm.SessionTransaction` is
- at: :ref:`unitofwork_transaction`.
- .. versionchanged:: 1.4 The scoping and API methods to work with the
- :class:`_orm.SessionTransaction` object directly have been simplified.
- .. seealso::
- :ref:`unitofwork_transaction`
- :meth:`.Session.begin`
- :meth:`.Session.begin_nested`
- :meth:`.Session.rollback`
- :meth:`.Session.commit`
- :meth:`.Session.in_transaction`
- :meth:`.Session.in_nested_transaction`
- :meth:`.Session.get_transaction`
- :meth:`.Session.get_nested_transaction`
- """
- _rollback_exception = None
- def __init__(
- self,
- session,
- parent=None,
- nested=False,
- autobegin=False,
- ):
- TransactionalContext._trans_ctx_check(session)
- self.session = session
- self._connections = {}
- self._parent = parent
- self.nested = nested
- if nested:
- self._previous_nested_transaction = session._nested_transaction
- self._state = ACTIVE
- if not parent and nested:
- raise sa_exc.InvalidRequestError(
- "Can't start a SAVEPOINT transaction when no existing "
- "transaction is in progress"
- )
- self._take_snapshot(autobegin=autobegin)
- # make sure transaction is assigned before we call the
- # dispatch
- self.session._transaction = self
- self.session.dispatch.after_transaction_create(self.session, self)
- @property
- def parent(self):
- """The parent :class:`.SessionTransaction` of this
- :class:`.SessionTransaction`.
- If this attribute is ``None``, indicates this
- :class:`.SessionTransaction` is at the top of the stack, and
- corresponds to a real "COMMIT"/"ROLLBACK"
- block. If non-``None``, then this is either a "subtransaction"
- or a "nested" / SAVEPOINT transaction. If the
- :attr:`.SessionTransaction.nested` attribute is ``True``, then
- this is a SAVEPOINT, and if ``False``, indicates this a subtransaction.
- .. versionadded:: 1.0.16 - use ._parent for previous versions
- """
- return self._parent
- nested = False
- """Indicates if this is a nested, or SAVEPOINT, transaction.
- When :attr:`.SessionTransaction.nested` is True, it is expected
- that :attr:`.SessionTransaction.parent` will be True as well.
- """
- @property
- def is_active(self):
- return self.session is not None and self._state is ACTIVE
- def _assert_active(
- self,
- prepared_ok=False,
- rollback_ok=False,
- deactive_ok=False,
- closed_msg="This transaction is closed",
- ):
- if self._state is COMMITTED:
- raise sa_exc.InvalidRequestError(
- "This session is in 'committed' state; no further "
- "SQL can be emitted within this transaction."
- )
- elif self._state is PREPARED:
- if not prepared_ok:
- raise sa_exc.InvalidRequestError(
- "This session is in 'prepared' state; no further "
- "SQL can be emitted within this transaction."
- )
- elif self._state is DEACTIVE:
- if not deactive_ok and not rollback_ok:
- if self._rollback_exception:
- raise sa_exc.PendingRollbackError(
- "This Session's transaction has been rolled back "
- "due to a previous exception during flush."
- " To begin a new transaction with this Session, "
- "first issue Session.rollback()."
- " Original exception was: %s"
- % self._rollback_exception,
- code="7s2a",
- )
- elif not deactive_ok:
- raise sa_exc.InvalidRequestError(
- "This session is in 'inactive' state, due to the "
- "SQL transaction being rolled back; no further "
- "SQL can be emitted within this transaction."
- )
- elif self._state is CLOSED:
- raise sa_exc.ResourceClosedError(closed_msg)
- @property
- def _is_transaction_boundary(self):
- return self.nested or not self._parent
- def connection(self, bindkey, execution_options=None, **kwargs):
- self._assert_active()
- bind = self.session.get_bind(bindkey, **kwargs)
- return self._connection_for_bind(bind, execution_options)
- def _begin(self, nested=False):
- self._assert_active()
- return SessionTransaction(self.session, self, nested=nested)
- def _iterate_self_and_parents(self, upto=None):
- current = self
- result = ()
- while current:
- result += (current,)
- if current._parent is upto:
- break
- elif current._parent is None:
- raise sa_exc.InvalidRequestError(
- "Transaction %s is not on the active transaction list"
- % (upto)
- )
- else:
- current = current._parent
- return result
- def _take_snapshot(self, autobegin=False):
- if not self._is_transaction_boundary:
- self._new = self._parent._new
- self._deleted = self._parent._deleted
- self._dirty = self._parent._dirty
- self._key_switches = self._parent._key_switches
- return
- if not autobegin and not self.session._flushing:
- self.session.flush()
- self._new = weakref.WeakKeyDictionary()
- self._deleted = weakref.WeakKeyDictionary()
- self._dirty = weakref.WeakKeyDictionary()
- self._key_switches = weakref.WeakKeyDictionary()
- def _restore_snapshot(self, dirty_only=False):
- """Restore the restoration state taken before a transaction began.
- Corresponds to a rollback.
- """
- assert self._is_transaction_boundary
- to_expunge = set(self._new).union(self.session._new)
- self.session._expunge_states(to_expunge, to_transient=True)
- for s, (oldkey, newkey) in self._key_switches.items():
- # we probably can do this conditionally based on
- # if we expunged or not, but safe_discard does that anyway
- self.session.identity_map.safe_discard(s)
- # restore the old key
- s.key = oldkey
- # now restore the object, but only if we didn't expunge
- if s not in to_expunge:
- self.session.identity_map.replace(s)
- for s in set(self._deleted).union(self.session._deleted):
- self.session._update_impl(s, revert_deletion=True)
- assert not self.session._deleted
- for s in self.session.identity_map.all_states():
- if not dirty_only or s.modified or s in self._dirty:
- s._expire(s.dict, self.session.identity_map._modified)
- def _remove_snapshot(self):
- """Remove the restoration state taken before a transaction began.
- Corresponds to a commit.
- """
- assert self._is_transaction_boundary
- if not self.nested and self.session.expire_on_commit:
- for s in self.session.identity_map.all_states():
- s._expire(s.dict, self.session.identity_map._modified)
- statelib.InstanceState._detach_states(
- list(self._deleted), self.session
- )
- self._deleted.clear()
- elif self.nested:
- self._parent._new.update(self._new)
- self._parent._dirty.update(self._dirty)
- self._parent._deleted.update(self._deleted)
- self._parent._key_switches.update(self._key_switches)
- def _connection_for_bind(self, bind, execution_options):
- self._assert_active()
- if bind in self._connections:
- if execution_options:
- util.warn(
- "Connection is already established for the "
- "given bind; execution_options ignored"
- )
- return self._connections[bind][0]
- local_connect = False
- should_commit = True
- if self._parent:
- conn = self._parent._connection_for_bind(bind, execution_options)
- if not self.nested:
- return conn
- else:
- if isinstance(bind, engine.Connection):
- conn = bind
- if conn.engine in self._connections:
- raise sa_exc.InvalidRequestError(
- "Session already has a Connection associated for the "
- "given Connection's Engine"
- )
- else:
- conn = bind.connect()
- local_connect = True
- try:
- if execution_options:
- conn = conn.execution_options(**execution_options)
- if self.session.twophase and self._parent is None:
- transaction = conn.begin_twophase()
- elif self.nested:
- transaction = conn.begin_nested()
- elif conn.in_transaction():
- # if given a future connection already in a transaction, don't
- # commit that transaction unless it is a savepoint
- if conn.in_nested_transaction():
- transaction = conn.get_nested_transaction()
- else:
- transaction = conn.get_transaction()
- should_commit = False
- else:
- transaction = conn.begin()
- except:
- # connection will not not be associated with this Session;
- # close it immediately so that it isn't closed under GC
- if local_connect:
- conn.close()
- raise
- else:
- bind_is_connection = isinstance(bind, engine.Connection)
- self._connections[conn] = self._connections[conn.engine] = (
- conn,
- transaction,
- should_commit,
- not bind_is_connection,
- )
- self.session.dispatch.after_begin(self.session, self, conn)
- return conn
- def prepare(self):
- if self._parent is not None or not self.session.twophase:
- raise sa_exc.InvalidRequestError(
- "'twophase' mode not enabled, or not root transaction; "
- "can't prepare."
- )
- self._prepare_impl()
- def _prepare_impl(self):
- self._assert_active()
- if self._parent is None or self.nested:
- self.session.dispatch.before_commit(self.session)
- stx = self.session._transaction
- if stx is not self:
- for subtransaction in stx._iterate_self_and_parents(upto=self):
- subtransaction.commit()
- if not self.session._flushing:
- for _flush_guard in range(100):
- if self.session._is_clean():
- break
- self.session.flush()
- else:
- raise exc.FlushError(
- "Over 100 subsequent flushes have occurred within "
- "session.commit() - is an after_flush() hook "
- "creating new objects?"
- )
- if self._parent is None and self.session.twophase:
- try:
- for t in set(self._connections.values()):
- t[1].prepare()
- except:
- with util.safe_reraise():
- self.rollback()
- self._state = PREPARED
- def commit(self, _to_root=False):
- self._assert_active(prepared_ok=True)
- if self._state is not PREPARED:
- self._prepare_impl()
- if self._parent is None or self.nested:
- for conn, trans, should_commit, autoclose in set(
- self._connections.values()
- ):
- if should_commit:
- trans.commit()
- self._state = COMMITTED
- self.session.dispatch.after_commit(self.session)
- self._remove_snapshot()
- self.close()
- if _to_root and self._parent:
- return self._parent.commit(_to_root=True)
- return self._parent
- def rollback(self, _capture_exception=False, _to_root=False):
- self._assert_active(prepared_ok=True, rollback_ok=True)
- stx = self.session._transaction
- if stx is not self:
- for subtransaction in stx._iterate_self_and_parents(upto=self):
- subtransaction.close()
- boundary = self
- rollback_err = None
- if self._state in (ACTIVE, PREPARED):
- for transaction in self._iterate_self_and_parents():
- if transaction._parent is None or transaction.nested:
- try:
- for t in set(transaction._connections.values()):
- t[1].rollback()
- transaction._state = DEACTIVE
- self.session.dispatch.after_rollback(self.session)
- except:
- rollback_err = sys.exc_info()
- finally:
- transaction._state = DEACTIVE
- transaction._restore_snapshot(
- dirty_only=transaction.nested
- )
- boundary = transaction
- break
- else:
- transaction._state = DEACTIVE
- sess = self.session
- if not rollback_err and not sess._is_clean():
- # if items were added, deleted, or mutated
- # here, we need to re-restore the snapshot
- util.warn(
- "Session's state has been changed on "
- "a non-active transaction - this state "
- "will be discarded."
- )
- boundary._restore_snapshot(dirty_only=boundary.nested)
- self.close()
- if self._parent and _capture_exception:
- self._parent._rollback_exception = sys.exc_info()[1]
- if rollback_err:
- util.raise_(rollback_err[1], with_traceback=rollback_err[2])
- sess.dispatch.after_soft_rollback(sess, self)
- if _to_root and self._parent:
- return self._parent.rollback(_to_root=True)
- return self._parent
- def close(self, invalidate=False):
- if self.nested:
- self.session._nested_transaction = (
- self._previous_nested_transaction
- )
- self.session._transaction = self._parent
- if self._parent is None:
- for connection, transaction, should_commit, autoclose in set(
- self._connections.values()
- ):
- if invalidate:
- connection.invalidate()
- if should_commit and transaction.is_active:
- transaction.close()
- if autoclose:
- connection.close()
- self._state = CLOSED
- self.session.dispatch.after_transaction_end(self.session, self)
- self.session = None
- self._connections = None
- def _get_subject(self):
- return self.session
- def _transaction_is_active(self):
- return self._state is ACTIVE
- def _transaction_is_closed(self):
- return self._state is CLOSED
- def _rollback_can_be_called(self):
- return self._state not in (COMMITTED, CLOSED)
- class Session(_SessionClassMethods):
- """Manages persistence operations for ORM-mapped objects.
- The Session's usage paradigm is described at :doc:`/orm/session`.
- """
- _is_asyncio = False
- @util.deprecated_params(
- autocommit=(
- "2.0",
- "The :paramref:`.Session.autocommit` parameter is deprecated "
- "and will be removed in SQLAlchemy version 2.0. The "
- ':class:`_orm.Session` now features "autobegin" behavior '
- "such that the :meth:`.Session.begin` method may be called "
- "if a transaction has not yet been started yet. See the section "
- ":ref:`session_explicit_begin` for background.",
- ),
- )
- def __init__(
- self,
- bind=None,
- autoflush=True,
- future=False,
- expire_on_commit=True,
- autocommit=False,
- twophase=False,
- binds=None,
- enable_baked_queries=True,
- info=None,
- query_cls=None,
- ):
- r"""Construct a new Session.
- See also the :class:`.sessionmaker` function which is used to
- generate a :class:`.Session`-producing callable with a given
- set of arguments.
- :param autocommit:
- Defaults to ``False``. When ``True``, the
- :class:`.Session` does not automatically begin transactions for
- individual statement executions, will acquire connections from the
- engine on an as-needed basis, releasing to the connection pool
- after each statement. Flushes will begin and commit (or possibly
- rollback) their own transaction if no transaction is present.
- When using this mode, the
- :meth:`.Session.begin` method may be used to explicitly start
- transactions, but the usual "autobegin" behavior is not present.
- :param autoflush: When ``True``, all query operations will issue a
- :meth:`~.Session.flush` call to this ``Session`` before proceeding.
- This is a convenience feature so that :meth:`~.Session.flush` need
- not be called repeatedly in order for database queries to retrieve
- results. It's typical that ``autoflush`` is used in conjunction
- with ``autocommit=False``. In this scenario, explicit calls to
- :meth:`~.Session.flush` are rarely needed; you usually only need to
- call :meth:`~.Session.commit` (which flushes) to finalize changes.
- .. seealso::
- :ref:`session_flushing` - additional background on autoflush
- :param bind: An optional :class:`_engine.Engine` or
- :class:`_engine.Connection` to
- which this ``Session`` should be bound. When specified, all SQL
- operations performed by this session will execute via this
- connectable.
- :param binds: A dictionary which may specify any number of
- :class:`_engine.Engine` or :class:`_engine.Connection`
- objects as the source of
- connectivity for SQL operations on a per-entity basis. The keys
- of the dictionary consist of any series of mapped classes,
- arbitrary Python classes that are bases for mapped classes,
- :class:`_schema.Table` objects and :class:`_orm.Mapper` objects.
- The
- values of the dictionary are then instances of
- :class:`_engine.Engine`
- or less commonly :class:`_engine.Connection` objects.
- Operations which
- proceed relative to a particular mapped class will consult this
- dictionary for the closest matching entity in order to determine
- which :class:`_engine.Engine` should be used for a particular SQL
- operation. The complete heuristics for resolution are
- described at :meth:`.Session.get_bind`. Usage looks like::
- Session = sessionmaker(binds={
- SomeMappedClass: create_engine('postgresql://engine1'),
- SomeDeclarativeBase: create_engine('postgresql://engine2'),
- some_mapper: create_engine('postgresql://engine3'),
- some_table: create_engine('postgresql://engine4'),
- })
- .. seealso::
- :ref:`session_partitioning`
- :meth:`.Session.bind_mapper`
- :meth:`.Session.bind_table`
- :meth:`.Session.get_bind`
- :param \class_: Specify an alternate class other than
- ``sqlalchemy.orm.session.Session`` which should be used by the
- returned class. This is the only argument that is local to the
- :class:`.sessionmaker` function, and is not sent directly to the
- constructor for ``Session``.
- :param enable_baked_queries: defaults to ``True``. A flag consumed
- by the :mod:`sqlalchemy.ext.baked` extension to determine if
- "baked queries" should be cached, as is the normal operation
- of this extension. When set to ``False``, caching as used by
- this particular extension is disabled.
- .. versionchanged:: 1.4 The ``sqlalchemy.ext.baked`` extension is
- legacy and is not used by any of SQLAlchemy's internals. This
- flag therefore only affects applications that are making explicit
- use of this extension within their own code.
- :param expire_on_commit: Defaults to ``True``. When ``True``, all
- instances will be fully expired after each :meth:`~.commit`,
- so that all attribute/object access subsequent to a completed
- transaction will load from the most recent database state.
- .. seealso::
- :ref:`session_committing`
- :param future: if True, use 2.0 style transactional and engine
- behavior. Future mode includes the following behaviors:
- * The :class:`_orm.Session` will not use "bound" metadata in order
- to locate an :class:`_engine.Engine`; the engine or engines in use
- must be specified to the constructor of :class:`_orm.Session` or
- otherwise be configured against the :class:`_orm.sessionmaker`
- in use
- * The "subtransactions" feature of :meth:`_orm.Session.begin` is
- removed in version 2.0 and is disabled when the future flag is
- set.
- * The behavior of the :paramref:`_orm.relationship.cascade_backrefs`
- flag on a :func:`_orm.relationship` will always assume
- "False" behavior.
- .. versionadded:: 1.4
- .. seealso::
- :ref:`migration_20_toplevel`
- :param info: optional dictionary of arbitrary data to be associated
- with this :class:`.Session`. Is available via the
- :attr:`.Session.info` attribute. Note the dictionary is copied at
- construction time so that modifications to the per-
- :class:`.Session` dictionary will be local to that
- :class:`.Session`.
- :param query_cls: Class which should be used to create new Query
- objects, as returned by the :meth:`~.Session.query` method.
- Defaults to :class:`_query.Query`.
- :param twophase: When ``True``, all transactions will be started as
- a "two phase" transaction, i.e. using the "two phase" semantics
- of the database in use along with an XID. During a
- :meth:`~.commit`, after :meth:`~.flush` has been issued for all
- attached databases, the :meth:`~.TwoPhaseTransaction.prepare`
- method on each database's :class:`.TwoPhaseTransaction` will be
- called. This allows each database to roll back the entire
- transaction, before each transaction is committed.
- """
- self.identity_map = identity.WeakInstanceDict()
- self._new = {} # InstanceState->object, strong refs object
- self._deleted = {} # same
- self.bind = bind
- self.__binds = {}
- self._flushing = False
- self._warn_on_events = False
- self._transaction = None
- self._nested_transaction = None
- self.future = future
- self.hash_key = _new_sessionid()
- self.autoflush = autoflush
- self.expire_on_commit = expire_on_commit
- self.enable_baked_queries = enable_baked_queries
- if autocommit:
- if future:
- raise sa_exc.ArgumentError(
- "Cannot use autocommit mode with future=True."
- )
- self.autocommit = True
- else:
- self.autocommit = False
- self.twophase = twophase
- self._query_cls = query_cls if query_cls else query.Query
- if info:
- self.info.update(info)
- if binds is not None:
- for key, bind in binds.items():
- self._add_bind(key, bind)
- _sessions[self.hash_key] = self
- # used by sqlalchemy.engine.util.TransactionalContext
- _trans_context_manager = None
- connection_callable = None
- def __enter__(self):
- return self
- def __exit__(self, type_, value, traceback):
- self.close()
- @util.contextmanager
- def _maker_context_manager(self):
- with self:
- with self.begin():
- yield self
- @property
- @util.deprecated_20(
- ":attr:`_orm.Session.transaction`",
- alternative="For context manager use, use "
- ":meth:`_orm.Session.begin`. To access "
- "the current root transaction, use "
- ":meth:`_orm.Session.get_transaction`.",
- warn_on_attribute_access=True,
- )
- def transaction(self):
- """The current active or inactive :class:`.SessionTransaction`.
- May be None if no transaction has begun yet.
- .. versionchanged:: 1.4 the :attr:`.Session.transaction` attribute
- is now a read-only descriptor that also may return None if no
- transaction has begun yet.
- """
- return self._legacy_transaction()
- def _legacy_transaction(self):
- if not self.future:
- self._autobegin()
- return self._transaction
- def in_transaction(self):
- """Return True if this :class:`_orm.Session` has begun a transaction.
- .. versionadded:: 1.4
- .. seealso::
- :attr:`_orm.Session.is_active`
- """
- return self._transaction is not None
- def in_nested_transaction(self):
- """Return True if this :class:`_orm.Session` has begun a nested
- transaction, e.g. SAVEPOINT.
- .. versionadded:: 1.4
- """
- return self._nested_transaction is not None
- def get_transaction(self):
- """Return the current root transaction in progress, if any.
- .. versionadded:: 1.4
- """
- trans = self._transaction
- while trans is not None and trans._parent is not None:
- trans = trans._parent
- return trans
- def get_nested_transaction(self):
- """Return the current nested transaction in progress, if any.
- .. versionadded:: 1.4
- """
- return self._nested_transaction
- @util.memoized_property
- def info(self):
- """A user-modifiable dictionary.
- The initial value of this dictionary can be populated using the
- ``info`` argument to the :class:`.Session` constructor or
- :class:`.sessionmaker` constructor or factory methods. The dictionary
- here is always local to this :class:`.Session` and can be modified
- independently of all other :class:`.Session` objects.
- """
- return {}
- def _autobegin(self):
- if not self.autocommit and self._transaction is None:
- trans = SessionTransaction(self, autobegin=True)
- assert self._transaction is trans
- return True
- return False
- @util.deprecated_params(
- subtransactions=(
- "2.0",
- "The :paramref:`_orm.Session.begin.subtransactions` flag is "
- "deprecated and "
- "will be removed in SQLAlchemy version 2.0. See "
- "the documentation at :ref:`session_subtransactions` for "
- "background on a compatible alternative pattern.",
- )
- )
- def begin(self, subtransactions=False, nested=False, _subtrans=False):
- """Begin a transaction, or nested transaction,
- on this :class:`.Session`, if one is not already begun.
- The :class:`_orm.Session` object features **autobegin** behavior,
- so that normally it is not necessary to call the
- :meth:`_orm.Session.begin`
- method explicitly. However, it may be used in order to control
- the scope of when the transactional state is begun.
- When used to begin the outermost transaction, an error is raised
- if this :class:`.Session` is already inside of a transaction.
- :param nested: if True, begins a SAVEPOINT transaction and is
- equivalent to calling :meth:`~.Session.begin_nested`. For
- documentation on SAVEPOINT transactions, please see
- :ref:`session_begin_nested`.
- :param subtransactions: if True, indicates that this
- :meth:`~.Session.begin` can create a "subtransaction".
- :return: the :class:`.SessionTransaction` object. Note that
- :class:`.SessionTransaction`
- acts as a Python context manager, allowing :meth:`.Session.begin`
- to be used in a "with" block. See :ref:`session_autocommit` for
- an example.
- .. seealso::
- :ref:`session_autobegin`
- :ref:`unitofwork_transaction`
- :meth:`.Session.begin_nested`
- """
- if subtransactions and self.future:
- raise NotImplementedError(
- "subtransactions are not implemented in future "
- "Session objects."
- )
- if self._autobegin():
- if not subtransactions and not nested and not _subtrans:
- return self._transaction
- if self._transaction is not None:
- if subtransactions or _subtrans or nested:
- trans = self._transaction._begin(nested=nested)
- assert self._transaction is trans
- if nested:
- self._nested_transaction = trans
- else:
- raise sa_exc.InvalidRequestError(
- "A transaction is already begun on this Session."
- )
- elif not self.autocommit:
- # outermost transaction. must be a not nested and not
- # a subtransaction
- assert not nested and not _subtrans and not subtransactions
- trans = SessionTransaction(self)
- assert self._transaction is trans
- else:
- # legacy autocommit mode
- assert not self.future
- trans = SessionTransaction(self, nested=nested)
- assert self._transaction is trans
- return self._transaction # needed for __enter__/__exit__ hook
- def begin_nested(self):
- """Begin a "nested" transaction on this Session, e.g. SAVEPOINT.
- The target database(s) and associated drivers must support SQL
- SAVEPOINT for this method to function correctly.
- For documentation on SAVEPOINT
- transactions, please see :ref:`session_begin_nested`.
- :return: the :class:`.SessionTransaction` object. Note that
- :class:`.SessionTransaction` acts as a context manager, allowing
- :meth:`.Session.begin_nested` to be used in a "with" block.
- See :ref:`session_begin_nested` for a usage example.
- .. seealso::
- :ref:`session_begin_nested`
- :ref:`pysqlite_serializable` - special workarounds required
- with the SQLite driver in order for SAVEPOINT to work
- correctly.
- """
- return self.begin(nested=True)
- def rollback(self):
- """Rollback the current transaction in progress.
- If no transaction is in progress, this method is a pass-through.
- In :term:`1.x-style` use, this method rolls back the topmost
- database transaction if no nested transactions are in effect, or
- to the current nested transaction if one is in effect.
- When
- :term:`2.0-style` use is in effect via the
- :paramref:`_orm.Session.future` flag, the method always rolls back
- the topmost database transaction, discarding any nested
- transactions that may be in progress.
- .. seealso::
- :ref:`session_rollback`
- :ref:`unitofwork_transaction`
- """
- if self._transaction is None:
- pass
- else:
- self._transaction.rollback(_to_root=self.future)
- def commit(self):
- """Flush pending changes and commit the current transaction.
- When the COMMIT operation is complete, all objects are fully
- :term:`expired`, erasing their internal contents, which will be
- automatically re-loaded when the objects are next accessed. In the
- interim, these objects are in an expired state and will not function if
- they are :term:`detached` from the :class:`.Session`. Additionally,
- this re-load operation is not supported when using asyncio-oriented
- APIs. The :paramref:`.Session.expire_on_commit` parameter may be used
- to disable this behavior.
- When there is no transaction in place for the :class:`.Session`,
- indicating that no operations were invoked on this :class:`.Session`
- since the previous call to :meth:`.Session.commit`, the method will
- begin and commit an internal-only "logical" transaction, that does not
- normally affect the database unless pending flush changes were
- detected, but will still invoke event handlers and object expiration
- rules.
- If :term:`1.x-style` use is in effect and there are currently
- SAVEPOINTs in progress via :meth:`_orm.Session.begin_nested`,
- the operation will release the current SAVEPOINT but not commit
- the outermost database transaction.
- If :term:`2.0-style` use is in effect via the
- :paramref:`_orm.Session.future` flag, the outermost database
- transaction is committed unconditionally, automatically releasing any
- SAVEPOINTs in effect.
- When using legacy "autocommit" mode, this method is only
- valid to call if a transaction is actually in progress, else
- an error is raised. Similarly, when using legacy "subtransactions",
- the method will instead close out the current "subtransaction",
- rather than the actual database transaction, if a transaction
- is in progress.
- .. seealso::
- :ref:`session_committing`
- :ref:`unitofwork_transaction`
- :ref:`asyncio_orm_avoid_lazyloads`
- """
- if self._transaction is None:
- if not self._autobegin():
- raise sa_exc.InvalidRequestError("No transaction is begun.")
- self._transaction.commit(_to_root=self.future)
- def prepare(self):
- """Prepare the current transaction in progress for two phase commit.
- If no transaction is in progress, this method raises an
- :exc:`~sqlalchemy.exc.InvalidRequestError`.
- Only root transactions of two phase sessions can be prepared. If the
- current transaction is not such, an
- :exc:`~sqlalchemy.exc.InvalidRequestError` is raised.
- """
- if self._transaction is None:
- if not self._autobegin():
- raise sa_exc.InvalidRequestError("No transaction is begun.")
- self._transaction.prepare()
- def connection(
- self,
- bind_arguments=None,
- close_with_result=False,
- execution_options=None,
- **kw
- ):
- r"""Return a :class:`_engine.Connection` object corresponding to this
- :class:`.Session` object's transactional state.
- If this :class:`.Session` is configured with ``autocommit=False``,
- either the :class:`_engine.Connection` corresponding to the current
- transaction is returned, or if no transaction is in progress, a new
- one is begun and the :class:`_engine.Connection`
- returned (note that no
- transactional state is established with the DBAPI until the first
- SQL statement is emitted).
- Alternatively, if this :class:`.Session` is configured with
- ``autocommit=True``, an ad-hoc :class:`_engine.Connection` is returned
- using :meth:`_engine.Engine.connect` on the underlying
- :class:`_engine.Engine`.
- Ambiguity in multi-bind or unbound :class:`.Session` objects can be
- resolved through any of the optional keyword arguments. This
- ultimately makes usage of the :meth:`.get_bind` method for resolution.
- :param bind_arguments: dictionary of bind arguments. May include
- "mapper", "bind", "clause", other custom arguments that are passed
- to :meth:`.Session.get_bind`.
- :param bind:
- deprecated; use bind_arguments
- :param mapper:
- deprecated; use bind_arguments
- :param clause:
- deprecated; use bind_arguments
- :param close_with_result: Passed to :meth:`_engine.Engine.connect`,
- indicating the :class:`_engine.Connection` should be considered
- "single use", automatically closing when the first result set is
- closed. This flag only has an effect if this :class:`.Session` is
- configured with ``autocommit=True`` and does not already have a
- transaction in progress.
- .. deprecated:: 1.4 this parameter is deprecated and will be removed
- in SQLAlchemy 2.0
- :param execution_options: a dictionary of execution options that will
- be passed to :meth:`_engine.Connection.execution_options`, **when the
- connection is first procured only**. If the connection is already
- present within the :class:`.Session`, a warning is emitted and
- the arguments are ignored.
- .. seealso::
- :ref:`session_transaction_isolation`
- :param \**kw:
- deprecated; use bind_arguments
- """
- if not bind_arguments:
- bind_arguments = kw
- bind = bind_arguments.pop("bind", None)
- if bind is None:
- bind = self.get_bind(**bind_arguments)
- return self._connection_for_bind(
- bind,
- close_with_result=close_with_result,
- execution_options=execution_options,
- )
- def _connection_for_bind(self, engine, execution_options=None, **kw):
- TransactionalContext._trans_ctx_check(self)
- if self._transaction is not None or self._autobegin():
- return self._transaction._connection_for_bind(
- engine, execution_options
- )
- assert self._transaction is None
- assert self.autocommit
- conn = engine.connect(**kw)
- if execution_options:
- conn = conn.execution_options(**execution_options)
- return conn
- def execute(
- self,
- statement,
- params=None,
- execution_options=util.EMPTY_DICT,
- bind_arguments=None,
- _parent_execute_state=None,
- _add_event=None,
- **kw
- ):
- r"""Execute a SQL expression construct.
- Returns a :class:`_engine.Result` object representing
- results of the statement execution.
- E.g.::
- from sqlalchemy import select
- result = session.execute(
- select(User).where(User.id == 5)
- )
- The API contract of :meth:`_orm.Session.execute` is similar to that
- of :meth:`_future.Connection.execute`, the :term:`2.0 style` version
- of :class:`_future.Connection`.
- .. versionchanged:: 1.4 the :meth:`_orm.Session.execute` method is
- now the primary point of ORM statement execution when using
- :term:`2.0 style` ORM usage.
- :param statement:
- An executable statement (i.e. an :class:`.Executable` expression
- such as :func:`_expression.select`).
- :param params:
- Optional dictionary, or list of dictionaries, containing
- bound parameter values. If a single dictionary, single-row
- execution occurs; if a list of dictionaries, an
- "executemany" will be invoked. The keys in each dictionary
- must correspond to parameter names present in the statement.
- :param execution_options: optional dictionary of execution options,
- which will be associated with the statement execution. This
- dictionary can provide a subset of the options that are accepted
- by :meth:`_engine.Connection.execution_options`, and may also
- provide additional options understood only in an ORM context.
- :param bind_arguments: dictionary of additional arguments to determine
- the bind. May include "mapper", "bind", or other custom arguments.
- Contents of this dictionary are passed to the
- :meth:`.Session.get_bind` method.
- :param mapper:
- deprecated; use the bind_arguments dictionary
- :param bind:
- deprecated; use the bind_arguments dictionary
- :param \**kw:
- deprecated; use the bind_arguments dictionary
- :return: a :class:`_engine.Result` object.
- """
- statement = coercions.expect(roles.StatementRole, statement)
- if kw:
- util.warn_deprecated_20(
- "Passing bind arguments to Session.execute() as keyword "
- "arguments is deprecated and will be removed SQLAlchemy 2.0. "
- "Please use the bind_arguments parameter."
- )
- if not bind_arguments:
- bind_arguments = kw
- else:
- bind_arguments.update(kw)
- elif not bind_arguments:
- bind_arguments = {}
- else:
- bind_arguments = dict(bind_arguments)
- if (
- statement._propagate_attrs.get("compile_state_plugin", None)
- == "orm"
- ):
- # note that even without "future" mode, we need
- compile_state_cls = CompileState._get_plugin_class_for_plugin(
- statement, "orm"
- )
- else:
- compile_state_cls = None
- execution_options = util.coerce_to_immutabledict(execution_options)
- if compile_state_cls is not None:
- (
- statement,
- execution_options,
- ) = compile_state_cls.orm_pre_session_exec(
- self,
- statement,
- params,
- execution_options,
- bind_arguments,
- _parent_execute_state is not None,
- )
- else:
- bind_arguments.setdefault("clause", statement)
- execution_options = execution_options.union(
- {"future_result": True}
- )
- if _parent_execute_state:
- events_todo = _parent_execute_state._remaining_events()
- else:
- events_todo = self.dispatch.do_orm_execute
- if _add_event:
- events_todo = list(events_todo) + [_add_event]
- if events_todo:
- orm_exec_state = ORMExecuteState(
- self,
- statement,
- params,
- execution_options,
- bind_arguments,
- compile_state_cls,
- events_todo,
- )
- for idx, fn in enumerate(events_todo):
- orm_exec_state._starting_event_idx = idx
- result = fn(orm_exec_state)
- if result:
- return result
- statement = orm_exec_state.statement
- execution_options = orm_exec_state.local_execution_options
- bind = self.get_bind(**bind_arguments)
- if self.autocommit:
- # legacy stuff, we can't use future_result w/ autocommit because
- # we rely upon close_with_result, also legacy. it's all
- # interrelated
- conn = self._connection_for_bind(bind, close_with_result=True)
- execution_options = execution_options.union(
- dict(future_result=False)
- )
- else:
- conn = self._connection_for_bind(bind)
- result = conn._execute_20(statement, params or {}, execution_options)
- if compile_state_cls:
- result = compile_state_cls.orm_setup_cursor_result(
- self,
- statement,
- params,
- execution_options,
- bind_arguments,
- result,
- )
- return result
- def scalar(
- self,
- statement,
- params=None,
- execution_options=util.EMPTY_DICT,
- bind_arguments=None,
- **kw
- ):
- """Execute a statement and return a scalar result.
- Usage and parameters are the same as that of
- :meth:`_orm.Session.execute`; the return result is a scalar Python
- value.
- """
- return self.execute(
- statement,
- params=params,
- execution_options=execution_options,
- bind_arguments=bind_arguments,
- **kw
- ).scalar()
- def scalars(
- self,
- statement,
- params=None,
- execution_options=util.EMPTY_DICT,
- bind_arguments=None,
- **kw
- ):
- """Execute a statement and return the results as scalars.
- Usage and parameters are the same as that of
- :meth:`_orm.Session.execute`; the return result is a
- :class:`_result.ScalarResult` filtering object which
- will return single elements rather than :class:`_row.Row` objects.
- :return: a :class:`_result.ScalarResult` object
- .. versionadded:: 1.4.24 Added :meth:`_orm.Session.scalars`
- .. versionadded:: 1.4.26 Added :meth:`_orm.scoped_session.scalars`
- """
- return self.execute(
- statement,
- params=params,
- execution_options=execution_options,
- bind_arguments=bind_arguments,
- **kw
- ).scalars()
- def close(self):
- """Close out the transactional resources and ORM objects used by this
- :class:`_orm.Session`.
- This expunges all ORM objects associated with this
- :class:`_orm.Session`, ends any transaction in progress and
- :term:`releases` any :class:`_engine.Connection` objects which this
- :class:`_orm.Session` itself has checked out from associated
- :class:`_engine.Engine` objects. The operation then leaves the
- :class:`_orm.Session` in a state which it may be used again.
- .. tip::
- The :meth:`_orm.Session.close` method **does not prevent the
- Session from being used again**. The :class:`_orm.Session` itself
- does not actually have a distinct "closed" state; it merely means
- the :class:`_orm.Session` will release all database connections
- and ORM objects.
- .. versionchanged:: 1.4 The :meth:`.Session.close` method does not
- immediately create a new :class:`.SessionTransaction` object;
- instead, the new :class:`.SessionTransaction` is created only if
- the :class:`.Session` is used again for a database operation.
- .. seealso::
- :ref:`session_closing` - detail on the semantics of
- :meth:`_orm.Session.close`
- """
- self._close_impl(invalidate=False)
- def invalidate(self):
- """Close this Session, using connection invalidation.
- This is a variant of :meth:`.Session.close` that will additionally
- ensure that the :meth:`_engine.Connection.invalidate`
- method will be called on each :class:`_engine.Connection` object
- that is currently in use for a transaction (typically there is only
- one connection unless the :class:`_orm.Session` is used with
- multiple engines).
- This can be called when the database is known to be in a state where
- the connections are no longer safe to be used.
- Below illustrates a scenario when using `gevent
- <https://www.gevent.org/>`_, which can produce ``Timeout`` exceptions
- that may mean the underlying connection should be discarded::
- import gevent
- try:
- sess = Session()
- sess.add(User())
- sess.commit()
- except gevent.Timeout:
- sess.invalidate()
- raise
- except:
- sess.rollback()
- raise
- The method additionally does everything that :meth:`_orm.Session.close`
- does, including that all ORM objects are expunged.
- """
- self._close_impl(invalidate=True)
- def _close_impl(self, invalidate):
- self.expunge_all()
- if self._transaction is not None:
- for transaction in self._transaction._iterate_self_and_parents():
- transaction.close(invalidate)
- def expunge_all(self):
- """Remove all object instances from this ``Session``.
- This is equivalent to calling ``expunge(obj)`` on all objects in this
- ``Session``.
- """
- all_states = self.identity_map.all_states() + list(self._new)
- self.identity_map._kill()
- self.identity_map = identity.WeakInstanceDict()
- self._new = {}
- self._deleted = {}
- statelib.InstanceState._detach_states(all_states, self)
- def _add_bind(self, key, bind):
- try:
- insp = inspect(key)
- except sa_exc.NoInspectionAvailable as err:
- if not isinstance(key, type):
- util.raise_(
- sa_exc.ArgumentError(
- "Not an acceptable bind target: %s" % key
- ),
- replace_context=err,
- )
- else:
- self.__binds[key] = bind
- else:
- if insp.is_selectable:
- self.__binds[insp] = bind
- elif insp.is_mapper:
- self.__binds[insp.class_] = bind
- for _selectable in insp._all_tables:
- self.__binds[_selectable] = bind
- else:
- raise sa_exc.ArgumentError(
- "Not an acceptable bind target: %s" % key
- )
- def bind_mapper(self, mapper, bind):
- """Associate a :class:`_orm.Mapper` or arbitrary Python class with a
- "bind", e.g. an :class:`_engine.Engine` or
- :class:`_engine.Connection`.
- The given entity is added to a lookup used by the
- :meth:`.Session.get_bind` method.
- :param mapper: a :class:`_orm.Mapper` object,
- or an instance of a mapped
- class, or any Python class that is the base of a set of mapped
- classes.
- :param bind: an :class:`_engine.Engine` or :class:`_engine.Connection`
- object.
- .. seealso::
- :ref:`session_partitioning`
- :paramref:`.Session.binds`
- :meth:`.Session.bind_table`
- """
- self._add_bind(mapper, bind)
- def bind_table(self, table, bind):
- """Associate a :class:`_schema.Table` with a "bind", e.g. an
- :class:`_engine.Engine`
- or :class:`_engine.Connection`.
- The given :class:`_schema.Table` is added to a lookup used by the
- :meth:`.Session.get_bind` method.
- :param table: a :class:`_schema.Table` object,
- which is typically the target
- of an ORM mapping, or is present within a selectable that is
- mapped.
- :param bind: an :class:`_engine.Engine` or :class:`_engine.Connection`
- object.
- .. seealso::
- :ref:`session_partitioning`
- :paramref:`.Session.binds`
- :meth:`.Session.bind_mapper`
- """
- self._add_bind(table, bind)
- def get_bind(
- self,
- mapper=None,
- clause=None,
- bind=None,
- _sa_skip_events=None,
- _sa_skip_for_implicit_returning=False,
- ):
- """Return a "bind" to which this :class:`.Session` is bound.
- The "bind" is usually an instance of :class:`_engine.Engine`,
- except in the case where the :class:`.Session` has been
- explicitly bound directly to a :class:`_engine.Connection`.
- For a multiply-bound or unbound :class:`.Session`, the
- ``mapper`` or ``clause`` arguments are used to determine the
- appropriate bind to return.
- Note that the "mapper" argument is usually present
- when :meth:`.Session.get_bind` is called via an ORM
- operation such as a :meth:`.Session.query`, each
- individual INSERT/UPDATE/DELETE operation within a
- :meth:`.Session.flush`, call, etc.
- The order of resolution is:
- 1. if mapper given and :paramref:`.Session.binds` is present,
- locate a bind based first on the mapper in use, then
- on the mapped class in use, then on any base classes that are
- present in the ``__mro__`` of the mapped class, from more specific
- superclasses to more general.
- 2. if clause given and ``Session.binds`` is present,
- locate a bind based on :class:`_schema.Table` objects
- found in the given clause present in ``Session.binds``.
- 3. if ``Session.binds`` is present, return that.
- 4. if clause given, attempt to return a bind
- linked to the :class:`_schema.MetaData` ultimately
- associated with the clause.
- 5. if mapper given, attempt to return a bind
- linked to the :class:`_schema.MetaData` ultimately
- associated with the :class:`_schema.Table` or other
- selectable to which the mapper is mapped.
- 6. No bind can be found, :exc:`~sqlalchemy.exc.UnboundExecutionError`
- is raised.
- Note that the :meth:`.Session.get_bind` method can be overridden on
- a user-defined subclass of :class:`.Session` to provide any kind
- of bind resolution scheme. See the example at
- :ref:`session_custom_partitioning`.
- :param mapper:
- Optional :func:`.mapper` mapped class or instance of
- :class:`_orm.Mapper`. The bind can be derived from a
- :class:`_orm.Mapper`
- first by consulting the "binds" map associated with this
- :class:`.Session`, and secondly by consulting the
- :class:`_schema.MetaData`
- associated with the :class:`_schema.Table` to which the
- :class:`_orm.Mapper`
- is mapped for a bind.
- :param clause:
- A :class:`_expression.ClauseElement` (i.e.
- :func:`_expression.select`,
- :func:`_expression.text`,
- etc.). If the ``mapper`` argument is not present or could not
- produce a bind, the given expression construct will be searched
- for a bound element, typically a :class:`_schema.Table`
- associated with
- bound :class:`_schema.MetaData`.
- .. seealso::
- :ref:`session_partitioning`
- :paramref:`.Session.binds`
- :meth:`.Session.bind_mapper`
- :meth:`.Session.bind_table`
- """
- # this function is documented as a subclassing hook, so we have
- # to call this method even if the return is simple
- if bind:
- return bind
- elif not self.__binds and self.bind:
- # simplest and most common case, we have a bind and no
- # per-mapper/table binds, we're done
- return self.bind
- # we don't have self.bind and either have self.__binds
- # or we don't have self.__binds (which is legacy). Look at the
- # mapper and the clause
- if mapper is clause is None:
- if self.bind:
- return self.bind
- else:
- raise sa_exc.UnboundExecutionError(
- "This session is not bound to a single Engine or "
- "Connection, and no context was provided to locate "
- "a binding."
- )
- # look more closely at the mapper.
- if mapper is not None:
- try:
- mapper = inspect(mapper)
- except sa_exc.NoInspectionAvailable as err:
- if isinstance(mapper, type):
- util.raise_(
- exc.UnmappedClassError(mapper),
- replace_context=err,
- )
- else:
- raise
- # match up the mapper or clause in the __binds
- if self.__binds:
- # matching mappers and selectables to entries in the
- # binds dictionary; supported use case.
- if mapper:
- for cls in mapper.class_.__mro__:
- if cls in self.__binds:
- return self.__binds[cls]
- if clause is None:
- clause = mapper.persist_selectable
- if clause is not None:
- plugin_subject = clause._propagate_attrs.get(
- "plugin_subject", None
- )
- if plugin_subject is not None:
- for cls in plugin_subject.mapper.class_.__mro__:
- if cls in self.__binds:
- return self.__binds[cls]
- for obj in visitors.iterate(clause):
- if obj in self.__binds:
- return self.__binds[obj]
- # none of the __binds matched, but we have a fallback bind.
- # return that
- if self.bind:
- return self.bind
- # now we are in legacy territory. looking for "bind" on tables
- # that are via bound metadata. this goes away in 2.0.
- future_msg = ""
- future_code = ""
- if mapper and clause is None:
- clause = mapper.persist_selectable
- if clause is not None:
- if clause.bind:
- if self.future:
- future_msg = (
- " A bind was located via legacy bound metadata, but "
- "since future=True is set on this Session, this "
- "bind is ignored."
- )
- else:
- util.warn_deprecated_20(
- "This Session located a target engine via bound "
- "metadata; as this functionality will be removed in "
- "SQLAlchemy 2.0, an Engine object should be passed "
- "to the Session() constructor directly."
- )
- return clause.bind
- if mapper:
- if mapper.persist_selectable.bind:
- if self.future:
- future_msg = (
- " A bind was located via legacy bound metadata, but "
- "since future=True is set on this Session, this "
- "bind is ignored."
- )
- else:
- util.warn_deprecated_20(
- "This Session located a target engine via bound "
- "metadata; as this functionality will be removed in "
- "SQLAlchemy 2.0, an Engine object should be passed "
- "to the Session() constructor directly."
- )
- return mapper.persist_selectable.bind
- context = []
- if mapper is not None:
- context.append("mapper %s" % mapper)
- if clause is not None:
- context.append("SQL expression")
- raise sa_exc.UnboundExecutionError(
- "Could not locate a bind configured on %s or this Session.%s"
- % (", ".join(context), future_msg),
- code=future_code,
- )
- def query(self, *entities, **kwargs):
- """Return a new :class:`_query.Query` object corresponding to this
- :class:`_orm.Session`.
- """
- return self._query_cls(entities, self, **kwargs)
- def _identity_lookup(
- self,
- mapper,
- primary_key_identity,
- identity_token=None,
- passive=attributes.PASSIVE_OFF,
- lazy_loaded_from=None,
- ):
- """Locate an object in the identity map.
- Given a primary key identity, constructs an identity key and then
- looks in the session's identity map. If present, the object may
- be run through unexpiration rules (e.g. load unloaded attributes,
- check if was deleted).
- e.g.::
- obj = session._identity_lookup(inspect(SomeClass), (1, ))
- :param mapper: mapper in use
- :param primary_key_identity: the primary key we are searching for, as
- a tuple.
- :param identity_token: identity token that should be used to create
- the identity key. Used as is, however overriding subclasses can
- repurpose this in order to interpret the value in a special way,
- such as if None then look among multiple target tokens.
- :param passive: passive load flag passed to
- :func:`.loading.get_from_identity`, which impacts the behavior if
- the object is found; the object may be validated and/or unexpired
- if the flag allows for SQL to be emitted.
- :param lazy_loaded_from: an :class:`.InstanceState` that is
- specifically asking for this identity as a related identity. Used
- for sharding schemes where there is a correspondence between an object
- and a related object being lazy-loaded (or otherwise
- relationship-loaded).
- :return: None if the object is not found in the identity map, *or*
- if the object was unexpired and found to have been deleted.
- if passive flags disallow SQL and the object is expired, returns
- PASSIVE_NO_RESULT. In all other cases the instance is returned.
- .. versionchanged:: 1.4.0 - the :meth:`.Session._identity_lookup`
- method was moved from :class:`_query.Query` to
- :class:`.Session`, to avoid having to instantiate the
- :class:`_query.Query` object.
- """
- key = mapper.identity_key_from_primary_key(
- primary_key_identity, identity_token=identity_token
- )
- return loading.get_from_identity(self, mapper, key, passive)
- @property
- @util.contextmanager
- def no_autoflush(self):
- """Return a context manager that disables autoflush.
- e.g.::
- with session.no_autoflush:
- some_object = SomeClass()
- session.add(some_object)
- # won't autoflush
- some_object.related_thing = session.query(SomeRelated).first()
- Operations that proceed within the ``with:`` block
- will not be subject to flushes occurring upon query
- access. This is useful when initializing a series
- of objects which involve existing database queries,
- where the uncompleted object should not yet be flushed.
- """
- autoflush = self.autoflush
- self.autoflush = False
- try:
- yield self
- finally:
- self.autoflush = autoflush
- def _autoflush(self):
- if self.autoflush and not self._flushing:
- try:
- self.flush()
- except sa_exc.StatementError as e:
- # note we are reraising StatementError as opposed to
- # raising FlushError with "chaining" to remain compatible
- # with code that catches StatementError, IntegrityError,
- # etc.
- e.add_detail(
- "raised as a result of Query-invoked autoflush; "
- "consider using a session.no_autoflush block if this "
- "flush is occurring prematurely"
- )
- util.raise_(e, with_traceback=sys.exc_info()[2])
- def refresh(self, instance, attribute_names=None, with_for_update=None):
- """Expire and refresh attributes on the given instance.
- The selected attributes will first be expired as they would when using
- :meth:`_orm.Session.expire`; then a SELECT statement will be issued to
- the database to refresh column-oriented attributes with the current
- value available in the current transaction.
- :func:`_orm.relationship` oriented attributes will also be immediately
- loaded if they were already eagerly loaded on the object, using the
- same eager loading strategy that they were loaded with originally.
- Unloaded relationship attributes will remain unloaded, as will
- relationship attributes that were originally lazy loaded.
- .. versionadded:: 1.4 - the :meth:`_orm.Session.refresh` method
- can also refresh eagerly loaded attributes.
- .. tip::
- While the :meth:`_orm.Session.refresh` method is capable of
- refreshing both column and relationship oriented attributes, its
- primary focus is on refreshing of local column-oriented attributes
- on a single instance. For more open ended "refresh" functionality,
- including the ability to refresh the attributes on many objects at
- once while having explicit control over relationship loader
- strategies, use the
- :ref:`populate existing <orm_queryguide_populate_existing>` feature
- instead.
- Note that a highly isolated transaction will return the same values as
- were previously read in that same transaction, regardless of changes
- in database state outside of that transaction. Refreshing
- attributes usually only makes sense at the start of a transaction
- where database rows have not yet been accessed.
- :param attribute_names: optional. An iterable collection of
- string attribute names indicating a subset of attributes to
- be refreshed.
- :param with_for_update: optional boolean ``True`` indicating FOR UPDATE
- should be used, or may be a dictionary containing flags to
- indicate a more specific set of FOR UPDATE flags for the SELECT;
- flags should match the parameters of
- :meth:`_query.Query.with_for_update`.
- Supersedes the :paramref:`.Session.refresh.lockmode` parameter.
- .. seealso::
- :ref:`session_expire` - introductory material
- :meth:`.Session.expire`
- :meth:`.Session.expire_all`
- :ref:`orm_queryguide_populate_existing` - allows any ORM query
- to refresh objects as they would be loaded normally.
- """
- try:
- state = attributes.instance_state(instance)
- except exc.NO_STATE as err:
- util.raise_(
- exc.UnmappedInstanceError(instance),
- replace_context=err,
- )
- self._expire_state(state, attribute_names)
- if with_for_update == {}:
- raise sa_exc.ArgumentError(
- "with_for_update should be the boolean value "
- "True, or a dictionary with options. "
- "A blank dictionary is ambiguous."
- )
- with_for_update = query.ForUpdateArg._from_argument(with_for_update)
- stmt = sql.select(object_mapper(instance))
- if (
- loading.load_on_ident(
- self,
- stmt,
- state.key,
- refresh_state=state,
- with_for_update=with_for_update,
- only_load_props=attribute_names,
- )
- is None
- ):
- raise sa_exc.InvalidRequestError(
- "Could not refresh instance '%s'" % instance_str(instance)
- )
- def expire_all(self):
- """Expires all persistent instances within this Session.
- When any attributes on a persistent instance is next accessed,
- a query will be issued using the
- :class:`.Session` object's current transactional context in order to
- load all expired attributes for the given instance. Note that
- a highly isolated transaction will return the same values as were
- previously read in that same transaction, regardless of changes
- in database state outside of that transaction.
- To expire individual objects and individual attributes
- on those objects, use :meth:`Session.expire`.
- The :class:`.Session` object's default behavior is to
- expire all state whenever the :meth:`Session.rollback`
- or :meth:`Session.commit` methods are called, so that new
- state can be loaded for the new transaction. For this reason,
- calling :meth:`Session.expire_all` should not be needed when
- autocommit is ``False``, assuming the transaction is isolated.
- .. seealso::
- :ref:`session_expire` - introductory material
- :meth:`.Session.expire`
- :meth:`.Session.refresh`
- :meth:`_orm.Query.populate_existing`
- """
- for state in self.identity_map.all_states():
- state._expire(state.dict, self.identity_map._modified)
- def expire(self, instance, attribute_names=None):
- """Expire the attributes on an instance.
- Marks the attributes of an instance as out of date. When an expired
- attribute is next accessed, a query will be issued to the
- :class:`.Session` object's current transactional context in order to
- load all expired attributes for the given instance. Note that
- a highly isolated transaction will return the same values as were
- previously read in that same transaction, regardless of changes
- in database state outside of that transaction.
- To expire all objects in the :class:`.Session` simultaneously,
- use :meth:`Session.expire_all`.
- The :class:`.Session` object's default behavior is to
- expire all state whenever the :meth:`Session.rollback`
- or :meth:`Session.commit` methods are called, so that new
- state can be loaded for the new transaction. For this reason,
- calling :meth:`Session.expire` only makes sense for the specific
- case that a non-ORM SQL statement was emitted in the current
- transaction.
- :param instance: The instance to be refreshed.
- :param attribute_names: optional list of string attribute names
- indicating a subset of attributes to be expired.
- .. seealso::
- :ref:`session_expire` - introductory material
- :meth:`.Session.expire`
- :meth:`.Session.refresh`
- :meth:`_orm.Query.populate_existing`
- """
- try:
- state = attributes.instance_state(instance)
- except exc.NO_STATE as err:
- util.raise_(
- exc.UnmappedInstanceError(instance),
- replace_context=err,
- )
- self._expire_state(state, attribute_names)
- def _expire_state(self, state, attribute_names):
- self._validate_persistent(state)
- if attribute_names:
- state._expire_attributes(state.dict, attribute_names)
- else:
- # pre-fetch the full cascade since the expire is going to
- # remove associations
- cascaded = list(
- state.manager.mapper.cascade_iterator("refresh-expire", state)
- )
- self._conditional_expire(state)
- for o, m, st_, dct_ in cascaded:
- self._conditional_expire(st_)
- def _conditional_expire(self, state, autoflush=None):
- """Expire a state if persistent, else expunge if pending"""
- if state.key:
- state._expire(state.dict, self.identity_map._modified)
- elif state in self._new:
- self._new.pop(state)
- state._detach(self)
- def expunge(self, instance):
- """Remove the `instance` from this ``Session``.
- This will free all internal references to the instance. Cascading
- will be applied according to the *expunge* cascade rule.
- """
- try:
- state = attributes.instance_state(instance)
- except exc.NO_STATE as err:
- util.raise_(
- exc.UnmappedInstanceError(instance),
- replace_context=err,
- )
- if state.session_id is not self.hash_key:
- raise sa_exc.InvalidRequestError(
- "Instance %s is not present in this Session" % state_str(state)
- )
- cascaded = list(
- state.manager.mapper.cascade_iterator("expunge", state)
- )
- self._expunge_states([state] + [st_ for o, m, st_, dct_ in cascaded])
- def _expunge_states(self, states, to_transient=False):
- for state in states:
- if state in self._new:
- self._new.pop(state)
- elif self.identity_map.contains_state(state):
- self.identity_map.safe_discard(state)
- self._deleted.pop(state, None)
- elif self._transaction:
- # state is "detached" from being deleted, but still present
- # in the transaction snapshot
- self._transaction._deleted.pop(state, None)
- statelib.InstanceState._detach_states(
- states, self, to_transient=to_transient
- )
- def _register_persistent(self, states):
- """Register all persistent objects from a flush.
- This is used both for pending objects moving to the persistent
- state as well as already persistent objects.
- """
- pending_to_persistent = self.dispatch.pending_to_persistent or None
- for state in states:
- mapper = _state_mapper(state)
- # prevent against last minute dereferences of the object
- obj = state.obj()
- if obj is not None:
- instance_key = mapper._identity_key_from_state(state)
- if (
- _none_set.intersection(instance_key[1])
- and not mapper.allow_partial_pks
- or _none_set.issuperset(instance_key[1])
- ):
- raise exc.FlushError(
- "Instance %s has a NULL identity key. If this is an "
- "auto-generated value, check that the database table "
- "allows generation of new primary key values, and "
- "that the mapped Column object is configured to "
- "expect these generated values. Ensure also that "
- "this flush() is not occurring at an inappropriate "
- "time, such as within a load() event."
- % state_str(state)
- )
- if state.key is None:
- state.key = instance_key
- elif state.key != instance_key:
- # primary key switch. use safe_discard() in case another
- # state has already replaced this one in the identity
- # map (see test/orm/test_naturalpks.py ReversePKsTest)
- self.identity_map.safe_discard(state)
- if state in self._transaction._key_switches:
- orig_key = self._transaction._key_switches[state][0]
- else:
- orig_key = state.key
- self._transaction._key_switches[state] = (
- orig_key,
- instance_key,
- )
- state.key = instance_key
- # there can be an existing state in the identity map
- # that is replaced when the primary keys of two instances
- # are swapped; see test/orm/test_naturalpks.py -> test_reverse
- old = self.identity_map.replace(state)
- if (
- old is not None
- and mapper._identity_key_from_state(old) == instance_key
- and old.obj() is not None
- ):
- util.warn(
- "Identity map already had an identity for %s, "
- "replacing it with newly flushed object. Are there "
- "load operations occurring inside of an event handler "
- "within the flush?" % (instance_key,)
- )
- state._orphaned_outside_of_session = False
- statelib.InstanceState._commit_all_states(
- ((state, state.dict) for state in states), self.identity_map
- )
- self._register_altered(states)
- if pending_to_persistent is not None:
- for state in states.intersection(self._new):
- pending_to_persistent(self, state)
- # remove from new last, might be the last strong ref
- for state in set(states).intersection(self._new):
- self._new.pop(state)
- def _register_altered(self, states):
- if self._transaction:
- for state in states:
- if state in self._new:
- self._transaction._new[state] = True
- else:
- self._transaction._dirty[state] = True
- def _remove_newly_deleted(self, states):
- persistent_to_deleted = self.dispatch.persistent_to_deleted or None
- for state in states:
- if self._transaction:
- self._transaction._deleted[state] = True
- if persistent_to_deleted is not None:
- # get a strong reference before we pop out of
- # self._deleted
- obj = state.obj() # noqa
- self.identity_map.safe_discard(state)
- self._deleted.pop(state, None)
- state._deleted = True
- # can't call state._detach() here, because this state
- # is still in the transaction snapshot and needs to be
- # tracked as part of that
- if persistent_to_deleted is not None:
- persistent_to_deleted(self, state)
- def add(self, instance, _warn=True):
- """Place an object into this :class:`_orm.Session`.
- Objects that are in the :term:`transient` state when passed to the
- :meth:`_orm.Session.add` method will move to the
- :term:`pending` state, until the next flush, at which point they
- will move to the :term:`persistent` state.
- Objects that are in the :term:`detached` state when passed to the
- :meth:`_orm.Session.add` method will move to the :term:`persistent`
- state directly.
- If the transaction used by the :class:`_orm.Session` is rolled back,
- objects which were transient when they were passed to
- :meth:`_orm.Session.add` will be moved back to the
- :term:`transient` state, and will no longer be present within this
- :class:`_orm.Session`.
- .. seealso::
- :meth:`_orm.Session.add_all`
- :ref:`session_adding` - at :ref:`session_basics`
- """
- if _warn and self._warn_on_events:
- self._flush_warning("Session.add()")
- try:
- state = attributes.instance_state(instance)
- except exc.NO_STATE as err:
- util.raise_(
- exc.UnmappedInstanceError(instance),
- replace_context=err,
- )
- self._save_or_update_state(state)
- def add_all(self, instances):
- """Add the given collection of instances to this :class:`_orm.Session`.
- See the documentation for :meth:`_orm.Session.add` for a general
- behavioral description.
- .. seealso::
- :meth:`_orm.Session.add`
- :ref:`session_adding` - at :ref:`session_basics`
- """
- if self._warn_on_events:
- self._flush_warning("Session.add_all()")
- for instance in instances:
- self.add(instance, _warn=False)
- def _save_or_update_state(self, state):
- state._orphaned_outside_of_session = False
- self._save_or_update_impl(state)
- mapper = _state_mapper(state)
- for o, m, st_, dct_ in mapper.cascade_iterator(
- "save-update", state, halt_on=self._contains_state
- ):
- self._save_or_update_impl(st_)
- def delete(self, instance):
- """Mark an instance as deleted.
- The object is assumed to be either :term:`persistent` or
- :term:`detached` when passed; after the method is called, the
- object will remain in the :term:`persistent` state until the next
- flush proceeds. During this time, the object will also be a member
- of the :attr:`_orm.Session.deleted` collection.
- When the next flush proceeds, the object will move to the
- :term:`deleted` state, indicating a ``DELETE`` statement was emitted
- for its row within the current transaction. When the transaction
- is successfully committed,
- the deleted object is moved to the :term:`detached` state and is
- no longer present within this :class:`_orm.Session`.
- .. seealso::
- :ref:`session_deleting` - at :ref:`session_basics`
- """
- if self._warn_on_events:
- self._flush_warning("Session.delete()")
- try:
- state = attributes.instance_state(instance)
- except exc.NO_STATE as err:
- util.raise_(
- exc.UnmappedInstanceError(instance),
- replace_context=err,
- )
- self._delete_impl(state, instance, head=True)
- def _delete_impl(self, state, obj, head):
- if state.key is None:
- if head:
- raise sa_exc.InvalidRequestError(
- "Instance '%s' is not persisted" % state_str(state)
- )
- else:
- return
- to_attach = self._before_attach(state, obj)
- if state in self._deleted:
- return
- self.identity_map.add(state)
- if to_attach:
- self._after_attach(state, obj)
- if head:
- # grab the cascades before adding the item to the deleted list
- # so that autoflush does not delete the item
- # the strong reference to the instance itself is significant here
- cascade_states = list(
- state.manager.mapper.cascade_iterator("delete", state)
- )
- self._deleted[state] = obj
- if head:
- for o, m, st_, dct_ in cascade_states:
- self._delete_impl(st_, o, False)
- def get(
- self,
- entity,
- ident,
- options=None,
- populate_existing=False,
- with_for_update=None,
- identity_token=None,
- execution_options=None,
- ):
- """Return an instance based on the given primary key identifier,
- or ``None`` if not found.
- E.g.::
- my_user = session.get(User, 5)
- some_object = session.get(VersionedFoo, (5, 10))
- some_object = session.get(
- VersionedFoo,
- {"id": 5, "version_id": 10}
- )
- .. versionadded:: 1.4 Added :meth:`_orm.Session.get`, which is moved
- from the now deprecated :meth:`_orm.Query.get` method.
- :meth:`_orm.Session.get` is special in that it provides direct
- access to the identity map of the :class:`.Session`.
- If the given primary key identifier is present
- in the local identity map, the object is returned
- directly from this collection and no SQL is emitted,
- unless the object has been marked fully expired.
- If not present,
- a SELECT is performed in order to locate the object.
- :meth:`_orm.Session.get` also will perform a check if
- the object is present in the identity map and
- marked as expired - a SELECT
- is emitted to refresh the object as well as to
- ensure that the row is still present.
- If not, :class:`~sqlalchemy.orm.exc.ObjectDeletedError` is raised.
- :param entity: a mapped class or :class:`.Mapper` indicating the
- type of entity to be loaded.
- :param ident: A scalar, tuple, or dictionary representing the
- primary key. For a composite (e.g. multiple column) primary key,
- a tuple or dictionary should be passed.
- For a single-column primary key, the scalar calling form is typically
- the most expedient. If the primary key of a row is the value "5",
- the call looks like::
- my_object = session.get(SomeClass, 5)
- The tuple form contains primary key values typically in
- the order in which they correspond to the mapped
- :class:`_schema.Table`
- object's primary key columns, or if the
- :paramref:`_orm.Mapper.primary_key` configuration parameter were
- used, in
- the order used for that parameter. For example, if the primary key
- of a row is represented by the integer
- digits "5, 10" the call would look like::
- my_object = session.get(SomeClass, (5, 10))
- The dictionary form should include as keys the mapped attribute names
- corresponding to each element of the primary key. If the mapped class
- has the attributes ``id``, ``version_id`` as the attributes which
- store the object's primary key value, the call would look like::
- my_object = session.get(SomeClass, {"id": 5, "version_id": 10})
- :param options: optional sequence of loader options which will be
- applied to the query, if one is emitted.
- :param populate_existing: causes the method to unconditionally emit
- a SQL query and refresh the object with the newly loaded data,
- regardless of whether or not the object is already present.
- :param with_for_update: optional boolean ``True`` indicating FOR UPDATE
- should be used, or may be a dictionary containing flags to
- indicate a more specific set of FOR UPDATE flags for the SELECT;
- flags should match the parameters of
- :meth:`_query.Query.with_for_update`.
- Supersedes the :paramref:`.Session.refresh.lockmode` parameter.
- :param execution_options: optional dictionary of execution options,
- which will be associated with the query execution if one is emitted.
- This dictionary can provide a subset of the options that are
- accepted by :meth:`_engine.Connection.execution_options`, and may
- also provide additional options understood only in an ORM context.
- .. versionadded:: 1.4.29
- .. seealso::
- :ref:`orm_queryguide_execution_options` - ORM-specific execution
- options
- :return: The object instance, or ``None``.
- """
- return self._get_impl(
- entity,
- ident,
- loading.load_on_pk_identity,
- options,
- populate_existing=populate_existing,
- with_for_update=with_for_update,
- identity_token=identity_token,
- execution_options=execution_options,
- )
- def _get_impl(
- self,
- entity,
- primary_key_identity,
- db_load_fn,
- options=None,
- populate_existing=False,
- with_for_update=None,
- identity_token=None,
- execution_options=None,
- ):
- # convert composite types to individual args
- if hasattr(primary_key_identity, "__composite_values__"):
- primary_key_identity = primary_key_identity.__composite_values__()
- mapper = inspect(entity)
- if not mapper or not mapper.is_mapper:
- raise sa_exc.ArgumentError(
- "Expected mapped class or mapper, got: %r" % entity
- )
- is_dict = isinstance(primary_key_identity, dict)
- if not is_dict:
- primary_key_identity = util.to_list(
- primary_key_identity, default=(None,)
- )
- if len(primary_key_identity) != len(mapper.primary_key):
- raise sa_exc.InvalidRequestError(
- "Incorrect number of values in identifier to formulate "
- "primary key for session.get(); primary key columns "
- "are %s" % ",".join("'%s'" % c for c in mapper.primary_key)
- )
- if is_dict:
- pk_synonyms = mapper._pk_synonyms
- if pk_synonyms:
- correct_keys = set(pk_synonyms).intersection(
- primary_key_identity
- )
- if correct_keys:
- primary_key_identity = dict(primary_key_identity)
- for k in correct_keys:
- primary_key_identity[
- pk_synonyms[k]
- ] = primary_key_identity[k]
- try:
- primary_key_identity = list(
- primary_key_identity[prop.key]
- for prop in mapper._identity_key_props
- )
- except KeyError as err:
- util.raise_(
- sa_exc.InvalidRequestError(
- "Incorrect names of values in identifier to formulate "
- "primary key for session.get(); primary key attribute "
- "names are %s (synonym names are also accepted)"
- % ",".join(
- "'%s'" % prop.key
- for prop in mapper._identity_key_props
- )
- ),
- replace_context=err,
- )
- if (
- not populate_existing
- and not mapper.always_refresh
- and with_for_update is None
- ):
- instance = self._identity_lookup(
- mapper, primary_key_identity, identity_token=identity_token
- )
- if instance is not None:
- # reject calls for id in identity map but class
- # mismatch.
- if not issubclass(instance.__class__, mapper.class_):
- return None
- return instance
- elif instance is attributes.PASSIVE_CLASS_MISMATCH:
- return None
- # set_label_style() not strictly necessary, however this will ensure
- # that tablename_colname style is used which at the moment is
- # asserted in a lot of unit tests :)
- load_options = context.QueryContext.default_load_options
- if populate_existing:
- load_options += {"_populate_existing": populate_existing}
- statement = sql.select(mapper).set_label_style(
- LABEL_STYLE_TABLENAME_PLUS_COL
- )
- if with_for_update is not None:
- statement._for_update_arg = query.ForUpdateArg._from_argument(
- with_for_update
- )
- if options:
- statement = statement.options(*options)
- if execution_options:
- statement = statement.execution_options(**execution_options)
- return db_load_fn(
- self,
- statement,
- primary_key_identity,
- load_options=load_options,
- )
- def merge(self, instance, load=True, options=None):
- """Copy the state of a given instance into a corresponding instance
- within this :class:`.Session`.
- :meth:`.Session.merge` examines the primary key attributes of the
- source instance, and attempts to reconcile it with an instance of the
- same primary key in the session. If not found locally, it attempts
- to load the object from the database based on primary key, and if
- none can be located, creates a new instance. The state of each
- attribute on the source instance is then copied to the target
- instance. The resulting target instance is then returned by the
- method; the original source instance is left unmodified, and
- un-associated with the :class:`.Session` if not already.
- This operation cascades to associated instances if the association is
- mapped with ``cascade="merge"``.
- See :ref:`unitofwork_merging` for a detailed discussion of merging.
- .. versionchanged:: 1.1 - :meth:`.Session.merge` will now reconcile
- pending objects with overlapping primary keys in the same way
- as persistent. See :ref:`change_3601` for discussion.
- :param instance: Instance to be merged.
- :param load: Boolean, when False, :meth:`.merge` switches into
- a "high performance" mode which causes it to forego emitting history
- events as well as all database access. This flag is used for
- cases such as transferring graphs of objects into a :class:`.Session`
- from a second level cache, or to transfer just-loaded objects
- into the :class:`.Session` owned by a worker thread or process
- without re-querying the database.
- The ``load=False`` use case adds the caveat that the given
- object has to be in a "clean" state, that is, has no pending changes
- to be flushed - even if the incoming object is detached from any
- :class:`.Session`. This is so that when
- the merge operation populates local attributes and
- cascades to related objects and
- collections, the values can be "stamped" onto the
- target object as is, without generating any history or attribute
- events, and without the need to reconcile the incoming data with
- any existing related objects or collections that might not
- be loaded. The resulting objects from ``load=False`` are always
- produced as "clean", so it is only appropriate that the given objects
- should be "clean" as well, else this suggests a mis-use of the
- method.
- :param options: optional sequence of loader options which will be
- applied to the :meth:`_orm.Session.get` method when the merge
- operation loads the existing version of the object from the database.
- .. versionadded:: 1.4.24
- .. seealso::
- :func:`.make_transient_to_detached` - provides for an alternative
- means of "merging" a single object into the :class:`.Session`
- """
- if self._warn_on_events:
- self._flush_warning("Session.merge()")
- _recursive = {}
- _resolve_conflict_map = {}
- if load:
- # flush current contents if we expect to load data
- self._autoflush()
- object_mapper(instance) # verify mapped
- autoflush = self.autoflush
- try:
- self.autoflush = False
- return self._merge(
- attributes.instance_state(instance),
- attributes.instance_dict(instance),
- load=load,
- options=options,
- _recursive=_recursive,
- _resolve_conflict_map=_resolve_conflict_map,
- )
- finally:
- self.autoflush = autoflush
- def _merge(
- self,
- state,
- state_dict,
- load=True,
- options=None,
- _recursive=None,
- _resolve_conflict_map=None,
- ):
- mapper = _state_mapper(state)
- if state in _recursive:
- return _recursive[state]
- new_instance = False
- key = state.key
- if key is None:
- if state in self._new:
- util.warn(
- "Instance %s is already pending in this Session yet is "
- "being merged again; this is probably not what you want "
- "to do" % state_str(state)
- )
- if not load:
- raise sa_exc.InvalidRequestError(
- "merge() with load=False option does not support "
- "objects transient (i.e. unpersisted) objects. flush() "
- "all changes on mapped instances before merging with "
- "load=False."
- )
- key = mapper._identity_key_from_state(state)
- key_is_persistent = attributes.NEVER_SET not in key[1] and (
- not _none_set.intersection(key[1])
- or (
- mapper.allow_partial_pks
- and not _none_set.issuperset(key[1])
- )
- )
- else:
- key_is_persistent = True
- if key in self.identity_map:
- try:
- merged = self.identity_map[key]
- except KeyError:
- # object was GC'ed right as we checked for it
- merged = None
- else:
- merged = None
- if merged is None:
- if key_is_persistent and key in _resolve_conflict_map:
- merged = _resolve_conflict_map[key]
- elif not load:
- if state.modified:
- raise sa_exc.InvalidRequestError(
- "merge() with load=False option does not support "
- "objects marked as 'dirty'. flush() all changes on "
- "mapped instances before merging with load=False."
- )
- merged = mapper.class_manager.new_instance()
- merged_state = attributes.instance_state(merged)
- merged_state.key = key
- self._update_impl(merged_state)
- new_instance = True
- elif key_is_persistent:
- merged = self.get(
- mapper.class_,
- key[1],
- identity_token=key[2],
- options=options,
- )
- if merged is None:
- merged = mapper.class_manager.new_instance()
- merged_state = attributes.instance_state(merged)
- merged_dict = attributes.instance_dict(merged)
- new_instance = True
- self._save_or_update_state(merged_state)
- else:
- merged_state = attributes.instance_state(merged)
- merged_dict = attributes.instance_dict(merged)
- _recursive[state] = merged
- _resolve_conflict_map[key] = merged
- # check that we didn't just pull the exact same
- # state out.
- if state is not merged_state:
- # version check if applicable
- if mapper.version_id_col is not None:
- existing_version = mapper._get_state_attr_by_column(
- state,
- state_dict,
- mapper.version_id_col,
- passive=attributes.PASSIVE_NO_INITIALIZE,
- )
- merged_version = mapper._get_state_attr_by_column(
- merged_state,
- merged_dict,
- mapper.version_id_col,
- passive=attributes.PASSIVE_NO_INITIALIZE,
- )
- if (
- existing_version is not attributes.PASSIVE_NO_RESULT
- and merged_version is not attributes.PASSIVE_NO_RESULT
- and existing_version != merged_version
- ):
- raise exc.StaleDataError(
- "Version id '%s' on merged state %s "
- "does not match existing version '%s'. "
- "Leave the version attribute unset when "
- "merging to update the most recent version."
- % (
- existing_version,
- state_str(merged_state),
- merged_version,
- )
- )
- merged_state.load_path = state.load_path
- merged_state.load_options = state.load_options
- # since we are copying load_options, we need to copy
- # the callables_ that would have been generated by those
- # load_options.
- # assumes that the callables we put in state.callables_
- # are not instance-specific (which they should not be)
- merged_state._copy_callables(state)
- for prop in mapper.iterate_properties:
- prop.merge(
- self,
- state,
- state_dict,
- merged_state,
- merged_dict,
- load,
- _recursive,
- _resolve_conflict_map,
- )
- if not load:
- # remove any history
- merged_state._commit_all(merged_dict, self.identity_map)
- merged_state.manager.dispatch._sa_event_merge_wo_load(
- merged_state, None
- )
- if new_instance:
- merged_state.manager.dispatch.load(merged_state, None)
- return merged
- def _validate_persistent(self, state):
- if not self.identity_map.contains_state(state):
- raise sa_exc.InvalidRequestError(
- "Instance '%s' is not persistent within this Session"
- % state_str(state)
- )
- def _save_impl(self, state):
- if state.key is not None:
- raise sa_exc.InvalidRequestError(
- "Object '%s' already has an identity - "
- "it can't be registered as pending" % state_str(state)
- )
- obj = state.obj()
- to_attach = self._before_attach(state, obj)
- if state not in self._new:
- self._new[state] = obj
- state.insert_order = len(self._new)
- if to_attach:
- self._after_attach(state, obj)
- def _update_impl(self, state, revert_deletion=False):
- if state.key is None:
- raise sa_exc.InvalidRequestError(
- "Instance '%s' is not persisted" % state_str(state)
- )
- if state._deleted:
- if revert_deletion:
- if not state._attached:
- return
- del state._deleted
- else:
- raise sa_exc.InvalidRequestError(
- "Instance '%s' has been deleted. "
- "Use the make_transient() "
- "function to send this object back "
- "to the transient state." % state_str(state)
- )
- obj = state.obj()
- # check for late gc
- if obj is None:
- return
- to_attach = self._before_attach(state, obj)
- self._deleted.pop(state, None)
- if revert_deletion:
- self.identity_map.replace(state)
- else:
- self.identity_map.add(state)
- if to_attach:
- self._after_attach(state, obj)
- elif revert_deletion:
- self.dispatch.deleted_to_persistent(self, state)
- def _save_or_update_impl(self, state):
- if state.key is None:
- self._save_impl(state)
- else:
- self._update_impl(state)
- def enable_relationship_loading(self, obj):
- """Associate an object with this :class:`.Session` for related
- object loading.
- .. warning::
- :meth:`.enable_relationship_loading` exists to serve special
- use cases and is not recommended for general use.
- Accesses of attributes mapped with :func:`_orm.relationship`
- will attempt to load a value from the database using this
- :class:`.Session` as the source of connectivity. The values
- will be loaded based on foreign key and primary key values
- present on this object - if not present, then those relationships
- will be unavailable.
- The object will be attached to this session, but will
- **not** participate in any persistence operations; its state
- for almost all purposes will remain either "transient" or
- "detached", except for the case of relationship loading.
- Also note that backrefs will often not work as expected.
- Altering a relationship-bound attribute on the target object
- may not fire off a backref event, if the effective value
- is what was already loaded from a foreign-key-holding value.
- The :meth:`.Session.enable_relationship_loading` method is
- similar to the ``load_on_pending`` flag on :func:`_orm.relationship`.
- Unlike that flag, :meth:`.Session.enable_relationship_loading` allows
- an object to remain transient while still being able to load
- related items.
- To make a transient object associated with a :class:`.Session`
- via :meth:`.Session.enable_relationship_loading` pending, add
- it to the :class:`.Session` using :meth:`.Session.add` normally.
- If the object instead represents an existing identity in the database,
- it should be merged using :meth:`.Session.merge`.
- :meth:`.Session.enable_relationship_loading` does not improve
- behavior when the ORM is used normally - object references should be
- constructed at the object level, not at the foreign key level, so
- that they are present in an ordinary way before flush()
- proceeds. This method is not intended for general use.
- .. seealso::
- :paramref:`_orm.relationship.load_on_pending` - this flag
- allows per-relationship loading of many-to-ones on items that
- are pending.
- :func:`.make_transient_to_detached` - allows for an object to
- be added to a :class:`.Session` without SQL emitted, which then
- will unexpire attributes on access.
- """
- try:
- state = attributes.instance_state(obj)
- except exc.NO_STATE as err:
- util.raise_(
- exc.UnmappedInstanceError(obj),
- replace_context=err,
- )
- to_attach = self._before_attach(state, obj)
- state._load_pending = True
- if to_attach:
- self._after_attach(state, obj)
- def _before_attach(self, state, obj):
- self._autobegin()
- if state.session_id == self.hash_key:
- return False
- if state.session_id and state.session_id in _sessions:
- raise sa_exc.InvalidRequestError(
- "Object '%s' is already attached to session '%s' "
- "(this is '%s')"
- % (state_str(state), state.session_id, self.hash_key)
- )
- self.dispatch.before_attach(self, state)
- return True
- def _after_attach(self, state, obj):
- state.session_id = self.hash_key
- if state.modified and state._strong_obj is None:
- state._strong_obj = obj
- self.dispatch.after_attach(self, state)
- if state.key:
- self.dispatch.detached_to_persistent(self, state)
- else:
- self.dispatch.transient_to_pending(self, state)
- def __contains__(self, instance):
- """Return True if the instance is associated with this session.
- The instance may be pending or persistent within the Session for a
- result of True.
- """
- try:
- state = attributes.instance_state(instance)
- except exc.NO_STATE as err:
- util.raise_(
- exc.UnmappedInstanceError(instance),
- replace_context=err,
- )
- return self._contains_state(state)
- def __iter__(self):
- """Iterate over all pending or persistent instances within this
- Session.
- """
- return iter(
- list(self._new.values()) + list(self.identity_map.values())
- )
- def _contains_state(self, state):
- return state in self._new or self.identity_map.contains_state(state)
- def flush(self, objects=None):
- """Flush all the object changes to the database.
- Writes out all pending object creations, deletions and modifications
- to the database as INSERTs, DELETEs, UPDATEs, etc. Operations are
- automatically ordered by the Session's unit of work dependency
- solver.
- Database operations will be issued in the current transactional
- context and do not affect the state of the transaction, unless an
- error occurs, in which case the entire transaction is rolled back.
- You may flush() as often as you like within a transaction to move
- changes from Python to the database's transaction buffer.
- For ``autocommit`` Sessions with no active manual transaction, flush()
- will create a transaction on the fly that surrounds the entire set of
- operations into the flush.
- :param objects: Optional; restricts the flush operation to operate
- only on elements that are in the given collection.
- This feature is for an extremely narrow set of use cases where
- particular objects may need to be operated upon before the
- full flush() occurs. It is not intended for general use.
- """
- if self._flushing:
- raise sa_exc.InvalidRequestError("Session is already flushing")
- if self._is_clean():
- return
- try:
- self._flushing = True
- self._flush(objects)
- finally:
- self._flushing = False
- def _flush_warning(self, method):
- util.warn(
- "Usage of the '%s' operation is not currently supported "
- "within the execution stage of the flush process. "
- "Results may not be consistent. Consider using alternative "
- "event listeners or connection-level operations instead." % method
- )
- def _is_clean(self):
- return (
- not self.identity_map.check_modified()
- and not self._deleted
- and not self._new
- )
- def _flush(self, objects=None):
- dirty = self._dirty_states
- if not dirty and not self._deleted and not self._new:
- self.identity_map._modified.clear()
- return
- flush_context = UOWTransaction(self)
- if self.dispatch.before_flush:
- self.dispatch.before_flush(self, flush_context, objects)
- # re-establish "dirty states" in case the listeners
- # added
- dirty = self._dirty_states
- deleted = set(self._deleted)
- new = set(self._new)
- dirty = set(dirty).difference(deleted)
- # create the set of all objects we want to operate upon
- if objects:
- # specific list passed in
- objset = set()
- for o in objects:
- try:
- state = attributes.instance_state(o)
- except exc.NO_STATE as err:
- util.raise_(
- exc.UnmappedInstanceError(o),
- replace_context=err,
- )
- objset.add(state)
- else:
- objset = None
- # store objects whose fate has been decided
- processed = set()
- # put all saves/updates into the flush context. detect top-level
- # orphans and throw them into deleted.
- if objset:
- proc = new.union(dirty).intersection(objset).difference(deleted)
- else:
- proc = new.union(dirty).difference(deleted)
- for state in proc:
- is_orphan = _state_mapper(state)._is_orphan(state)
- is_persistent_orphan = is_orphan and state.has_identity
- if (
- is_orphan
- and not is_persistent_orphan
- and state._orphaned_outside_of_session
- ):
- self._expunge_states([state])
- else:
- _reg = flush_context.register_object(
- state, isdelete=is_persistent_orphan
- )
- assert _reg, "Failed to add object to the flush context!"
- processed.add(state)
- # put all remaining deletes into the flush context.
- if objset:
- proc = deleted.intersection(objset).difference(processed)
- else:
- proc = deleted.difference(processed)
- for state in proc:
- _reg = flush_context.register_object(state, isdelete=True)
- assert _reg, "Failed to add object to the flush context!"
- if not flush_context.has_work:
- return
- flush_context.transaction = transaction = self.begin(_subtrans=True)
- try:
- self._warn_on_events = True
- try:
- flush_context.execute()
- finally:
- self._warn_on_events = False
- self.dispatch.after_flush(self, flush_context)
- flush_context.finalize_flush_changes()
- if not objects and self.identity_map._modified:
- len_ = len(self.identity_map._modified)
- statelib.InstanceState._commit_all_states(
- [
- (state, state.dict)
- for state in self.identity_map._modified
- ],
- instance_dict=self.identity_map,
- )
- util.warn(
- "Attribute history events accumulated on %d "
- "previously clean instances "
- "within inner-flush event handlers have been "
- "reset, and will not result in database updates. "
- "Consider using set_committed_value() within "
- "inner-flush event handlers to avoid this warning." % len_
- )
- # useful assertions:
- # if not objects:
- # assert not self.identity_map._modified
- # else:
- # assert self.identity_map._modified == \
- # self.identity_map._modified.difference(objects)
- self.dispatch.after_flush_postexec(self, flush_context)
- transaction.commit()
- except:
- with util.safe_reraise():
- transaction.rollback(_capture_exception=True)
- def bulk_save_objects(
- self,
- objects,
- return_defaults=False,
- update_changed_only=True,
- preserve_order=True,
- ):
- """Perform a bulk save of the given list of objects.
- The bulk save feature allows mapped objects to be used as the
- source of simple INSERT and UPDATE operations which can be more easily
- grouped together into higher performing "executemany"
- operations; the extraction of data from the objects is also performed
- using a lower-latency process that ignores whether or not attributes
- have actually been modified in the case of UPDATEs, and also ignores
- SQL expressions.
- The objects as given are not added to the session and no additional
- state is established on them. If the
- :paramref:`_orm.Session.bulk_save_objects.return_defaults` flag is set,
- then server-generated primary key values will be assigned to the
- returned objects, but **not server side defaults**; this is a
- limitation in the implementation. If stateful objects are desired,
- please use the standard :meth:`_orm.Session.add_all` approach or
- as an alternative newer mass-insert features such as
- :ref:`orm_dml_returning_objects`.
- .. legacy::
- The bulk save feature allows for a lower-latency INSERT/UPDATE
- of rows at the expense of most other unit-of-work features.
- Features such as object management, relationship handling,
- and SQL clause support are silently omitted in favor of raw
- INSERT/UPDATES of records.
- In SQLAlchemy 2.0, improved versions of the bulk insert/update
- methods are introduced, with clearer behavior and
- documentation, new capabilities, and much better performance.
- For 1.4 use, **please read the list of caveats at**
- :ref:`bulk_operations_caveats` **before using this method, and
- fully test and confirm the functionality of all code developed
- using these systems.**
- :param objects: a sequence of mapped object instances. The mapped
- objects are persisted as is, and are **not** associated with the
- :class:`.Session` afterwards.
- For each object, whether the object is sent as an INSERT or an
- UPDATE is dependent on the same rules used by the :class:`.Session`
- in traditional operation; if the object has the
- :attr:`.InstanceState.key`
- attribute set, then the object is assumed to be "detached" and
- will result in an UPDATE. Otherwise, an INSERT is used.
- In the case of an UPDATE, statements are grouped based on which
- attributes have changed, and are thus to be the subject of each
- SET clause. If ``update_changed_only`` is False, then all
- attributes present within each object are applied to the UPDATE
- statement, which may help in allowing the statements to be grouped
- together into a larger executemany(), and will also reduce the
- overhead of checking history on attributes.
- :param return_defaults: when True, rows that are missing values which
- generate defaults, namely integer primary key defaults and sequences,
- will be inserted **one at a time**, so that the primary key value
- is available. In particular this will allow joined-inheritance
- and other multi-table mappings to insert correctly without the need
- to provide primary key values ahead of time; however,
- :paramref:`.Session.bulk_save_objects.return_defaults` **greatly
- reduces the performance gains** of the method overall. It is strongly
- advised to please use the standard :meth:`_orm.Session.add_all`
- approach.
- :param update_changed_only: when True, UPDATE statements are rendered
- based on those attributes in each state that have logged changes.
- When False, all attributes present are rendered into the SET clause
- with the exception of primary key attributes.
- :param preserve_order: when True, the order of inserts and updates
- matches exactly the order in which the objects are given. When
- False, common types of objects are grouped into inserts
- and updates, to allow for more batching opportunities.
- .. versionadded:: 1.3
- .. seealso::
- :ref:`bulk_operations`
- :meth:`.Session.bulk_insert_mappings`
- :meth:`.Session.bulk_update_mappings`
- """
- obj_states = (attributes.instance_state(obj) for obj in objects)
- if not preserve_order:
- # the purpose of this sort is just so that common mappers
- # and persistence states are grouped together, so that groupby
- # will return a single group for a particular type of mapper.
- # it's not trying to be deterministic beyond that.
- obj_states = sorted(
- obj_states,
- key=lambda state: (id(state.mapper), state.key is not None),
- )
- def grouping_key(state):
- return (state.mapper, state.key is not None)
- for (mapper, isupdate), states in itertools.groupby(
- obj_states, grouping_key
- ):
- self._bulk_save_mappings(
- mapper,
- states,
- isupdate,
- True,
- return_defaults,
- update_changed_only,
- False,
- )
- def bulk_insert_mappings(
- self, mapper, mappings, return_defaults=False, render_nulls=False
- ):
- """Perform a bulk insert of the given list of mapping dictionaries.
- The bulk insert feature allows plain Python dictionaries to be used as
- the source of simple INSERT operations which can be more easily
- grouped together into higher performing "executemany"
- operations. Using dictionaries, there is no "history" or session
- state management features in use, reducing latency when inserting
- large numbers of simple rows.
- The values within the dictionaries as given are typically passed
- without modification into Core :meth:`_expression.Insert` constructs,
- after
- organizing the values within them across the tables to which
- the given mapper is mapped.
- .. versionadded:: 1.0.0
- .. legacy::
- The bulk insert feature allows for a lower-latency INSERT
- of rows at the expense of most other unit-of-work features.
- Features such as object management, relationship handling,
- and SQL clause support are silently omitted in favor of raw
- INSERT of records.
- In SQLAlchemy 2.0, improved versions of the bulk insert/update
- methods are introduced, with clearer behavior and
- documentation, new capabilities, and much better performance.
- For 1.4 use, **please read the list of caveats at**
- :ref:`bulk_operations_caveats` **before using this method, and
- fully test and confirm the functionality of all code developed
- using these systems.**
- :param mapper: a mapped class, or the actual :class:`_orm.Mapper`
- object,
- representing the single kind of object represented within the mapping
- list.
- :param mappings: a sequence of dictionaries, each one containing the
- state of the mapped row to be inserted, in terms of the attribute
- names on the mapped class. If the mapping refers to multiple tables,
- such as a joined-inheritance mapping, each dictionary must contain all
- keys to be populated into all tables.
- :param return_defaults: when True, rows that are missing values which
- generate defaults, namely integer primary key defaults and sequences,
- will be inserted **one at a time**, so that the primary key value
- is available. In particular this will allow joined-inheritance
- and other multi-table mappings to insert correctly without the need
- to provide primary
- key values ahead of time; however,
- :paramref:`.Session.bulk_insert_mappings.return_defaults`
- **greatly reduces the performance gains** of the method overall.
- If the rows
- to be inserted only refer to a single table, then there is no
- reason this flag should be set as the returned default information
- is not used.
- :param render_nulls: When True, a value of ``None`` will result
- in a NULL value being included in the INSERT statement, rather
- than the column being omitted from the INSERT. This allows all
- the rows being INSERTed to have the identical set of columns which
- allows the full set of rows to be batched to the DBAPI. Normally,
- each column-set that contains a different combination of NULL values
- than the previous row must omit a different series of columns from
- the rendered INSERT statement, which means it must be emitted as a
- separate statement. By passing this flag, the full set of rows
- are guaranteed to be batchable into one batch; the cost however is
- that server-side defaults which are invoked by an omitted column will
- be skipped, so care must be taken to ensure that these are not
- necessary.
- .. warning::
- When this flag is set, **server side default SQL values will
- not be invoked** for those columns that are inserted as NULL;
- the NULL value will be sent explicitly. Care must be taken
- to ensure that no server-side default functions need to be
- invoked for the operation as a whole.
- .. versionadded:: 1.1
- .. seealso::
- :ref:`bulk_operations`
- :meth:`.Session.bulk_save_objects`
- :meth:`.Session.bulk_update_mappings`
- """
- self._bulk_save_mappings(
- mapper,
- mappings,
- False,
- False,
- return_defaults,
- False,
- render_nulls,
- )
- def bulk_update_mappings(self, mapper, mappings):
- """Perform a bulk update of the given list of mapping dictionaries.
- The bulk update feature allows plain Python dictionaries to be used as
- the source of simple UPDATE operations which can be more easily
- grouped together into higher performing "executemany"
- operations. Using dictionaries, there is no "history" or session
- state management features in use, reducing latency when updating
- large numbers of simple rows.
- .. versionadded:: 1.0.0
- .. legacy::
- The bulk update feature allows for a lower-latency UPDATE
- of rows at the expense of most other unit-of-work features.
- Features such as object management, relationship handling,
- and SQL clause support are silently omitted in favor of raw
- UPDATES of records.
- In SQLAlchemy 2.0, improved versions of the bulk insert/update
- methods are introduced, with clearer behavior and
- documentation, new capabilities, and much better performance.
- For 1.4 use, **please read the list of caveats at**
- :ref:`bulk_operations_caveats` **before using this method, and
- fully test and confirm the functionality of all code developed
- using these systems.**
- :param mapper: a mapped class, or the actual :class:`_orm.Mapper`
- object,
- representing the single kind of object represented within the mapping
- list.
- :param mappings: a sequence of dictionaries, each one containing the
- state of the mapped row to be updated, in terms of the attribute names
- on the mapped class. If the mapping refers to multiple tables, such
- as a joined-inheritance mapping, each dictionary may contain keys
- corresponding to all tables. All those keys which are present and
- are not part of the primary key are applied to the SET clause of the
- UPDATE statement; the primary key values, which are required, are
- applied to the WHERE clause.
- .. seealso::
- :ref:`bulk_operations`
- :meth:`.Session.bulk_insert_mappings`
- :meth:`.Session.bulk_save_objects`
- """
- self._bulk_save_mappings(
- mapper, mappings, True, False, False, False, False
- )
- def _bulk_save_mappings(
- self,
- mapper,
- mappings,
- isupdate,
- isstates,
- return_defaults,
- update_changed_only,
- render_nulls,
- ):
- mapper = _class_to_mapper(mapper)
- self._flushing = True
- transaction = self.begin(_subtrans=True)
- try:
- if isupdate:
- persistence._bulk_update(
- mapper,
- mappings,
- transaction,
- isstates,
- update_changed_only,
- )
- else:
- persistence._bulk_insert(
- mapper,
- mappings,
- transaction,
- isstates,
- return_defaults,
- render_nulls,
- )
- transaction.commit()
- except:
- with util.safe_reraise():
- transaction.rollback(_capture_exception=True)
- finally:
- self._flushing = False
- def is_modified(self, instance, include_collections=True):
- r"""Return ``True`` if the given instance has locally
- modified attributes.
- This method retrieves the history for each instrumented
- attribute on the instance and performs a comparison of the current
- value to its previously committed value, if any.
- It is in effect a more expensive and accurate
- version of checking for the given instance in the
- :attr:`.Session.dirty` collection; a full test for
- each attribute's net "dirty" status is performed.
- E.g.::
- return session.is_modified(someobject)
- A few caveats to this method apply:
- * Instances present in the :attr:`.Session.dirty` collection may
- report ``False`` when tested with this method. This is because
- the object may have received change events via attribute mutation,
- thus placing it in :attr:`.Session.dirty`, but ultimately the state
- is the same as that loaded from the database, resulting in no net
- change here.
- * Scalar attributes may not have recorded the previously set
- value when a new value was applied, if the attribute was not loaded,
- or was expired, at the time the new value was received - in these
- cases, the attribute is assumed to have a change, even if there is
- ultimately no net change against its database value. SQLAlchemy in
- most cases does not need the "old" value when a set event occurs, so
- it skips the expense of a SQL call if the old value isn't present,
- based on the assumption that an UPDATE of the scalar value is
- usually needed, and in those few cases where it isn't, is less
- expensive on average than issuing a defensive SELECT.
- The "old" value is fetched unconditionally upon set only if the
- attribute container has the ``active_history`` flag set to ``True``.
- This flag is set typically for primary key attributes and scalar
- object references that are not a simple many-to-one. To set this
- flag for any arbitrary mapped column, use the ``active_history``
- argument with :func:`.column_property`.
- :param instance: mapped instance to be tested for pending changes.
- :param include_collections: Indicates if multivalued collections
- should be included in the operation. Setting this to ``False`` is a
- way to detect only local-column based properties (i.e. scalar columns
- or many-to-one foreign keys) that would result in an UPDATE for this
- instance upon flush.
- """
- state = object_state(instance)
- if not state.modified:
- return False
- dict_ = state.dict
- for attr in state.manager.attributes:
- if (
- not include_collections
- and hasattr(attr.impl, "get_collection")
- ) or not hasattr(attr.impl, "get_history"):
- continue
- (added, unchanged, deleted) = attr.impl.get_history(
- state, dict_, passive=attributes.NO_CHANGE
- )
- if added or deleted:
- return True
- else:
- return False
- @property
- def is_active(self):
- """True if this :class:`.Session` not in "partial rollback" state.
- .. versionchanged:: 1.4 The :class:`_orm.Session` no longer begins
- a new transaction immediately, so this attribute will be False
- when the :class:`_orm.Session` is first instantiated.
- "partial rollback" state typically indicates that the flush process
- of the :class:`_orm.Session` has failed, and that the
- :meth:`_orm.Session.rollback` method must be emitted in order to
- fully roll back the transaction.
- If this :class:`_orm.Session` is not in a transaction at all, the
- :class:`_orm.Session` will autobegin when it is first used, so in this
- case :attr:`_orm.Session.is_active` will return True.
- Otherwise, if this :class:`_orm.Session` is within a transaction,
- and that transaction has not been rolled back internally, the
- :attr:`_orm.Session.is_active` will also return True.
- .. seealso::
- :ref:`faq_session_rollback`
- :meth:`_orm.Session.in_transaction`
- """
- if self.autocommit:
- return (
- self._transaction is not None and self._transaction.is_active
- )
- else:
- return self._transaction is None or self._transaction.is_active
- identity_map = None
- """A mapping of object identities to objects themselves.
- Iterating through ``Session.identity_map.values()`` provides
- access to the full set of persistent objects (i.e., those
- that have row identity) currently in the session.
- .. seealso::
- :func:`.identity_key` - helper function to produce the keys used
- in this dictionary.
- """
- @property
- def _dirty_states(self):
- """The set of all persistent states considered dirty.
- This method returns all states that were modified including
- those that were possibly deleted.
- """
- return self.identity_map._dirty_states()
- @property
- def dirty(self):
- """The set of all persistent instances considered dirty.
- E.g.::
- some_mapped_object in session.dirty
- Instances are considered dirty when they were modified but not
- deleted.
- Note that this 'dirty' calculation is 'optimistic'; most
- attribute-setting or collection modification operations will
- mark an instance as 'dirty' and place it in this set, even if
- there is no net change to the attribute's value. At flush
- time, the value of each attribute is compared to its
- previously saved value, and if there's no net change, no SQL
- operation will occur (this is a more expensive operation so
- it's only done at flush time).
- To check if an instance has actionable net changes to its
- attributes, use the :meth:`.Session.is_modified` method.
- """
- return util.IdentitySet(
- [
- state.obj()
- for state in self._dirty_states
- if state not in self._deleted
- ]
- )
- @property
- def deleted(self):
- "The set of all instances marked as 'deleted' within this ``Session``"
- return util.IdentitySet(list(self._deleted.values()))
- @property
- def new(self):
- "The set of all instances marked as 'new' within this ``Session``."
- return util.IdentitySet(list(self._new.values()))
- class sessionmaker(_SessionClassMethods):
- """A configurable :class:`.Session` factory.
- The :class:`.sessionmaker` factory generates new
- :class:`.Session` objects when called, creating them given
- the configurational arguments established here.
- e.g.::
- from sqlalchemy import create_engine
- from sqlalchemy.orm import sessionmaker
- # an Engine, which the Session will use for connection
- # resources
- engine = create_engine('postgresql://scott:tiger@localhost/')
- Session = sessionmaker(engine)
- with Session() as session:
- session.add(some_object)
- session.add(some_other_object)
- session.commit()
- Context manager use is optional; otherwise, the returned
- :class:`_orm.Session` object may be closed explicitly via the
- :meth:`_orm.Session.close` method. Using a
- ``try:/finally:`` block is optional, however will ensure that the close
- takes place even if there are database errors::
- session = Session()
- try:
- session.add(some_object)
- session.add(some_other_object)
- session.commit()
- finally:
- session.close()
- :class:`.sessionmaker` acts as a factory for :class:`_orm.Session`
- objects in the same way as an :class:`_engine.Engine` acts as a factory
- for :class:`_engine.Connection` objects. In this way it also includes
- a :meth:`_orm.sessionmaker.begin` method, that provides a context
- manager which both begins and commits a transaction, as well as closes
- out the :class:`_orm.Session` when complete, rolling back the transaction
- if any errors occur::
- Session = sessionmaker(engine)
- with Session.begin() as session:
- session.add(some_object)
- session.add(some_other_object)
- # commits transaction, closes session
- .. versionadded:: 1.4
- When calling upon :class:`_orm.sessionmaker` to construct a
- :class:`_orm.Session`, keyword arguments may also be passed to the
- method; these arguments will override that of the globally configured
- parameters. Below we use a :class:`_orm.sessionmaker` bound to a certain
- :class:`_engine.Engine` to produce a :class:`_orm.Session` that is instead
- bound to a specific :class:`_engine.Connection` procured from that engine::
- Session = sessionmaker(engine)
- # bind an individual session to a connection
- with engine.connect() as connection:
- with Session(bind=connection) as session:
- # work with session
- The class also includes a method :meth:`_orm.sessionmaker.configure`, which
- can be used to specify additional keyword arguments to the factory, which
- will take effect for subsequent :class:`.Session` objects generated. This
- is usually used to associate one or more :class:`_engine.Engine` objects
- with an existing
- :class:`.sessionmaker` factory before it is first used::
- # application starts, sessionmaker does not have
- # an engine bound yet
- Session = sessionmaker()
- # ... later, when an engine URL is read from a configuration
- # file or other events allow the engine to be created
- engine = create_engine('sqlite:///foo.db')
- Session.configure(bind=engine)
- sess = Session()
- # work with session
- .. seealso::
- :ref:`session_getting` - introductory text on creating
- sessions using :class:`.sessionmaker`.
- """
- def __init__(
- self,
- bind=None,
- class_=Session,
- autoflush=True,
- autocommit=False,
- expire_on_commit=True,
- info=None,
- **kw
- ):
- r"""Construct a new :class:`.sessionmaker`.
- All arguments here except for ``class_`` correspond to arguments
- accepted by :class:`.Session` directly. See the
- :meth:`.Session.__init__` docstring for more details on parameters.
- :param bind: a :class:`_engine.Engine` or other :class:`.Connectable`
- with
- which newly created :class:`.Session` objects will be associated.
- :param class\_: class to use in order to create new :class:`.Session`
- objects. Defaults to :class:`.Session`.
- :param autoflush: The autoflush setting to use with newly created
- :class:`.Session` objects.
- :param autocommit: The autocommit setting to use with newly created
- :class:`.Session` objects.
- :param expire_on_commit=True: the
- :paramref:`_orm.Session.expire_on_commit` setting to use
- with newly created :class:`.Session` objects.
- :param info: optional dictionary of information that will be available
- via :attr:`.Session.info`. Note this dictionary is *updated*, not
- replaced, when the ``info`` parameter is specified to the specific
- :class:`.Session` construction operation.
- :param \**kw: all other keyword arguments are passed to the
- constructor of newly created :class:`.Session` objects.
- """
- kw["bind"] = bind
- kw["autoflush"] = autoflush
- kw["autocommit"] = autocommit
- kw["expire_on_commit"] = expire_on_commit
- if info is not None:
- kw["info"] = info
- self.kw = kw
- # make our own subclass of the given class, so that
- # events can be associated with it specifically.
- self.class_ = type(class_.__name__, (class_,), {})
- def begin(self):
- """Produce a context manager that both provides a new
- :class:`_orm.Session` as well as a transaction that commits.
- e.g.::
- Session = sessionmaker(some_engine)
- with Session.begin() as session:
- session.add(some_object)
- # commits transaction, closes session
- .. versionadded:: 1.4
- """
- session = self()
- return session._maker_context_manager()
- def __call__(self, **local_kw):
- """Produce a new :class:`.Session` object using the configuration
- established in this :class:`.sessionmaker`.
- In Python, the ``__call__`` method is invoked on an object when
- it is "called" in the same way as a function::
- Session = sessionmaker()
- session = Session() # invokes sessionmaker.__call__()
- """
- for k, v in self.kw.items():
- if k == "info" and "info" in local_kw:
- d = v.copy()
- d.update(local_kw["info"])
- local_kw["info"] = d
- else:
- local_kw.setdefault(k, v)
- return self.class_(**local_kw)
- def configure(self, **new_kw):
- """(Re)configure the arguments for this sessionmaker.
- e.g.::
- Session = sessionmaker()
- Session.configure(bind=create_engine('sqlite://'))
- """
- self.kw.update(new_kw)
- def __repr__(self):
- return "%s(class_=%r, %s)" % (
- self.__class__.__name__,
- self.class_.__name__,
- ", ".join("%s=%r" % (k, v) for k, v in self.kw.items()),
- )
- def close_all_sessions():
- """Close all sessions in memory.
- This function consults a global registry of all :class:`.Session` objects
- and calls :meth:`.Session.close` on them, which resets them to a clean
- state.
- This function is not for general use but may be useful for test suites
- within the teardown scheme.
- .. versionadded:: 1.3
- """
- for sess in _sessions.values():
- sess.close()
- def make_transient(instance):
- """Alter the state of the given instance so that it is :term:`transient`.
- .. note::
- :func:`.make_transient` is a special-case function for
- advanced use cases only.
- The given mapped instance is assumed to be in the :term:`persistent` or
- :term:`detached` state. The function will remove its association with any
- :class:`.Session` as well as its :attr:`.InstanceState.identity`. The
- effect is that the object will behave as though it were newly constructed,
- except retaining any attribute / collection values that were loaded at the
- time of the call. The :attr:`.InstanceState.deleted` flag is also reset
- if this object had been deleted as a result of using
- :meth:`.Session.delete`.
- .. warning::
- :func:`.make_transient` does **not** "unexpire" or otherwise eagerly
- load ORM-mapped attributes that are not currently loaded at the time
- the function is called. This includes attributes which:
- * were expired via :meth:`.Session.expire`
- * were expired as the natural effect of committing a session
- transaction, e.g. :meth:`.Session.commit`
- * are normally :term:`lazy loaded` but are not currently loaded
- * are "deferred" via :ref:`deferred` and are not yet loaded
- * were not present in the query which loaded this object, such as that
- which is common in joined table inheritance and other scenarios.
- After :func:`.make_transient` is called, unloaded attributes such
- as those above will normally resolve to the value ``None`` when
- accessed, or an empty collection for a collection-oriented attribute.
- As the object is transient and un-associated with any database
- identity, it will no longer retrieve these values.
- .. seealso::
- :func:`.make_transient_to_detached`
- """
- state = attributes.instance_state(instance)
- s = _state_session(state)
- if s:
- s._expunge_states([state])
- # remove expired state
- state.expired_attributes.clear()
- # remove deferred callables
- if state.callables:
- del state.callables
- if state.key:
- del state.key
- if state._deleted:
- del state._deleted
- def make_transient_to_detached(instance):
- """Make the given transient instance :term:`detached`.
- .. note::
- :func:`.make_transient_to_detached` is a special-case function for
- advanced use cases only.
- All attribute history on the given instance
- will be reset as though the instance were freshly loaded
- from a query. Missing attributes will be marked as expired.
- The primary key attributes of the object, which are required, will be made
- into the "key" of the instance.
- The object can then be added to a session, or merged
- possibly with the load=False flag, at which point it will look
- as if it were loaded that way, without emitting SQL.
- This is a special use case function that differs from a normal
- call to :meth:`.Session.merge` in that a given persistent state
- can be manufactured without any SQL calls.
- .. seealso::
- :func:`.make_transient`
- :meth:`.Session.enable_relationship_loading`
- """
- state = attributes.instance_state(instance)
- if state.session_id or state.key:
- raise sa_exc.InvalidRequestError("Given object must be transient")
- state.key = state.mapper._identity_key_from_state(state)
- if state._deleted:
- del state._deleted
- state._commit_all(state.dict)
- state._expire_attributes(state.dict, state.unloaded_expirable)
- def object_session(instance):
- """Return the :class:`.Session` to which the given instance belongs.
- This is essentially the same as the :attr:`.InstanceState.session`
- accessor. See that attribute for details.
- """
- try:
- state = attributes.instance_state(instance)
- except exc.NO_STATE as err:
- util.raise_(
- exc.UnmappedInstanceError(instance),
- replace_context=err,
- )
- else:
- return _state_session(state)
- _new_sessionid = util.counter()
|