attributes.py 91 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845
  1. # orm/attributes.py
  2. # Copyright (C) 2005-2025 the SQLAlchemy authors and contributors
  3. # <see AUTHORS file>
  4. #
  5. # This module is part of SQLAlchemy and is released under
  6. # the MIT License: https://www.opensource.org/licenses/mit-license.php
  7. # mypy: allow-untyped-defs, allow-untyped-calls
  8. """Defines instrumentation for class attributes and their interaction
  9. with instances.
  10. This module is usually not directly visible to user applications, but
  11. defines a large part of the ORM's interactivity.
  12. """
  13. from __future__ import annotations
  14. import dataclasses
  15. import operator
  16. from typing import Any
  17. from typing import Callable
  18. from typing import cast
  19. from typing import ClassVar
  20. from typing import Dict
  21. from typing import Iterable
  22. from typing import List
  23. from typing import NamedTuple
  24. from typing import Optional
  25. from typing import overload
  26. from typing import Sequence
  27. from typing import Tuple
  28. from typing import Type
  29. from typing import TYPE_CHECKING
  30. from typing import TypeVar
  31. from typing import Union
  32. from . import collections
  33. from . import exc as orm_exc
  34. from . import interfaces
  35. from ._typing import insp_is_aliased_class
  36. from .base import _DeclarativeMapped
  37. from .base import ATTR_EMPTY
  38. from .base import ATTR_WAS_SET
  39. from .base import CALLABLES_OK
  40. from .base import DEFERRED_HISTORY_LOAD
  41. from .base import INCLUDE_PENDING_MUTATIONS # noqa
  42. from .base import INIT_OK
  43. from .base import instance_dict as instance_dict
  44. from .base import instance_state as instance_state
  45. from .base import instance_str
  46. from .base import LOAD_AGAINST_COMMITTED
  47. from .base import LoaderCallableStatus
  48. from .base import manager_of_class as manager_of_class
  49. from .base import Mapped as Mapped # noqa
  50. from .base import NEVER_SET # noqa
  51. from .base import NO_AUTOFLUSH
  52. from .base import NO_CHANGE # noqa
  53. from .base import NO_KEY
  54. from .base import NO_RAISE
  55. from .base import NO_VALUE
  56. from .base import NON_PERSISTENT_OK # noqa
  57. from .base import opt_manager_of_class as opt_manager_of_class
  58. from .base import PASSIVE_CLASS_MISMATCH # noqa
  59. from .base import PASSIVE_NO_FETCH
  60. from .base import PASSIVE_NO_FETCH_RELATED # noqa
  61. from .base import PASSIVE_NO_INITIALIZE
  62. from .base import PASSIVE_NO_RESULT
  63. from .base import PASSIVE_OFF
  64. from .base import PASSIVE_ONLY_PERSISTENT
  65. from .base import PASSIVE_RETURN_NO_VALUE
  66. from .base import PassiveFlag
  67. from .base import RELATED_OBJECT_OK # noqa
  68. from .base import SQL_OK # noqa
  69. from .base import SQLORMExpression
  70. from .base import state_str
  71. from .. import event
  72. from .. import exc
  73. from .. import inspection
  74. from .. import util
  75. from ..event import dispatcher
  76. from ..event import EventTarget
  77. from ..sql import base as sql_base
  78. from ..sql import cache_key
  79. from ..sql import coercions
  80. from ..sql import roles
  81. from ..sql import visitors
  82. from ..sql.cache_key import HasCacheKey
  83. from ..sql.visitors import _TraverseInternalsType
  84. from ..sql.visitors import InternalTraversal
  85. from ..util.typing import Literal
  86. from ..util.typing import Self
  87. from ..util.typing import TypeGuard
  88. if TYPE_CHECKING:
  89. from ._typing import _EntityType
  90. from ._typing import _ExternalEntityType
  91. from ._typing import _InstanceDict
  92. from ._typing import _InternalEntityType
  93. from ._typing import _LoaderCallable
  94. from ._typing import _O
  95. from .collections import _AdaptedCollectionProtocol
  96. from .collections import CollectionAdapter
  97. from .interfaces import MapperProperty
  98. from .relationships import RelationshipProperty
  99. from .state import InstanceState
  100. from .util import AliasedInsp
  101. from .writeonly import WriteOnlyAttributeImpl
  102. from ..event.base import _Dispatch
  103. from ..sql._typing import _ColumnExpressionArgument
  104. from ..sql._typing import _DMLColumnArgument
  105. from ..sql._typing import _InfoType
  106. from ..sql._typing import _PropagateAttrsType
  107. from ..sql.annotation import _AnnotationDict
  108. from ..sql.elements import ColumnElement
  109. from ..sql.elements import Label
  110. from ..sql.operators import OperatorType
  111. from ..sql.selectable import FromClause
  112. _T = TypeVar("_T")
  113. _T_co = TypeVar("_T_co", bound=Any, covariant=True)
  114. _AllPendingType = Sequence[
  115. Tuple[Optional["InstanceState[Any]"], Optional[object]]
  116. ]
  117. _UNKNOWN_ATTR_KEY = object()
  118. @inspection._self_inspects
  119. class QueryableAttribute(
  120. _DeclarativeMapped[_T_co],
  121. SQLORMExpression[_T_co],
  122. interfaces.InspectionAttr,
  123. interfaces.PropComparator[_T_co],
  124. roles.JoinTargetRole,
  125. roles.OnClauseRole,
  126. sql_base.Immutable,
  127. cache_key.SlotsMemoizedHasCacheKey,
  128. util.MemoizedSlots,
  129. EventTarget,
  130. ):
  131. """Base class for :term:`descriptor` objects that intercept
  132. attribute events on behalf of a :class:`.MapperProperty`
  133. object. The actual :class:`.MapperProperty` is accessible
  134. via the :attr:`.QueryableAttribute.property`
  135. attribute.
  136. .. seealso::
  137. :class:`.InstrumentedAttribute`
  138. :class:`.MapperProperty`
  139. :attr:`_orm.Mapper.all_orm_descriptors`
  140. :attr:`_orm.Mapper.attrs`
  141. """
  142. __slots__ = (
  143. "class_",
  144. "key",
  145. "impl",
  146. "comparator",
  147. "property",
  148. "parent",
  149. "expression",
  150. "_of_type",
  151. "_extra_criteria",
  152. "_slots_dispatch",
  153. "_propagate_attrs",
  154. "_doc",
  155. )
  156. is_attribute = True
  157. dispatch: dispatcher[QueryableAttribute[_T_co]]
  158. class_: _ExternalEntityType[Any]
  159. key: str
  160. parententity: _InternalEntityType[Any]
  161. impl: AttributeImpl
  162. comparator: interfaces.PropComparator[_T_co]
  163. _of_type: Optional[_InternalEntityType[Any]]
  164. _extra_criteria: Tuple[ColumnElement[bool], ...]
  165. _doc: Optional[str]
  166. # PropComparator has a __visit_name__ to participate within
  167. # traversals. Disambiguate the attribute vs. a comparator.
  168. __visit_name__ = "orm_instrumented_attribute"
  169. def __init__(
  170. self,
  171. class_: _ExternalEntityType[_O],
  172. key: str,
  173. parententity: _InternalEntityType[_O],
  174. comparator: interfaces.PropComparator[_T_co],
  175. impl: Optional[AttributeImpl] = None,
  176. of_type: Optional[_InternalEntityType[Any]] = None,
  177. extra_criteria: Tuple[ColumnElement[bool], ...] = (),
  178. ):
  179. self.class_ = class_
  180. self.key = key
  181. self._parententity = self.parent = parententity
  182. # this attribute is non-None after mappers are set up, however in the
  183. # interim class manager setup, there's a check for None to see if it
  184. # needs to be populated, so we assign None here leaving the attribute
  185. # in a temporarily not-type-correct state
  186. self.impl = impl # type: ignore
  187. assert comparator is not None
  188. self.comparator = comparator
  189. self._of_type = of_type
  190. self._extra_criteria = extra_criteria
  191. self._doc = None
  192. manager = opt_manager_of_class(class_)
  193. # manager is None in the case of AliasedClass
  194. if manager:
  195. # propagate existing event listeners from
  196. # immediate superclass
  197. for base in manager._bases:
  198. if key in base:
  199. self.dispatch._update(base[key].dispatch)
  200. if base[key].dispatch._active_history:
  201. self.dispatch._active_history = True # type: ignore
  202. _cache_key_traversal = [
  203. ("key", visitors.ExtendedInternalTraversal.dp_string),
  204. ("_parententity", visitors.ExtendedInternalTraversal.dp_multi),
  205. ("_of_type", visitors.ExtendedInternalTraversal.dp_multi),
  206. ("_extra_criteria", visitors.InternalTraversal.dp_clauseelement_list),
  207. ]
  208. def __reduce__(self) -> Any:
  209. # this method is only used in terms of the
  210. # sqlalchemy.ext.serializer extension
  211. return (
  212. _queryable_attribute_unreduce,
  213. (
  214. self.key,
  215. self._parententity.mapper.class_,
  216. self._parententity,
  217. self._parententity.entity,
  218. ),
  219. )
  220. @property
  221. def _impl_uses_objects(self) -> bool:
  222. return self.impl.uses_objects
  223. def get_history(
  224. self, instance: Any, passive: PassiveFlag = PASSIVE_OFF
  225. ) -> History:
  226. return self.impl.get_history(
  227. instance_state(instance), instance_dict(instance), passive
  228. )
  229. @property
  230. def info(self) -> _InfoType:
  231. """Return the 'info' dictionary for the underlying SQL element.
  232. The behavior here is as follows:
  233. * If the attribute is a column-mapped property, i.e.
  234. :class:`.ColumnProperty`, which is mapped directly
  235. to a schema-level :class:`_schema.Column` object, this attribute
  236. will return the :attr:`.SchemaItem.info` dictionary associated
  237. with the core-level :class:`_schema.Column` object.
  238. * If the attribute is a :class:`.ColumnProperty` but is mapped to
  239. any other kind of SQL expression other than a
  240. :class:`_schema.Column`,
  241. the attribute will refer to the :attr:`.MapperProperty.info`
  242. dictionary associated directly with the :class:`.ColumnProperty`,
  243. assuming the SQL expression itself does not have its own ``.info``
  244. attribute (which should be the case, unless a user-defined SQL
  245. construct has defined one).
  246. * If the attribute refers to any other kind of
  247. :class:`.MapperProperty`, including :class:`.Relationship`,
  248. the attribute will refer to the :attr:`.MapperProperty.info`
  249. dictionary associated with that :class:`.MapperProperty`.
  250. * To access the :attr:`.MapperProperty.info` dictionary of the
  251. :class:`.MapperProperty` unconditionally, including for a
  252. :class:`.ColumnProperty` that's associated directly with a
  253. :class:`_schema.Column`, the attribute can be referred to using
  254. :attr:`.QueryableAttribute.property` attribute, as
  255. ``MyClass.someattribute.property.info``.
  256. .. seealso::
  257. :attr:`.SchemaItem.info`
  258. :attr:`.MapperProperty.info`
  259. """
  260. return self.comparator.info
  261. parent: _InternalEntityType[Any]
  262. """Return an inspection instance representing the parent.
  263. This will be either an instance of :class:`_orm.Mapper`
  264. or :class:`.AliasedInsp`, depending upon the nature
  265. of the parent entity which this attribute is associated
  266. with.
  267. """
  268. expression: ColumnElement[_T_co]
  269. """The SQL expression object represented by this
  270. :class:`.QueryableAttribute`.
  271. This will typically be an instance of a :class:`_sql.ColumnElement`
  272. subclass representing a column expression.
  273. """
  274. def _memoized_attr_expression(self) -> ColumnElement[_T]:
  275. annotations: _AnnotationDict
  276. # applies only to Proxy() as used by hybrid.
  277. # currently is an exception to typing rather than feeding through
  278. # non-string keys.
  279. # ideally Proxy() would have a separate set of methods to deal
  280. # with this case.
  281. entity_namespace = self._entity_namespace
  282. assert isinstance(entity_namespace, HasCacheKey)
  283. if self.key is _UNKNOWN_ATTR_KEY:
  284. annotations = {"entity_namespace": entity_namespace}
  285. else:
  286. annotations = {
  287. "proxy_key": self.key,
  288. "proxy_owner": self._parententity,
  289. "entity_namespace": entity_namespace,
  290. }
  291. ce = self.comparator.__clause_element__()
  292. try:
  293. if TYPE_CHECKING:
  294. assert isinstance(ce, ColumnElement)
  295. anno = ce._annotate
  296. except AttributeError as ae:
  297. raise exc.InvalidRequestError(
  298. 'When interpreting attribute "%s" as a SQL expression, '
  299. "expected __clause_element__() to return "
  300. "a ClauseElement object, got: %r" % (self, ce)
  301. ) from ae
  302. else:
  303. return anno(annotations)
  304. def _memoized_attr__propagate_attrs(self) -> _PropagateAttrsType:
  305. # this suits the case in coercions where we don't actually
  306. # call ``__clause_element__()`` but still need to get
  307. # resolved._propagate_attrs. See #6558.
  308. return util.immutabledict(
  309. {
  310. "compile_state_plugin": "orm",
  311. "plugin_subject": self._parentmapper,
  312. }
  313. )
  314. @property
  315. def _entity_namespace(self) -> _InternalEntityType[Any]:
  316. return self._parententity
  317. @property
  318. def _annotations(self) -> _AnnotationDict:
  319. return self.__clause_element__()._annotations
  320. def __clause_element__(self) -> ColumnElement[_T_co]:
  321. return self.expression
  322. @property
  323. def _from_objects(self) -> List[FromClause]:
  324. return self.expression._from_objects
  325. def _bulk_update_tuples(
  326. self, value: Any
  327. ) -> Sequence[Tuple[_DMLColumnArgument, Any]]:
  328. """Return setter tuples for a bulk UPDATE."""
  329. return self.comparator._bulk_update_tuples(value)
  330. def adapt_to_entity(self, adapt_to_entity: AliasedInsp[Any]) -> Self:
  331. assert not self._of_type
  332. return self.__class__(
  333. adapt_to_entity.entity,
  334. self.key,
  335. impl=self.impl,
  336. comparator=self.comparator.adapt_to_entity(adapt_to_entity),
  337. parententity=adapt_to_entity,
  338. )
  339. def of_type(self, entity: _EntityType[_T]) -> QueryableAttribute[_T]:
  340. return QueryableAttribute(
  341. self.class_,
  342. self.key,
  343. self._parententity,
  344. impl=self.impl,
  345. comparator=self.comparator.of_type(entity),
  346. of_type=inspection.inspect(entity),
  347. extra_criteria=self._extra_criteria,
  348. )
  349. def and_(
  350. self, *clauses: _ColumnExpressionArgument[bool]
  351. ) -> QueryableAttribute[bool]:
  352. if TYPE_CHECKING:
  353. assert isinstance(self.comparator, RelationshipProperty.Comparator)
  354. exprs = tuple(
  355. coercions.expect(roles.WhereHavingRole, clause)
  356. for clause in util.coerce_generator_arg(clauses)
  357. )
  358. return QueryableAttribute(
  359. self.class_,
  360. self.key,
  361. self._parententity,
  362. impl=self.impl,
  363. comparator=self.comparator.and_(*exprs),
  364. of_type=self._of_type,
  365. extra_criteria=self._extra_criteria + exprs,
  366. )
  367. def _clone(self, **kw: Any) -> QueryableAttribute[_T]:
  368. return QueryableAttribute(
  369. self.class_,
  370. self.key,
  371. self._parententity,
  372. impl=self.impl,
  373. comparator=self.comparator,
  374. of_type=self._of_type,
  375. extra_criteria=self._extra_criteria,
  376. )
  377. def label(self, name: Optional[str]) -> Label[_T_co]:
  378. return self.__clause_element__().label(name)
  379. def operate(
  380. self, op: OperatorType, *other: Any, **kwargs: Any
  381. ) -> ColumnElement[Any]:
  382. return op(self.comparator, *other, **kwargs) # type: ignore[no-any-return] # noqa: E501
  383. def reverse_operate(
  384. self, op: OperatorType, other: Any, **kwargs: Any
  385. ) -> ColumnElement[Any]:
  386. return op(other, self.comparator, **kwargs) # type: ignore[no-any-return] # noqa: E501
  387. def hasparent(
  388. self, state: InstanceState[Any], optimistic: bool = False
  389. ) -> bool:
  390. return self.impl.hasparent(state, optimistic=optimistic) is not False
  391. def _column_strategy_attrs(self) -> Sequence[QueryableAttribute[Any]]:
  392. return (self,)
  393. def __getattr__(self, key: str) -> Any:
  394. try:
  395. return util.MemoizedSlots.__getattr__(self, key)
  396. except AttributeError:
  397. pass
  398. try:
  399. return getattr(self.comparator, key)
  400. except AttributeError as err:
  401. raise AttributeError(
  402. "Neither %r object nor %r object associated with %s "
  403. "has an attribute %r"
  404. % (
  405. type(self).__name__,
  406. type(self.comparator).__name__,
  407. self,
  408. key,
  409. )
  410. ) from err
  411. def __str__(self) -> str:
  412. return f"{self.class_.__name__}.{self.key}"
  413. def _memoized_attr_property(self) -> Optional[MapperProperty[Any]]:
  414. return self.comparator.property
  415. def _queryable_attribute_unreduce(
  416. key: str,
  417. mapped_class: Type[_O],
  418. parententity: _InternalEntityType[_O],
  419. entity: _ExternalEntityType[Any],
  420. ) -> Any:
  421. # this method is only used in terms of the
  422. # sqlalchemy.ext.serializer extension
  423. if insp_is_aliased_class(parententity):
  424. return entity._get_from_serialized(key, mapped_class, parententity)
  425. else:
  426. return getattr(entity, key)
  427. class InstrumentedAttribute(QueryableAttribute[_T_co]):
  428. """Class bound instrumented attribute which adds basic
  429. :term:`descriptor` methods.
  430. See :class:`.QueryableAttribute` for a description of most features.
  431. """
  432. __slots__ = ()
  433. inherit_cache = True
  434. """:meta private:"""
  435. # hack to make __doc__ writeable on instances of
  436. # InstrumentedAttribute, while still keeping classlevel
  437. # __doc__ correct
  438. @util.rw_hybridproperty
  439. def __doc__(self) -> Optional[str]:
  440. return self._doc
  441. @__doc__.setter # type: ignore
  442. def __doc__(self, value: Optional[str]) -> None:
  443. self._doc = value
  444. @__doc__.classlevel # type: ignore
  445. def __doc__(cls) -> Optional[str]:
  446. return super().__doc__
  447. def __set__(self, instance: object, value: Any) -> None:
  448. self.impl.set(
  449. instance_state(instance), instance_dict(instance), value, None
  450. )
  451. def __delete__(self, instance: object) -> None:
  452. self.impl.delete(instance_state(instance), instance_dict(instance))
  453. @overload
  454. def __get__(
  455. self, instance: None, owner: Any
  456. ) -> InstrumentedAttribute[_T_co]: ...
  457. @overload
  458. def __get__(self, instance: object, owner: Any) -> _T_co: ...
  459. def __get__(
  460. self, instance: Optional[object], owner: Any
  461. ) -> Union[InstrumentedAttribute[_T_co], _T_co]:
  462. if instance is None:
  463. return self
  464. dict_ = instance_dict(instance)
  465. if self.impl.supports_population and self.key in dict_:
  466. return dict_[self.key] # type: ignore[no-any-return]
  467. else:
  468. try:
  469. state = instance_state(instance)
  470. except AttributeError as err:
  471. raise orm_exc.UnmappedInstanceError(instance) from err
  472. return self.impl.get(state, dict_) # type: ignore[no-any-return]
  473. @dataclasses.dataclass(frozen=True)
  474. class AdHocHasEntityNamespace(HasCacheKey):
  475. _traverse_internals: ClassVar[_TraverseInternalsType] = [
  476. ("_entity_namespace", InternalTraversal.dp_has_cache_key),
  477. ]
  478. # py37 compat, no slots=True on dataclass
  479. __slots__ = ("_entity_namespace",)
  480. _entity_namespace: _InternalEntityType[Any]
  481. is_mapper: ClassVar[bool] = False
  482. is_aliased_class: ClassVar[bool] = False
  483. @property
  484. def entity_namespace(self):
  485. return self._entity_namespace.entity_namespace
  486. def create_proxied_attribute(
  487. descriptor: Any,
  488. ) -> Callable[..., QueryableAttribute[Any]]:
  489. """Create an QueryableAttribute / user descriptor hybrid.
  490. Returns a new QueryableAttribute type that delegates descriptor
  491. behavior and getattr() to the given descriptor.
  492. """
  493. # TODO: can move this to descriptor_props if the need for this
  494. # function is removed from ext/hybrid.py
  495. class Proxy(QueryableAttribute[_T_co]):
  496. """Presents the :class:`.QueryableAttribute` interface as a
  497. proxy on top of a Python descriptor / :class:`.PropComparator`
  498. combination.
  499. """
  500. _extra_criteria = ()
  501. # the attribute error catches inside of __getattr__ basically create a
  502. # singularity if you try putting slots on this too
  503. # __slots__ = ("descriptor", "original_property", "_comparator")
  504. def __init__(
  505. self,
  506. class_: _ExternalEntityType[Any],
  507. key: str,
  508. descriptor: Any,
  509. comparator: interfaces.PropComparator[_T_co],
  510. adapt_to_entity: Optional[AliasedInsp[Any]] = None,
  511. doc: Optional[str] = None,
  512. original_property: Optional[QueryableAttribute[_T_co]] = None,
  513. ):
  514. self.class_ = class_
  515. self.key = key
  516. self.descriptor = descriptor
  517. self.original_property = original_property
  518. self._comparator = comparator
  519. self._adapt_to_entity = adapt_to_entity
  520. self._doc = self.__doc__ = doc
  521. @property
  522. def _parententity(self): # type: ignore[override]
  523. return inspection.inspect(self.class_, raiseerr=False)
  524. @property
  525. def parent(self): # type: ignore[override]
  526. return inspection.inspect(self.class_, raiseerr=False)
  527. _is_internal_proxy = True
  528. _cache_key_traversal = [
  529. ("key", visitors.ExtendedInternalTraversal.dp_string),
  530. ("_parententity", visitors.ExtendedInternalTraversal.dp_multi),
  531. ]
  532. def _column_strategy_attrs(self) -> Sequence[QueryableAttribute[Any]]:
  533. prop = self.original_property
  534. if prop is None:
  535. return ()
  536. else:
  537. return prop._column_strategy_attrs()
  538. @property
  539. def _impl_uses_objects(self):
  540. return (
  541. self.original_property is not None
  542. and getattr(self.class_, self.key).impl.uses_objects
  543. )
  544. @property
  545. def _entity_namespace(self):
  546. if hasattr(self._comparator, "_parententity"):
  547. return self._comparator._parententity
  548. else:
  549. # used by hybrid attributes which try to remain
  550. # agnostic of any ORM concepts like mappers
  551. return AdHocHasEntityNamespace(self._parententity)
  552. @property
  553. def property(self):
  554. return self.comparator.property
  555. @util.memoized_property
  556. def comparator(self):
  557. if callable(self._comparator):
  558. self._comparator = self._comparator()
  559. if self._adapt_to_entity:
  560. self._comparator = self._comparator.adapt_to_entity(
  561. self._adapt_to_entity
  562. )
  563. return self._comparator
  564. def adapt_to_entity(self, adapt_to_entity):
  565. return self.__class__(
  566. adapt_to_entity.entity,
  567. self.key,
  568. self.descriptor,
  569. self._comparator,
  570. adapt_to_entity,
  571. )
  572. def _clone(self, **kw):
  573. return self.__class__(
  574. self.class_,
  575. self.key,
  576. self.descriptor,
  577. self._comparator,
  578. adapt_to_entity=self._adapt_to_entity,
  579. original_property=self.original_property,
  580. )
  581. def __get__(self, instance, owner):
  582. retval = self.descriptor.__get__(instance, owner)
  583. # detect if this is a plain Python @property, which just returns
  584. # itself for class level access. If so, then return us.
  585. # Otherwise, return the object returned by the descriptor.
  586. if retval is self.descriptor and instance is None:
  587. return self
  588. else:
  589. return retval
  590. def __str__(self) -> str:
  591. return f"{self.class_.__name__}.{self.key}"
  592. def __getattr__(self, attribute):
  593. """Delegate __getattr__ to the original descriptor and/or
  594. comparator."""
  595. # this is unfortunately very complicated, and is easily prone
  596. # to recursion overflows when implementations of related
  597. # __getattr__ schemes are changed
  598. try:
  599. return util.MemoizedSlots.__getattr__(self, attribute)
  600. except AttributeError:
  601. pass
  602. try:
  603. return getattr(descriptor, attribute)
  604. except AttributeError as err:
  605. if attribute == "comparator":
  606. raise AttributeError("comparator") from err
  607. try:
  608. # comparator itself might be unreachable
  609. comparator = self.comparator
  610. except AttributeError as err2:
  611. raise AttributeError(
  612. "Neither %r object nor unconfigured comparator "
  613. "object associated with %s has an attribute %r"
  614. % (type(descriptor).__name__, self, attribute)
  615. ) from err2
  616. else:
  617. try:
  618. return getattr(comparator, attribute)
  619. except AttributeError as err3:
  620. raise AttributeError(
  621. "Neither %r object nor %r object "
  622. "associated with %s has an attribute %r"
  623. % (
  624. type(descriptor).__name__,
  625. type(comparator).__name__,
  626. self,
  627. attribute,
  628. )
  629. ) from err3
  630. Proxy.__name__ = type(descriptor).__name__ + "Proxy"
  631. util.monkeypatch_proxied_specials(
  632. Proxy, type(descriptor), name="descriptor", from_instance=descriptor
  633. )
  634. return Proxy
  635. OP_REMOVE = util.symbol("REMOVE")
  636. OP_APPEND = util.symbol("APPEND")
  637. OP_REPLACE = util.symbol("REPLACE")
  638. OP_BULK_REPLACE = util.symbol("BULK_REPLACE")
  639. OP_MODIFIED = util.symbol("MODIFIED")
  640. class AttributeEventToken:
  641. """A token propagated throughout the course of a chain of attribute
  642. events.
  643. Serves as an indicator of the source of the event and also provides
  644. a means of controlling propagation across a chain of attribute
  645. operations.
  646. The :class:`.Event` object is sent as the ``initiator`` argument
  647. when dealing with events such as :meth:`.AttributeEvents.append`,
  648. :meth:`.AttributeEvents.set`,
  649. and :meth:`.AttributeEvents.remove`.
  650. The :class:`.Event` object is currently interpreted by the backref
  651. event handlers, and is used to control the propagation of operations
  652. across two mutually-dependent attributes.
  653. .. versionchanged:: 2.0 Changed the name from ``AttributeEvent``
  654. to ``AttributeEventToken``.
  655. :attribute impl: The :class:`.AttributeImpl` which is the current event
  656. initiator.
  657. :attribute op: The symbol :attr:`.OP_APPEND`, :attr:`.OP_REMOVE`,
  658. :attr:`.OP_REPLACE`, or :attr:`.OP_BULK_REPLACE`, indicating the
  659. source operation.
  660. """
  661. __slots__ = "impl", "op", "parent_token"
  662. def __init__(self, attribute_impl: AttributeImpl, op: util.symbol):
  663. self.impl = attribute_impl
  664. self.op = op
  665. self.parent_token = self.impl.parent_token
  666. def __eq__(self, other):
  667. return (
  668. isinstance(other, AttributeEventToken)
  669. and other.impl is self.impl
  670. and other.op == self.op
  671. )
  672. @property
  673. def key(self):
  674. return self.impl.key
  675. def hasparent(self, state):
  676. return self.impl.hasparent(state)
  677. AttributeEvent = AttributeEventToken # legacy
  678. Event = AttributeEventToken # legacy
  679. class AttributeImpl:
  680. """internal implementation for instrumented attributes."""
  681. collection: bool
  682. default_accepts_scalar_loader: bool
  683. uses_objects: bool
  684. supports_population: bool
  685. dynamic: bool
  686. _is_has_collection_adapter = False
  687. _replace_token: AttributeEventToken
  688. _remove_token: AttributeEventToken
  689. _append_token: AttributeEventToken
  690. def __init__(
  691. self,
  692. class_: _ExternalEntityType[_O],
  693. key: str,
  694. callable_: Optional[_LoaderCallable],
  695. dispatch: _Dispatch[QueryableAttribute[Any]],
  696. trackparent: bool = False,
  697. compare_function: Optional[Callable[..., bool]] = None,
  698. active_history: bool = False,
  699. parent_token: Optional[AttributeEventToken] = None,
  700. load_on_unexpire: bool = True,
  701. send_modified_events: bool = True,
  702. accepts_scalar_loader: Optional[bool] = None,
  703. **kwargs: Any,
  704. ):
  705. r"""Construct an AttributeImpl.
  706. :param \class_: associated class
  707. :param key: string name of the attribute
  708. :param \callable_:
  709. optional function which generates a callable based on a parent
  710. instance, which produces the "default" values for a scalar or
  711. collection attribute when it's first accessed, if not present
  712. already.
  713. :param trackparent:
  714. if True, attempt to track if an instance has a parent attached
  715. to it via this attribute.
  716. :param compare_function:
  717. a function that compares two values which are normally
  718. assignable to this attribute.
  719. :param active_history:
  720. indicates that get_history() should always return the "old" value,
  721. even if it means executing a lazy callable upon attribute change.
  722. :param parent_token:
  723. Usually references the MapperProperty, used as a key for
  724. the hasparent() function to identify an "owning" attribute.
  725. Allows multiple AttributeImpls to all match a single
  726. owner attribute.
  727. :param load_on_unexpire:
  728. if False, don't include this attribute in a load-on-expired
  729. operation, i.e. the "expired_attribute_loader" process.
  730. The attribute can still be in the "expired" list and be
  731. considered to be "expired". Previously, this flag was called
  732. "expire_missing" and is only used by a deferred column
  733. attribute.
  734. :param send_modified_events:
  735. if False, the InstanceState._modified_event method will have no
  736. effect; this means the attribute will never show up as changed in a
  737. history entry.
  738. """
  739. self.class_ = class_
  740. self.key = key
  741. self.callable_ = callable_
  742. self.dispatch = dispatch
  743. self.trackparent = trackparent
  744. self.parent_token = parent_token or self
  745. self.send_modified_events = send_modified_events
  746. if compare_function is None:
  747. self.is_equal = operator.eq
  748. else:
  749. self.is_equal = compare_function
  750. if accepts_scalar_loader is not None:
  751. self.accepts_scalar_loader = accepts_scalar_loader
  752. else:
  753. self.accepts_scalar_loader = self.default_accepts_scalar_loader
  754. _deferred_history = kwargs.pop("_deferred_history", False)
  755. self._deferred_history = _deferred_history
  756. if active_history:
  757. self.dispatch._active_history = True
  758. self.load_on_unexpire = load_on_unexpire
  759. self._modified_token = AttributeEventToken(self, OP_MODIFIED)
  760. __slots__ = (
  761. "class_",
  762. "key",
  763. "callable_",
  764. "dispatch",
  765. "trackparent",
  766. "parent_token",
  767. "send_modified_events",
  768. "is_equal",
  769. "load_on_unexpire",
  770. "_modified_token",
  771. "accepts_scalar_loader",
  772. "_deferred_history",
  773. )
  774. def __str__(self) -> str:
  775. return f"{self.class_.__name__}.{self.key}"
  776. def _get_active_history(self):
  777. """Backwards compat for impl.active_history"""
  778. return self.dispatch._active_history
  779. def _set_active_history(self, value):
  780. self.dispatch._active_history = value
  781. active_history = property(_get_active_history, _set_active_history)
  782. def hasparent(
  783. self, state: InstanceState[Any], optimistic: bool = False
  784. ) -> bool:
  785. """Return the boolean value of a `hasparent` flag attached to
  786. the given state.
  787. The `optimistic` flag determines what the default return value
  788. should be if no `hasparent` flag can be located.
  789. As this function is used to determine if an instance is an
  790. *orphan*, instances that were loaded from storage should be
  791. assumed to not be orphans, until a True/False value for this
  792. flag is set.
  793. An instance attribute that is loaded by a callable function
  794. will also not have a `hasparent` flag.
  795. """
  796. msg = "This AttributeImpl is not configured to track parents."
  797. assert self.trackparent, msg
  798. return (
  799. state.parents.get(id(self.parent_token), optimistic) is not False
  800. )
  801. def sethasparent(
  802. self,
  803. state: InstanceState[Any],
  804. parent_state: InstanceState[Any],
  805. value: bool,
  806. ) -> None:
  807. """Set a boolean flag on the given item corresponding to
  808. whether or not it is attached to a parent object via the
  809. attribute represented by this ``InstrumentedAttribute``.
  810. """
  811. msg = "This AttributeImpl is not configured to track parents."
  812. assert self.trackparent, msg
  813. id_ = id(self.parent_token)
  814. if value:
  815. state.parents[id_] = parent_state
  816. else:
  817. if id_ in state.parents:
  818. last_parent = state.parents[id_]
  819. if (
  820. last_parent is not False
  821. and last_parent.key != parent_state.key
  822. ):
  823. if last_parent.obj() is None:
  824. raise orm_exc.StaleDataError(
  825. "Removing state %s from parent "
  826. "state %s along attribute '%s', "
  827. "but the parent record "
  828. "has gone stale, can't be sure this "
  829. "is the most recent parent."
  830. % (
  831. state_str(state),
  832. state_str(parent_state),
  833. self.key,
  834. )
  835. )
  836. return
  837. state.parents[id_] = False
  838. def get_history(
  839. self,
  840. state: InstanceState[Any],
  841. dict_: _InstanceDict,
  842. passive: PassiveFlag = PASSIVE_OFF,
  843. ) -> History:
  844. raise NotImplementedError()
  845. def get_all_pending(
  846. self,
  847. state: InstanceState[Any],
  848. dict_: _InstanceDict,
  849. passive: PassiveFlag = PASSIVE_NO_INITIALIZE,
  850. ) -> _AllPendingType:
  851. """Return a list of tuples of (state, obj)
  852. for all objects in this attribute's current state
  853. + history.
  854. Only applies to object-based attributes.
  855. This is an inlining of existing functionality
  856. which roughly corresponds to:
  857. get_state_history(
  858. state,
  859. key,
  860. passive=PASSIVE_NO_INITIALIZE).sum()
  861. """
  862. raise NotImplementedError()
  863. def _default_value(
  864. self, state: InstanceState[Any], dict_: _InstanceDict
  865. ) -> Any:
  866. """Produce an empty value for an uninitialized scalar attribute."""
  867. assert self.key not in dict_, (
  868. "_default_value should only be invoked for an "
  869. "uninitialized or expired attribute"
  870. )
  871. value = None
  872. for fn in self.dispatch.init_scalar:
  873. ret = fn(state, value, dict_)
  874. if ret is not ATTR_EMPTY:
  875. value = ret
  876. return value
  877. def get(
  878. self,
  879. state: InstanceState[Any],
  880. dict_: _InstanceDict,
  881. passive: PassiveFlag = PASSIVE_OFF,
  882. ) -> Any:
  883. """Retrieve a value from the given object.
  884. If a callable is assembled on this object's attribute, and
  885. passive is False, the callable will be executed and the
  886. resulting value will be set as the new value for this attribute.
  887. """
  888. if self.key in dict_:
  889. return dict_[self.key]
  890. else:
  891. # if history present, don't load
  892. key = self.key
  893. if (
  894. key not in state.committed_state
  895. or state.committed_state[key] is NO_VALUE
  896. ):
  897. if not passive & CALLABLES_OK:
  898. return PASSIVE_NO_RESULT
  899. value = self._fire_loader_callables(state, key, passive)
  900. if value is PASSIVE_NO_RESULT or value is NO_VALUE:
  901. return value
  902. elif value is ATTR_WAS_SET:
  903. try:
  904. return dict_[key]
  905. except KeyError as err:
  906. # TODO: no test coverage here.
  907. raise KeyError(
  908. "Deferred loader for attribute "
  909. "%r failed to populate "
  910. "correctly" % key
  911. ) from err
  912. elif value is not ATTR_EMPTY:
  913. return self.set_committed_value(state, dict_, value)
  914. if not passive & INIT_OK:
  915. return NO_VALUE
  916. else:
  917. return self._default_value(state, dict_)
  918. def _fire_loader_callables(
  919. self, state: InstanceState[Any], key: str, passive: PassiveFlag
  920. ) -> Any:
  921. if (
  922. self.accepts_scalar_loader
  923. and self.load_on_unexpire
  924. and key in state.expired_attributes
  925. ):
  926. return state._load_expired(state, passive)
  927. elif key in state.callables:
  928. callable_ = state.callables[key]
  929. return callable_(state, passive)
  930. elif self.callable_:
  931. return self.callable_(state, passive)
  932. else:
  933. return ATTR_EMPTY
  934. def append(
  935. self,
  936. state: InstanceState[Any],
  937. dict_: _InstanceDict,
  938. value: Any,
  939. initiator: Optional[AttributeEventToken],
  940. passive: PassiveFlag = PASSIVE_OFF,
  941. ) -> None:
  942. self.set(state, dict_, value, initiator, passive=passive)
  943. def remove(
  944. self,
  945. state: InstanceState[Any],
  946. dict_: _InstanceDict,
  947. value: Any,
  948. initiator: Optional[AttributeEventToken],
  949. passive: PassiveFlag = PASSIVE_OFF,
  950. ) -> None:
  951. self.set(
  952. state, dict_, None, initiator, passive=passive, check_old=value
  953. )
  954. def pop(
  955. self,
  956. state: InstanceState[Any],
  957. dict_: _InstanceDict,
  958. value: Any,
  959. initiator: Optional[AttributeEventToken],
  960. passive: PassiveFlag = PASSIVE_OFF,
  961. ) -> None:
  962. self.set(
  963. state,
  964. dict_,
  965. None,
  966. initiator,
  967. passive=passive,
  968. check_old=value,
  969. pop=True,
  970. )
  971. def set(
  972. self,
  973. state: InstanceState[Any],
  974. dict_: _InstanceDict,
  975. value: Any,
  976. initiator: Optional[AttributeEventToken] = None,
  977. passive: PassiveFlag = PASSIVE_OFF,
  978. check_old: Any = None,
  979. pop: bool = False,
  980. ) -> None:
  981. raise NotImplementedError()
  982. def delete(self, state: InstanceState[Any], dict_: _InstanceDict) -> None:
  983. raise NotImplementedError()
  984. def get_committed_value(
  985. self,
  986. state: InstanceState[Any],
  987. dict_: _InstanceDict,
  988. passive: PassiveFlag = PASSIVE_OFF,
  989. ) -> Any:
  990. """return the unchanged value of this attribute"""
  991. if self.key in state.committed_state:
  992. value = state.committed_state[self.key]
  993. if value is NO_VALUE:
  994. return None
  995. else:
  996. return value
  997. else:
  998. return self.get(state, dict_, passive=passive)
  999. def set_committed_value(self, state, dict_, value):
  1000. """set an attribute value on the given instance and 'commit' it."""
  1001. dict_[self.key] = value
  1002. state._commit(dict_, [self.key])
  1003. return value
  1004. class ScalarAttributeImpl(AttributeImpl):
  1005. """represents a scalar value-holding InstrumentedAttribute."""
  1006. default_accepts_scalar_loader = True
  1007. uses_objects = False
  1008. supports_population = True
  1009. collection = False
  1010. dynamic = False
  1011. __slots__ = "_replace_token", "_append_token", "_remove_token"
  1012. def __init__(self, *arg, **kw):
  1013. super().__init__(*arg, **kw)
  1014. self._replace_token = self._append_token = AttributeEventToken(
  1015. self, OP_REPLACE
  1016. )
  1017. self._remove_token = AttributeEventToken(self, OP_REMOVE)
  1018. def delete(self, state: InstanceState[Any], dict_: _InstanceDict) -> None:
  1019. if self.dispatch._active_history:
  1020. old = self.get(state, dict_, PASSIVE_RETURN_NO_VALUE)
  1021. else:
  1022. old = dict_.get(self.key, NO_VALUE)
  1023. if self.dispatch.remove:
  1024. self.fire_remove_event(state, dict_, old, self._remove_token)
  1025. state._modified_event(dict_, self, old)
  1026. existing = dict_.pop(self.key, NO_VALUE)
  1027. if (
  1028. existing is NO_VALUE
  1029. and old is NO_VALUE
  1030. and not state.expired
  1031. and self.key not in state.expired_attributes
  1032. ):
  1033. raise AttributeError("%s object does not have a value" % self)
  1034. def get_history(
  1035. self,
  1036. state: InstanceState[Any],
  1037. dict_: Dict[str, Any],
  1038. passive: PassiveFlag = PASSIVE_OFF,
  1039. ) -> History:
  1040. if self.key in dict_:
  1041. return History.from_scalar_attribute(self, state, dict_[self.key])
  1042. elif self.key in state.committed_state:
  1043. return History.from_scalar_attribute(self, state, NO_VALUE)
  1044. else:
  1045. if passive & INIT_OK:
  1046. passive ^= INIT_OK
  1047. current = self.get(state, dict_, passive=passive)
  1048. if current is PASSIVE_NO_RESULT:
  1049. return HISTORY_BLANK
  1050. else:
  1051. return History.from_scalar_attribute(self, state, current)
  1052. def set(
  1053. self,
  1054. state: InstanceState[Any],
  1055. dict_: Dict[str, Any],
  1056. value: Any,
  1057. initiator: Optional[AttributeEventToken] = None,
  1058. passive: PassiveFlag = PASSIVE_OFF,
  1059. check_old: Optional[object] = None,
  1060. pop: bool = False,
  1061. ) -> None:
  1062. if self.dispatch._active_history:
  1063. old = self.get(state, dict_, PASSIVE_RETURN_NO_VALUE)
  1064. else:
  1065. old = dict_.get(self.key, NO_VALUE)
  1066. if self.dispatch.set:
  1067. value = self.fire_replace_event(
  1068. state, dict_, value, old, initiator
  1069. )
  1070. state._modified_event(dict_, self, old)
  1071. dict_[self.key] = value
  1072. def fire_replace_event(
  1073. self,
  1074. state: InstanceState[Any],
  1075. dict_: _InstanceDict,
  1076. value: _T,
  1077. previous: Any,
  1078. initiator: Optional[AttributeEventToken],
  1079. ) -> _T:
  1080. for fn in self.dispatch.set:
  1081. value = fn(
  1082. state, value, previous, initiator or self._replace_token
  1083. )
  1084. return value
  1085. def fire_remove_event(
  1086. self,
  1087. state: InstanceState[Any],
  1088. dict_: _InstanceDict,
  1089. value: Any,
  1090. initiator: Optional[AttributeEventToken],
  1091. ) -> None:
  1092. for fn in self.dispatch.remove:
  1093. fn(state, value, initiator or self._remove_token)
  1094. class ScalarObjectAttributeImpl(ScalarAttributeImpl):
  1095. """represents a scalar-holding InstrumentedAttribute,
  1096. where the target object is also instrumented.
  1097. Adds events to delete/set operations.
  1098. """
  1099. default_accepts_scalar_loader = False
  1100. uses_objects = True
  1101. supports_population = True
  1102. collection = False
  1103. __slots__ = ()
  1104. def delete(self, state: InstanceState[Any], dict_: _InstanceDict) -> None:
  1105. if self.dispatch._active_history:
  1106. old = self.get(
  1107. state,
  1108. dict_,
  1109. passive=PASSIVE_ONLY_PERSISTENT
  1110. | NO_AUTOFLUSH
  1111. | LOAD_AGAINST_COMMITTED,
  1112. )
  1113. else:
  1114. old = self.get(
  1115. state,
  1116. dict_,
  1117. passive=PASSIVE_NO_FETCH ^ INIT_OK
  1118. | LOAD_AGAINST_COMMITTED
  1119. | NO_RAISE,
  1120. )
  1121. self.fire_remove_event(state, dict_, old, self._remove_token)
  1122. existing = dict_.pop(self.key, NO_VALUE)
  1123. # if the attribute is expired, we currently have no way to tell
  1124. # that an object-attribute was expired vs. not loaded. So
  1125. # for this test, we look to see if the object has a DB identity.
  1126. if (
  1127. existing is NO_VALUE
  1128. and old is not PASSIVE_NO_RESULT
  1129. and state.key is None
  1130. ):
  1131. raise AttributeError("%s object does not have a value" % self)
  1132. def get_history(
  1133. self,
  1134. state: InstanceState[Any],
  1135. dict_: _InstanceDict,
  1136. passive: PassiveFlag = PASSIVE_OFF,
  1137. ) -> History:
  1138. if self.key in dict_:
  1139. current = dict_[self.key]
  1140. else:
  1141. if passive & INIT_OK:
  1142. passive ^= INIT_OK
  1143. current = self.get(state, dict_, passive=passive)
  1144. if current is PASSIVE_NO_RESULT:
  1145. return HISTORY_BLANK
  1146. if not self._deferred_history:
  1147. return History.from_object_attribute(self, state, current)
  1148. else:
  1149. original = state.committed_state.get(self.key, _NO_HISTORY)
  1150. if original is PASSIVE_NO_RESULT:
  1151. loader_passive = passive | (
  1152. PASSIVE_ONLY_PERSISTENT
  1153. | NO_AUTOFLUSH
  1154. | LOAD_AGAINST_COMMITTED
  1155. | NO_RAISE
  1156. | DEFERRED_HISTORY_LOAD
  1157. )
  1158. original = self._fire_loader_callables(
  1159. state, self.key, loader_passive
  1160. )
  1161. return History.from_object_attribute(
  1162. self, state, current, original=original
  1163. )
  1164. def get_all_pending(
  1165. self,
  1166. state: InstanceState[Any],
  1167. dict_: _InstanceDict,
  1168. passive: PassiveFlag = PASSIVE_NO_INITIALIZE,
  1169. ) -> _AllPendingType:
  1170. if self.key in dict_:
  1171. current = dict_[self.key]
  1172. elif passive & CALLABLES_OK:
  1173. current = self.get(state, dict_, passive=passive)
  1174. else:
  1175. return []
  1176. ret: _AllPendingType
  1177. # can't use __hash__(), can't use __eq__() here
  1178. if (
  1179. current is not None
  1180. and current is not PASSIVE_NO_RESULT
  1181. and current is not NO_VALUE
  1182. ):
  1183. ret = [(instance_state(current), current)]
  1184. else:
  1185. ret = [(None, None)]
  1186. if self.key in state.committed_state:
  1187. original = state.committed_state[self.key]
  1188. if (
  1189. original is not None
  1190. and original is not PASSIVE_NO_RESULT
  1191. and original is not NO_VALUE
  1192. and original is not current
  1193. ):
  1194. ret.append((instance_state(original), original))
  1195. return ret
  1196. def set(
  1197. self,
  1198. state: InstanceState[Any],
  1199. dict_: _InstanceDict,
  1200. value: Any,
  1201. initiator: Optional[AttributeEventToken] = None,
  1202. passive: PassiveFlag = PASSIVE_OFF,
  1203. check_old: Any = None,
  1204. pop: bool = False,
  1205. ) -> None:
  1206. """Set a value on the given InstanceState."""
  1207. if self.dispatch._active_history:
  1208. old = self.get(
  1209. state,
  1210. dict_,
  1211. passive=PASSIVE_ONLY_PERSISTENT
  1212. | NO_AUTOFLUSH
  1213. | LOAD_AGAINST_COMMITTED,
  1214. )
  1215. else:
  1216. old = self.get(
  1217. state,
  1218. dict_,
  1219. passive=PASSIVE_NO_FETCH ^ INIT_OK
  1220. | LOAD_AGAINST_COMMITTED
  1221. | NO_RAISE,
  1222. )
  1223. if (
  1224. check_old is not None
  1225. and old is not PASSIVE_NO_RESULT
  1226. and check_old is not old
  1227. ):
  1228. if pop:
  1229. return
  1230. else:
  1231. raise ValueError(
  1232. "Object %s not associated with %s on attribute '%s'"
  1233. % (instance_str(check_old), state_str(state), self.key)
  1234. )
  1235. value = self.fire_replace_event(state, dict_, value, old, initiator)
  1236. dict_[self.key] = value
  1237. def fire_remove_event(
  1238. self,
  1239. state: InstanceState[Any],
  1240. dict_: _InstanceDict,
  1241. value: Any,
  1242. initiator: Optional[AttributeEventToken],
  1243. ) -> None:
  1244. if self.trackparent and value not in (
  1245. None,
  1246. PASSIVE_NO_RESULT,
  1247. NO_VALUE,
  1248. ):
  1249. self.sethasparent(instance_state(value), state, False)
  1250. for fn in self.dispatch.remove:
  1251. fn(state, value, initiator or self._remove_token)
  1252. state._modified_event(dict_, self, value)
  1253. def fire_replace_event(
  1254. self,
  1255. state: InstanceState[Any],
  1256. dict_: _InstanceDict,
  1257. value: _T,
  1258. previous: Any,
  1259. initiator: Optional[AttributeEventToken],
  1260. ) -> _T:
  1261. if self.trackparent:
  1262. if previous is not value and previous not in (
  1263. None,
  1264. PASSIVE_NO_RESULT,
  1265. NO_VALUE,
  1266. ):
  1267. self.sethasparent(instance_state(previous), state, False)
  1268. for fn in self.dispatch.set:
  1269. value = fn(
  1270. state, value, previous, initiator or self._replace_token
  1271. )
  1272. state._modified_event(dict_, self, previous)
  1273. if self.trackparent:
  1274. if value is not None:
  1275. self.sethasparent(instance_state(value), state, True)
  1276. return value
  1277. class HasCollectionAdapter:
  1278. __slots__ = ()
  1279. collection: bool
  1280. _is_has_collection_adapter = True
  1281. def _dispose_previous_collection(
  1282. self,
  1283. state: InstanceState[Any],
  1284. collection: _AdaptedCollectionProtocol,
  1285. adapter: CollectionAdapter,
  1286. fire_event: bool,
  1287. ) -> None:
  1288. raise NotImplementedError()
  1289. @overload
  1290. def get_collection(
  1291. self,
  1292. state: InstanceState[Any],
  1293. dict_: _InstanceDict,
  1294. user_data: Literal[None] = ...,
  1295. passive: Literal[PassiveFlag.PASSIVE_OFF] = ...,
  1296. ) -> CollectionAdapter: ...
  1297. @overload
  1298. def get_collection(
  1299. self,
  1300. state: InstanceState[Any],
  1301. dict_: _InstanceDict,
  1302. user_data: _AdaptedCollectionProtocol = ...,
  1303. passive: PassiveFlag = ...,
  1304. ) -> CollectionAdapter: ...
  1305. @overload
  1306. def get_collection(
  1307. self,
  1308. state: InstanceState[Any],
  1309. dict_: _InstanceDict,
  1310. user_data: Optional[_AdaptedCollectionProtocol] = ...,
  1311. passive: PassiveFlag = ...,
  1312. ) -> Union[
  1313. Literal[LoaderCallableStatus.PASSIVE_NO_RESULT], CollectionAdapter
  1314. ]: ...
  1315. def get_collection(
  1316. self,
  1317. state: InstanceState[Any],
  1318. dict_: _InstanceDict,
  1319. user_data: Optional[_AdaptedCollectionProtocol] = None,
  1320. passive: PassiveFlag = PassiveFlag.PASSIVE_OFF,
  1321. ) -> Union[
  1322. Literal[LoaderCallableStatus.PASSIVE_NO_RESULT], CollectionAdapter
  1323. ]:
  1324. raise NotImplementedError()
  1325. def set(
  1326. self,
  1327. state: InstanceState[Any],
  1328. dict_: _InstanceDict,
  1329. value: Any,
  1330. initiator: Optional[AttributeEventToken] = None,
  1331. passive: PassiveFlag = PassiveFlag.PASSIVE_OFF,
  1332. check_old: Any = None,
  1333. pop: bool = False,
  1334. _adapt: bool = True,
  1335. ) -> None:
  1336. raise NotImplementedError()
  1337. if TYPE_CHECKING:
  1338. def _is_collection_attribute_impl(
  1339. impl: AttributeImpl,
  1340. ) -> TypeGuard[CollectionAttributeImpl]: ...
  1341. else:
  1342. _is_collection_attribute_impl = operator.attrgetter("collection")
  1343. class CollectionAttributeImpl(HasCollectionAdapter, AttributeImpl):
  1344. """A collection-holding attribute that instruments changes in membership.
  1345. Only handles collections of instrumented objects.
  1346. InstrumentedCollectionAttribute holds an arbitrary, user-specified
  1347. container object (defaulting to a list) and brokers access to the
  1348. CollectionAdapter, a "view" onto that object that presents consistent bag
  1349. semantics to the orm layer independent of the user data implementation.
  1350. """
  1351. uses_objects = True
  1352. collection = True
  1353. default_accepts_scalar_loader = False
  1354. supports_population = True
  1355. dynamic = False
  1356. _bulk_replace_token: AttributeEventToken
  1357. __slots__ = (
  1358. "copy",
  1359. "collection_factory",
  1360. "_append_token",
  1361. "_remove_token",
  1362. "_bulk_replace_token",
  1363. "_duck_typed_as",
  1364. )
  1365. def __init__(
  1366. self,
  1367. class_,
  1368. key,
  1369. callable_,
  1370. dispatch,
  1371. typecallable=None,
  1372. trackparent=False,
  1373. copy_function=None,
  1374. compare_function=None,
  1375. **kwargs,
  1376. ):
  1377. super().__init__(
  1378. class_,
  1379. key,
  1380. callable_,
  1381. dispatch,
  1382. trackparent=trackparent,
  1383. compare_function=compare_function,
  1384. **kwargs,
  1385. )
  1386. if copy_function is None:
  1387. copy_function = self.__copy
  1388. self.copy = copy_function
  1389. self.collection_factory = typecallable
  1390. self._append_token = AttributeEventToken(self, OP_APPEND)
  1391. self._remove_token = AttributeEventToken(self, OP_REMOVE)
  1392. self._bulk_replace_token = AttributeEventToken(self, OP_BULK_REPLACE)
  1393. self._duck_typed_as = util.duck_type_collection(
  1394. self.collection_factory()
  1395. )
  1396. if getattr(self.collection_factory, "_sa_linker", None):
  1397. @event.listens_for(self, "init_collection")
  1398. def link(target, collection, collection_adapter):
  1399. collection._sa_linker(collection_adapter)
  1400. @event.listens_for(self, "dispose_collection")
  1401. def unlink(target, collection, collection_adapter):
  1402. collection._sa_linker(None)
  1403. def __copy(self, item):
  1404. return [y for y in collections.collection_adapter(item)]
  1405. def get_history(
  1406. self,
  1407. state: InstanceState[Any],
  1408. dict_: _InstanceDict,
  1409. passive: PassiveFlag = PASSIVE_OFF,
  1410. ) -> History:
  1411. current = self.get(state, dict_, passive=passive)
  1412. if current is PASSIVE_NO_RESULT:
  1413. if (
  1414. passive & PassiveFlag.INCLUDE_PENDING_MUTATIONS
  1415. and self.key in state._pending_mutations
  1416. ):
  1417. pending = state._pending_mutations[self.key]
  1418. return pending.merge_with_history(HISTORY_BLANK)
  1419. else:
  1420. return HISTORY_BLANK
  1421. else:
  1422. if passive & PassiveFlag.INCLUDE_PENDING_MUTATIONS:
  1423. # this collection is loaded / present. should not be any
  1424. # pending mutations
  1425. assert self.key not in state._pending_mutations
  1426. return History.from_collection(self, state, current)
  1427. def get_all_pending(
  1428. self,
  1429. state: InstanceState[Any],
  1430. dict_: _InstanceDict,
  1431. passive: PassiveFlag = PASSIVE_NO_INITIALIZE,
  1432. ) -> _AllPendingType:
  1433. # NOTE: passive is ignored here at the moment
  1434. if self.key not in dict_:
  1435. return []
  1436. current = dict_[self.key]
  1437. current = getattr(current, "_sa_adapter")
  1438. if self.key in state.committed_state:
  1439. original = state.committed_state[self.key]
  1440. if original is not NO_VALUE:
  1441. current_states = [
  1442. ((c is not None) and instance_state(c) or None, c)
  1443. for c in current
  1444. ]
  1445. original_states = [
  1446. ((c is not None) and instance_state(c) or None, c)
  1447. for c in original
  1448. ]
  1449. current_set = dict(current_states)
  1450. original_set = dict(original_states)
  1451. return (
  1452. [
  1453. (s, o)
  1454. for s, o in current_states
  1455. if s not in original_set
  1456. ]
  1457. + [(s, o) for s, o in current_states if s in original_set]
  1458. + [
  1459. (s, o)
  1460. for s, o in original_states
  1461. if s not in current_set
  1462. ]
  1463. )
  1464. return [(instance_state(o), o) for o in current]
  1465. def fire_append_event(
  1466. self,
  1467. state: InstanceState[Any],
  1468. dict_: _InstanceDict,
  1469. value: _T,
  1470. initiator: Optional[AttributeEventToken],
  1471. key: Optional[Any],
  1472. ) -> _T:
  1473. for fn in self.dispatch.append:
  1474. value = fn(state, value, initiator or self._append_token, key=key)
  1475. state._modified_event(dict_, self, NO_VALUE, True)
  1476. if self.trackparent and value is not None:
  1477. self.sethasparent(instance_state(value), state, True)
  1478. return value
  1479. def fire_append_wo_mutation_event(
  1480. self,
  1481. state: InstanceState[Any],
  1482. dict_: _InstanceDict,
  1483. value: _T,
  1484. initiator: Optional[AttributeEventToken],
  1485. key: Optional[Any],
  1486. ) -> _T:
  1487. for fn in self.dispatch.append_wo_mutation:
  1488. value = fn(state, value, initiator or self._append_token, key=key)
  1489. return value
  1490. def fire_pre_remove_event(
  1491. self,
  1492. state: InstanceState[Any],
  1493. dict_: _InstanceDict,
  1494. initiator: Optional[AttributeEventToken],
  1495. key: Optional[Any],
  1496. ) -> None:
  1497. """A special event used for pop() operations.
  1498. The "remove" event needs to have the item to be removed passed to
  1499. it, which in the case of pop from a set, we don't have a way to access
  1500. the item before the operation. the event is used for all pop()
  1501. operations (even though set.pop is the one where it is really needed).
  1502. """
  1503. state._modified_event(dict_, self, NO_VALUE, True)
  1504. def fire_remove_event(
  1505. self,
  1506. state: InstanceState[Any],
  1507. dict_: _InstanceDict,
  1508. value: Any,
  1509. initiator: Optional[AttributeEventToken],
  1510. key: Optional[Any],
  1511. ) -> None:
  1512. if self.trackparent and value is not None:
  1513. self.sethasparent(instance_state(value), state, False)
  1514. for fn in self.dispatch.remove:
  1515. fn(state, value, initiator or self._remove_token, key=key)
  1516. state._modified_event(dict_, self, NO_VALUE, True)
  1517. def delete(self, state: InstanceState[Any], dict_: _InstanceDict) -> None:
  1518. if self.key not in dict_:
  1519. return
  1520. state._modified_event(dict_, self, NO_VALUE, True)
  1521. collection = self.get_collection(state, state.dict)
  1522. collection.clear_with_event()
  1523. # key is always present because we checked above. e.g.
  1524. # del is a no-op if collection not present.
  1525. del dict_[self.key]
  1526. def _default_value(
  1527. self, state: InstanceState[Any], dict_: _InstanceDict
  1528. ) -> _AdaptedCollectionProtocol:
  1529. """Produce an empty collection for an un-initialized attribute"""
  1530. assert self.key not in dict_, (
  1531. "_default_value should only be invoked for an "
  1532. "uninitialized or expired attribute"
  1533. )
  1534. if self.key in state._empty_collections:
  1535. return state._empty_collections[self.key]
  1536. adapter, user_data = self._initialize_collection(state)
  1537. adapter._set_empty(user_data)
  1538. return user_data
  1539. def _initialize_collection(
  1540. self, state: InstanceState[Any]
  1541. ) -> Tuple[CollectionAdapter, _AdaptedCollectionProtocol]:
  1542. adapter, collection = state.manager.initialize_collection(
  1543. self.key, state, self.collection_factory
  1544. )
  1545. self.dispatch.init_collection(state, collection, adapter)
  1546. return adapter, collection
  1547. def append(
  1548. self,
  1549. state: InstanceState[Any],
  1550. dict_: _InstanceDict,
  1551. value: Any,
  1552. initiator: Optional[AttributeEventToken],
  1553. passive: PassiveFlag = PASSIVE_OFF,
  1554. ) -> None:
  1555. collection = self.get_collection(
  1556. state, dict_, user_data=None, passive=passive
  1557. )
  1558. if collection is PASSIVE_NO_RESULT:
  1559. value = self.fire_append_event(
  1560. state, dict_, value, initiator, key=NO_KEY
  1561. )
  1562. assert (
  1563. self.key not in dict_
  1564. ), "Collection was loaded during event handling."
  1565. state._get_pending_mutation(self.key).append(value)
  1566. else:
  1567. if TYPE_CHECKING:
  1568. assert isinstance(collection, CollectionAdapter)
  1569. collection.append_with_event(value, initiator)
  1570. def remove(
  1571. self,
  1572. state: InstanceState[Any],
  1573. dict_: _InstanceDict,
  1574. value: Any,
  1575. initiator: Optional[AttributeEventToken],
  1576. passive: PassiveFlag = PASSIVE_OFF,
  1577. ) -> None:
  1578. collection = self.get_collection(
  1579. state, state.dict, user_data=None, passive=passive
  1580. )
  1581. if collection is PASSIVE_NO_RESULT:
  1582. self.fire_remove_event(state, dict_, value, initiator, key=NO_KEY)
  1583. assert (
  1584. self.key not in dict_
  1585. ), "Collection was loaded during event handling."
  1586. state._get_pending_mutation(self.key).remove(value)
  1587. else:
  1588. if TYPE_CHECKING:
  1589. assert isinstance(collection, CollectionAdapter)
  1590. collection.remove_with_event(value, initiator)
  1591. def pop(
  1592. self,
  1593. state: InstanceState[Any],
  1594. dict_: _InstanceDict,
  1595. value: Any,
  1596. initiator: Optional[AttributeEventToken],
  1597. passive: PassiveFlag = PASSIVE_OFF,
  1598. ) -> None:
  1599. try:
  1600. # TODO: better solution here would be to add
  1601. # a "popper" role to collections.py to complement
  1602. # "remover".
  1603. self.remove(state, dict_, value, initiator, passive=passive)
  1604. except (ValueError, KeyError, IndexError):
  1605. pass
  1606. def set(
  1607. self,
  1608. state: InstanceState[Any],
  1609. dict_: _InstanceDict,
  1610. value: Any,
  1611. initiator: Optional[AttributeEventToken] = None,
  1612. passive: PassiveFlag = PassiveFlag.PASSIVE_OFF,
  1613. check_old: Any = None,
  1614. pop: bool = False,
  1615. _adapt: bool = True,
  1616. ) -> None:
  1617. iterable = orig_iterable = value
  1618. new_keys = None
  1619. # pulling a new collection first so that an adaptation exception does
  1620. # not trigger a lazy load of the old collection.
  1621. new_collection, user_data = self._initialize_collection(state)
  1622. if _adapt:
  1623. if new_collection._converter is not None:
  1624. iterable = new_collection._converter(iterable)
  1625. else:
  1626. setting_type = util.duck_type_collection(iterable)
  1627. receiving_type = self._duck_typed_as
  1628. if setting_type is not receiving_type:
  1629. given = (
  1630. iterable is None
  1631. and "None"
  1632. or iterable.__class__.__name__
  1633. )
  1634. wanted = self._duck_typed_as.__name__
  1635. raise TypeError(
  1636. "Incompatible collection type: %s is not %s-like"
  1637. % (given, wanted)
  1638. )
  1639. # If the object is an adapted collection, return the (iterable)
  1640. # adapter.
  1641. if hasattr(iterable, "_sa_iterator"):
  1642. iterable = iterable._sa_iterator()
  1643. elif setting_type is dict:
  1644. new_keys = list(iterable)
  1645. iterable = iterable.values()
  1646. else:
  1647. iterable = iter(iterable)
  1648. elif util.duck_type_collection(iterable) is dict:
  1649. new_keys = list(value)
  1650. new_values = list(iterable)
  1651. evt = self._bulk_replace_token
  1652. self.dispatch.bulk_replace(state, new_values, evt, keys=new_keys)
  1653. # propagate NO_RAISE in passive through to the get() for the
  1654. # existing object (ticket #8862)
  1655. old = self.get(
  1656. state,
  1657. dict_,
  1658. passive=PASSIVE_ONLY_PERSISTENT ^ (passive & PassiveFlag.NO_RAISE),
  1659. )
  1660. if old is PASSIVE_NO_RESULT:
  1661. old = self._default_value(state, dict_)
  1662. elif old is orig_iterable:
  1663. # ignore re-assignment of the current collection, as happens
  1664. # implicitly with in-place operators (foo.collection |= other)
  1665. return
  1666. # place a copy of "old" in state.committed_state
  1667. state._modified_event(dict_, self, old, True)
  1668. old_collection = old._sa_adapter
  1669. dict_[self.key] = user_data
  1670. collections.bulk_replace(
  1671. new_values, old_collection, new_collection, initiator=evt
  1672. )
  1673. self._dispose_previous_collection(state, old, old_collection, True)
  1674. def _dispose_previous_collection(
  1675. self,
  1676. state: InstanceState[Any],
  1677. collection: _AdaptedCollectionProtocol,
  1678. adapter: CollectionAdapter,
  1679. fire_event: bool,
  1680. ) -> None:
  1681. del collection._sa_adapter
  1682. # discarding old collection make sure it is not referenced in empty
  1683. # collections.
  1684. state._empty_collections.pop(self.key, None)
  1685. if fire_event:
  1686. self.dispatch.dispose_collection(state, collection, adapter)
  1687. def _invalidate_collection(
  1688. self, collection: _AdaptedCollectionProtocol
  1689. ) -> None:
  1690. adapter = getattr(collection, "_sa_adapter")
  1691. adapter.invalidated = True
  1692. def set_committed_value(
  1693. self, state: InstanceState[Any], dict_: _InstanceDict, value: Any
  1694. ) -> _AdaptedCollectionProtocol:
  1695. """Set an attribute value on the given instance and 'commit' it."""
  1696. collection, user_data = self._initialize_collection(state)
  1697. if value:
  1698. collection.append_multiple_without_event(value)
  1699. state.dict[self.key] = user_data
  1700. state._commit(dict_, [self.key])
  1701. if self.key in state._pending_mutations:
  1702. # pending items exist. issue a modified event,
  1703. # add/remove new items.
  1704. state._modified_event(dict_, self, user_data, True)
  1705. pending = state._pending_mutations.pop(self.key)
  1706. added = pending.added_items
  1707. removed = pending.deleted_items
  1708. for item in added:
  1709. collection.append_without_event(item)
  1710. for item in removed:
  1711. collection.remove_without_event(item)
  1712. return user_data
  1713. @overload
  1714. def get_collection(
  1715. self,
  1716. state: InstanceState[Any],
  1717. dict_: _InstanceDict,
  1718. user_data: Literal[None] = ...,
  1719. passive: Literal[PassiveFlag.PASSIVE_OFF] = ...,
  1720. ) -> CollectionAdapter: ...
  1721. @overload
  1722. def get_collection(
  1723. self,
  1724. state: InstanceState[Any],
  1725. dict_: _InstanceDict,
  1726. user_data: _AdaptedCollectionProtocol = ...,
  1727. passive: PassiveFlag = ...,
  1728. ) -> CollectionAdapter: ...
  1729. @overload
  1730. def get_collection(
  1731. self,
  1732. state: InstanceState[Any],
  1733. dict_: _InstanceDict,
  1734. user_data: Optional[_AdaptedCollectionProtocol] = ...,
  1735. passive: PassiveFlag = PASSIVE_OFF,
  1736. ) -> Union[
  1737. Literal[LoaderCallableStatus.PASSIVE_NO_RESULT], CollectionAdapter
  1738. ]: ...
  1739. def get_collection(
  1740. self,
  1741. state: InstanceState[Any],
  1742. dict_: _InstanceDict,
  1743. user_data: Optional[_AdaptedCollectionProtocol] = None,
  1744. passive: PassiveFlag = PASSIVE_OFF,
  1745. ) -> Union[
  1746. Literal[LoaderCallableStatus.PASSIVE_NO_RESULT], CollectionAdapter
  1747. ]:
  1748. """Retrieve the CollectionAdapter associated with the given state.
  1749. if user_data is None, retrieves it from the state using normal
  1750. "get()" rules, which will fire lazy callables or return the "empty"
  1751. collection value.
  1752. """
  1753. if user_data is None:
  1754. fetch_user_data = self.get(state, dict_, passive=passive)
  1755. if fetch_user_data is LoaderCallableStatus.PASSIVE_NO_RESULT:
  1756. return fetch_user_data
  1757. else:
  1758. user_data = cast("_AdaptedCollectionProtocol", fetch_user_data)
  1759. return user_data._sa_adapter
  1760. def backref_listeners(
  1761. attribute: QueryableAttribute[Any], key: str, uselist: bool
  1762. ) -> None:
  1763. """Apply listeners to synchronize a two-way relationship."""
  1764. # use easily recognizable names for stack traces.
  1765. # in the sections marked "tokens to test for a recursive loop",
  1766. # this is somewhat brittle and very performance-sensitive logic
  1767. # that is specific to how we might arrive at each event. a marker
  1768. # that can target us directly to arguments being invoked against
  1769. # the impl might be simpler, but could interfere with other systems.
  1770. parent_token = attribute.impl.parent_token
  1771. parent_impl = attribute.impl
  1772. def _acceptable_key_err(child_state, initiator, child_impl):
  1773. raise ValueError(
  1774. "Bidirectional attribute conflict detected: "
  1775. 'Passing object %s to attribute "%s" '
  1776. 'triggers a modify event on attribute "%s" '
  1777. 'via the backref "%s".'
  1778. % (
  1779. state_str(child_state),
  1780. initiator.parent_token,
  1781. child_impl.parent_token,
  1782. attribute.impl.parent_token,
  1783. )
  1784. )
  1785. def emit_backref_from_scalar_set_event(
  1786. state, child, oldchild, initiator, **kw
  1787. ):
  1788. if oldchild is child:
  1789. return child
  1790. if (
  1791. oldchild is not None
  1792. and oldchild is not PASSIVE_NO_RESULT
  1793. and oldchild is not NO_VALUE
  1794. ):
  1795. # With lazy=None, there's no guarantee that the full collection is
  1796. # present when updating via a backref.
  1797. old_state, old_dict = (
  1798. instance_state(oldchild),
  1799. instance_dict(oldchild),
  1800. )
  1801. impl = old_state.manager[key].impl
  1802. # tokens to test for a recursive loop.
  1803. if not impl.collection and not impl.dynamic:
  1804. check_recursive_token = impl._replace_token
  1805. else:
  1806. check_recursive_token = impl._remove_token
  1807. if initiator is not check_recursive_token:
  1808. impl.pop(
  1809. old_state,
  1810. old_dict,
  1811. state.obj(),
  1812. parent_impl._append_token,
  1813. passive=PASSIVE_NO_FETCH,
  1814. )
  1815. if child is not None:
  1816. child_state, child_dict = (
  1817. instance_state(child),
  1818. instance_dict(child),
  1819. )
  1820. child_impl = child_state.manager[key].impl
  1821. if (
  1822. initiator.parent_token is not parent_token
  1823. and initiator.parent_token is not child_impl.parent_token
  1824. ):
  1825. _acceptable_key_err(state, initiator, child_impl)
  1826. # tokens to test for a recursive loop.
  1827. check_append_token = child_impl._append_token
  1828. check_bulk_replace_token = (
  1829. child_impl._bulk_replace_token
  1830. if _is_collection_attribute_impl(child_impl)
  1831. else None
  1832. )
  1833. if (
  1834. initiator is not check_append_token
  1835. and initiator is not check_bulk_replace_token
  1836. ):
  1837. child_impl.append(
  1838. child_state,
  1839. child_dict,
  1840. state.obj(),
  1841. initiator,
  1842. passive=PASSIVE_NO_FETCH,
  1843. )
  1844. return child
  1845. def emit_backref_from_collection_append_event(
  1846. state, child, initiator, **kw
  1847. ):
  1848. if child is None:
  1849. return
  1850. child_state, child_dict = instance_state(child), instance_dict(child)
  1851. child_impl = child_state.manager[key].impl
  1852. if (
  1853. initiator.parent_token is not parent_token
  1854. and initiator.parent_token is not child_impl.parent_token
  1855. ):
  1856. _acceptable_key_err(state, initiator, child_impl)
  1857. # tokens to test for a recursive loop.
  1858. check_append_token = child_impl._append_token
  1859. check_bulk_replace_token = (
  1860. child_impl._bulk_replace_token
  1861. if _is_collection_attribute_impl(child_impl)
  1862. else None
  1863. )
  1864. if (
  1865. initiator is not check_append_token
  1866. and initiator is not check_bulk_replace_token
  1867. ):
  1868. child_impl.append(
  1869. child_state,
  1870. child_dict,
  1871. state.obj(),
  1872. initiator,
  1873. passive=PASSIVE_NO_FETCH,
  1874. )
  1875. return child
  1876. def emit_backref_from_collection_remove_event(
  1877. state, child, initiator, **kw
  1878. ):
  1879. if (
  1880. child is not None
  1881. and child is not PASSIVE_NO_RESULT
  1882. and child is not NO_VALUE
  1883. ):
  1884. child_state, child_dict = (
  1885. instance_state(child),
  1886. instance_dict(child),
  1887. )
  1888. child_impl = child_state.manager[key].impl
  1889. check_replace_token: Optional[AttributeEventToken]
  1890. # tokens to test for a recursive loop.
  1891. if not child_impl.collection and not child_impl.dynamic:
  1892. check_remove_token = child_impl._remove_token
  1893. check_replace_token = child_impl._replace_token
  1894. check_for_dupes_on_remove = uselist and not parent_impl.dynamic
  1895. else:
  1896. check_remove_token = child_impl._remove_token
  1897. check_replace_token = (
  1898. child_impl._bulk_replace_token
  1899. if _is_collection_attribute_impl(child_impl)
  1900. else None
  1901. )
  1902. check_for_dupes_on_remove = False
  1903. if (
  1904. initiator is not check_remove_token
  1905. and initiator is not check_replace_token
  1906. ):
  1907. if not check_for_dupes_on_remove or not util.has_dupes(
  1908. # when this event is called, the item is usually
  1909. # present in the list, except for a pop() operation.
  1910. state.dict[parent_impl.key],
  1911. child,
  1912. ):
  1913. child_impl.pop(
  1914. child_state,
  1915. child_dict,
  1916. state.obj(),
  1917. initiator,
  1918. passive=PASSIVE_NO_FETCH,
  1919. )
  1920. if uselist:
  1921. event.listen(
  1922. attribute,
  1923. "append",
  1924. emit_backref_from_collection_append_event,
  1925. retval=True,
  1926. raw=True,
  1927. include_key=True,
  1928. )
  1929. else:
  1930. event.listen(
  1931. attribute,
  1932. "set",
  1933. emit_backref_from_scalar_set_event,
  1934. retval=True,
  1935. raw=True,
  1936. include_key=True,
  1937. )
  1938. # TODO: need coverage in test/orm/ of remove event
  1939. event.listen(
  1940. attribute,
  1941. "remove",
  1942. emit_backref_from_collection_remove_event,
  1943. retval=True,
  1944. raw=True,
  1945. include_key=True,
  1946. )
  1947. _NO_HISTORY = util.symbol("NO_HISTORY")
  1948. _NO_STATE_SYMBOLS = frozenset([id(PASSIVE_NO_RESULT), id(NO_VALUE)])
  1949. class History(NamedTuple):
  1950. """A 3-tuple of added, unchanged and deleted values,
  1951. representing the changes which have occurred on an instrumented
  1952. attribute.
  1953. The easiest way to get a :class:`.History` object for a particular
  1954. attribute on an object is to use the :func:`_sa.inspect` function::
  1955. from sqlalchemy import inspect
  1956. hist = inspect(myobject).attrs.myattribute.history
  1957. Each tuple member is an iterable sequence:
  1958. * ``added`` - the collection of items added to the attribute (the first
  1959. tuple element).
  1960. * ``unchanged`` - the collection of items that have not changed on the
  1961. attribute (the second tuple element).
  1962. * ``deleted`` - the collection of items that have been removed from the
  1963. attribute (the third tuple element).
  1964. """
  1965. added: Union[Tuple[()], List[Any]]
  1966. unchanged: Union[Tuple[()], List[Any]]
  1967. deleted: Union[Tuple[()], List[Any]]
  1968. def __bool__(self) -> bool:
  1969. return self != HISTORY_BLANK
  1970. def empty(self) -> bool:
  1971. """Return True if this :class:`.History` has no changes
  1972. and no existing, unchanged state.
  1973. """
  1974. return not bool((self.added or self.deleted) or self.unchanged)
  1975. def sum(self) -> Sequence[Any]:
  1976. """Return a collection of added + unchanged + deleted."""
  1977. return (
  1978. (self.added or []) + (self.unchanged or []) + (self.deleted or [])
  1979. )
  1980. def non_deleted(self) -> Sequence[Any]:
  1981. """Return a collection of added + unchanged."""
  1982. return (self.added or []) + (self.unchanged or [])
  1983. def non_added(self) -> Sequence[Any]:
  1984. """Return a collection of unchanged + deleted."""
  1985. return (self.unchanged or []) + (self.deleted or [])
  1986. def has_changes(self) -> bool:
  1987. """Return True if this :class:`.History` has changes."""
  1988. return bool(self.added or self.deleted)
  1989. def _merge(self, added: Iterable[Any], deleted: Iterable[Any]) -> History:
  1990. return History(
  1991. list(self.added) + list(added),
  1992. self.unchanged,
  1993. list(self.deleted) + list(deleted),
  1994. )
  1995. def as_state(self) -> History:
  1996. return History(
  1997. [
  1998. (c is not None) and instance_state(c) or None
  1999. for c in self.added
  2000. ],
  2001. [
  2002. (c is not None) and instance_state(c) or None
  2003. for c in self.unchanged
  2004. ],
  2005. [
  2006. (c is not None) and instance_state(c) or None
  2007. for c in self.deleted
  2008. ],
  2009. )
  2010. @classmethod
  2011. def from_scalar_attribute(
  2012. cls,
  2013. attribute: ScalarAttributeImpl,
  2014. state: InstanceState[Any],
  2015. current: Any,
  2016. ) -> History:
  2017. original = state.committed_state.get(attribute.key, _NO_HISTORY)
  2018. deleted: Union[Tuple[()], List[Any]]
  2019. if original is _NO_HISTORY:
  2020. if current is NO_VALUE:
  2021. return cls((), (), ())
  2022. else:
  2023. return cls((), [current], ())
  2024. # don't let ClauseElement expressions here trip things up
  2025. elif (
  2026. current is not NO_VALUE
  2027. and attribute.is_equal(current, original) is True
  2028. ):
  2029. return cls((), [current], ())
  2030. else:
  2031. # current convention on native scalars is to not
  2032. # include information
  2033. # about missing previous value in "deleted", but
  2034. # we do include None, which helps in some primary
  2035. # key situations
  2036. if id(original) in _NO_STATE_SYMBOLS:
  2037. deleted = ()
  2038. # indicate a "del" operation occurred when we don't have
  2039. # the previous value as: ([None], (), ())
  2040. if id(current) in _NO_STATE_SYMBOLS:
  2041. current = None
  2042. else:
  2043. deleted = [original]
  2044. if current is NO_VALUE:
  2045. return cls((), (), deleted)
  2046. else:
  2047. return cls([current], (), deleted)
  2048. @classmethod
  2049. def from_object_attribute(
  2050. cls,
  2051. attribute: ScalarObjectAttributeImpl,
  2052. state: InstanceState[Any],
  2053. current: Any,
  2054. original: Any = _NO_HISTORY,
  2055. ) -> History:
  2056. deleted: Union[Tuple[()], List[Any]]
  2057. if original is _NO_HISTORY:
  2058. original = state.committed_state.get(attribute.key, _NO_HISTORY)
  2059. if original is _NO_HISTORY:
  2060. if current is NO_VALUE:
  2061. return cls((), (), ())
  2062. else:
  2063. return cls((), [current], ())
  2064. elif current is original and current is not NO_VALUE:
  2065. return cls((), [current], ())
  2066. else:
  2067. # current convention on related objects is to not
  2068. # include information
  2069. # about missing previous value in "deleted", and
  2070. # to also not include None - the dependency.py rules
  2071. # ignore the None in any case.
  2072. if id(original) in _NO_STATE_SYMBOLS or original is None:
  2073. deleted = ()
  2074. # indicate a "del" operation occurred when we don't have
  2075. # the previous value as: ([None], (), ())
  2076. if id(current) in _NO_STATE_SYMBOLS:
  2077. current = None
  2078. else:
  2079. deleted = [original]
  2080. if current is NO_VALUE:
  2081. return cls((), (), deleted)
  2082. else:
  2083. return cls([current], (), deleted)
  2084. @classmethod
  2085. def from_collection(
  2086. cls,
  2087. attribute: CollectionAttributeImpl,
  2088. state: InstanceState[Any],
  2089. current: Any,
  2090. ) -> History:
  2091. original = state.committed_state.get(attribute.key, _NO_HISTORY)
  2092. if current is NO_VALUE:
  2093. return cls((), (), ())
  2094. current = getattr(current, "_sa_adapter")
  2095. if original is NO_VALUE:
  2096. return cls(list(current), (), ())
  2097. elif original is _NO_HISTORY:
  2098. return cls((), list(current), ())
  2099. else:
  2100. current_states = [
  2101. ((c is not None) and instance_state(c) or None, c)
  2102. for c in current
  2103. ]
  2104. original_states = [
  2105. ((c is not None) and instance_state(c) or None, c)
  2106. for c in original
  2107. ]
  2108. current_set = dict(current_states)
  2109. original_set = dict(original_states)
  2110. return cls(
  2111. [o for s, o in current_states if s not in original_set],
  2112. [o for s, o in current_states if s in original_set],
  2113. [o for s, o in original_states if s not in current_set],
  2114. )
  2115. HISTORY_BLANK = History((), (), ())
  2116. def get_history(
  2117. obj: object, key: str, passive: PassiveFlag = PASSIVE_OFF
  2118. ) -> History:
  2119. """Return a :class:`.History` record for the given object
  2120. and attribute key.
  2121. This is the **pre-flush** history for a given attribute, which is
  2122. reset each time the :class:`.Session` flushes changes to the
  2123. current database transaction.
  2124. .. note::
  2125. Prefer to use the :attr:`.AttributeState.history` and
  2126. :meth:`.AttributeState.load_history` accessors to retrieve the
  2127. :class:`.History` for instance attributes.
  2128. :param obj: an object whose class is instrumented by the
  2129. attributes package.
  2130. :param key: string attribute name.
  2131. :param passive: indicates loading behavior for the attribute
  2132. if the value is not already present. This is a
  2133. bitflag attribute, which defaults to the symbol
  2134. :attr:`.PASSIVE_OFF` indicating all necessary SQL
  2135. should be emitted.
  2136. .. seealso::
  2137. :attr:`.AttributeState.history`
  2138. :meth:`.AttributeState.load_history` - retrieve history
  2139. using loader callables if the value is not locally present.
  2140. """
  2141. return get_state_history(instance_state(obj), key, passive)
  2142. def get_state_history(
  2143. state: InstanceState[Any], key: str, passive: PassiveFlag = PASSIVE_OFF
  2144. ) -> History:
  2145. return state.get_history(key, passive)
  2146. def has_parent(
  2147. cls: Type[_O], obj: _O, key: str, optimistic: bool = False
  2148. ) -> bool:
  2149. """TODO"""
  2150. manager = manager_of_class(cls)
  2151. state = instance_state(obj)
  2152. return manager.has_parent(state, key, optimistic)
  2153. def register_attribute(
  2154. class_: Type[_O],
  2155. key: str,
  2156. *,
  2157. comparator: interfaces.PropComparator[_T],
  2158. parententity: _InternalEntityType[_O],
  2159. doc: Optional[str] = None,
  2160. **kw: Any,
  2161. ) -> InstrumentedAttribute[_T]:
  2162. desc = register_descriptor(
  2163. class_, key, comparator=comparator, parententity=parententity, doc=doc
  2164. )
  2165. register_attribute_impl(class_, key, **kw)
  2166. return desc
  2167. def register_attribute_impl(
  2168. class_: Type[_O],
  2169. key: str,
  2170. uselist: bool = False,
  2171. callable_: Optional[_LoaderCallable] = None,
  2172. useobject: bool = False,
  2173. impl_class: Optional[Type[AttributeImpl]] = None,
  2174. backref: Optional[str] = None,
  2175. **kw: Any,
  2176. ) -> QueryableAttribute[Any]:
  2177. manager = manager_of_class(class_)
  2178. if uselist:
  2179. factory = kw.pop("typecallable", None)
  2180. typecallable = manager.instrument_collection_class(
  2181. key, factory or list
  2182. )
  2183. else:
  2184. typecallable = kw.pop("typecallable", None)
  2185. dispatch = cast(
  2186. "_Dispatch[QueryableAttribute[Any]]", manager[key].dispatch
  2187. ) # noqa: E501
  2188. impl: AttributeImpl
  2189. if impl_class:
  2190. # TODO: this appears to be the WriteOnlyAttributeImpl /
  2191. # DynamicAttributeImpl constructor which is hardcoded
  2192. impl = cast("Type[WriteOnlyAttributeImpl]", impl_class)(
  2193. class_, key, dispatch, **kw
  2194. )
  2195. elif uselist:
  2196. impl = CollectionAttributeImpl(
  2197. class_, key, callable_, dispatch, typecallable=typecallable, **kw
  2198. )
  2199. elif useobject:
  2200. impl = ScalarObjectAttributeImpl(
  2201. class_, key, callable_, dispatch, **kw
  2202. )
  2203. else:
  2204. impl = ScalarAttributeImpl(class_, key, callable_, dispatch, **kw)
  2205. manager[key].impl = impl
  2206. if backref:
  2207. backref_listeners(manager[key], backref, uselist)
  2208. manager.post_configure_attribute(key)
  2209. return manager[key]
  2210. def register_descriptor(
  2211. class_: Type[Any],
  2212. key: str,
  2213. *,
  2214. comparator: interfaces.PropComparator[_T],
  2215. parententity: _InternalEntityType[Any],
  2216. doc: Optional[str] = None,
  2217. ) -> InstrumentedAttribute[_T]:
  2218. manager = manager_of_class(class_)
  2219. descriptor = InstrumentedAttribute(
  2220. class_, key, comparator=comparator, parententity=parententity
  2221. )
  2222. descriptor.__doc__ = doc # type: ignore
  2223. manager.instrument_attribute(key, descriptor)
  2224. return descriptor
  2225. def unregister_attribute(class_: Type[Any], key: str) -> None:
  2226. manager_of_class(class_).uninstrument_attribute(key)
  2227. def init_collection(obj: object, key: str) -> CollectionAdapter:
  2228. """Initialize a collection attribute and return the collection adapter.
  2229. This function is used to provide direct access to collection internals
  2230. for a previously unloaded attribute. e.g.::
  2231. collection_adapter = init_collection(someobject, "elements")
  2232. for elem in values:
  2233. collection_adapter.append_without_event(elem)
  2234. For an easier way to do the above, see
  2235. :func:`~sqlalchemy.orm.attributes.set_committed_value`.
  2236. :param obj: a mapped object
  2237. :param key: string attribute name where the collection is located.
  2238. """
  2239. state = instance_state(obj)
  2240. dict_ = state.dict
  2241. return init_state_collection(state, dict_, key)
  2242. def init_state_collection(
  2243. state: InstanceState[Any], dict_: _InstanceDict, key: str
  2244. ) -> CollectionAdapter:
  2245. """Initialize a collection attribute and return the collection adapter.
  2246. Discards any existing collection which may be there.
  2247. """
  2248. attr = state.manager[key].impl
  2249. if TYPE_CHECKING:
  2250. assert isinstance(attr, HasCollectionAdapter)
  2251. old = dict_.pop(key, None) # discard old collection
  2252. if old is not None:
  2253. old_collection = old._sa_adapter
  2254. attr._dispose_previous_collection(state, old, old_collection, False)
  2255. user_data = attr._default_value(state, dict_)
  2256. adapter: CollectionAdapter = attr.get_collection(
  2257. state, dict_, user_data, passive=PassiveFlag.PASSIVE_NO_FETCH
  2258. )
  2259. adapter._reset_empty()
  2260. return adapter
  2261. def set_committed_value(instance: object, key: str, value: Any) -> None:
  2262. """Set the value of an attribute with no history events.
  2263. Cancels any previous history present. The value should be
  2264. a scalar value for scalar-holding attributes, or
  2265. an iterable for any collection-holding attribute.
  2266. This is the same underlying method used when a lazy loader
  2267. fires off and loads additional data from the database.
  2268. In particular, this method can be used by application code
  2269. which has loaded additional attributes or collections through
  2270. separate queries, which can then be attached to an instance
  2271. as though it were part of its original loaded state.
  2272. """
  2273. state, dict_ = instance_state(instance), instance_dict(instance)
  2274. state.manager[key].impl.set_committed_value(state, dict_, value)
  2275. def set_attribute(
  2276. instance: object,
  2277. key: str,
  2278. value: Any,
  2279. initiator: Optional[AttributeEventToken] = None,
  2280. ) -> None:
  2281. """Set the value of an attribute, firing history events.
  2282. This function may be used regardless of instrumentation
  2283. applied directly to the class, i.e. no descriptors are required.
  2284. Custom attribute management schemes will need to make usage
  2285. of this method to establish attribute state as understood
  2286. by SQLAlchemy.
  2287. :param instance: the object that will be modified
  2288. :param key: string name of the attribute
  2289. :param value: value to assign
  2290. :param initiator: an instance of :class:`.Event` that would have
  2291. been propagated from a previous event listener. This argument
  2292. is used when the :func:`.set_attribute` function is being used within
  2293. an existing event listening function where an :class:`.Event` object
  2294. is being supplied; the object may be used to track the origin of the
  2295. chain of events.
  2296. .. versionadded:: 1.2.3
  2297. """
  2298. state, dict_ = instance_state(instance), instance_dict(instance)
  2299. state.manager[key].impl.set(state, dict_, value, initiator)
  2300. def get_attribute(instance: object, key: str) -> Any:
  2301. """Get the value of an attribute, firing any callables required.
  2302. This function may be used regardless of instrumentation
  2303. applied directly to the class, i.e. no descriptors are required.
  2304. Custom attribute management schemes will need to make usage
  2305. of this method to make usage of attribute state as understood
  2306. by SQLAlchemy.
  2307. """
  2308. state, dict_ = instance_state(instance), instance_dict(instance)
  2309. return state.manager[key].impl.get(state, dict_)
  2310. def del_attribute(instance: object, key: str) -> None:
  2311. """Delete the value of an attribute, firing history events.
  2312. This function may be used regardless of instrumentation
  2313. applied directly to the class, i.e. no descriptors are required.
  2314. Custom attribute management schemes will need to make usage
  2315. of this method to establish attribute state as understood
  2316. by SQLAlchemy.
  2317. """
  2318. state, dict_ = instance_state(instance), instance_dict(instance)
  2319. state.manager[key].impl.delete(state, dict_)
  2320. def flag_modified(instance: object, key: str) -> None:
  2321. """Mark an attribute on an instance as 'modified'.
  2322. This sets the 'modified' flag on the instance and
  2323. establishes an unconditional change event for the given attribute.
  2324. The attribute must have a value present, else an
  2325. :class:`.InvalidRequestError` is raised.
  2326. To mark an object "dirty" without referring to any specific attribute
  2327. so that it is considered within a flush, use the
  2328. :func:`.attributes.flag_dirty` call.
  2329. .. seealso::
  2330. :func:`.attributes.flag_dirty`
  2331. """
  2332. state, dict_ = instance_state(instance), instance_dict(instance)
  2333. impl = state.manager[key].impl
  2334. impl.dispatch.modified(state, impl._modified_token)
  2335. state._modified_event(dict_, impl, NO_VALUE, is_userland=True)
  2336. def flag_dirty(instance: object) -> None:
  2337. """Mark an instance as 'dirty' without any specific attribute mentioned.
  2338. This is a special operation that will allow the object to travel through
  2339. the flush process for interception by events such as
  2340. :meth:`.SessionEvents.before_flush`. Note that no SQL will be emitted in
  2341. the flush process for an object that has no changes, even if marked dirty
  2342. via this method. However, a :meth:`.SessionEvents.before_flush` handler
  2343. will be able to see the object in the :attr:`.Session.dirty` collection and
  2344. may establish changes on it, which will then be included in the SQL
  2345. emitted.
  2346. .. versionadded:: 1.2
  2347. .. seealso::
  2348. :func:`.attributes.flag_modified`
  2349. """
  2350. state, dict_ = instance_state(instance), instance_dict(instance)
  2351. state._modified_event(dict_, None, NO_VALUE, is_userland=True)