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,811 @@
1
+ # orm/path_registry.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
+ """Path tracking utilities, representing mapper graph traversals.
8
+
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from functools import reduce
14
+ from itertools import chain
15
+ import logging
16
+ import operator
17
+ from typing import Any
18
+ from typing import cast
19
+ from typing import Dict
20
+ from typing import Iterator
21
+ from typing import List
22
+ from typing import Optional
23
+ from typing import overload
24
+ from typing import Sequence
25
+ from typing import Tuple
26
+ from typing import TYPE_CHECKING
27
+ from typing import Union
28
+
29
+ from . import base as orm_base
30
+ from ._typing import insp_is_mapper_property
31
+ from .. import exc
32
+ from .. import util
33
+ from ..sql import visitors
34
+ from ..sql.cache_key import HasCacheKey
35
+
36
+ if TYPE_CHECKING:
37
+ from ._typing import _InternalEntityType
38
+ from .interfaces import StrategizedProperty
39
+ from .mapper import Mapper
40
+ from .relationships import RelationshipProperty
41
+ from .util import AliasedInsp
42
+ from ..sql.cache_key import _CacheKeyTraversalType
43
+ from ..sql.elements import BindParameter
44
+ from ..sql.visitors import anon_map
45
+ from ..util.typing import _LiteralStar
46
+ from ..util.typing import TypeGuard
47
+
48
+ def is_root(path: PathRegistry) -> TypeGuard[RootRegistry]: ...
49
+
50
+ def is_entity(path: PathRegistry) -> TypeGuard[AbstractEntityRegistry]: ...
51
+
52
+ else:
53
+ is_root = operator.attrgetter("is_root")
54
+ is_entity = operator.attrgetter("is_entity")
55
+
56
+
57
+ _SerializedPath = List[Any]
58
+ _StrPathToken = str
59
+ _PathElementType = Union[
60
+ _StrPathToken, "_InternalEntityType[Any]", "StrategizedProperty[Any]"
61
+ ]
62
+
63
+ # the representation is in fact
64
+ # a tuple with alternating:
65
+ # [_InternalEntityType[Any], Union[str, StrategizedProperty[Any]],
66
+ # _InternalEntityType[Any], Union[str, StrategizedProperty[Any]], ...]
67
+ # this might someday be a tuple of 2-tuples instead, but paths can be
68
+ # chopped at odd intervals as well so this is less flexible
69
+ _PathRepresentation = Tuple[_PathElementType, ...]
70
+
71
+ # NOTE: these names are weird since the array is 0-indexed,
72
+ # the "_Odd" entries are at 0, 2, 4, etc
73
+ _OddPathRepresentation = Sequence["_InternalEntityType[Any]"]
74
+ _EvenPathRepresentation = Sequence[Union["StrategizedProperty[Any]", str]]
75
+
76
+
77
+ log = logging.getLogger(__name__)
78
+
79
+
80
+ def _unreduce_path(path: _SerializedPath) -> PathRegistry:
81
+ return PathRegistry.deserialize(path)
82
+
83
+
84
+ _WILDCARD_TOKEN: _LiteralStar = "*"
85
+ _DEFAULT_TOKEN = "_sa_default"
86
+
87
+
88
+ class PathRegistry(HasCacheKey):
89
+ """Represent query load paths and registry functions.
90
+
91
+ Basically represents structures like:
92
+
93
+ (<User mapper>, "orders", <Order mapper>, "items", <Item mapper>)
94
+
95
+ These structures are generated by things like
96
+ query options (joinedload(), subqueryload(), etc.) and are
97
+ used to compose keys stored in the query._attributes dictionary
98
+ for various options.
99
+
100
+ They are then re-composed at query compile/result row time as
101
+ the query is formed and as rows are fetched, where they again
102
+ serve to compose keys to look up options in the context.attributes
103
+ dictionary, which is copied from query._attributes.
104
+
105
+ The path structure has a limited amount of caching, where each
106
+ "root" ultimately pulls from a fixed registry associated with
107
+ the first mapper, that also contains elements for each of its
108
+ property keys. However paths longer than two elements, which
109
+ are the exception rather than the rule, are generated on an
110
+ as-needed basis.
111
+
112
+ """
113
+
114
+ __slots__ = ()
115
+
116
+ is_token = False
117
+ is_root = False
118
+ has_entity = False
119
+ is_property = False
120
+ is_entity = False
121
+
122
+ is_unnatural: bool
123
+
124
+ path: _PathRepresentation
125
+ natural_path: _PathRepresentation
126
+ parent: Optional[PathRegistry]
127
+ root: RootRegistry
128
+
129
+ _cache_key_traversal: _CacheKeyTraversalType = [
130
+ ("path", visitors.ExtendedInternalTraversal.dp_has_cache_key_list)
131
+ ]
132
+
133
+ def __eq__(self, other: Any) -> bool:
134
+ try:
135
+ return other is not None and self.path == other._path_for_compare
136
+ except AttributeError:
137
+ util.warn(
138
+ "Comparison of PathRegistry to %r is not supported"
139
+ % (type(other))
140
+ )
141
+ return False
142
+
143
+ def __ne__(self, other: Any) -> bool:
144
+ try:
145
+ return other is None or self.path != other._path_for_compare
146
+ except AttributeError:
147
+ util.warn(
148
+ "Comparison of PathRegistry to %r is not supported"
149
+ % (type(other))
150
+ )
151
+ return True
152
+
153
+ @property
154
+ def _path_for_compare(self) -> Optional[_PathRepresentation]:
155
+ return self.path
156
+
157
+ def odd_element(self, index: int) -> _InternalEntityType[Any]:
158
+ return self.path[index] # type: ignore
159
+
160
+ def set(self, attributes: Dict[Any, Any], key: Any, value: Any) -> None:
161
+ log.debug("set '%s' on path '%s' to '%s'", key, self, value)
162
+ attributes[(key, self.natural_path)] = value
163
+
164
+ def setdefault(
165
+ self, attributes: Dict[Any, Any], key: Any, value: Any
166
+ ) -> None:
167
+ log.debug("setdefault '%s' on path '%s' to '%s'", key, self, value)
168
+ attributes.setdefault((key, self.natural_path), value)
169
+
170
+ def get(
171
+ self, attributes: Dict[Any, Any], key: Any, value: Optional[Any] = None
172
+ ) -> Any:
173
+ key = (key, self.natural_path)
174
+ if key in attributes:
175
+ return attributes[key]
176
+ else:
177
+ return value
178
+
179
+ def __len__(self) -> int:
180
+ return len(self.path)
181
+
182
+ def __hash__(self) -> int:
183
+ return id(self)
184
+
185
+ @overload
186
+ def __getitem__(self, entity: _StrPathToken) -> TokenRegistry: ...
187
+
188
+ @overload
189
+ def __getitem__(self, entity: int) -> _PathElementType: ...
190
+
191
+ @overload
192
+ def __getitem__(self, entity: slice) -> _PathRepresentation: ...
193
+
194
+ @overload
195
+ def __getitem__(
196
+ self, entity: _InternalEntityType[Any]
197
+ ) -> AbstractEntityRegistry: ...
198
+
199
+ @overload
200
+ def __getitem__(
201
+ self, entity: StrategizedProperty[Any]
202
+ ) -> PropRegistry: ...
203
+
204
+ def __getitem__(
205
+ self,
206
+ entity: Union[
207
+ _StrPathToken,
208
+ int,
209
+ slice,
210
+ _InternalEntityType[Any],
211
+ StrategizedProperty[Any],
212
+ ],
213
+ ) -> Union[
214
+ TokenRegistry,
215
+ _PathElementType,
216
+ _PathRepresentation,
217
+ PropRegistry,
218
+ AbstractEntityRegistry,
219
+ ]:
220
+ raise NotImplementedError()
221
+
222
+ # TODO: what are we using this for?
223
+ @property
224
+ def length(self) -> int:
225
+ return len(self.path)
226
+
227
+ def pairs(
228
+ self,
229
+ ) -> Iterator[
230
+ Tuple[_InternalEntityType[Any], Union[str, StrategizedProperty[Any]]]
231
+ ]:
232
+ odd_path = cast(_OddPathRepresentation, self.path)
233
+ even_path = cast(_EvenPathRepresentation, odd_path)
234
+ for i in range(0, len(odd_path), 2):
235
+ yield odd_path[i], even_path[i + 1]
236
+
237
+ def contains_mapper(self, mapper: Mapper[Any]) -> bool:
238
+ _m_path = cast(_OddPathRepresentation, self.path)
239
+ for path_mapper in [_m_path[i] for i in range(0, len(_m_path), 2)]:
240
+ if path_mapper.mapper.isa(mapper):
241
+ return True
242
+ else:
243
+ return False
244
+
245
+ def contains(self, attributes: Dict[Any, Any], key: Any) -> bool:
246
+ return (key, self.path) in attributes
247
+
248
+ def __reduce__(self) -> Any:
249
+ return _unreduce_path, (self.serialize(),)
250
+
251
+ @classmethod
252
+ def _serialize_path(cls, path: _PathRepresentation) -> _SerializedPath:
253
+ _m_path = cast(_OddPathRepresentation, path)
254
+ _p_path = cast(_EvenPathRepresentation, path)
255
+
256
+ return list(
257
+ zip(
258
+ tuple(
259
+ m.class_ if (m.is_mapper or m.is_aliased_class) else str(m)
260
+ for m in [_m_path[i] for i in range(0, len(_m_path), 2)]
261
+ ),
262
+ tuple(
263
+ p.key if insp_is_mapper_property(p) else str(p)
264
+ for p in [_p_path[i] for i in range(1, len(_p_path), 2)]
265
+ )
266
+ + (None,),
267
+ )
268
+ )
269
+
270
+ @classmethod
271
+ def _deserialize_path(cls, path: _SerializedPath) -> _PathRepresentation:
272
+ def _deserialize_mapper_token(mcls: Any) -> Any:
273
+ return (
274
+ # note: we likely dont want configure=True here however
275
+ # this is maintained at the moment for backwards compatibility
276
+ orm_base._inspect_mapped_class(mcls, configure=True)
277
+ if mcls not in PathToken._intern
278
+ else PathToken._intern[mcls]
279
+ )
280
+
281
+ def _deserialize_key_token(mcls: Any, key: Any) -> Any:
282
+ if key is None:
283
+ return None
284
+ elif key in PathToken._intern:
285
+ return PathToken._intern[key]
286
+ else:
287
+ mp = orm_base._inspect_mapped_class(mcls, configure=True)
288
+ assert mp is not None
289
+ return mp.attrs[key]
290
+
291
+ p = tuple(
292
+ chain(
293
+ *[
294
+ (
295
+ _deserialize_mapper_token(mcls),
296
+ _deserialize_key_token(mcls, key),
297
+ )
298
+ for mcls, key in path
299
+ ]
300
+ )
301
+ )
302
+ if p and p[-1] is None:
303
+ p = p[0:-1]
304
+ return p
305
+
306
+ def serialize(self) -> _SerializedPath:
307
+ path = self.path
308
+ return self._serialize_path(path)
309
+
310
+ @classmethod
311
+ def deserialize(cls, path: _SerializedPath) -> PathRegistry:
312
+ assert path is not None
313
+ p = cls._deserialize_path(path)
314
+ return cls.coerce(p)
315
+
316
+ @overload
317
+ @classmethod
318
+ def per_mapper(cls, mapper: Mapper[Any]) -> CachingEntityRegistry: ...
319
+
320
+ @overload
321
+ @classmethod
322
+ def per_mapper(cls, mapper: AliasedInsp[Any]) -> SlotsEntityRegistry: ...
323
+
324
+ @classmethod
325
+ def per_mapper(
326
+ cls, mapper: _InternalEntityType[Any]
327
+ ) -> AbstractEntityRegistry:
328
+ if mapper.is_mapper:
329
+ return CachingEntityRegistry(cls.root, mapper)
330
+ else:
331
+ return SlotsEntityRegistry(cls.root, mapper)
332
+
333
+ @classmethod
334
+ def coerce(cls, raw: _PathRepresentation) -> PathRegistry:
335
+ def _red(prev: PathRegistry, next_: _PathElementType) -> PathRegistry:
336
+ return prev[next_]
337
+
338
+ # can't quite get mypy to appreciate this one :)
339
+ return reduce(_red, raw, cls.root) # type: ignore
340
+
341
+ def __add__(self, other: PathRegistry) -> PathRegistry:
342
+ def _red(prev: PathRegistry, next_: _PathElementType) -> PathRegistry:
343
+ return prev[next_]
344
+
345
+ return reduce(_red, other.path, self)
346
+
347
+ def __str__(self) -> str:
348
+ return f"ORM Path[{' -> '.join(str(elem) for elem in self.path)}]"
349
+
350
+ def __repr__(self) -> str:
351
+ return f"{self.__class__.__name__}({self.path!r})"
352
+
353
+
354
+ class CreatesToken(PathRegistry):
355
+ __slots__ = ()
356
+
357
+ is_aliased_class: bool
358
+ is_root: bool
359
+
360
+ def token(self, token: _StrPathToken) -> TokenRegistry:
361
+ if token.endswith(f":{_WILDCARD_TOKEN}"):
362
+ return TokenRegistry(self, token)
363
+ elif token.endswith(f":{_DEFAULT_TOKEN}"):
364
+ return TokenRegistry(self.root, token)
365
+ else:
366
+ raise exc.ArgumentError(f"invalid token: {token}")
367
+
368
+
369
+ class RootRegistry(CreatesToken):
370
+ """Root registry, defers to mappers so that
371
+ paths are maintained per-root-mapper.
372
+
373
+ """
374
+
375
+ __slots__ = ()
376
+
377
+ inherit_cache = True
378
+
379
+ path = natural_path = ()
380
+ has_entity = False
381
+ is_aliased_class = False
382
+ is_root = True
383
+ is_unnatural = False
384
+
385
+ def _getitem(
386
+ self, entity: Any
387
+ ) -> Union[TokenRegistry, AbstractEntityRegistry]:
388
+ if entity in PathToken._intern:
389
+ if TYPE_CHECKING:
390
+ assert isinstance(entity, _StrPathToken)
391
+ return TokenRegistry(self, PathToken._intern[entity])
392
+ else:
393
+ try:
394
+ return entity._path_registry # type: ignore
395
+ except AttributeError:
396
+ raise IndexError(
397
+ f"invalid argument for RootRegistry.__getitem__: {entity}"
398
+ )
399
+
400
+ def _truncate_recursive(self) -> RootRegistry:
401
+ return self
402
+
403
+ if not TYPE_CHECKING:
404
+ __getitem__ = _getitem
405
+
406
+
407
+ PathRegistry.root = RootRegistry()
408
+
409
+
410
+ class PathToken(orm_base.InspectionAttr, HasCacheKey, str):
411
+ """cacheable string token"""
412
+
413
+ _intern: Dict[str, PathToken] = {}
414
+
415
+ def _gen_cache_key(
416
+ self, anon_map: anon_map, bindparams: List[BindParameter[Any]]
417
+ ) -> Tuple[Any, ...]:
418
+ return (str(self),)
419
+
420
+ @property
421
+ def _path_for_compare(self) -> Optional[_PathRepresentation]:
422
+ return None
423
+
424
+ @classmethod
425
+ def intern(cls, strvalue: str) -> PathToken:
426
+ if strvalue in cls._intern:
427
+ return cls._intern[strvalue]
428
+ else:
429
+ cls._intern[strvalue] = result = PathToken(strvalue)
430
+ return result
431
+
432
+
433
+ class TokenRegistry(PathRegistry):
434
+ __slots__ = ("token", "parent", "path", "natural_path")
435
+
436
+ inherit_cache = True
437
+
438
+ token: _StrPathToken
439
+ parent: CreatesToken
440
+
441
+ def __init__(self, parent: CreatesToken, token: _StrPathToken):
442
+ token = PathToken.intern(token)
443
+
444
+ self.token = token
445
+ self.parent = parent
446
+ self.path = parent.path + (token,)
447
+ self.natural_path = parent.natural_path + (token,)
448
+
449
+ has_entity = False
450
+
451
+ is_token = True
452
+
453
+ def generate_for_superclasses(self) -> Iterator[PathRegistry]:
454
+ # NOTE: this method is no longer used. consider removal
455
+ parent = self.parent
456
+ if is_root(parent):
457
+ yield self
458
+ return
459
+
460
+ if TYPE_CHECKING:
461
+ assert isinstance(parent, AbstractEntityRegistry)
462
+ if not parent.is_aliased_class:
463
+ for mp_ent in parent.mapper.iterate_to_root():
464
+ yield TokenRegistry(parent.parent[mp_ent], self.token)
465
+ elif (
466
+ parent.is_aliased_class
467
+ and cast(
468
+ "AliasedInsp[Any]",
469
+ parent.entity,
470
+ )._is_with_polymorphic
471
+ ):
472
+ yield self
473
+ for ent in cast(
474
+ "AliasedInsp[Any]", parent.entity
475
+ )._with_polymorphic_entities:
476
+ yield TokenRegistry(parent.parent[ent], self.token)
477
+ else:
478
+ yield self
479
+
480
+ def _generate_natural_for_superclasses(
481
+ self,
482
+ ) -> Iterator[_PathRepresentation]:
483
+ parent = self.parent
484
+ if is_root(parent):
485
+ yield self.natural_path
486
+ return
487
+
488
+ if TYPE_CHECKING:
489
+ assert isinstance(parent, AbstractEntityRegistry)
490
+ for mp_ent in parent.mapper.iterate_to_root():
491
+ yield TokenRegistry(parent.parent[mp_ent], self.token).natural_path
492
+ if (
493
+ parent.is_aliased_class
494
+ and cast(
495
+ "AliasedInsp[Any]",
496
+ parent.entity,
497
+ )._is_with_polymorphic
498
+ ):
499
+ yield self.natural_path
500
+ for ent in cast(
501
+ "AliasedInsp[Any]", parent.entity
502
+ )._with_polymorphic_entities:
503
+ yield (
504
+ TokenRegistry(parent.parent[ent], self.token).natural_path
505
+ )
506
+ else:
507
+ yield self.natural_path
508
+
509
+ def _getitem(self, entity: Any) -> Any:
510
+ try:
511
+ return self.path[entity]
512
+ except TypeError as err:
513
+ raise IndexError(f"{entity}") from err
514
+
515
+ if not TYPE_CHECKING:
516
+ __getitem__ = _getitem
517
+
518
+
519
+ class PropRegistry(PathRegistry):
520
+ __slots__ = (
521
+ "prop",
522
+ "parent",
523
+ "path",
524
+ "natural_path",
525
+ "has_entity",
526
+ "entity",
527
+ "mapper",
528
+ "_wildcard_path_loader_key",
529
+ "_default_path_loader_key",
530
+ "_loader_key",
531
+ "is_unnatural",
532
+ )
533
+ inherit_cache = True
534
+ is_property = True
535
+
536
+ prop: StrategizedProperty[Any]
537
+ mapper: Optional[Mapper[Any]]
538
+ entity: Optional[_InternalEntityType[Any]]
539
+
540
+ def __init__(
541
+ self, parent: AbstractEntityRegistry, prop: StrategizedProperty[Any]
542
+ ):
543
+
544
+ # restate this path in terms of the
545
+ # given StrategizedProperty's parent.
546
+ insp = cast("_InternalEntityType[Any]", parent[-1])
547
+ natural_parent: AbstractEntityRegistry = parent
548
+
549
+ # inherit "is_unnatural" from the parent
550
+ self.is_unnatural = parent.parent.is_unnatural or bool(
551
+ parent.mapper.inherits
552
+ )
553
+
554
+ if not insp.is_aliased_class or insp._use_mapper_path: # type: ignore
555
+ parent = natural_parent = parent.parent[prop.parent]
556
+ elif (
557
+ insp.is_aliased_class
558
+ and insp.with_polymorphic_mappers
559
+ and prop.parent in insp.with_polymorphic_mappers
560
+ ):
561
+ subclass_entity: _InternalEntityType[Any] = parent[-1]._entity_for_mapper(prop.parent) # type: ignore # noqa: E501
562
+ parent = parent.parent[subclass_entity]
563
+
564
+ # when building a path where with_polymorphic() is in use,
565
+ # special logic to determine the "natural path" when subclass
566
+ # entities are used.
567
+ #
568
+ # here we are trying to distinguish between a path that starts
569
+ # on a the with_polymorhpic entity vs. one that starts on a
570
+ # normal entity that introduces a with_polymorphic() in the
571
+ # middle using of_type():
572
+ #
573
+ # # as in test_polymorphic_rel->
574
+ # # test_subqueryload_on_subclass_uses_path_correctly
575
+ # wp = with_polymorphic(RegularEntity, "*")
576
+ # sess.query(wp).options(someload(wp.SomeSubEntity.foos))
577
+ #
578
+ # vs
579
+ #
580
+ # # as in test_relationship->JoinedloadWPolyOfTypeContinued
581
+ # wp = with_polymorphic(SomeFoo, "*")
582
+ # sess.query(RegularEntity).options(
583
+ # someload(RegularEntity.foos.of_type(wp))
584
+ # .someload(wp.SubFoo.bar)
585
+ # )
586
+ #
587
+ # in the former case, the Query as it generates a path that we
588
+ # want to match will be in terms of the with_polymorphic at the
589
+ # beginning. in the latter case, Query will generate simple
590
+ # paths that don't know about this with_polymorphic, so we must
591
+ # use a separate natural path.
592
+ #
593
+ #
594
+ if parent.parent:
595
+ natural_parent = parent.parent[subclass_entity.mapper]
596
+ self.is_unnatural = True
597
+ else:
598
+ natural_parent = parent
599
+ elif (
600
+ natural_parent.parent
601
+ and insp.is_aliased_class
602
+ and prop.parent # this should always be the case here
603
+ is not insp.mapper
604
+ and insp.mapper.isa(prop.parent)
605
+ ):
606
+ natural_parent = parent.parent[prop.parent]
607
+
608
+ self.prop = prop
609
+ self.parent = parent
610
+ self.path = parent.path + (prop,)
611
+ self.natural_path = natural_parent.natural_path + (prop,)
612
+
613
+ self.has_entity = prop._links_to_entity
614
+ if prop._is_relationship:
615
+ if TYPE_CHECKING:
616
+ assert isinstance(prop, RelationshipProperty)
617
+ self.entity = prop.entity
618
+ self.mapper = prop.mapper
619
+ else:
620
+ self.entity = None
621
+ self.mapper = None
622
+
623
+ self._wildcard_path_loader_key = (
624
+ "loader",
625
+ parent.natural_path + self.prop._wildcard_token,
626
+ )
627
+ self._default_path_loader_key = self.prop._default_path_loader_key
628
+ self._loader_key = ("loader", self.natural_path)
629
+
630
+ def _truncate_recursive(self) -> PropRegistry:
631
+ earliest = None
632
+ for i, token in enumerate(reversed(self.path[:-1])):
633
+ if token is self.prop:
634
+ earliest = i
635
+
636
+ if earliest is None:
637
+ return self
638
+ else:
639
+ return self.coerce(self.path[0 : -(earliest + 1)]) # type: ignore
640
+
641
+ @property
642
+ def entity_path(self) -> AbstractEntityRegistry:
643
+ assert self.entity is not None
644
+ return self[self.entity]
645
+
646
+ def _getitem(
647
+ self, entity: Union[int, slice, _InternalEntityType[Any]]
648
+ ) -> Union[AbstractEntityRegistry, _PathElementType, _PathRepresentation]:
649
+ if isinstance(entity, (int, slice)):
650
+ return self.path[entity]
651
+ else:
652
+ return SlotsEntityRegistry(self, entity)
653
+
654
+ if not TYPE_CHECKING:
655
+ __getitem__ = _getitem
656
+
657
+
658
+ class AbstractEntityRegistry(CreatesToken):
659
+ __slots__ = (
660
+ "key",
661
+ "parent",
662
+ "is_aliased_class",
663
+ "path",
664
+ "entity",
665
+ "natural_path",
666
+ )
667
+
668
+ has_entity = True
669
+ is_entity = True
670
+
671
+ parent: Union[RootRegistry, PropRegistry]
672
+ key: _InternalEntityType[Any]
673
+ entity: _InternalEntityType[Any]
674
+ is_aliased_class: bool
675
+
676
+ def __init__(
677
+ self,
678
+ parent: Union[RootRegistry, PropRegistry],
679
+ entity: _InternalEntityType[Any],
680
+ ):
681
+ self.key = entity
682
+ self.parent = parent
683
+ self.is_aliased_class = entity.is_aliased_class
684
+ self.entity = entity
685
+ self.path = parent.path + (entity,)
686
+
687
+ # the "natural path" is the path that we get when Query is traversing
688
+ # from the lead entities into the various relationships; it corresponds
689
+ # to the structure of mappers and relationships. when we are given a
690
+ # path that comes from loader options, as of 1.3 it can have ac-hoc
691
+ # with_polymorphic() and other AliasedInsp objects inside of it, which
692
+ # are usually not present in mappings. So here we track both the
693
+ # "enhanced" path in self.path and the "natural" path that doesn't
694
+ # include those objects so these two traversals can be matched up.
695
+
696
+ # the test here for "(self.is_aliased_class or parent.is_unnatural)"
697
+ # are to avoid the more expensive conditional logic that follows if we
698
+ # know we don't have to do it. This conditional can just as well be
699
+ # "if parent.path:", it just is more function calls.
700
+ #
701
+ # This is basically the only place that the "is_unnatural" flag
702
+ # actually changes behavior.
703
+ if parent.path and (self.is_aliased_class or parent.is_unnatural):
704
+ # this is an infrequent code path used only for loader strategies
705
+ # that also make use of of_type().
706
+ if entity.mapper.isa(parent.natural_path[-1].mapper): # type: ignore # noqa: E501
707
+ self.natural_path = parent.natural_path + (entity.mapper,)
708
+ else:
709
+ self.natural_path = parent.natural_path + (
710
+ parent.natural_path[-1].entity, # type: ignore
711
+ )
712
+ # it seems to make sense that since these paths get mixed up
713
+ # with statements that are cached or not, we should make
714
+ # sure the natural path is cacheable across different occurrences
715
+ # of equivalent AliasedClass objects. however, so far this
716
+ # does not seem to be needed for whatever reason.
717
+ # elif not parent.path and self.is_aliased_class:
718
+ # self.natural_path = (self.entity._generate_cache_key()[0], )
719
+ else:
720
+ self.natural_path = self.path
721
+
722
+ def _truncate_recursive(self) -> AbstractEntityRegistry:
723
+ return self.parent._truncate_recursive()[self.entity]
724
+
725
+ @property
726
+ def root_entity(self) -> _InternalEntityType[Any]:
727
+ return self.odd_element(0)
728
+
729
+ @property
730
+ def entity_path(self) -> PathRegistry:
731
+ return self
732
+
733
+ @property
734
+ def mapper(self) -> Mapper[Any]:
735
+ return self.entity.mapper
736
+
737
+ def __bool__(self) -> bool:
738
+ return True
739
+
740
+ def _getitem(
741
+ self, entity: Any
742
+ ) -> Union[_PathElementType, _PathRepresentation, PathRegistry]:
743
+ if isinstance(entity, (int, slice)):
744
+ return self.path[entity]
745
+ elif entity in PathToken._intern:
746
+ return TokenRegistry(self, PathToken._intern[entity])
747
+ else:
748
+ return PropRegistry(self, entity)
749
+
750
+ if not TYPE_CHECKING:
751
+ __getitem__ = _getitem
752
+
753
+
754
+ class SlotsEntityRegistry(AbstractEntityRegistry):
755
+ # for aliased class, return lightweight, no-cycles created
756
+ # version
757
+ inherit_cache = True
758
+
759
+
760
+ class _ERDict(Dict[Any, Any]):
761
+ def __init__(self, registry: CachingEntityRegistry):
762
+ self.registry = registry
763
+
764
+ def __missing__(self, key: Any) -> PropRegistry:
765
+ self[key] = item = PropRegistry(self.registry, key)
766
+
767
+ return item
768
+
769
+
770
+ class CachingEntityRegistry(AbstractEntityRegistry):
771
+ # for long lived mapper, return dict based caching
772
+ # version that creates reference cycles
773
+
774
+ __slots__ = ("_cache",)
775
+
776
+ inherit_cache = True
777
+
778
+ def __init__(
779
+ self,
780
+ parent: Union[RootRegistry, PropRegistry],
781
+ entity: _InternalEntityType[Any],
782
+ ):
783
+ super().__init__(parent, entity)
784
+ self._cache = _ERDict(self)
785
+
786
+ def pop(self, key: Any, default: Any) -> Any:
787
+ return self._cache.pop(key, default)
788
+
789
+ def _getitem(self, entity: Any) -> Any:
790
+ if isinstance(entity, (int, slice)):
791
+ return self.path[entity]
792
+ elif isinstance(entity, PathToken):
793
+ return TokenRegistry(self, entity)
794
+ else:
795
+ return self._cache[entity]
796
+
797
+ if not TYPE_CHECKING:
798
+ __getitem__ = _getitem
799
+
800
+
801
+ if TYPE_CHECKING:
802
+
803
+ def path_is_entity(
804
+ path: PathRegistry,
805
+ ) -> TypeGuard[AbstractEntityRegistry]: ...
806
+
807
+ def path_is_property(path: PathRegistry) -> TypeGuard[PropRegistry]: ...
808
+
809
+ else:
810
+ path_is_entity = operator.attrgetter("is_entity")
811
+ path_is_property = operator.attrgetter("is_property")