relationships.py 125 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466
  1. # orm/relationships.py
  2. # Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
  3. # <see AUTHORS file>
  4. #
  5. # This module is part of SQLAlchemy and is released under
  6. # the MIT License: https://www.opensource.org/licenses/mit-license.php
  7. """Heuristics related to join conditions as used in
  8. :func:`_orm.relationship`.
  9. Provides the :class:`.JoinCondition` object, which encapsulates
  10. SQL annotation and aliasing behavior focused on the `primaryjoin`
  11. and `secondaryjoin` aspects of :func:`_orm.relationship`.
  12. """
  13. from __future__ import annotations
  14. import collections
  15. from collections import abc
  16. import dataclasses
  17. import inspect as _py_inspect
  18. import re
  19. import typing
  20. from typing import Any
  21. from typing import Callable
  22. from typing import cast
  23. from typing import Collection
  24. from typing import Dict
  25. from typing import Generic
  26. from typing import Iterable
  27. from typing import Iterator
  28. from typing import List
  29. from typing import NamedTuple
  30. from typing import NoReturn
  31. from typing import Optional
  32. from typing import Sequence
  33. from typing import Set
  34. from typing import Tuple
  35. from typing import Type
  36. from typing import TypeVar
  37. from typing import Union
  38. import weakref
  39. from . import attributes
  40. from . import strategy_options
  41. from ._typing import insp_is_aliased_class
  42. from ._typing import is_has_collection_adapter
  43. from .base import _DeclarativeMapped
  44. from .base import _is_mapped_class
  45. from .base import class_mapper
  46. from .base import DynamicMapped
  47. from .base import LoaderCallableStatus
  48. from .base import PassiveFlag
  49. from .base import state_str
  50. from .base import WriteOnlyMapped
  51. from .interfaces import _AttributeOptions
  52. from .interfaces import _IntrospectsAnnotations
  53. from .interfaces import MANYTOMANY
  54. from .interfaces import MANYTOONE
  55. from .interfaces import ONETOMANY
  56. from .interfaces import PropComparator
  57. from .interfaces import RelationshipDirection
  58. from .interfaces import StrategizedProperty
  59. from .util import _orm_annotate
  60. from .util import _orm_deannotate
  61. from .util import CascadeOptions
  62. from .. import exc as sa_exc
  63. from .. import Exists
  64. from .. import log
  65. from .. import schema
  66. from .. import sql
  67. from .. import util
  68. from ..inspection import inspect
  69. from ..sql import coercions
  70. from ..sql import expression
  71. from ..sql import operators
  72. from ..sql import roles
  73. from ..sql import visitors
  74. from ..sql._typing import _ColumnExpressionArgument
  75. from ..sql._typing import _HasClauseElement
  76. from ..sql.annotation import _safe_annotate
  77. from ..sql.elements import ColumnClause
  78. from ..sql.elements import ColumnElement
  79. from ..sql.util import _deep_annotate
  80. from ..sql.util import _deep_deannotate
  81. from ..sql.util import _shallow_annotate
  82. from ..sql.util import adapt_criterion_to_null
  83. from ..sql.util import ClauseAdapter
  84. from ..sql.util import join_condition
  85. from ..sql.util import selectables_overlap
  86. from ..sql.util import visit_binary_product
  87. from ..util.typing import de_optionalize_union_types
  88. from ..util.typing import Literal
  89. from ..util.typing import resolve_name_to_real_class_name
  90. if typing.TYPE_CHECKING:
  91. from ._typing import _EntityType
  92. from ._typing import _ExternalEntityType
  93. from ._typing import _IdentityKeyType
  94. from ._typing import _InstanceDict
  95. from ._typing import _InternalEntityType
  96. from ._typing import _O
  97. from ._typing import _RegistryType
  98. from .base import Mapped
  99. from .clsregistry import _class_resolver
  100. from .clsregistry import _ModNS
  101. from .decl_base import _ClassScanMapperConfig
  102. from .dependency import DependencyProcessor
  103. from .mapper import Mapper
  104. from .query import Query
  105. from .session import Session
  106. from .state import InstanceState
  107. from .strategies import LazyLoader
  108. from .util import AliasedClass
  109. from .util import AliasedInsp
  110. from ..sql._typing import _CoreAdapterProto
  111. from ..sql._typing import _EquivalentColumnMap
  112. from ..sql._typing import _InfoType
  113. from ..sql.annotation import _AnnotationDict
  114. from ..sql.annotation import SupportsAnnotations
  115. from ..sql.elements import BinaryExpression
  116. from ..sql.elements import BindParameter
  117. from ..sql.elements import ClauseElement
  118. from ..sql.schema import Table
  119. from ..sql.selectable import FromClause
  120. from ..util.typing import _AnnotationScanType
  121. from ..util.typing import RODescriptorReference
  122. _T = TypeVar("_T", bound=Any)
  123. _T1 = TypeVar("_T1", bound=Any)
  124. _T2 = TypeVar("_T2", bound=Any)
  125. _PT = TypeVar("_PT", bound=Any)
  126. _PT2 = TypeVar("_PT2", bound=Any)
  127. _RelationshipArgumentType = Union[
  128. str,
  129. Type[_T],
  130. Callable[[], Type[_T]],
  131. "Mapper[_T]",
  132. "AliasedClass[_T]",
  133. Callable[[], "Mapper[_T]"],
  134. Callable[[], "AliasedClass[_T]"],
  135. ]
  136. _LazyLoadArgumentType = Literal[
  137. "select",
  138. "joined",
  139. "selectin",
  140. "subquery",
  141. "raise",
  142. "raise_on_sql",
  143. "noload",
  144. "immediate",
  145. "write_only",
  146. "dynamic",
  147. True,
  148. False,
  149. None,
  150. ]
  151. _RelationshipJoinConditionArgument = Union[
  152. str, _ColumnExpressionArgument[bool]
  153. ]
  154. _RelationshipSecondaryArgument = Union[
  155. "FromClause", str, Callable[[], "FromClause"]
  156. ]
  157. _ORMOrderByArgument = Union[
  158. Literal[False],
  159. str,
  160. _ColumnExpressionArgument[Any],
  161. Callable[[], _ColumnExpressionArgument[Any]],
  162. Callable[[], Iterable[_ColumnExpressionArgument[Any]]],
  163. Iterable[Union[str, _ColumnExpressionArgument[Any]]],
  164. ]
  165. ORMBackrefArgument = Union[str, Tuple[str, Dict[str, Any]]]
  166. _ORMColCollectionElement = Union[
  167. ColumnClause[Any],
  168. _HasClauseElement[Any],
  169. roles.DMLColumnRole,
  170. "Mapped[Any]",
  171. ]
  172. _ORMColCollectionArgument = Union[
  173. str,
  174. Sequence[_ORMColCollectionElement],
  175. Callable[[], Sequence[_ORMColCollectionElement]],
  176. Callable[[], _ORMColCollectionElement],
  177. _ORMColCollectionElement,
  178. ]
  179. _CEA = TypeVar("_CEA", bound=_ColumnExpressionArgument[Any])
  180. _CE = TypeVar("_CE", bound="ColumnElement[Any]")
  181. _ColumnPairIterable = Iterable[Tuple[ColumnElement[Any], ColumnElement[Any]]]
  182. _ColumnPairs = Sequence[Tuple[ColumnElement[Any], ColumnElement[Any]]]
  183. _MutableColumnPairs = List[Tuple[ColumnElement[Any], ColumnElement[Any]]]
  184. def remote(expr: _CEA) -> _CEA:
  185. """Annotate a portion of a primaryjoin expression
  186. with a 'remote' annotation.
  187. See the section :ref:`relationship_custom_foreign` for a
  188. description of use.
  189. .. seealso::
  190. :ref:`relationship_custom_foreign`
  191. :func:`.foreign`
  192. """
  193. return _annotate_columns( # type: ignore
  194. coercions.expect(roles.ColumnArgumentRole, expr), {"remote": True}
  195. )
  196. def foreign(expr: _CEA) -> _CEA:
  197. """Annotate a portion of a primaryjoin expression
  198. with a 'foreign' annotation.
  199. See the section :ref:`relationship_custom_foreign` for a
  200. description of use.
  201. .. seealso::
  202. :ref:`relationship_custom_foreign`
  203. :func:`.remote`
  204. """
  205. return _annotate_columns( # type: ignore
  206. coercions.expect(roles.ColumnArgumentRole, expr), {"foreign": True}
  207. )
  208. @dataclasses.dataclass
  209. class _RelationshipArg(Generic[_T1, _T2]):
  210. """stores a user-defined parameter value that must be resolved and
  211. parsed later at mapper configuration time.
  212. """
  213. __slots__ = "name", "argument", "resolved"
  214. name: str
  215. argument: _T1
  216. resolved: Optional[_T2]
  217. def _is_populated(self) -> bool:
  218. return self.argument is not None
  219. def _resolve_against_registry(
  220. self, clsregistry_resolver: Callable[[str, bool], _class_resolver]
  221. ) -> None:
  222. attr_value = self.argument
  223. if isinstance(attr_value, str):
  224. self.resolved = clsregistry_resolver(
  225. attr_value, self.name == "secondary"
  226. )()
  227. elif callable(attr_value) and not _is_mapped_class(attr_value):
  228. self.resolved = attr_value()
  229. else:
  230. self.resolved = attr_value
  231. _RelationshipOrderByArg = Union[Literal[False], Tuple[ColumnElement[Any], ...]]
  232. class _RelationshipArgs(NamedTuple):
  233. """stores user-passed parameters that are resolved at mapper configuration
  234. time.
  235. """
  236. secondary: _RelationshipArg[
  237. Optional[_RelationshipSecondaryArgument],
  238. Optional[FromClause],
  239. ]
  240. primaryjoin: _RelationshipArg[
  241. Optional[_RelationshipJoinConditionArgument],
  242. Optional[ColumnElement[Any]],
  243. ]
  244. secondaryjoin: _RelationshipArg[
  245. Optional[_RelationshipJoinConditionArgument],
  246. Optional[ColumnElement[Any]],
  247. ]
  248. order_by: _RelationshipArg[_ORMOrderByArgument, _RelationshipOrderByArg]
  249. foreign_keys: _RelationshipArg[
  250. Optional[_ORMColCollectionArgument], Set[ColumnElement[Any]]
  251. ]
  252. remote_side: _RelationshipArg[
  253. Optional[_ORMColCollectionArgument], Set[ColumnElement[Any]]
  254. ]
  255. @log.class_logger
  256. class RelationshipProperty(
  257. _IntrospectsAnnotations, StrategizedProperty[_T], log.Identified
  258. ):
  259. """Describes an object property that holds a single item or list
  260. of items that correspond to a related database table.
  261. Public constructor is the :func:`_orm.relationship` function.
  262. .. seealso::
  263. :ref:`relationship_config_toplevel`
  264. """
  265. strategy_wildcard_key = strategy_options._RELATIONSHIP_TOKEN
  266. inherit_cache = True
  267. """:meta private:"""
  268. _links_to_entity = True
  269. _is_relationship = True
  270. _overlaps: Sequence[str]
  271. _lazy_strategy: LazyLoader
  272. _persistence_only = dict(
  273. passive_deletes=False,
  274. passive_updates=True,
  275. enable_typechecks=True,
  276. active_history=False,
  277. cascade_backrefs=False,
  278. )
  279. _dependency_processor: Optional[DependencyProcessor] = None
  280. primaryjoin: ColumnElement[bool]
  281. secondaryjoin: Optional[ColumnElement[bool]]
  282. secondary: Optional[FromClause]
  283. _join_condition: JoinCondition
  284. order_by: _RelationshipOrderByArg
  285. _user_defined_foreign_keys: Set[ColumnElement[Any]]
  286. _calculated_foreign_keys: Set[ColumnElement[Any]]
  287. remote_side: Set[ColumnElement[Any]]
  288. local_columns: Set[ColumnElement[Any]]
  289. synchronize_pairs: _ColumnPairs
  290. secondary_synchronize_pairs: Optional[_ColumnPairs]
  291. local_remote_pairs: Optional[_ColumnPairs]
  292. direction: RelationshipDirection
  293. _init_args: _RelationshipArgs
  294. def __init__(
  295. self,
  296. argument: Optional[_RelationshipArgumentType[_T]] = None,
  297. secondary: Optional[_RelationshipSecondaryArgument] = None,
  298. *,
  299. uselist: Optional[bool] = None,
  300. collection_class: Optional[
  301. Union[Type[Collection[Any]], Callable[[], Collection[Any]]]
  302. ] = None,
  303. primaryjoin: Optional[_RelationshipJoinConditionArgument] = None,
  304. secondaryjoin: Optional[_RelationshipJoinConditionArgument] = None,
  305. back_populates: Optional[str] = None,
  306. order_by: _ORMOrderByArgument = False,
  307. backref: Optional[ORMBackrefArgument] = None,
  308. overlaps: Optional[str] = None,
  309. post_update: bool = False,
  310. cascade: str = "save-update, merge",
  311. viewonly: bool = False,
  312. attribute_options: Optional[_AttributeOptions] = None,
  313. lazy: _LazyLoadArgumentType = "select",
  314. passive_deletes: Union[Literal["all"], bool] = False,
  315. passive_updates: bool = True,
  316. active_history: bool = False,
  317. enable_typechecks: bool = True,
  318. foreign_keys: Optional[_ORMColCollectionArgument] = None,
  319. remote_side: Optional[_ORMColCollectionArgument] = None,
  320. join_depth: Optional[int] = None,
  321. comparator_factory: Optional[
  322. Type[RelationshipProperty.Comparator[Any]]
  323. ] = None,
  324. single_parent: bool = False,
  325. innerjoin: bool = False,
  326. distinct_target_key: Optional[bool] = None,
  327. load_on_pending: bool = False,
  328. query_class: Optional[Type[Query[Any]]] = None,
  329. info: Optional[_InfoType] = None,
  330. omit_join: Literal[None, False] = None,
  331. sync_backref: Optional[bool] = None,
  332. doc: Optional[str] = None,
  333. bake_queries: Literal[True] = True,
  334. cascade_backrefs: Literal[False] = False,
  335. _local_remote_pairs: Optional[_ColumnPairs] = None,
  336. _legacy_inactive_history_style: bool = False,
  337. ):
  338. super().__init__(attribute_options=attribute_options)
  339. self.uselist = uselist
  340. self.argument = argument
  341. self._init_args = _RelationshipArgs(
  342. _RelationshipArg("secondary", secondary, None),
  343. _RelationshipArg("primaryjoin", primaryjoin, None),
  344. _RelationshipArg("secondaryjoin", secondaryjoin, None),
  345. _RelationshipArg("order_by", order_by, None),
  346. _RelationshipArg("foreign_keys", foreign_keys, None),
  347. _RelationshipArg("remote_side", remote_side, None),
  348. )
  349. self.post_update = post_update
  350. self.viewonly = viewonly
  351. if viewonly:
  352. self._warn_for_persistence_only_flags(
  353. passive_deletes=passive_deletes,
  354. passive_updates=passive_updates,
  355. enable_typechecks=enable_typechecks,
  356. active_history=active_history,
  357. cascade_backrefs=cascade_backrefs,
  358. )
  359. if viewonly and sync_backref:
  360. raise sa_exc.ArgumentError(
  361. "sync_backref and viewonly cannot both be True"
  362. )
  363. self.sync_backref = sync_backref
  364. self.lazy = lazy
  365. self.single_parent = single_parent
  366. self.collection_class = collection_class
  367. self.passive_deletes = passive_deletes
  368. if cascade_backrefs:
  369. raise sa_exc.ArgumentError(
  370. "The 'cascade_backrefs' parameter passed to "
  371. "relationship() may only be set to False."
  372. )
  373. self.passive_updates = passive_updates
  374. self.enable_typechecks = enable_typechecks
  375. self.query_class = query_class
  376. self.innerjoin = innerjoin
  377. self.distinct_target_key = distinct_target_key
  378. self.doc = doc
  379. self.active_history = active_history
  380. self._legacy_inactive_history_style = _legacy_inactive_history_style
  381. self.join_depth = join_depth
  382. if omit_join:
  383. util.warn(
  384. "setting omit_join to True is not supported; selectin "
  385. "loading of this relationship may not work correctly if this "
  386. "flag is set explicitly. omit_join optimization is "
  387. "automatically detected for conditions under which it is "
  388. "supported."
  389. )
  390. self.omit_join = omit_join
  391. self.local_remote_pairs = _local_remote_pairs
  392. self.load_on_pending = load_on_pending
  393. self.comparator_factory = (
  394. comparator_factory or RelationshipProperty.Comparator
  395. )
  396. util.set_creation_order(self)
  397. if info is not None:
  398. self.info.update(info)
  399. self.strategy_key = (("lazy", self.lazy),)
  400. self._reverse_property: Set[RelationshipProperty[Any]] = set()
  401. if overlaps:
  402. self._overlaps = set(re.split(r"\s*,\s*", overlaps)) # type: ignore # noqa: E501
  403. else:
  404. self._overlaps = ()
  405. # mypy ignoring the @property setter
  406. self.cascade = cascade # type: ignore
  407. self.back_populates = back_populates
  408. if self.back_populates:
  409. if backref:
  410. raise sa_exc.ArgumentError(
  411. "backref and back_populates keyword arguments "
  412. "are mutually exclusive"
  413. )
  414. self.backref = None
  415. else:
  416. self.backref = backref
  417. def _warn_for_persistence_only_flags(self, **kw: Any) -> None:
  418. for k, v in kw.items():
  419. if v != self._persistence_only[k]:
  420. # we are warning here rather than warn deprecated as this is a
  421. # configuration mistake, and Python shows regular warnings more
  422. # aggressively than deprecation warnings by default. Unlike the
  423. # case of setting viewonly with cascade, the settings being
  424. # warned about here are not actively doing the wrong thing
  425. # against viewonly=True, so it is not as urgent to have these
  426. # raise an error.
  427. util.warn(
  428. "Setting %s on relationship() while also "
  429. "setting viewonly=True does not make sense, as a "
  430. "viewonly=True relationship does not perform persistence "
  431. "operations. This configuration may raise an error "
  432. "in a future release." % (k,)
  433. )
  434. def instrument_class(self, mapper: Mapper[Any]) -> None:
  435. attributes.register_descriptor(
  436. mapper.class_,
  437. self.key,
  438. comparator=self.comparator_factory(self, mapper),
  439. parententity=mapper,
  440. doc=self.doc,
  441. )
  442. class Comparator(util.MemoizedSlots, PropComparator[_PT]):
  443. """Produce boolean, comparison, and other operators for
  444. :class:`.RelationshipProperty` attributes.
  445. See the documentation for :class:`.PropComparator` for a brief
  446. overview of ORM level operator definition.
  447. .. seealso::
  448. :class:`.PropComparator`
  449. :class:`.ColumnProperty.Comparator`
  450. :class:`.ColumnOperators`
  451. :ref:`types_operators`
  452. :attr:`.TypeEngine.comparator_factory`
  453. """
  454. __slots__ = (
  455. "entity",
  456. "mapper",
  457. "property",
  458. "_of_type",
  459. "_extra_criteria",
  460. )
  461. prop: RODescriptorReference[RelationshipProperty[_PT]]
  462. _of_type: Optional[_EntityType[_PT]]
  463. def __init__(
  464. self,
  465. prop: RelationshipProperty[_PT],
  466. parentmapper: _InternalEntityType[Any],
  467. adapt_to_entity: Optional[AliasedInsp[Any]] = None,
  468. of_type: Optional[_EntityType[_PT]] = None,
  469. extra_criteria: Tuple[ColumnElement[bool], ...] = (),
  470. ):
  471. """Construction of :class:`.RelationshipProperty.Comparator`
  472. is internal to the ORM's attribute mechanics.
  473. """
  474. self.prop = prop
  475. self._parententity = parentmapper
  476. self._adapt_to_entity = adapt_to_entity
  477. if of_type:
  478. self._of_type = of_type
  479. else:
  480. self._of_type = None
  481. self._extra_criteria = extra_criteria
  482. def adapt_to_entity(
  483. self, adapt_to_entity: AliasedInsp[Any]
  484. ) -> RelationshipProperty.Comparator[Any]:
  485. return self.__class__(
  486. self.prop,
  487. self._parententity,
  488. adapt_to_entity=adapt_to_entity,
  489. of_type=self._of_type,
  490. )
  491. entity: _InternalEntityType[_PT]
  492. """The target entity referred to by this
  493. :class:`.RelationshipProperty.Comparator`.
  494. This is either a :class:`_orm.Mapper` or :class:`.AliasedInsp`
  495. object.
  496. This is the "target" or "remote" side of the
  497. :func:`_orm.relationship`.
  498. """
  499. mapper: Mapper[_PT]
  500. """The target :class:`_orm.Mapper` referred to by this
  501. :class:`.RelationshipProperty.Comparator`.
  502. This is the "target" or "remote" side of the
  503. :func:`_orm.relationship`.
  504. """
  505. def _memoized_attr_entity(self) -> _InternalEntityType[_PT]:
  506. if self._of_type:
  507. return inspect(self._of_type) # type: ignore
  508. else:
  509. return self.prop.entity
  510. def _memoized_attr_mapper(self) -> Mapper[_PT]:
  511. return self.entity.mapper
  512. def _source_selectable(self) -> FromClause:
  513. if self._adapt_to_entity:
  514. return self._adapt_to_entity.selectable
  515. else:
  516. return self.property.parent._with_polymorphic_selectable
  517. def __clause_element__(self) -> ColumnElement[bool]:
  518. adapt_from = self._source_selectable()
  519. if self._of_type:
  520. of_type_entity = inspect(self._of_type)
  521. else:
  522. of_type_entity = None
  523. (
  524. pj,
  525. sj,
  526. source,
  527. dest,
  528. secondary,
  529. target_adapter,
  530. ) = self.prop._create_joins(
  531. source_selectable=adapt_from,
  532. source_polymorphic=True,
  533. of_type_entity=of_type_entity,
  534. alias_secondary=True,
  535. extra_criteria=self._extra_criteria,
  536. )
  537. if sj is not None:
  538. return pj & sj
  539. else:
  540. return pj
  541. def of_type(self, class_: _EntityType[Any]) -> PropComparator[_PT]:
  542. r"""Redefine this object in terms of a polymorphic subclass.
  543. See :meth:`.PropComparator.of_type` for an example.
  544. """
  545. return RelationshipProperty.Comparator(
  546. self.prop,
  547. self._parententity,
  548. adapt_to_entity=self._adapt_to_entity,
  549. of_type=class_,
  550. extra_criteria=self._extra_criteria,
  551. )
  552. def and_(
  553. self, *criteria: _ColumnExpressionArgument[bool]
  554. ) -> PropComparator[Any]:
  555. """Add AND criteria.
  556. See :meth:`.PropComparator.and_` for an example.
  557. .. versionadded:: 1.4
  558. """
  559. exprs = tuple(
  560. coercions.expect(roles.WhereHavingRole, clause)
  561. for clause in util.coerce_generator_arg(criteria)
  562. )
  563. return RelationshipProperty.Comparator(
  564. self.prop,
  565. self._parententity,
  566. adapt_to_entity=self._adapt_to_entity,
  567. of_type=self._of_type,
  568. extra_criteria=self._extra_criteria + exprs,
  569. )
  570. def in_(self, other: Any) -> NoReturn:
  571. """Produce an IN clause - this is not implemented
  572. for :func:`_orm.relationship`-based attributes at this time.
  573. """
  574. raise NotImplementedError(
  575. "in_() not yet supported for "
  576. "relationships. For a simple "
  577. "many-to-one, use in_() against "
  578. "the set of foreign key values."
  579. )
  580. # https://github.com/python/mypy/issues/4266
  581. __hash__ = None # type: ignore
  582. def __eq__(self, other: Any) -> ColumnElement[bool]: # type: ignore[override] # noqa: E501
  583. """Implement the ``==`` operator.
  584. In a many-to-one context, such as::
  585. MyClass.some_prop == <some object>
  586. this will typically produce a
  587. clause such as::
  588. mytable.related_id == <some id>
  589. Where ``<some id>`` is the primary key of the given
  590. object.
  591. The ``==`` operator provides partial functionality for non-
  592. many-to-one comparisons:
  593. * Comparisons against collections are not supported.
  594. Use :meth:`~.Relationship.Comparator.contains`.
  595. * Compared to a scalar one-to-many, will produce a
  596. clause that compares the target columns in the parent to
  597. the given target.
  598. * Compared to a scalar many-to-many, an alias
  599. of the association table will be rendered as
  600. well, forming a natural join that is part of the
  601. main body of the query. This will not work for
  602. queries that go beyond simple AND conjunctions of
  603. comparisons, such as those which use OR. Use
  604. explicit joins, outerjoins, or
  605. :meth:`~.Relationship.Comparator.has` for
  606. more comprehensive non-many-to-one scalar
  607. membership tests.
  608. * Comparisons against ``None`` given in a one-to-many
  609. or many-to-many context produce a NOT EXISTS clause.
  610. """
  611. if other is None or isinstance(other, expression.Null):
  612. if self.property.direction in [ONETOMANY, MANYTOMANY]:
  613. return ~self._criterion_exists()
  614. else:
  615. return _orm_annotate(
  616. self.property._optimized_compare(
  617. None, adapt_source=self.adapter
  618. )
  619. )
  620. elif self.property.uselist:
  621. raise sa_exc.InvalidRequestError(
  622. "Can't compare a collection to an object or collection; "
  623. "use contains() to test for membership."
  624. )
  625. else:
  626. return _orm_annotate(
  627. self.property._optimized_compare(
  628. other, adapt_source=self.adapter
  629. )
  630. )
  631. def _criterion_exists(
  632. self,
  633. criterion: Optional[_ColumnExpressionArgument[bool]] = None,
  634. **kwargs: Any,
  635. ) -> Exists:
  636. where_criteria = (
  637. coercions.expect(roles.WhereHavingRole, criterion)
  638. if criterion is not None
  639. else None
  640. )
  641. if getattr(self, "_of_type", None):
  642. info: Optional[_InternalEntityType[Any]] = inspect(
  643. self._of_type
  644. )
  645. assert info is not None
  646. target_mapper, to_selectable, is_aliased_class = (
  647. info.mapper,
  648. info.selectable,
  649. info.is_aliased_class,
  650. )
  651. if self.property._is_self_referential and not is_aliased_class:
  652. to_selectable = to_selectable._anonymous_fromclause()
  653. single_crit = target_mapper._single_table_criterion
  654. if single_crit is not None:
  655. if where_criteria is not None:
  656. where_criteria = single_crit & where_criteria
  657. else:
  658. where_criteria = single_crit
  659. else:
  660. is_aliased_class = False
  661. to_selectable = None
  662. if self.adapter:
  663. source_selectable = self._source_selectable()
  664. else:
  665. source_selectable = None
  666. (
  667. pj,
  668. sj,
  669. source,
  670. dest,
  671. secondary,
  672. target_adapter,
  673. ) = self.property._create_joins(
  674. dest_selectable=to_selectable,
  675. source_selectable=source_selectable,
  676. )
  677. for k in kwargs:
  678. crit = getattr(self.property.mapper.class_, k) == kwargs[k]
  679. if where_criteria is None:
  680. where_criteria = crit
  681. else:
  682. where_criteria = where_criteria & crit
  683. # annotate the *local* side of the join condition, in the case
  684. # of pj + sj this is the full primaryjoin, in the case of just
  685. # pj its the local side of the primaryjoin.
  686. if sj is not None:
  687. j = _orm_annotate(pj) & sj
  688. else:
  689. j = _orm_annotate(pj, exclude=self.property.remote_side)
  690. if (
  691. where_criteria is not None
  692. and target_adapter
  693. and not is_aliased_class
  694. ):
  695. # limit this adapter to annotated only?
  696. where_criteria = target_adapter.traverse(where_criteria)
  697. # only have the "joined left side" of what we
  698. # return be subject to Query adaption. The right
  699. # side of it is used for an exists() subquery and
  700. # should not correlate or otherwise reach out
  701. # to anything in the enclosing query.
  702. if where_criteria is not None:
  703. where_criteria = where_criteria._annotate(
  704. {"no_replacement_traverse": True}
  705. )
  706. crit = j & sql.True_._ifnone(where_criteria)
  707. if secondary is not None:
  708. ex = (
  709. sql.exists(1)
  710. .where(crit)
  711. .select_from(dest, secondary)
  712. .correlate_except(dest, secondary)
  713. )
  714. else:
  715. ex = (
  716. sql.exists(1)
  717. .where(crit)
  718. .select_from(dest)
  719. .correlate_except(dest)
  720. )
  721. return ex
  722. def any(
  723. self,
  724. criterion: Optional[_ColumnExpressionArgument[bool]] = None,
  725. **kwargs: Any,
  726. ) -> ColumnElement[bool]:
  727. """Produce an expression that tests a collection against
  728. particular criterion, using EXISTS.
  729. An expression like::
  730. session.query(MyClass).filter(
  731. MyClass.somereference.any(SomeRelated.x==2)
  732. )
  733. Will produce a query like::
  734. SELECT * FROM my_table WHERE
  735. EXISTS (SELECT 1 FROM related WHERE related.my_id=my_table.id
  736. AND related.x=2)
  737. Because :meth:`~.Relationship.Comparator.any` uses
  738. a correlated subquery, its performance is not nearly as
  739. good when compared against large target tables as that of
  740. using a join.
  741. :meth:`~.Relationship.Comparator.any` is particularly
  742. useful for testing for empty collections::
  743. session.query(MyClass).filter(
  744. ~MyClass.somereference.any()
  745. )
  746. will produce::
  747. SELECT * FROM my_table WHERE
  748. NOT (EXISTS (SELECT 1 FROM related WHERE
  749. related.my_id=my_table.id))
  750. :meth:`~.Relationship.Comparator.any` is only
  751. valid for collections, i.e. a :func:`_orm.relationship`
  752. that has ``uselist=True``. For scalar references,
  753. use :meth:`~.Relationship.Comparator.has`.
  754. """
  755. if not self.property.uselist:
  756. raise sa_exc.InvalidRequestError(
  757. "'any()' not implemented for scalar "
  758. "attributes. Use has()."
  759. )
  760. return self._criterion_exists(criterion, **kwargs)
  761. def has(
  762. self,
  763. criterion: Optional[_ColumnExpressionArgument[bool]] = None,
  764. **kwargs: Any,
  765. ) -> ColumnElement[bool]:
  766. """Produce an expression that tests a scalar reference against
  767. particular criterion, using EXISTS.
  768. An expression like::
  769. session.query(MyClass).filter(
  770. MyClass.somereference.has(SomeRelated.x==2)
  771. )
  772. Will produce a query like::
  773. SELECT * FROM my_table WHERE
  774. EXISTS (SELECT 1 FROM related WHERE
  775. related.id==my_table.related_id AND related.x=2)
  776. Because :meth:`~.Relationship.Comparator.has` uses
  777. a correlated subquery, its performance is not nearly as
  778. good when compared against large target tables as that of
  779. using a join.
  780. :meth:`~.Relationship.Comparator.has` is only
  781. valid for scalar references, i.e. a :func:`_orm.relationship`
  782. that has ``uselist=False``. For collection references,
  783. use :meth:`~.Relationship.Comparator.any`.
  784. """
  785. if self.property.uselist:
  786. raise sa_exc.InvalidRequestError(
  787. "'has()' not implemented for collections. " "Use any()."
  788. )
  789. return self._criterion_exists(criterion, **kwargs)
  790. def contains(
  791. self, other: _ColumnExpressionArgument[Any], **kwargs: Any
  792. ) -> ColumnElement[bool]:
  793. """Return a simple expression that tests a collection for
  794. containment of a particular item.
  795. :meth:`~.Relationship.Comparator.contains` is
  796. only valid for a collection, i.e. a
  797. :func:`_orm.relationship` that implements
  798. one-to-many or many-to-many with ``uselist=True``.
  799. When used in a simple one-to-many context, an
  800. expression like::
  801. MyClass.contains(other)
  802. Produces a clause like::
  803. mytable.id == <some id>
  804. Where ``<some id>`` is the value of the foreign key
  805. attribute on ``other`` which refers to the primary
  806. key of its parent object. From this it follows that
  807. :meth:`~.Relationship.Comparator.contains` is
  808. very useful when used with simple one-to-many
  809. operations.
  810. For many-to-many operations, the behavior of
  811. :meth:`~.Relationship.Comparator.contains`
  812. has more caveats. The association table will be
  813. rendered in the statement, producing an "implicit"
  814. join, that is, includes multiple tables in the FROM
  815. clause which are equated in the WHERE clause::
  816. query(MyClass).filter(MyClass.contains(other))
  817. Produces a query like::
  818. SELECT * FROM my_table, my_association_table AS
  819. my_association_table_1 WHERE
  820. my_table.id = my_association_table_1.parent_id
  821. AND my_association_table_1.child_id = <some id>
  822. Where ``<some id>`` would be the primary key of
  823. ``other``. From the above, it is clear that
  824. :meth:`~.Relationship.Comparator.contains`
  825. will **not** work with many-to-many collections when
  826. used in queries that move beyond simple AND
  827. conjunctions, such as multiple
  828. :meth:`~.Relationship.Comparator.contains`
  829. expressions joined by OR. In such cases subqueries or
  830. explicit "outer joins" will need to be used instead.
  831. See :meth:`~.Relationship.Comparator.any` for
  832. a less-performant alternative using EXISTS, or refer
  833. to :meth:`_query.Query.outerjoin`
  834. as well as :ref:`orm_queryguide_joins`
  835. for more details on constructing outer joins.
  836. kwargs may be ignored by this operator but are required for API
  837. conformance.
  838. """
  839. if not self.prop.uselist:
  840. raise sa_exc.InvalidRequestError(
  841. "'contains' not implemented for scalar "
  842. "attributes. Use =="
  843. )
  844. clause = self.prop._optimized_compare(
  845. other, adapt_source=self.adapter
  846. )
  847. if self.prop.secondaryjoin is not None:
  848. clause.negation_clause = self.__negated_contains_or_equals(
  849. other
  850. )
  851. return clause
  852. def __negated_contains_or_equals(
  853. self, other: Any
  854. ) -> ColumnElement[bool]:
  855. if self.prop.direction == MANYTOONE:
  856. state = attributes.instance_state(other)
  857. def state_bindparam(
  858. local_col: ColumnElement[Any],
  859. state: InstanceState[Any],
  860. remote_col: ColumnElement[Any],
  861. ) -> BindParameter[Any]:
  862. dict_ = state.dict
  863. return sql.bindparam(
  864. local_col.key,
  865. type_=local_col.type,
  866. unique=True,
  867. callable_=self.prop._get_attr_w_warn_on_none(
  868. self.prop.mapper, state, dict_, remote_col
  869. ),
  870. )
  871. def adapt(col: _CE) -> _CE:
  872. if self.adapter:
  873. return self.adapter(col)
  874. else:
  875. return col
  876. if self.property._use_get:
  877. return sql.and_(
  878. *[
  879. sql.or_(
  880. adapt(x)
  881. != state_bindparam(adapt(x), state, y),
  882. adapt(x) == None,
  883. )
  884. for (x, y) in self.property.local_remote_pairs
  885. ]
  886. )
  887. criterion = sql.and_(
  888. *[
  889. x == y
  890. for (x, y) in zip(
  891. self.property.mapper.primary_key,
  892. self.property.mapper.primary_key_from_instance(other),
  893. )
  894. ]
  895. )
  896. return ~self._criterion_exists(criterion)
  897. def __ne__(self, other: Any) -> ColumnElement[bool]: # type: ignore[override] # noqa: E501
  898. """Implement the ``!=`` operator.
  899. In a many-to-one context, such as::
  900. MyClass.some_prop != <some object>
  901. This will typically produce a clause such as::
  902. mytable.related_id != <some id>
  903. Where ``<some id>`` is the primary key of the
  904. given object.
  905. The ``!=`` operator provides partial functionality for non-
  906. many-to-one comparisons:
  907. * Comparisons against collections are not supported.
  908. Use
  909. :meth:`~.Relationship.Comparator.contains`
  910. in conjunction with :func:`_expression.not_`.
  911. * Compared to a scalar one-to-many, will produce a
  912. clause that compares the target columns in the parent to
  913. the given target.
  914. * Compared to a scalar many-to-many, an alias
  915. of the association table will be rendered as
  916. well, forming a natural join that is part of the
  917. main body of the query. This will not work for
  918. queries that go beyond simple AND conjunctions of
  919. comparisons, such as those which use OR. Use
  920. explicit joins, outerjoins, or
  921. :meth:`~.Relationship.Comparator.has` in
  922. conjunction with :func:`_expression.not_` for
  923. more comprehensive non-many-to-one scalar
  924. membership tests.
  925. * Comparisons against ``None`` given in a one-to-many
  926. or many-to-many context produce an EXISTS clause.
  927. """
  928. if other is None or isinstance(other, expression.Null):
  929. if self.property.direction == MANYTOONE:
  930. return _orm_annotate(
  931. ~self.property._optimized_compare(
  932. None, adapt_source=self.adapter
  933. )
  934. )
  935. else:
  936. return self._criterion_exists()
  937. elif self.property.uselist:
  938. raise sa_exc.InvalidRequestError(
  939. "Can't compare a collection"
  940. " to an object or collection; use "
  941. "contains() to test for membership."
  942. )
  943. else:
  944. return _orm_annotate(self.__negated_contains_or_equals(other))
  945. def _memoized_attr_property(self) -> RelationshipProperty[_PT]:
  946. self.prop.parent._check_configure()
  947. return self.prop
  948. def _with_parent(
  949. self,
  950. instance: object,
  951. alias_secondary: bool = True,
  952. from_entity: Optional[_EntityType[Any]] = None,
  953. ) -> ColumnElement[bool]:
  954. assert instance is not None
  955. adapt_source: Optional[_CoreAdapterProto] = None
  956. if from_entity is not None:
  957. insp: Optional[_InternalEntityType[Any]] = inspect(from_entity)
  958. assert insp is not None
  959. if insp_is_aliased_class(insp):
  960. adapt_source = insp._adapter.adapt_clause
  961. return self._optimized_compare(
  962. instance,
  963. value_is_parent=True,
  964. adapt_source=adapt_source,
  965. alias_secondary=alias_secondary,
  966. )
  967. def _optimized_compare(
  968. self,
  969. state: Any,
  970. value_is_parent: bool = False,
  971. adapt_source: Optional[_CoreAdapterProto] = None,
  972. alias_secondary: bool = True,
  973. ) -> ColumnElement[bool]:
  974. if state is not None:
  975. try:
  976. state = inspect(state)
  977. except sa_exc.NoInspectionAvailable:
  978. state = None
  979. if state is None or not getattr(state, "is_instance", False):
  980. raise sa_exc.ArgumentError(
  981. "Mapped instance expected for relationship "
  982. "comparison to object. Classes, queries and other "
  983. "SQL elements are not accepted in this context; for "
  984. "comparison with a subquery, "
  985. "use %s.has(**criteria)." % self
  986. )
  987. reverse_direction = not value_is_parent
  988. if state is None:
  989. return self._lazy_none_clause(
  990. reverse_direction, adapt_source=adapt_source
  991. )
  992. if not reverse_direction:
  993. criterion, bind_to_col = (
  994. self._lazy_strategy._lazywhere,
  995. self._lazy_strategy._bind_to_col,
  996. )
  997. else:
  998. criterion, bind_to_col = (
  999. self._lazy_strategy._rev_lazywhere,
  1000. self._lazy_strategy._rev_bind_to_col,
  1001. )
  1002. if reverse_direction:
  1003. mapper = self.mapper
  1004. else:
  1005. mapper = self.parent
  1006. dict_ = attributes.instance_dict(state.obj())
  1007. def visit_bindparam(bindparam: BindParameter[Any]) -> None:
  1008. if bindparam._identifying_key in bind_to_col:
  1009. bindparam.callable = self._get_attr_w_warn_on_none(
  1010. mapper,
  1011. state,
  1012. dict_,
  1013. bind_to_col[bindparam._identifying_key],
  1014. )
  1015. if self.secondary is not None and alias_secondary:
  1016. criterion = ClauseAdapter(
  1017. self.secondary._anonymous_fromclause()
  1018. ).traverse(criterion)
  1019. criterion = visitors.cloned_traverse(
  1020. criterion, {}, {"bindparam": visit_bindparam}
  1021. )
  1022. if adapt_source:
  1023. criterion = adapt_source(criterion)
  1024. return criterion
  1025. def _get_attr_w_warn_on_none(
  1026. self,
  1027. mapper: Mapper[Any],
  1028. state: InstanceState[Any],
  1029. dict_: _InstanceDict,
  1030. column: ColumnElement[Any],
  1031. ) -> Callable[[], Any]:
  1032. """Create the callable that is used in a many-to-one expression.
  1033. E.g.::
  1034. u1 = s.query(User).get(5)
  1035. expr = Address.user == u1
  1036. Above, the SQL should be "address.user_id = 5". The callable
  1037. returned by this method produces the value "5" based on the identity
  1038. of ``u1``.
  1039. """
  1040. # in this callable, we're trying to thread the needle through
  1041. # a wide variety of scenarios, including:
  1042. #
  1043. # * the object hasn't been flushed yet and there's no value for
  1044. # the attribute as of yet
  1045. #
  1046. # * the object hasn't been flushed yet but it has a user-defined
  1047. # value
  1048. #
  1049. # * the object has a value but it's expired and not locally present
  1050. #
  1051. # * the object has a value but it's expired and not locally present,
  1052. # and the object is also detached
  1053. #
  1054. # * The object hadn't been flushed yet, there was no value, but
  1055. # later, the object has been expired and detached, and *now*
  1056. # they're trying to evaluate it
  1057. #
  1058. # * the object had a value, but it was changed to a new value, and
  1059. # then expired
  1060. #
  1061. # * the object had a value, but it was changed to a new value, and
  1062. # then expired, then the object was detached
  1063. #
  1064. # * the object has a user-set value, but it's None and we don't do
  1065. # the comparison correctly for that so warn
  1066. #
  1067. prop = mapper.get_property_by_column(column)
  1068. # by invoking this method, InstanceState will track the last known
  1069. # value for this key each time the attribute is to be expired.
  1070. # this feature was added explicitly for use in this method.
  1071. state._track_last_known_value(prop.key)
  1072. lkv_fixed = state._last_known_values
  1073. def _go() -> Any:
  1074. assert lkv_fixed is not None
  1075. last_known = to_return = lkv_fixed[prop.key]
  1076. existing_is_available = (
  1077. last_known is not LoaderCallableStatus.NO_VALUE
  1078. )
  1079. # we support that the value may have changed. so here we
  1080. # try to get the most recent value including re-fetching.
  1081. # only if we can't get a value now due to detachment do we return
  1082. # the last known value
  1083. current_value = mapper._get_state_attr_by_column(
  1084. state,
  1085. dict_,
  1086. column,
  1087. passive=PassiveFlag.PASSIVE_OFF
  1088. if state.persistent
  1089. else PassiveFlag.PASSIVE_NO_FETCH ^ PassiveFlag.INIT_OK,
  1090. )
  1091. if current_value is LoaderCallableStatus.NEVER_SET:
  1092. if not existing_is_available:
  1093. raise sa_exc.InvalidRequestError(
  1094. "Can't resolve value for column %s on object "
  1095. "%s; no value has been set for this column"
  1096. % (column, state_str(state))
  1097. )
  1098. elif current_value is LoaderCallableStatus.PASSIVE_NO_RESULT:
  1099. if not existing_is_available:
  1100. raise sa_exc.InvalidRequestError(
  1101. "Can't resolve value for column %s on object "
  1102. "%s; the object is detached and the value was "
  1103. "expired" % (column, state_str(state))
  1104. )
  1105. else:
  1106. to_return = current_value
  1107. if to_return is None:
  1108. util.warn(
  1109. "Got None for value of column %s; this is unsupported "
  1110. "for a relationship comparison and will not "
  1111. "currently produce an IS comparison "
  1112. "(but may in a future release)" % column
  1113. )
  1114. return to_return
  1115. return _go
  1116. def _lazy_none_clause(
  1117. self,
  1118. reverse_direction: bool = False,
  1119. adapt_source: Optional[_CoreAdapterProto] = None,
  1120. ) -> ColumnElement[bool]:
  1121. if not reverse_direction:
  1122. criterion, bind_to_col = (
  1123. self._lazy_strategy._lazywhere,
  1124. self._lazy_strategy._bind_to_col,
  1125. )
  1126. else:
  1127. criterion, bind_to_col = (
  1128. self._lazy_strategy._rev_lazywhere,
  1129. self._lazy_strategy._rev_bind_to_col,
  1130. )
  1131. criterion = adapt_criterion_to_null(criterion, bind_to_col)
  1132. if adapt_source:
  1133. criterion = adapt_source(criterion)
  1134. return criterion
  1135. def __str__(self) -> str:
  1136. return str(self.parent.class_.__name__) + "." + self.key
  1137. def merge(
  1138. self,
  1139. session: Session,
  1140. source_state: InstanceState[Any],
  1141. source_dict: _InstanceDict,
  1142. dest_state: InstanceState[Any],
  1143. dest_dict: _InstanceDict,
  1144. load: bool,
  1145. _recursive: Dict[Any, object],
  1146. _resolve_conflict_map: Dict[_IdentityKeyType[Any], object],
  1147. ) -> None:
  1148. if load:
  1149. for r in self._reverse_property:
  1150. if (source_state, r) in _recursive:
  1151. return
  1152. if "merge" not in self._cascade:
  1153. return
  1154. if self.key not in source_dict:
  1155. return
  1156. if self.uselist:
  1157. impl = source_state.get_impl(self.key)
  1158. assert is_has_collection_adapter(impl)
  1159. instances_iterable = impl.get_collection(source_state, source_dict)
  1160. # if this is a CollectionAttributeImpl, then empty should
  1161. # be False, otherwise "self.key in source_dict" should not be
  1162. # True
  1163. assert not instances_iterable.empty if impl.collection else True
  1164. if load:
  1165. # for a full merge, pre-load the destination collection,
  1166. # so that individual _merge of each item pulls from identity
  1167. # map for those already present.
  1168. # also assumes CollectionAttributeImpl behavior of loading
  1169. # "old" list in any case
  1170. dest_state.get_impl(self.key).get(
  1171. dest_state, dest_dict, passive=PassiveFlag.PASSIVE_MERGE
  1172. )
  1173. dest_list = []
  1174. for current in instances_iterable:
  1175. current_state = attributes.instance_state(current)
  1176. current_dict = attributes.instance_dict(current)
  1177. _recursive[(current_state, self)] = True
  1178. obj = session._merge(
  1179. current_state,
  1180. current_dict,
  1181. load=load,
  1182. _recursive=_recursive,
  1183. _resolve_conflict_map=_resolve_conflict_map,
  1184. )
  1185. if obj is not None:
  1186. dest_list.append(obj)
  1187. if not load:
  1188. coll = attributes.init_state_collection(
  1189. dest_state, dest_dict, self.key
  1190. )
  1191. for c in dest_list:
  1192. coll.append_without_event(c)
  1193. else:
  1194. dest_impl = dest_state.get_impl(self.key)
  1195. assert is_has_collection_adapter(dest_impl)
  1196. dest_impl.set(
  1197. dest_state,
  1198. dest_dict,
  1199. dest_list,
  1200. _adapt=False,
  1201. passive=PassiveFlag.PASSIVE_MERGE,
  1202. )
  1203. else:
  1204. current = source_dict[self.key]
  1205. if current is not None:
  1206. current_state = attributes.instance_state(current)
  1207. current_dict = attributes.instance_dict(current)
  1208. _recursive[(current_state, self)] = True
  1209. obj = session._merge(
  1210. current_state,
  1211. current_dict,
  1212. load=load,
  1213. _recursive=_recursive,
  1214. _resolve_conflict_map=_resolve_conflict_map,
  1215. )
  1216. else:
  1217. obj = None
  1218. if not load:
  1219. dest_dict[self.key] = obj
  1220. else:
  1221. dest_state.get_impl(self.key).set(
  1222. dest_state, dest_dict, obj, None
  1223. )
  1224. def _value_as_iterable(
  1225. self,
  1226. state: InstanceState[_O],
  1227. dict_: _InstanceDict,
  1228. key: str,
  1229. passive: PassiveFlag = PassiveFlag.PASSIVE_OFF,
  1230. ) -> Sequence[Tuple[InstanceState[_O], _O]]:
  1231. """Return a list of tuples (state, obj) for the given
  1232. key.
  1233. returns an empty list if the value is None/empty/PASSIVE_NO_RESULT
  1234. """
  1235. impl = state.manager[key].impl
  1236. x = impl.get(state, dict_, passive=passive)
  1237. if x is LoaderCallableStatus.PASSIVE_NO_RESULT or x is None:
  1238. return []
  1239. elif is_has_collection_adapter(impl):
  1240. return [
  1241. (attributes.instance_state(o), o)
  1242. for o in impl.get_collection(state, dict_, x, passive=passive)
  1243. ]
  1244. else:
  1245. return [(attributes.instance_state(x), x)]
  1246. def cascade_iterator(
  1247. self,
  1248. type_: str,
  1249. state: InstanceState[Any],
  1250. dict_: _InstanceDict,
  1251. visited_states: Set[InstanceState[Any]],
  1252. halt_on: Optional[Callable[[InstanceState[Any]], bool]] = None,
  1253. ) -> Iterator[Tuple[Any, Mapper[Any], InstanceState[Any], _InstanceDict]]:
  1254. # assert type_ in self._cascade
  1255. # only actively lazy load on the 'delete' cascade
  1256. if type_ != "delete" or self.passive_deletes:
  1257. passive = PassiveFlag.PASSIVE_NO_INITIALIZE
  1258. else:
  1259. passive = PassiveFlag.PASSIVE_OFF | PassiveFlag.NO_RAISE
  1260. if type_ == "save-update":
  1261. tuples = state.manager[self.key].impl.get_all_pending(state, dict_)
  1262. else:
  1263. tuples = self._value_as_iterable(
  1264. state, dict_, self.key, passive=passive
  1265. )
  1266. skip_pending = (
  1267. type_ == "refresh-expire" and "delete-orphan" not in self._cascade
  1268. )
  1269. for instance_state, c in tuples:
  1270. if instance_state in visited_states:
  1271. continue
  1272. if c is None:
  1273. # would like to emit a warning here, but
  1274. # would not be consistent with collection.append(None)
  1275. # current behavior of silently skipping.
  1276. # see [ticket:2229]
  1277. continue
  1278. assert instance_state is not None
  1279. instance_dict = attributes.instance_dict(c)
  1280. if halt_on and halt_on(instance_state):
  1281. continue
  1282. if skip_pending and not instance_state.key:
  1283. continue
  1284. instance_mapper = instance_state.manager.mapper
  1285. if not instance_mapper.isa(self.mapper.class_manager.mapper):
  1286. raise AssertionError(
  1287. "Attribute '%s' on class '%s' "
  1288. "doesn't handle objects "
  1289. "of type '%s'"
  1290. % (self.key, self.parent.class_, c.__class__)
  1291. )
  1292. visited_states.add(instance_state)
  1293. yield c, instance_mapper, instance_state, instance_dict
  1294. @property
  1295. def _effective_sync_backref(self) -> bool:
  1296. if self.viewonly:
  1297. return False
  1298. else:
  1299. return self.sync_backref is not False
  1300. @staticmethod
  1301. def _check_sync_backref(
  1302. rel_a: RelationshipProperty[Any], rel_b: RelationshipProperty[Any]
  1303. ) -> None:
  1304. if rel_a.viewonly and rel_b.sync_backref:
  1305. raise sa_exc.InvalidRequestError(
  1306. "Relationship %s cannot specify sync_backref=True since %s "
  1307. "includes viewonly=True." % (rel_b, rel_a)
  1308. )
  1309. if (
  1310. rel_a.viewonly
  1311. and not rel_b.viewonly
  1312. and rel_b.sync_backref is not False
  1313. ):
  1314. rel_b.sync_backref = False
  1315. def _add_reverse_property(self, key: str) -> None:
  1316. other = self.mapper.get_property(key, _configure_mappers=False)
  1317. if not isinstance(other, RelationshipProperty):
  1318. raise sa_exc.InvalidRequestError(
  1319. "back_populates on relationship '%s' refers to attribute '%s' "
  1320. "that is not a relationship. The back_populates parameter "
  1321. "should refer to the name of a relationship on the target "
  1322. "class." % (self, other)
  1323. )
  1324. # viewonly and sync_backref cases
  1325. # 1. self.viewonly==True and other.sync_backref==True -> error
  1326. # 2. self.viewonly==True and other.viewonly==False and
  1327. # other.sync_backref==None -> warn sync_backref=False, set to False
  1328. self._check_sync_backref(self, other)
  1329. # 3. other.viewonly==True and self.sync_backref==True -> error
  1330. # 4. other.viewonly==True and self.viewonly==False and
  1331. # self.sync_backref==None -> warn sync_backref=False, set to False
  1332. self._check_sync_backref(other, self)
  1333. self._reverse_property.add(other)
  1334. other._reverse_property.add(self)
  1335. other._setup_entity()
  1336. if not other.mapper.common_parent(self.parent):
  1337. raise sa_exc.ArgumentError(
  1338. "reverse_property %r on "
  1339. "relationship %s references relationship %s, which "
  1340. "does not reference mapper %s"
  1341. % (key, self, other, self.parent)
  1342. )
  1343. if (
  1344. other._configure_started
  1345. and self.direction in (ONETOMANY, MANYTOONE)
  1346. and self.direction == other.direction
  1347. ):
  1348. raise sa_exc.ArgumentError(
  1349. "%s and back-reference %s are "
  1350. "both of the same direction %r. Did you mean to "
  1351. "set remote_side on the many-to-one side ?"
  1352. % (other, self, self.direction)
  1353. )
  1354. @util.memoized_property
  1355. def entity(self) -> _InternalEntityType[_T]:
  1356. """Return the target mapped entity, which is an inspect() of the
  1357. class or aliased class that is referenced by this
  1358. :class:`.RelationshipProperty`.
  1359. """
  1360. self.parent._check_configure()
  1361. return self.entity
  1362. @util.memoized_property
  1363. def mapper(self) -> Mapper[_T]:
  1364. """Return the targeted :class:`_orm.Mapper` for this
  1365. :class:`.RelationshipProperty`.
  1366. """
  1367. return self.entity.mapper
  1368. def do_init(self) -> None:
  1369. self._check_conflicts()
  1370. self._process_dependent_arguments()
  1371. self._setup_entity()
  1372. self._setup_registry_dependencies()
  1373. self._setup_join_conditions()
  1374. self._check_cascade_settings(self._cascade)
  1375. self._post_init()
  1376. self._generate_backref()
  1377. self._join_condition._warn_for_conflicting_sync_targets()
  1378. super().do_init()
  1379. self._lazy_strategy = cast(
  1380. "LazyLoader", self._get_strategy((("lazy", "select"),))
  1381. )
  1382. def _setup_registry_dependencies(self) -> None:
  1383. self.parent.mapper.registry._set_depends_on(
  1384. self.entity.mapper.registry
  1385. )
  1386. def _process_dependent_arguments(self) -> None:
  1387. """Convert incoming configuration arguments to their
  1388. proper form.
  1389. Callables are resolved, ORM annotations removed.
  1390. """
  1391. # accept callables for other attributes which may require
  1392. # deferred initialization. This technique is used
  1393. # by declarative "string configs" and some recipes.
  1394. init_args = self._init_args
  1395. for attr in (
  1396. "order_by",
  1397. "primaryjoin",
  1398. "secondaryjoin",
  1399. "secondary",
  1400. "foreign_keys",
  1401. "remote_side",
  1402. ):
  1403. rel_arg = getattr(init_args, attr)
  1404. rel_arg._resolve_against_registry(self._clsregistry_resolvers[1])
  1405. # remove "annotations" which are present if mapped class
  1406. # descriptors are used to create the join expression.
  1407. for attr in "primaryjoin", "secondaryjoin":
  1408. rel_arg = getattr(init_args, attr)
  1409. val = rel_arg.resolved
  1410. if val is not None:
  1411. rel_arg.resolved = _orm_deannotate(
  1412. coercions.expect(
  1413. roles.ColumnArgumentRole, val, argname=attr
  1414. )
  1415. )
  1416. secondary = init_args.secondary.resolved
  1417. if secondary is not None and _is_mapped_class(secondary):
  1418. raise sa_exc.ArgumentError(
  1419. "secondary argument %s passed to to relationship() %s must "
  1420. "be a Table object or other FROM clause; can't send a mapped "
  1421. "class directly as rows in 'secondary' are persisted "
  1422. "independently of a class that is mapped "
  1423. "to that same table." % (secondary, self)
  1424. )
  1425. # ensure expressions in self.order_by, foreign_keys,
  1426. # remote_side are all columns, not strings.
  1427. if (
  1428. init_args.order_by.resolved is not False
  1429. and init_args.order_by.resolved is not None
  1430. ):
  1431. self.order_by = tuple(
  1432. coercions.expect(
  1433. roles.ColumnArgumentRole, x, argname="order_by"
  1434. )
  1435. for x in util.to_list(init_args.order_by.resolved)
  1436. )
  1437. else:
  1438. self.order_by = False
  1439. self._user_defined_foreign_keys = util.column_set(
  1440. coercions.expect(
  1441. roles.ColumnArgumentRole, x, argname="foreign_keys"
  1442. )
  1443. for x in util.to_column_set(init_args.foreign_keys.resolved)
  1444. )
  1445. self.remote_side = util.column_set(
  1446. coercions.expect(
  1447. roles.ColumnArgumentRole, x, argname="remote_side"
  1448. )
  1449. for x in util.to_column_set(init_args.remote_side.resolved)
  1450. )
  1451. def declarative_scan(
  1452. self,
  1453. decl_scan: _ClassScanMapperConfig,
  1454. registry: _RegistryType,
  1455. cls: Type[Any],
  1456. originating_module: Optional[str],
  1457. key: str,
  1458. mapped_container: Optional[Type[Mapped[Any]]],
  1459. annotation: Optional[_AnnotationScanType],
  1460. extracted_mapped_annotation: Optional[_AnnotationScanType],
  1461. is_dataclass_field: bool,
  1462. ) -> None:
  1463. argument = extracted_mapped_annotation
  1464. if extracted_mapped_annotation is None:
  1465. if self.argument is None:
  1466. self._raise_for_required(key, cls)
  1467. else:
  1468. return
  1469. argument = extracted_mapped_annotation
  1470. assert originating_module is not None
  1471. is_write_only = mapped_container is not None and issubclass(
  1472. mapped_container, WriteOnlyMapped
  1473. )
  1474. if is_write_only:
  1475. self.lazy = "write_only"
  1476. self.strategy_key = (("lazy", self.lazy),)
  1477. is_dynamic = mapped_container is not None and issubclass(
  1478. mapped_container, DynamicMapped
  1479. )
  1480. if is_dynamic:
  1481. self.lazy = "dynamic"
  1482. self.strategy_key = (("lazy", self.lazy),)
  1483. argument = de_optionalize_union_types(argument)
  1484. if hasattr(argument, "__origin__"):
  1485. arg_origin = argument.__origin__
  1486. if isinstance(arg_origin, type) and issubclass(
  1487. arg_origin, abc.Collection
  1488. ):
  1489. if self.collection_class is None:
  1490. if _py_inspect.isabstract(arg_origin):
  1491. raise sa_exc.ArgumentError(
  1492. f"Collection annotation type {arg_origin} cannot "
  1493. "be instantiated; please provide an explicit "
  1494. "'collection_class' parameter "
  1495. "(e.g. list, set, etc.) to the "
  1496. "relationship() function to accompany this "
  1497. "annotation"
  1498. )
  1499. self.collection_class = arg_origin
  1500. elif not is_write_only and not is_dynamic:
  1501. self.uselist = False
  1502. if argument.__args__: # type: ignore
  1503. if isinstance(arg_origin, type) and issubclass(
  1504. arg_origin, typing.Mapping
  1505. ):
  1506. type_arg = argument.__args__[-1] # type: ignore
  1507. else:
  1508. type_arg = argument.__args__[0] # type: ignore
  1509. if hasattr(type_arg, "__forward_arg__"):
  1510. str_argument = type_arg.__forward_arg__
  1511. argument = resolve_name_to_real_class_name(
  1512. str_argument, originating_module
  1513. )
  1514. else:
  1515. argument = type_arg
  1516. else:
  1517. raise sa_exc.ArgumentError(
  1518. f"Generic alias {argument} requires an argument"
  1519. )
  1520. elif hasattr(argument, "__forward_arg__"):
  1521. argument = argument.__forward_arg__
  1522. argument = resolve_name_to_real_class_name(
  1523. argument, originating_module
  1524. )
  1525. if (
  1526. self.collection_class is None
  1527. and not is_write_only
  1528. and not is_dynamic
  1529. ):
  1530. self.uselist = False
  1531. # ticket #8759
  1532. # if a lead argument was given to relationship(), like
  1533. # `relationship("B")`, use that, don't replace it with class we
  1534. # found in the annotation. The declarative_scan() method call here is
  1535. # still useful, as we continue to derive collection type and do
  1536. # checking of the annotation in any case.
  1537. if self.argument is None:
  1538. self.argument = cast("_RelationshipArgumentType[_T]", argument)
  1539. @util.preload_module("sqlalchemy.orm.mapper")
  1540. def _setup_entity(self, __argument: Any = None) -> None:
  1541. if "entity" in self.__dict__:
  1542. return
  1543. mapperlib = util.preloaded.orm_mapper
  1544. if __argument:
  1545. argument = __argument
  1546. else:
  1547. argument = self.argument
  1548. resolved_argument: _ExternalEntityType[Any]
  1549. if isinstance(argument, str):
  1550. # we might want to cleanup clsregistry API to make this
  1551. # more straightforward
  1552. resolved_argument = cast(
  1553. "_ExternalEntityType[Any]",
  1554. self._clsregistry_resolve_name(argument)(),
  1555. )
  1556. elif callable(argument) and not isinstance(
  1557. argument, (type, mapperlib.Mapper)
  1558. ):
  1559. resolved_argument = argument()
  1560. else:
  1561. resolved_argument = argument
  1562. entity: _InternalEntityType[Any]
  1563. if isinstance(resolved_argument, type):
  1564. entity = class_mapper(resolved_argument, configure=False)
  1565. else:
  1566. try:
  1567. entity = inspect(resolved_argument)
  1568. except sa_exc.NoInspectionAvailable:
  1569. entity = None # type: ignore
  1570. if not hasattr(entity, "mapper"):
  1571. raise sa_exc.ArgumentError(
  1572. "relationship '%s' expects "
  1573. "a class or a mapper argument (received: %s)"
  1574. % (self.key, type(resolved_argument))
  1575. )
  1576. self.entity = entity
  1577. self.target = self.entity.persist_selectable
  1578. def _setup_join_conditions(self) -> None:
  1579. self._join_condition = jc = JoinCondition(
  1580. parent_persist_selectable=self.parent.persist_selectable,
  1581. child_persist_selectable=self.entity.persist_selectable,
  1582. parent_local_selectable=self.parent.local_table,
  1583. child_local_selectable=self.entity.local_table,
  1584. primaryjoin=self._init_args.primaryjoin.resolved,
  1585. secondary=self._init_args.secondary.resolved,
  1586. secondaryjoin=self._init_args.secondaryjoin.resolved,
  1587. parent_equivalents=self.parent._equivalent_columns,
  1588. child_equivalents=self.mapper._equivalent_columns,
  1589. consider_as_foreign_keys=self._user_defined_foreign_keys,
  1590. local_remote_pairs=self.local_remote_pairs,
  1591. remote_side=self.remote_side,
  1592. self_referential=self._is_self_referential,
  1593. prop=self,
  1594. support_sync=not self.viewonly,
  1595. can_be_synced_fn=self._columns_are_mapped,
  1596. )
  1597. self.primaryjoin = jc.primaryjoin
  1598. self.secondaryjoin = jc.secondaryjoin
  1599. self.secondary = jc.secondary
  1600. self.direction = jc.direction
  1601. self.local_remote_pairs = jc.local_remote_pairs
  1602. self.remote_side = jc.remote_columns
  1603. self.local_columns = jc.local_columns
  1604. self.synchronize_pairs = jc.synchronize_pairs
  1605. self._calculated_foreign_keys = jc.foreign_key_columns
  1606. self.secondary_synchronize_pairs = jc.secondary_synchronize_pairs
  1607. @property
  1608. def _clsregistry_resolve_arg(
  1609. self,
  1610. ) -> Callable[[str, bool], _class_resolver]:
  1611. return self._clsregistry_resolvers[1]
  1612. @property
  1613. def _clsregistry_resolve_name(
  1614. self,
  1615. ) -> Callable[[str], Callable[[], Union[Type[Any], Table, _ModNS]]]:
  1616. return self._clsregistry_resolvers[0]
  1617. @util.memoized_property
  1618. @util.preload_module("sqlalchemy.orm.clsregistry")
  1619. def _clsregistry_resolvers(
  1620. self,
  1621. ) -> Tuple[
  1622. Callable[[str], Callable[[], Union[Type[Any], Table, _ModNS]]],
  1623. Callable[[str, bool], _class_resolver],
  1624. ]:
  1625. _resolver = util.preloaded.orm_clsregistry._resolver
  1626. return _resolver(self.parent.class_, self)
  1627. def _check_conflicts(self) -> None:
  1628. """Test that this relationship is legal, warn about
  1629. inheritance conflicts."""
  1630. if self.parent.non_primary and not class_mapper(
  1631. self.parent.class_, configure=False
  1632. ).has_property(self.key):
  1633. raise sa_exc.ArgumentError(
  1634. "Attempting to assign a new "
  1635. "relationship '%s' to a non-primary mapper on "
  1636. "class '%s'. New relationships can only be added "
  1637. "to the primary mapper, i.e. the very first mapper "
  1638. "created for class '%s' "
  1639. % (
  1640. self.key,
  1641. self.parent.class_.__name__,
  1642. self.parent.class_.__name__,
  1643. )
  1644. )
  1645. @property
  1646. def cascade(self) -> CascadeOptions:
  1647. """Return the current cascade setting for this
  1648. :class:`.RelationshipProperty`.
  1649. """
  1650. return self._cascade
  1651. @cascade.setter
  1652. def cascade(self, cascade: Union[str, CascadeOptions]) -> None:
  1653. self._set_cascade(cascade)
  1654. def _set_cascade(self, cascade_arg: Union[str, CascadeOptions]) -> None:
  1655. cascade = CascadeOptions(cascade_arg)
  1656. if self.viewonly:
  1657. cascade = CascadeOptions(
  1658. cascade.intersection(CascadeOptions._viewonly_cascades)
  1659. )
  1660. if "mapper" in self.__dict__:
  1661. self._check_cascade_settings(cascade)
  1662. self._cascade = cascade
  1663. if self._dependency_processor:
  1664. self._dependency_processor.cascade = cascade
  1665. def _check_cascade_settings(self, cascade: CascadeOptions) -> None:
  1666. if (
  1667. cascade.delete_orphan
  1668. and not self.single_parent
  1669. and (self.direction is MANYTOMANY or self.direction is MANYTOONE)
  1670. ):
  1671. raise sa_exc.ArgumentError(
  1672. "For %(direction)s relationship %(rel)s, delete-orphan "
  1673. "cascade is normally "
  1674. 'configured only on the "one" side of a one-to-many '
  1675. "relationship, "
  1676. 'and not on the "many" side of a many-to-one or many-to-many '
  1677. "relationship. "
  1678. "To force this relationship to allow a particular "
  1679. '"%(relatedcls)s" object to be referenced by only '
  1680. 'a single "%(clsname)s" object at a time via the '
  1681. "%(rel)s relationship, which "
  1682. "would allow "
  1683. "delete-orphan cascade to take place in this direction, set "
  1684. "the single_parent=True flag."
  1685. % {
  1686. "rel": self,
  1687. "direction": "many-to-one"
  1688. if self.direction is MANYTOONE
  1689. else "many-to-many",
  1690. "clsname": self.parent.class_.__name__,
  1691. "relatedcls": self.mapper.class_.__name__,
  1692. },
  1693. code="bbf0",
  1694. )
  1695. if self.passive_deletes == "all" and (
  1696. "delete" in cascade or "delete-orphan" in cascade
  1697. ):
  1698. raise sa_exc.ArgumentError(
  1699. "On %s, can't set passive_deletes='all' in conjunction "
  1700. "with 'delete' or 'delete-orphan' cascade" % self
  1701. )
  1702. if cascade.delete_orphan:
  1703. self.mapper.primary_mapper()._delete_orphans.append(
  1704. (self.key, self.parent.class_)
  1705. )
  1706. def _persists_for(self, mapper: Mapper[Any]) -> bool:
  1707. """Return True if this property will persist values on behalf
  1708. of the given mapper.
  1709. """
  1710. return (
  1711. self.key in mapper.relationships
  1712. and mapper.relationships[self.key] is self
  1713. )
  1714. def _columns_are_mapped(self, *cols: ColumnElement[Any]) -> bool:
  1715. """Return True if all columns in the given collection are
  1716. mapped by the tables referenced by this :class:`.RelationshipProperty`.
  1717. """
  1718. secondary = self._init_args.secondary.resolved
  1719. for c in cols:
  1720. if secondary is not None and secondary.c.contains_column(c):
  1721. continue
  1722. if not self.parent.persist_selectable.c.contains_column(
  1723. c
  1724. ) and not self.target.c.contains_column(c):
  1725. return False
  1726. return True
  1727. def _generate_backref(self) -> None:
  1728. """Interpret the 'backref' instruction to create a
  1729. :func:`_orm.relationship` complementary to this one."""
  1730. if self.parent.non_primary:
  1731. return
  1732. if self.backref is not None and not self.back_populates:
  1733. kwargs: Dict[str, Any]
  1734. if isinstance(self.backref, str):
  1735. backref_key, kwargs = self.backref, {}
  1736. else:
  1737. backref_key, kwargs = self.backref
  1738. mapper = self.mapper.primary_mapper()
  1739. if not mapper.concrete:
  1740. check = set(mapper.iterate_to_root()).union(
  1741. mapper.self_and_descendants
  1742. )
  1743. for m in check:
  1744. if m.has_property(backref_key) and not m.concrete:
  1745. raise sa_exc.ArgumentError(
  1746. "Error creating backref "
  1747. "'%s' on relationship '%s': property of that "
  1748. "name exists on mapper '%s'"
  1749. % (backref_key, self, m)
  1750. )
  1751. # determine primaryjoin/secondaryjoin for the
  1752. # backref. Use the one we had, so that
  1753. # a custom join doesn't have to be specified in
  1754. # both directions.
  1755. if self.secondary is not None:
  1756. # for many to many, just switch primaryjoin/
  1757. # secondaryjoin. use the annotated
  1758. # pj/sj on the _join_condition.
  1759. pj = kwargs.pop(
  1760. "primaryjoin",
  1761. self._join_condition.secondaryjoin_minus_local,
  1762. )
  1763. sj = kwargs.pop(
  1764. "secondaryjoin",
  1765. self._join_condition.primaryjoin_minus_local,
  1766. )
  1767. else:
  1768. pj = kwargs.pop(
  1769. "primaryjoin",
  1770. self._join_condition.primaryjoin_reverse_remote,
  1771. )
  1772. sj = kwargs.pop("secondaryjoin", None)
  1773. if sj:
  1774. raise sa_exc.InvalidRequestError(
  1775. "Can't assign 'secondaryjoin' on a backref "
  1776. "against a non-secondary relationship."
  1777. )
  1778. foreign_keys = kwargs.pop(
  1779. "foreign_keys", self._user_defined_foreign_keys
  1780. )
  1781. parent = self.parent.primary_mapper()
  1782. kwargs.setdefault("viewonly", self.viewonly)
  1783. kwargs.setdefault("post_update", self.post_update)
  1784. kwargs.setdefault("passive_updates", self.passive_updates)
  1785. kwargs.setdefault("sync_backref", self.sync_backref)
  1786. self.back_populates = backref_key
  1787. relationship = RelationshipProperty(
  1788. parent,
  1789. self.secondary,
  1790. primaryjoin=pj,
  1791. secondaryjoin=sj,
  1792. foreign_keys=foreign_keys,
  1793. back_populates=self.key,
  1794. **kwargs,
  1795. )
  1796. mapper._configure_property(
  1797. backref_key, relationship, warn_for_existing=True
  1798. )
  1799. if self.back_populates:
  1800. self._add_reverse_property(self.back_populates)
  1801. @util.preload_module("sqlalchemy.orm.dependency")
  1802. def _post_init(self) -> None:
  1803. dependency = util.preloaded.orm_dependency
  1804. if self.uselist is None:
  1805. self.uselist = self.direction is not MANYTOONE
  1806. if not self.viewonly:
  1807. self._dependency_processor = ( # type: ignore
  1808. dependency.DependencyProcessor.from_relationship
  1809. )(self)
  1810. @util.memoized_property
  1811. def _use_get(self) -> bool:
  1812. """memoize the 'use_get' attribute of this RelationshipLoader's
  1813. lazyloader."""
  1814. strategy = self._lazy_strategy
  1815. return strategy.use_get
  1816. @util.memoized_property
  1817. def _is_self_referential(self) -> bool:
  1818. return self.mapper.common_parent(self.parent)
  1819. def _create_joins(
  1820. self,
  1821. source_polymorphic: bool = False,
  1822. source_selectable: Optional[FromClause] = None,
  1823. dest_selectable: Optional[FromClause] = None,
  1824. of_type_entity: Optional[_InternalEntityType[Any]] = None,
  1825. alias_secondary: bool = False,
  1826. extra_criteria: Tuple[ColumnElement[bool], ...] = (),
  1827. ) -> Tuple[
  1828. ColumnElement[bool],
  1829. Optional[ColumnElement[bool]],
  1830. FromClause,
  1831. FromClause,
  1832. Optional[FromClause],
  1833. Optional[ClauseAdapter],
  1834. ]:
  1835. aliased = False
  1836. if alias_secondary and self.secondary is not None:
  1837. aliased = True
  1838. if source_selectable is None:
  1839. if source_polymorphic and self.parent.with_polymorphic:
  1840. source_selectable = self.parent._with_polymorphic_selectable
  1841. if of_type_entity:
  1842. dest_mapper = of_type_entity.mapper
  1843. if dest_selectable is None:
  1844. dest_selectable = of_type_entity.selectable
  1845. aliased = True
  1846. else:
  1847. dest_mapper = self.mapper
  1848. if dest_selectable is None:
  1849. dest_selectable = self.entity.selectable
  1850. if self.mapper.with_polymorphic:
  1851. aliased = True
  1852. if self._is_self_referential and source_selectable is None:
  1853. dest_selectable = dest_selectable._anonymous_fromclause()
  1854. aliased = True
  1855. elif (
  1856. dest_selectable is not self.mapper._with_polymorphic_selectable
  1857. or self.mapper.with_polymorphic
  1858. ):
  1859. aliased = True
  1860. single_crit = dest_mapper._single_table_criterion
  1861. aliased = aliased or (
  1862. source_selectable is not None
  1863. and (
  1864. source_selectable
  1865. is not self.parent._with_polymorphic_selectable
  1866. or source_selectable._is_subquery
  1867. )
  1868. )
  1869. (
  1870. primaryjoin,
  1871. secondaryjoin,
  1872. secondary,
  1873. target_adapter,
  1874. dest_selectable,
  1875. ) = self._join_condition.join_targets(
  1876. source_selectable,
  1877. dest_selectable,
  1878. aliased,
  1879. single_crit,
  1880. extra_criteria,
  1881. )
  1882. if source_selectable is None:
  1883. source_selectable = self.parent.local_table
  1884. if dest_selectable is None:
  1885. dest_selectable = self.entity.local_table
  1886. return (
  1887. primaryjoin,
  1888. secondaryjoin,
  1889. source_selectable,
  1890. dest_selectable,
  1891. secondary,
  1892. target_adapter,
  1893. )
  1894. def _annotate_columns(element: _CE, annotations: _AnnotationDict) -> _CE:
  1895. def clone(elem: _CE) -> _CE:
  1896. if isinstance(elem, expression.ColumnClause):
  1897. elem = elem._annotate(annotations.copy()) # type: ignore
  1898. elem._copy_internals(clone=clone)
  1899. return elem
  1900. if element is not None:
  1901. element = clone(element)
  1902. clone = None # type: ignore # remove gc cycles
  1903. return element
  1904. class JoinCondition:
  1905. primaryjoin_initial: Optional[ColumnElement[bool]]
  1906. primaryjoin: ColumnElement[bool]
  1907. secondaryjoin: Optional[ColumnElement[bool]]
  1908. secondary: Optional[FromClause]
  1909. prop: RelationshipProperty[Any]
  1910. synchronize_pairs: _ColumnPairs
  1911. secondary_synchronize_pairs: _ColumnPairs
  1912. direction: RelationshipDirection
  1913. parent_persist_selectable: FromClause
  1914. child_persist_selectable: FromClause
  1915. parent_local_selectable: FromClause
  1916. child_local_selectable: FromClause
  1917. _local_remote_pairs: Optional[_ColumnPairs]
  1918. def __init__(
  1919. self,
  1920. parent_persist_selectable: FromClause,
  1921. child_persist_selectable: FromClause,
  1922. parent_local_selectable: FromClause,
  1923. child_local_selectable: FromClause,
  1924. *,
  1925. primaryjoin: Optional[ColumnElement[bool]] = None,
  1926. secondary: Optional[FromClause] = None,
  1927. secondaryjoin: Optional[ColumnElement[bool]] = None,
  1928. parent_equivalents: Optional[_EquivalentColumnMap] = None,
  1929. child_equivalents: Optional[_EquivalentColumnMap] = None,
  1930. consider_as_foreign_keys: Any = None,
  1931. local_remote_pairs: Optional[_ColumnPairs] = None,
  1932. remote_side: Any = None,
  1933. self_referential: Any = False,
  1934. prop: RelationshipProperty[Any],
  1935. support_sync: bool = True,
  1936. can_be_synced_fn: Callable[..., bool] = lambda *c: True,
  1937. ):
  1938. self.parent_persist_selectable = parent_persist_selectable
  1939. self.parent_local_selectable = parent_local_selectable
  1940. self.child_persist_selectable = child_persist_selectable
  1941. self.child_local_selectable = child_local_selectable
  1942. self.parent_equivalents = parent_equivalents
  1943. self.child_equivalents = child_equivalents
  1944. self.primaryjoin_initial = primaryjoin
  1945. self.secondaryjoin = secondaryjoin
  1946. self.secondary = secondary
  1947. self.consider_as_foreign_keys = consider_as_foreign_keys
  1948. self._local_remote_pairs = local_remote_pairs
  1949. self._remote_side = remote_side
  1950. self.prop = prop
  1951. self.self_referential = self_referential
  1952. self.support_sync = support_sync
  1953. self.can_be_synced_fn = can_be_synced_fn
  1954. self._determine_joins()
  1955. assert self.primaryjoin is not None
  1956. self._sanitize_joins()
  1957. self._annotate_fks()
  1958. self._annotate_remote()
  1959. self._annotate_local()
  1960. self._annotate_parentmapper()
  1961. self._setup_pairs()
  1962. self._check_foreign_cols(self.primaryjoin, True)
  1963. if self.secondaryjoin is not None:
  1964. self._check_foreign_cols(self.secondaryjoin, False)
  1965. self._determine_direction()
  1966. self._check_remote_side()
  1967. self._log_joins()
  1968. def _log_joins(self) -> None:
  1969. log = self.prop.logger
  1970. log.info("%s setup primary join %s", self.prop, self.primaryjoin)
  1971. log.info("%s setup secondary join %s", self.prop, self.secondaryjoin)
  1972. log.info(
  1973. "%s synchronize pairs [%s]",
  1974. self.prop,
  1975. ",".join(
  1976. "(%s => %s)" % (l, r) for (l, r) in self.synchronize_pairs
  1977. ),
  1978. )
  1979. log.info(
  1980. "%s secondary synchronize pairs [%s]",
  1981. self.prop,
  1982. ",".join(
  1983. "(%s => %s)" % (l, r)
  1984. for (l, r) in self.secondary_synchronize_pairs or []
  1985. ),
  1986. )
  1987. log.info(
  1988. "%s local/remote pairs [%s]",
  1989. self.prop,
  1990. ",".join(
  1991. "(%s / %s)" % (l, r) for (l, r) in self.local_remote_pairs
  1992. ),
  1993. )
  1994. log.info(
  1995. "%s remote columns [%s]",
  1996. self.prop,
  1997. ",".join("%s" % col for col in self.remote_columns),
  1998. )
  1999. log.info(
  2000. "%s local columns [%s]",
  2001. self.prop,
  2002. ",".join("%s" % col for col in self.local_columns),
  2003. )
  2004. log.info("%s relationship direction %s", self.prop, self.direction)
  2005. def _sanitize_joins(self) -> None:
  2006. """remove the parententity annotation from our join conditions which
  2007. can leak in here based on some declarative patterns and maybe others.
  2008. "parentmapper" is relied upon both by the ORM evaluator as well as
  2009. the use case in _join_fixture_inh_selfref_w_entity
  2010. that relies upon it being present, see :ticket:`3364`.
  2011. """
  2012. self.primaryjoin = _deep_deannotate(
  2013. self.primaryjoin, values=("parententity", "proxy_key")
  2014. )
  2015. if self.secondaryjoin is not None:
  2016. self.secondaryjoin = _deep_deannotate(
  2017. self.secondaryjoin, values=("parententity", "proxy_key")
  2018. )
  2019. def _determine_joins(self) -> None:
  2020. """Determine the 'primaryjoin' and 'secondaryjoin' attributes,
  2021. if not passed to the constructor already.
  2022. This is based on analysis of the foreign key relationships
  2023. between the parent and target mapped selectables.
  2024. """
  2025. if self.secondaryjoin is not None and self.secondary is None:
  2026. raise sa_exc.ArgumentError(
  2027. "Property %s specified with secondary "
  2028. "join condition but "
  2029. "no secondary argument" % self.prop
  2030. )
  2031. # find a join between the given mapper's mapped table and
  2032. # the given table. will try the mapper's local table first
  2033. # for more specificity, then if not found will try the more
  2034. # general mapped table, which in the case of inheritance is
  2035. # a join.
  2036. try:
  2037. consider_as_foreign_keys = self.consider_as_foreign_keys or None
  2038. if self.secondary is not None:
  2039. if self.secondaryjoin is None:
  2040. self.secondaryjoin = join_condition(
  2041. self.child_persist_selectable,
  2042. self.secondary,
  2043. a_subset=self.child_local_selectable,
  2044. consider_as_foreign_keys=consider_as_foreign_keys,
  2045. )
  2046. if self.primaryjoin_initial is None:
  2047. self.primaryjoin = join_condition(
  2048. self.parent_persist_selectable,
  2049. self.secondary,
  2050. a_subset=self.parent_local_selectable,
  2051. consider_as_foreign_keys=consider_as_foreign_keys,
  2052. )
  2053. else:
  2054. self.primaryjoin = self.primaryjoin_initial
  2055. else:
  2056. if self.primaryjoin_initial is None:
  2057. self.primaryjoin = join_condition(
  2058. self.parent_persist_selectable,
  2059. self.child_persist_selectable,
  2060. a_subset=self.parent_local_selectable,
  2061. consider_as_foreign_keys=consider_as_foreign_keys,
  2062. )
  2063. else:
  2064. self.primaryjoin = self.primaryjoin_initial
  2065. except sa_exc.NoForeignKeysError as nfe:
  2066. if self.secondary is not None:
  2067. raise sa_exc.NoForeignKeysError(
  2068. "Could not determine join "
  2069. "condition between parent/child tables on "
  2070. "relationship %s - there are no foreign keys "
  2071. "linking these tables via secondary table '%s'. "
  2072. "Ensure that referencing columns are associated "
  2073. "with a ForeignKey or ForeignKeyConstraint, or "
  2074. "specify 'primaryjoin' and 'secondaryjoin' "
  2075. "expressions." % (self.prop, self.secondary)
  2076. ) from nfe
  2077. else:
  2078. raise sa_exc.NoForeignKeysError(
  2079. "Could not determine join "
  2080. "condition between parent/child tables on "
  2081. "relationship %s - there are no foreign keys "
  2082. "linking these tables. "
  2083. "Ensure that referencing columns are associated "
  2084. "with a ForeignKey or ForeignKeyConstraint, or "
  2085. "specify a 'primaryjoin' expression." % self.prop
  2086. ) from nfe
  2087. except sa_exc.AmbiguousForeignKeysError as afe:
  2088. if self.secondary is not None:
  2089. raise sa_exc.AmbiguousForeignKeysError(
  2090. "Could not determine join "
  2091. "condition between parent/child tables on "
  2092. "relationship %s - there are multiple foreign key "
  2093. "paths linking the tables via secondary table '%s'. "
  2094. "Specify the 'foreign_keys' "
  2095. "argument, providing a list of those columns which "
  2096. "should be counted as containing a foreign key "
  2097. "reference from the secondary table to each of the "
  2098. "parent and child tables." % (self.prop, self.secondary)
  2099. ) from afe
  2100. else:
  2101. raise sa_exc.AmbiguousForeignKeysError(
  2102. "Could not determine join "
  2103. "condition between parent/child tables on "
  2104. "relationship %s - there are multiple foreign key "
  2105. "paths linking the tables. Specify the "
  2106. "'foreign_keys' argument, providing a list of those "
  2107. "columns which should be counted as containing a "
  2108. "foreign key reference to the parent table." % self.prop
  2109. ) from afe
  2110. @property
  2111. def primaryjoin_minus_local(self) -> ColumnElement[bool]:
  2112. return _deep_deannotate(self.primaryjoin, values=("local", "remote"))
  2113. @property
  2114. def secondaryjoin_minus_local(self) -> ColumnElement[bool]:
  2115. assert self.secondaryjoin is not None
  2116. return _deep_deannotate(self.secondaryjoin, values=("local", "remote"))
  2117. @util.memoized_property
  2118. def primaryjoin_reverse_remote(self) -> ColumnElement[bool]:
  2119. """Return the primaryjoin condition suitable for the
  2120. "reverse" direction.
  2121. If the primaryjoin was delivered here with pre-existing
  2122. "remote" annotations, the local/remote annotations
  2123. are reversed. Otherwise, the local/remote annotations
  2124. are removed.
  2125. """
  2126. if self._has_remote_annotations:
  2127. def replace(element: _CE, **kw: Any) -> Optional[_CE]:
  2128. if "remote" in element._annotations:
  2129. v = dict(element._annotations)
  2130. del v["remote"]
  2131. v["local"] = True
  2132. return element._with_annotations(v)
  2133. elif "local" in element._annotations:
  2134. v = dict(element._annotations)
  2135. del v["local"]
  2136. v["remote"] = True
  2137. return element._with_annotations(v)
  2138. return None
  2139. return visitors.replacement_traverse(self.primaryjoin, {}, replace)
  2140. else:
  2141. if self._has_foreign_annotations:
  2142. # TODO: coverage
  2143. return _deep_deannotate(
  2144. self.primaryjoin, values=("local", "remote")
  2145. )
  2146. else:
  2147. return _deep_deannotate(self.primaryjoin)
  2148. def _has_annotation(self, clause: ClauseElement, annotation: str) -> bool:
  2149. for col in visitors.iterate(clause, {}):
  2150. if annotation in col._annotations:
  2151. return True
  2152. else:
  2153. return False
  2154. @util.memoized_property
  2155. def _has_foreign_annotations(self) -> bool:
  2156. return self._has_annotation(self.primaryjoin, "foreign")
  2157. @util.memoized_property
  2158. def _has_remote_annotations(self) -> bool:
  2159. return self._has_annotation(self.primaryjoin, "remote")
  2160. def _annotate_fks(self) -> None:
  2161. """Annotate the primaryjoin and secondaryjoin
  2162. structures with 'foreign' annotations marking columns
  2163. considered as foreign.
  2164. """
  2165. if self._has_foreign_annotations:
  2166. return
  2167. if self.consider_as_foreign_keys:
  2168. self._annotate_from_fk_list()
  2169. else:
  2170. self._annotate_present_fks()
  2171. def _annotate_from_fk_list(self) -> None:
  2172. def check_fk(element: _CE, **kw: Any) -> Optional[_CE]:
  2173. if element in self.consider_as_foreign_keys:
  2174. return element._annotate({"foreign": True})
  2175. return None
  2176. self.primaryjoin = visitors.replacement_traverse(
  2177. self.primaryjoin, {}, check_fk
  2178. )
  2179. if self.secondaryjoin is not None:
  2180. self.secondaryjoin = visitors.replacement_traverse(
  2181. self.secondaryjoin, {}, check_fk
  2182. )
  2183. def _annotate_present_fks(self) -> None:
  2184. if self.secondary is not None:
  2185. secondarycols = util.column_set(self.secondary.c)
  2186. else:
  2187. secondarycols = set()
  2188. def is_foreign(
  2189. a: ColumnElement[Any], b: ColumnElement[Any]
  2190. ) -> Optional[ColumnElement[Any]]:
  2191. if isinstance(a, schema.Column) and isinstance(b, schema.Column):
  2192. if a.references(b):
  2193. return a
  2194. elif b.references(a):
  2195. return b
  2196. if secondarycols:
  2197. if a in secondarycols and b not in secondarycols:
  2198. return a
  2199. elif b in secondarycols and a not in secondarycols:
  2200. return b
  2201. return None
  2202. def visit_binary(binary: BinaryExpression[Any]) -> None:
  2203. if not isinstance(
  2204. binary.left, sql.ColumnElement
  2205. ) or not isinstance(binary.right, sql.ColumnElement):
  2206. return
  2207. if (
  2208. "foreign" not in binary.left._annotations
  2209. and "foreign" not in binary.right._annotations
  2210. ):
  2211. col = is_foreign(binary.left, binary.right)
  2212. if col is not None:
  2213. if col.compare(binary.left):
  2214. binary.left = binary.left._annotate({"foreign": True})
  2215. elif col.compare(binary.right):
  2216. binary.right = binary.right._annotate(
  2217. {"foreign": True}
  2218. )
  2219. self.primaryjoin = visitors.cloned_traverse(
  2220. self.primaryjoin, {}, {"binary": visit_binary}
  2221. )
  2222. if self.secondaryjoin is not None:
  2223. self.secondaryjoin = visitors.cloned_traverse(
  2224. self.secondaryjoin, {}, {"binary": visit_binary}
  2225. )
  2226. def _refers_to_parent_table(self) -> bool:
  2227. """Return True if the join condition contains column
  2228. comparisons where both columns are in both tables.
  2229. """
  2230. pt = self.parent_persist_selectable
  2231. mt = self.child_persist_selectable
  2232. result = False
  2233. def visit_binary(binary: BinaryExpression[Any]) -> None:
  2234. nonlocal result
  2235. c, f = binary.left, binary.right
  2236. if (
  2237. isinstance(c, expression.ColumnClause)
  2238. and isinstance(f, expression.ColumnClause)
  2239. and pt.is_derived_from(c.table)
  2240. and pt.is_derived_from(f.table)
  2241. and mt.is_derived_from(c.table)
  2242. and mt.is_derived_from(f.table)
  2243. ):
  2244. result = True
  2245. visitors.traverse(self.primaryjoin, {}, {"binary": visit_binary})
  2246. return result
  2247. def _tables_overlap(self) -> bool:
  2248. """Return True if parent/child tables have some overlap."""
  2249. return selectables_overlap(
  2250. self.parent_persist_selectable, self.child_persist_selectable
  2251. )
  2252. def _annotate_remote(self) -> None:
  2253. """Annotate the primaryjoin and secondaryjoin
  2254. structures with 'remote' annotations marking columns
  2255. considered as part of the 'remote' side.
  2256. """
  2257. if self._has_remote_annotations:
  2258. return
  2259. if self.secondary is not None:
  2260. self._annotate_remote_secondary()
  2261. elif self._local_remote_pairs or self._remote_side:
  2262. self._annotate_remote_from_args()
  2263. elif self._refers_to_parent_table():
  2264. self._annotate_selfref(
  2265. lambda col: "foreign" in col._annotations, False
  2266. )
  2267. elif self._tables_overlap():
  2268. self._annotate_remote_with_overlap()
  2269. else:
  2270. self._annotate_remote_distinct_selectables()
  2271. def _annotate_remote_secondary(self) -> None:
  2272. """annotate 'remote' in primaryjoin, secondaryjoin
  2273. when 'secondary' is present.
  2274. """
  2275. assert self.secondary is not None
  2276. fixed_secondary = self.secondary
  2277. def repl(element: _CE, **kw: Any) -> Optional[_CE]:
  2278. if fixed_secondary.c.contains_column(element):
  2279. return element._annotate({"remote": True})
  2280. return None
  2281. self.primaryjoin = visitors.replacement_traverse(
  2282. self.primaryjoin, {}, repl
  2283. )
  2284. assert self.secondaryjoin is not None
  2285. self.secondaryjoin = visitors.replacement_traverse(
  2286. self.secondaryjoin, {}, repl
  2287. )
  2288. def _annotate_selfref(
  2289. self, fn: Callable[[ColumnElement[Any]], bool], remote_side_given: bool
  2290. ) -> None:
  2291. """annotate 'remote' in primaryjoin, secondaryjoin
  2292. when the relationship is detected as self-referential.
  2293. """
  2294. def visit_binary(binary: BinaryExpression[Any]) -> None:
  2295. equated = binary.left.compare(binary.right)
  2296. if isinstance(binary.left, expression.ColumnClause) and isinstance(
  2297. binary.right, expression.ColumnClause
  2298. ):
  2299. # assume one to many - FKs are "remote"
  2300. if fn(binary.left):
  2301. binary.left = binary.left._annotate({"remote": True})
  2302. if fn(binary.right) and not equated:
  2303. binary.right = binary.right._annotate({"remote": True})
  2304. elif not remote_side_given:
  2305. self._warn_non_column_elements()
  2306. self.primaryjoin = visitors.cloned_traverse(
  2307. self.primaryjoin, {}, {"binary": visit_binary}
  2308. )
  2309. def _annotate_remote_from_args(self) -> None:
  2310. """annotate 'remote' in primaryjoin, secondaryjoin
  2311. when the 'remote_side' or '_local_remote_pairs'
  2312. arguments are used.
  2313. """
  2314. if self._local_remote_pairs:
  2315. if self._remote_side:
  2316. raise sa_exc.ArgumentError(
  2317. "remote_side argument is redundant "
  2318. "against more detailed _local_remote_side "
  2319. "argument."
  2320. )
  2321. remote_side = [r for (l, r) in self._local_remote_pairs]
  2322. else:
  2323. remote_side = self._remote_side
  2324. if self._refers_to_parent_table():
  2325. self._annotate_selfref(lambda col: col in remote_side, True)
  2326. else:
  2327. def repl(element: _CE, **kw: Any) -> Optional[_CE]:
  2328. # use set() to avoid generating ``__eq__()`` expressions
  2329. # against each element
  2330. if element in set(remote_side):
  2331. return element._annotate({"remote": True})
  2332. return None
  2333. self.primaryjoin = visitors.replacement_traverse(
  2334. self.primaryjoin, {}, repl
  2335. )
  2336. def _annotate_remote_with_overlap(self) -> None:
  2337. """annotate 'remote' in primaryjoin, secondaryjoin
  2338. when the parent/child tables have some set of
  2339. tables in common, though is not a fully self-referential
  2340. relationship.
  2341. """
  2342. def visit_binary(binary: BinaryExpression[Any]) -> None:
  2343. binary.left, binary.right = proc_left_right(
  2344. binary.left, binary.right
  2345. )
  2346. binary.right, binary.left = proc_left_right(
  2347. binary.right, binary.left
  2348. )
  2349. check_entities = (
  2350. self.prop is not None and self.prop.mapper is not self.prop.parent
  2351. )
  2352. def proc_left_right(
  2353. left: ColumnElement[Any], right: ColumnElement[Any]
  2354. ) -> Tuple[ColumnElement[Any], ColumnElement[Any]]:
  2355. if isinstance(left, expression.ColumnClause) and isinstance(
  2356. right, expression.ColumnClause
  2357. ):
  2358. if self.child_persist_selectable.c.contains_column(
  2359. right
  2360. ) and self.parent_persist_selectable.c.contains_column(left):
  2361. right = right._annotate({"remote": True})
  2362. elif (
  2363. check_entities
  2364. and right._annotations.get("parentmapper") is self.prop.mapper
  2365. ):
  2366. right = right._annotate({"remote": True})
  2367. elif (
  2368. check_entities
  2369. and left._annotations.get("parentmapper") is self.prop.mapper
  2370. ):
  2371. left = left._annotate({"remote": True})
  2372. else:
  2373. self._warn_non_column_elements()
  2374. return left, right
  2375. self.primaryjoin = visitors.cloned_traverse(
  2376. self.primaryjoin, {}, {"binary": visit_binary}
  2377. )
  2378. def _annotate_remote_distinct_selectables(self) -> None:
  2379. """annotate 'remote' in primaryjoin, secondaryjoin
  2380. when the parent/child tables are entirely
  2381. separate.
  2382. """
  2383. def repl(element: _CE, **kw: Any) -> Optional[_CE]:
  2384. if self.child_persist_selectable.c.contains_column(element) and (
  2385. not self.parent_local_selectable.c.contains_column(element)
  2386. or self.child_local_selectable.c.contains_column(element)
  2387. ):
  2388. return element._annotate({"remote": True})
  2389. return None
  2390. self.primaryjoin = visitors.replacement_traverse(
  2391. self.primaryjoin, {}, repl
  2392. )
  2393. def _warn_non_column_elements(self) -> None:
  2394. util.warn(
  2395. "Non-simple column elements in primary "
  2396. "join condition for property %s - consider using "
  2397. "remote() annotations to mark the remote side." % self.prop
  2398. )
  2399. def _annotate_local(self) -> None:
  2400. """Annotate the primaryjoin and secondaryjoin
  2401. structures with 'local' annotations.
  2402. This annotates all column elements found
  2403. simultaneously in the parent table
  2404. and the join condition that don't have a
  2405. 'remote' annotation set up from
  2406. _annotate_remote() or user-defined.
  2407. """
  2408. if self._has_annotation(self.primaryjoin, "local"):
  2409. return
  2410. if self._local_remote_pairs:
  2411. local_side = util.column_set(
  2412. [l for (l, r) in self._local_remote_pairs]
  2413. )
  2414. else:
  2415. local_side = util.column_set(self.parent_persist_selectable.c)
  2416. def locals_(element: _CE, **kw: Any) -> Optional[_CE]:
  2417. if "remote" not in element._annotations and element in local_side:
  2418. return element._annotate({"local": True})
  2419. return None
  2420. self.primaryjoin = visitors.replacement_traverse(
  2421. self.primaryjoin, {}, locals_
  2422. )
  2423. def _annotate_parentmapper(self) -> None:
  2424. def parentmappers_(element: _CE, **kw: Any) -> Optional[_CE]:
  2425. if "remote" in element._annotations:
  2426. return element._annotate({"parentmapper": self.prop.mapper})
  2427. elif "local" in element._annotations:
  2428. return element._annotate({"parentmapper": self.prop.parent})
  2429. return None
  2430. self.primaryjoin = visitors.replacement_traverse(
  2431. self.primaryjoin, {}, parentmappers_
  2432. )
  2433. def _check_remote_side(self) -> None:
  2434. if not self.local_remote_pairs:
  2435. raise sa_exc.ArgumentError(
  2436. "Relationship %s could "
  2437. "not determine any unambiguous local/remote column "
  2438. "pairs based on join condition and remote_side "
  2439. "arguments. "
  2440. "Consider using the remote() annotation to "
  2441. "accurately mark those elements of the join "
  2442. "condition that are on the remote side of "
  2443. "the relationship." % (self.prop,)
  2444. )
  2445. else:
  2446. not_target = util.column_set(
  2447. self.parent_persist_selectable.c
  2448. ).difference(self.child_persist_selectable.c)
  2449. for _, rmt in self.local_remote_pairs:
  2450. if rmt in not_target:
  2451. util.warn(
  2452. "Expression %s is marked as 'remote', but these "
  2453. "column(s) are local to the local side. The "
  2454. "remote() annotation is needed only for a "
  2455. "self-referential relationship where both sides "
  2456. "of the relationship refer to the same tables."
  2457. % (rmt,)
  2458. )
  2459. def _check_foreign_cols(
  2460. self, join_condition: ColumnElement[bool], primary: bool
  2461. ) -> None:
  2462. """Check the foreign key columns collected and emit error
  2463. messages."""
  2464. can_sync = False
  2465. foreign_cols = self._gather_columns_with_annotation(
  2466. join_condition, "foreign"
  2467. )
  2468. has_foreign = bool(foreign_cols)
  2469. if primary:
  2470. can_sync = bool(self.synchronize_pairs)
  2471. else:
  2472. can_sync = bool(self.secondary_synchronize_pairs)
  2473. if (
  2474. self.support_sync
  2475. and can_sync
  2476. or (not self.support_sync and has_foreign)
  2477. ):
  2478. return
  2479. # from here below is just determining the best error message
  2480. # to report. Check for a join condition using any operator
  2481. # (not just ==), perhaps they need to turn on "viewonly=True".
  2482. if self.support_sync and has_foreign and not can_sync:
  2483. err = (
  2484. "Could not locate any simple equality expressions "
  2485. "involving locally mapped foreign key columns for "
  2486. "%s join condition "
  2487. "'%s' on relationship %s."
  2488. % (
  2489. primary and "primary" or "secondary",
  2490. join_condition,
  2491. self.prop,
  2492. )
  2493. )
  2494. err += (
  2495. " Ensure that referencing columns are associated "
  2496. "with a ForeignKey or ForeignKeyConstraint, or are "
  2497. "annotated in the join condition with the foreign() "
  2498. "annotation. To allow comparison operators other than "
  2499. "'==', the relationship can be marked as viewonly=True."
  2500. )
  2501. raise sa_exc.ArgumentError(err)
  2502. else:
  2503. err = (
  2504. "Could not locate any relevant foreign key columns "
  2505. "for %s join condition '%s' on relationship %s."
  2506. % (
  2507. primary and "primary" or "secondary",
  2508. join_condition,
  2509. self.prop,
  2510. )
  2511. )
  2512. err += (
  2513. " Ensure that referencing columns are associated "
  2514. "with a ForeignKey or ForeignKeyConstraint, or are "
  2515. "annotated in the join condition with the foreign() "
  2516. "annotation."
  2517. )
  2518. raise sa_exc.ArgumentError(err)
  2519. def _determine_direction(self) -> None:
  2520. """Determine if this relationship is one to many, many to one,
  2521. many to many.
  2522. """
  2523. if self.secondaryjoin is not None:
  2524. self.direction = MANYTOMANY
  2525. else:
  2526. parentcols = util.column_set(self.parent_persist_selectable.c)
  2527. targetcols = util.column_set(self.child_persist_selectable.c)
  2528. # fk collection which suggests ONETOMANY.
  2529. onetomany_fk = targetcols.intersection(self.foreign_key_columns)
  2530. # fk collection which suggests MANYTOONE.
  2531. manytoone_fk = parentcols.intersection(self.foreign_key_columns)
  2532. if onetomany_fk and manytoone_fk:
  2533. # fks on both sides. test for overlap of local/remote
  2534. # with foreign key.
  2535. # we will gather columns directly from their annotations
  2536. # without deannotating, so that we can distinguish on a column
  2537. # that refers to itself.
  2538. # 1. columns that are both remote and FK suggest
  2539. # onetomany.
  2540. onetomany_local = self._gather_columns_with_annotation(
  2541. self.primaryjoin, "remote", "foreign"
  2542. )
  2543. # 2. columns that are FK but are not remote (e.g. local)
  2544. # suggest manytoone.
  2545. manytoone_local = {
  2546. c
  2547. for c in self._gather_columns_with_annotation(
  2548. self.primaryjoin, "foreign"
  2549. )
  2550. if "remote" not in c._annotations
  2551. }
  2552. # 3. if both collections are present, remove columns that
  2553. # refer to themselves. This is for the case of
  2554. # and_(Me.id == Me.remote_id, Me.version == Me.version)
  2555. if onetomany_local and manytoone_local:
  2556. self_equated = self.remote_columns.intersection(
  2557. self.local_columns
  2558. )
  2559. onetomany_local = onetomany_local.difference(self_equated)
  2560. manytoone_local = manytoone_local.difference(self_equated)
  2561. # at this point, if only one or the other collection is
  2562. # present, we know the direction, otherwise it's still
  2563. # ambiguous.
  2564. if onetomany_local and not manytoone_local:
  2565. self.direction = ONETOMANY
  2566. elif manytoone_local and not onetomany_local:
  2567. self.direction = MANYTOONE
  2568. else:
  2569. raise sa_exc.ArgumentError(
  2570. "Can't determine relationship"
  2571. " direction for relationship '%s' - foreign "
  2572. "key columns within the join condition are present "
  2573. "in both the parent and the child's mapped tables. "
  2574. "Ensure that only those columns referring "
  2575. "to a parent column are marked as foreign, "
  2576. "either via the foreign() annotation or "
  2577. "via the foreign_keys argument." % self.prop
  2578. )
  2579. elif onetomany_fk:
  2580. self.direction = ONETOMANY
  2581. elif manytoone_fk:
  2582. self.direction = MANYTOONE
  2583. else:
  2584. raise sa_exc.ArgumentError(
  2585. "Can't determine relationship "
  2586. "direction for relationship '%s' - foreign "
  2587. "key columns are present in neither the parent "
  2588. "nor the child's mapped tables" % self.prop
  2589. )
  2590. def _deannotate_pairs(
  2591. self, collection: _ColumnPairIterable
  2592. ) -> _MutableColumnPairs:
  2593. """provide deannotation for the various lists of
  2594. pairs, so that using them in hashes doesn't incur
  2595. high-overhead __eq__() comparisons against
  2596. original columns mapped.
  2597. """
  2598. return [(x._deannotate(), y._deannotate()) for x, y in collection]
  2599. def _setup_pairs(self) -> None:
  2600. sync_pairs: _MutableColumnPairs = []
  2601. lrp: util.OrderedSet[
  2602. Tuple[ColumnElement[Any], ColumnElement[Any]]
  2603. ] = util.OrderedSet([])
  2604. secondary_sync_pairs: _MutableColumnPairs = []
  2605. def go(
  2606. joincond: ColumnElement[bool],
  2607. collection: _MutableColumnPairs,
  2608. ) -> None:
  2609. def visit_binary(
  2610. binary: BinaryExpression[Any],
  2611. left: ColumnElement[Any],
  2612. right: ColumnElement[Any],
  2613. ) -> None:
  2614. if (
  2615. "remote" in right._annotations
  2616. and "remote" not in left._annotations
  2617. and self.can_be_synced_fn(left)
  2618. ):
  2619. lrp.add((left, right))
  2620. elif (
  2621. "remote" in left._annotations
  2622. and "remote" not in right._annotations
  2623. and self.can_be_synced_fn(right)
  2624. ):
  2625. lrp.add((right, left))
  2626. if binary.operator is operators.eq and self.can_be_synced_fn(
  2627. left, right
  2628. ):
  2629. if "foreign" in right._annotations:
  2630. collection.append((left, right))
  2631. elif "foreign" in left._annotations:
  2632. collection.append((right, left))
  2633. visit_binary_product(visit_binary, joincond)
  2634. for joincond, collection in [
  2635. (self.primaryjoin, sync_pairs),
  2636. (self.secondaryjoin, secondary_sync_pairs),
  2637. ]:
  2638. if joincond is None:
  2639. continue
  2640. go(joincond, collection)
  2641. self.local_remote_pairs = self._deannotate_pairs(lrp)
  2642. self.synchronize_pairs = self._deannotate_pairs(sync_pairs)
  2643. self.secondary_synchronize_pairs = self._deannotate_pairs(
  2644. secondary_sync_pairs
  2645. )
  2646. _track_overlapping_sync_targets: weakref.WeakKeyDictionary[
  2647. ColumnElement[Any],
  2648. weakref.WeakKeyDictionary[
  2649. RelationshipProperty[Any], ColumnElement[Any]
  2650. ],
  2651. ] = weakref.WeakKeyDictionary()
  2652. def _warn_for_conflicting_sync_targets(self) -> None:
  2653. if not self.support_sync:
  2654. return
  2655. # we would like to detect if we are synchronizing any column
  2656. # pairs in conflict with another relationship that wishes to sync
  2657. # an entirely different column to the same target. This is a
  2658. # very rare edge case so we will try to minimize the memory/overhead
  2659. # impact of this check
  2660. for from_, to_ in [
  2661. (from_, to_) for (from_, to_) in self.synchronize_pairs
  2662. ] + [
  2663. (from_, to_) for (from_, to_) in self.secondary_synchronize_pairs
  2664. ]:
  2665. # save ourselves a ton of memory and overhead by only
  2666. # considering columns that are subject to a overlapping
  2667. # FK constraints at the core level. This condition can arise
  2668. # if multiple relationships overlap foreign() directly, but
  2669. # we're going to assume it's typically a ForeignKeyConstraint-
  2670. # level configuration that benefits from this warning.
  2671. if to_ not in self._track_overlapping_sync_targets:
  2672. self._track_overlapping_sync_targets[
  2673. to_
  2674. ] = weakref.WeakKeyDictionary({self.prop: from_})
  2675. else:
  2676. other_props = []
  2677. prop_to_from = self._track_overlapping_sync_targets[to_]
  2678. for pr, fr_ in prop_to_from.items():
  2679. if (
  2680. not pr.mapper._dispose_called
  2681. and pr not in self.prop._reverse_property
  2682. and pr.key not in self.prop._overlaps
  2683. and self.prop.key not in pr._overlaps
  2684. # note: the "__*" symbol is used internally by
  2685. # SQLAlchemy as a general means of suppressing the
  2686. # overlaps warning for some extension cases, however
  2687. # this is not currently
  2688. # a publicly supported symbol and may change at
  2689. # any time.
  2690. and "__*" not in self.prop._overlaps
  2691. and "__*" not in pr._overlaps
  2692. and not self.prop.parent.is_sibling(pr.parent)
  2693. and not self.prop.mapper.is_sibling(pr.mapper)
  2694. and not self.prop.parent.is_sibling(pr.mapper)
  2695. and not self.prop.mapper.is_sibling(pr.parent)
  2696. and (
  2697. self.prop.key != pr.key
  2698. or not self.prop.parent.common_parent(pr.parent)
  2699. )
  2700. ):
  2701. other_props.append((pr, fr_))
  2702. if other_props:
  2703. util.warn(
  2704. "relationship '%s' will copy column %s to column %s, "
  2705. "which conflicts with relationship(s): %s. "
  2706. "If this is not the intention, consider if these "
  2707. "relationships should be linked with "
  2708. "back_populates, or if viewonly=True should be "
  2709. "applied to one or more if they are read-only. "
  2710. "For the less common case that foreign key "
  2711. "constraints are partially overlapping, the "
  2712. "orm.foreign() "
  2713. "annotation can be used to isolate the columns that "
  2714. "should be written towards. To silence this "
  2715. "warning, add the parameter 'overlaps=\"%s\"' to the "
  2716. "'%s' relationship."
  2717. % (
  2718. self.prop,
  2719. from_,
  2720. to_,
  2721. ", ".join(
  2722. sorted(
  2723. "'%s' (copies %s to %s)" % (pr, fr_, to_)
  2724. for (pr, fr_) in other_props
  2725. )
  2726. ),
  2727. ",".join(sorted(pr.key for pr, fr in other_props)),
  2728. self.prop,
  2729. ),
  2730. code="qzyx",
  2731. )
  2732. self._track_overlapping_sync_targets[to_][self.prop] = from_
  2733. @util.memoized_property
  2734. def remote_columns(self) -> Set[ColumnElement[Any]]:
  2735. return self._gather_join_annotations("remote")
  2736. @util.memoized_property
  2737. def local_columns(self) -> Set[ColumnElement[Any]]:
  2738. return self._gather_join_annotations("local")
  2739. @util.memoized_property
  2740. def foreign_key_columns(self) -> Set[ColumnElement[Any]]:
  2741. return self._gather_join_annotations("foreign")
  2742. def _gather_join_annotations(
  2743. self, annotation: str
  2744. ) -> Set[ColumnElement[Any]]:
  2745. s = set(
  2746. self._gather_columns_with_annotation(self.primaryjoin, annotation)
  2747. )
  2748. if self.secondaryjoin is not None:
  2749. s.update(
  2750. self._gather_columns_with_annotation(
  2751. self.secondaryjoin, annotation
  2752. )
  2753. )
  2754. return {x._deannotate() for x in s}
  2755. def _gather_columns_with_annotation(
  2756. self, clause: ColumnElement[Any], *annotation: Iterable[str]
  2757. ) -> Set[ColumnElement[Any]]:
  2758. annotation_set = set(annotation)
  2759. return {
  2760. cast(ColumnElement[Any], col)
  2761. for col in visitors.iterate(clause, {})
  2762. if annotation_set.issubset(col._annotations)
  2763. }
  2764. def join_targets(
  2765. self,
  2766. source_selectable: Optional[FromClause],
  2767. dest_selectable: FromClause,
  2768. aliased: bool,
  2769. single_crit: Optional[ColumnElement[bool]] = None,
  2770. extra_criteria: Tuple[ColumnElement[bool], ...] = (),
  2771. ) -> Tuple[
  2772. ColumnElement[bool],
  2773. Optional[ColumnElement[bool]],
  2774. Optional[FromClause],
  2775. Optional[ClauseAdapter],
  2776. FromClause,
  2777. ]:
  2778. """Given a source and destination selectable, create a
  2779. join between them.
  2780. This takes into account aliasing the join clause
  2781. to reference the appropriate corresponding columns
  2782. in the target objects, as well as the extra child
  2783. criterion, equivalent column sets, etc.
  2784. """
  2785. # place a barrier on the destination such that
  2786. # replacement traversals won't ever dig into it.
  2787. # its internal structure remains fixed
  2788. # regardless of context.
  2789. dest_selectable = _shallow_annotate(
  2790. dest_selectable, {"no_replacement_traverse": True}
  2791. )
  2792. primaryjoin, secondaryjoin, secondary = (
  2793. self.primaryjoin,
  2794. self.secondaryjoin,
  2795. self.secondary,
  2796. )
  2797. # adjust the join condition for single table inheritance,
  2798. # in the case that the join is to a subclass
  2799. # this is analogous to the
  2800. # "_adjust_for_single_table_inheritance()" method in Query.
  2801. if single_crit is not None:
  2802. if secondaryjoin is not None:
  2803. secondaryjoin = secondaryjoin & single_crit
  2804. else:
  2805. primaryjoin = primaryjoin & single_crit
  2806. if extra_criteria:
  2807. def mark_unrelated_columns_as_ok_to_adapt(
  2808. elem: SupportsAnnotations, annotations: _AnnotationDict
  2809. ) -> SupportsAnnotations:
  2810. """note unrelated columns in the "extra criteria" as OK
  2811. to adapt, even though they are not part of our "local"
  2812. or "remote" side.
  2813. see #9779 for this case
  2814. """
  2815. parentmapper_for_element = elem._annotations.get(
  2816. "parentmapper", None
  2817. )
  2818. if (
  2819. parentmapper_for_element is not self.prop.parent
  2820. and parentmapper_for_element is not self.prop.mapper
  2821. ):
  2822. return _safe_annotate(elem, annotations)
  2823. else:
  2824. return elem
  2825. extra_criteria = tuple(
  2826. _deep_annotate(
  2827. elem,
  2828. {"ok_to_adapt_in_join_condition": True},
  2829. annotate_callable=mark_unrelated_columns_as_ok_to_adapt,
  2830. )
  2831. for elem in extra_criteria
  2832. )
  2833. if secondaryjoin is not None:
  2834. secondaryjoin = secondaryjoin & sql.and_(*extra_criteria)
  2835. else:
  2836. primaryjoin = primaryjoin & sql.and_(*extra_criteria)
  2837. if aliased:
  2838. if secondary is not None:
  2839. secondary = secondary._anonymous_fromclause(flat=True)
  2840. primary_aliasizer = ClauseAdapter(
  2841. secondary, exclude_fn=_ColInAnnotations("local")
  2842. )
  2843. secondary_aliasizer = ClauseAdapter(
  2844. dest_selectable, equivalents=self.child_equivalents
  2845. ).chain(primary_aliasizer)
  2846. if source_selectable is not None:
  2847. primary_aliasizer = ClauseAdapter(
  2848. secondary, exclude_fn=_ColInAnnotations("local")
  2849. ).chain(
  2850. ClauseAdapter(
  2851. source_selectable,
  2852. equivalents=self.parent_equivalents,
  2853. )
  2854. )
  2855. secondaryjoin = secondary_aliasizer.traverse(secondaryjoin)
  2856. else:
  2857. primary_aliasizer = ClauseAdapter(
  2858. dest_selectable,
  2859. exclude_fn=_ColInAnnotations("local"),
  2860. equivalents=self.child_equivalents,
  2861. )
  2862. if source_selectable is not None:
  2863. primary_aliasizer.chain(
  2864. ClauseAdapter(
  2865. source_selectable,
  2866. exclude_fn=_ColInAnnotations("remote"),
  2867. equivalents=self.parent_equivalents,
  2868. )
  2869. )
  2870. secondary_aliasizer = None
  2871. primaryjoin = primary_aliasizer.traverse(primaryjoin)
  2872. target_adapter = secondary_aliasizer or primary_aliasizer
  2873. target_adapter.exclude_fn = None
  2874. else:
  2875. target_adapter = None
  2876. return (
  2877. primaryjoin,
  2878. secondaryjoin,
  2879. secondary,
  2880. target_adapter,
  2881. dest_selectable,
  2882. )
  2883. def create_lazy_clause(
  2884. self, reverse_direction: bool = False
  2885. ) -> Tuple[
  2886. ColumnElement[bool],
  2887. Dict[str, ColumnElement[Any]],
  2888. Dict[ColumnElement[Any], ColumnElement[Any]],
  2889. ]:
  2890. binds: Dict[ColumnElement[Any], BindParameter[Any]] = {}
  2891. equated_columns: Dict[ColumnElement[Any], ColumnElement[Any]] = {}
  2892. has_secondary = self.secondaryjoin is not None
  2893. if has_secondary:
  2894. lookup = collections.defaultdict(list)
  2895. for l, r in self.local_remote_pairs:
  2896. lookup[l].append((l, r))
  2897. equated_columns[r] = l
  2898. elif not reverse_direction:
  2899. for l, r in self.local_remote_pairs:
  2900. equated_columns[r] = l
  2901. else:
  2902. for l, r in self.local_remote_pairs:
  2903. equated_columns[l] = r
  2904. def col_to_bind(
  2905. element: ColumnElement[Any], **kw: Any
  2906. ) -> Optional[BindParameter[Any]]:
  2907. if (
  2908. (not reverse_direction and "local" in element._annotations)
  2909. or reverse_direction
  2910. and (
  2911. (has_secondary and element in lookup)
  2912. or (not has_secondary and "remote" in element._annotations)
  2913. )
  2914. ):
  2915. if element not in binds:
  2916. binds[element] = sql.bindparam(
  2917. None, None, type_=element.type, unique=True
  2918. )
  2919. return binds[element]
  2920. return None
  2921. lazywhere = self.primaryjoin
  2922. if self.secondaryjoin is None or not reverse_direction:
  2923. lazywhere = visitors.replacement_traverse(
  2924. lazywhere, {}, col_to_bind
  2925. )
  2926. if self.secondaryjoin is not None:
  2927. secondaryjoin = self.secondaryjoin
  2928. if reverse_direction:
  2929. secondaryjoin = visitors.replacement_traverse(
  2930. secondaryjoin, {}, col_to_bind
  2931. )
  2932. lazywhere = sql.and_(lazywhere, secondaryjoin)
  2933. bind_to_col = {binds[col].key: col for col in binds}
  2934. return lazywhere, bind_to_col, equated_columns
  2935. class _ColInAnnotations:
  2936. """Serializable object that tests for a name in c._annotations."""
  2937. __slots__ = ("name",)
  2938. def __init__(self, name: str):
  2939. self.name = name
  2940. def __call__(self, c: ClauseElement) -> bool:
  2941. return (
  2942. self.name in c._annotations
  2943. or "ok_to_adapt_in_join_condition" in c._annotations
  2944. )
  2945. class Relationship( # type: ignore
  2946. RelationshipProperty[_T],
  2947. _DeclarativeMapped[_T],
  2948. WriteOnlyMapped[_T], # not compatible with Mapped[_T]
  2949. DynamicMapped[_T], # not compatible with Mapped[_T]
  2950. ):
  2951. """Describes an object property that holds a single item or list
  2952. of items that correspond to a related database table.
  2953. Public constructor is the :func:`_orm.relationship` function.
  2954. .. seealso::
  2955. :ref:`relationship_config_toplevel`
  2956. .. versionchanged:: 2.0 Added :class:`_orm.Relationship` as a Declarative
  2957. compatible subclass for :class:`_orm.RelationshipProperty`.
  2958. """
  2959. inherit_cache = True
  2960. """:meta private:"""