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