SQLAlchemy 2.0.47__cp313-cp313t-win32.whl

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