SQLAlchemy 2.1.0rc1__cp315-cp315-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 (276) hide show
  1. sqlalchemy/__init__.py +299 -0
  2. sqlalchemy/connectors/__init__.py +18 -0
  3. sqlalchemy/connectors/aioodbc.py +171 -0
  4. sqlalchemy/connectors/asyncio.py +476 -0
  5. sqlalchemy/connectors/pyodbc.py +248 -0
  6. sqlalchemy/dialects/__init__.py +62 -0
  7. sqlalchemy/dialects/_typing.py +29 -0
  8. sqlalchemy/dialects/mssql/__init__.py +88 -0
  9. sqlalchemy/dialects/mssql/aioodbc.py +63 -0
  10. sqlalchemy/dialects/mssql/base.py +4833 -0
  11. sqlalchemy/dialects/mssql/information_schema.py +345 -0
  12. sqlalchemy/dialects/mssql/json.py +140 -0
  13. sqlalchemy/dialects/mssql/mssqlpython.py +242 -0
  14. sqlalchemy/dialects/mssql/provision.py +196 -0
  15. sqlalchemy/dialects/mssql/pymssql.py +130 -0
  16. sqlalchemy/dialects/mssql/pyodbc.py +697 -0
  17. sqlalchemy/dialects/mysql/__init__.py +106 -0
  18. sqlalchemy/dialects/mysql/_mariadb_shim.py +312 -0
  19. sqlalchemy/dialects/mysql/aiomysql.py +260 -0
  20. sqlalchemy/dialects/mysql/asyncmy.py +241 -0
  21. sqlalchemy/dialects/mysql/base.py +3896 -0
  22. sqlalchemy/dialects/mysql/cymysql.py +107 -0
  23. sqlalchemy/dialects/mysql/dml.py +279 -0
  24. sqlalchemy/dialects/mysql/enumerated.py +277 -0
  25. sqlalchemy/dialects/mysql/expression.py +146 -0
  26. sqlalchemy/dialects/mysql/json.py +92 -0
  27. sqlalchemy/dialects/mysql/mariadb.py +67 -0
  28. sqlalchemy/dialects/mysql/mariadbconnector.py +314 -0
  29. sqlalchemy/dialects/mysql/mysqlconnector.py +291 -0
  30. sqlalchemy/dialects/mysql/mysqldb.py +318 -0
  31. sqlalchemy/dialects/mysql/provision.py +153 -0
  32. sqlalchemy/dialects/mysql/pymysql.py +188 -0
  33. sqlalchemy/dialects/mysql/pyodbc.py +157 -0
  34. sqlalchemy/dialects/mysql/reflection.py +724 -0
  35. sqlalchemy/dialects/mysql/reserved_words.py +570 -0
  36. sqlalchemy/dialects/mysql/types.py +845 -0
  37. sqlalchemy/dialects/oracle/__init__.py +85 -0
  38. sqlalchemy/dialects/oracle/base.py +3847 -0
  39. sqlalchemy/dialects/oracle/cx_oracle.py +1736 -0
  40. sqlalchemy/dialects/oracle/dictionary.py +507 -0
  41. sqlalchemy/dialects/oracle/json.py +157 -0
  42. sqlalchemy/dialects/oracle/oracledb.py +898 -0
  43. sqlalchemy/dialects/oracle/provision.py +288 -0
  44. sqlalchemy/dialects/oracle/types.py +367 -0
  45. sqlalchemy/dialects/oracle/vector.py +366 -0
  46. sqlalchemy/dialects/postgresql/__init__.py +170 -0
  47. sqlalchemy/dialects/postgresql/_psycopg_common.py +232 -0
  48. sqlalchemy/dialects/postgresql/array.py +534 -0
  49. sqlalchemy/dialects/postgresql/asyncpg.py +1318 -0
  50. sqlalchemy/dialects/postgresql/base.py +5935 -0
  51. sqlalchemy/dialects/postgresql/bitstring.py +327 -0
  52. sqlalchemy/dialects/postgresql/dml.py +360 -0
  53. sqlalchemy/dialects/postgresql/ext.py +599 -0
  54. sqlalchemy/dialects/postgresql/hstore.py +422 -0
  55. sqlalchemy/dialects/postgresql/json.py +411 -0
  56. sqlalchemy/dialects/postgresql/named_types.py +535 -0
  57. sqlalchemy/dialects/postgresql/operators.py +129 -0
  58. sqlalchemy/dialects/postgresql/pg8000.py +655 -0
  59. sqlalchemy/dialects/postgresql/pg_catalog.py +345 -0
  60. sqlalchemy/dialects/postgresql/provision.py +202 -0
  61. sqlalchemy/dialects/postgresql/psycopg.py +800 -0
  62. sqlalchemy/dialects/postgresql/psycopg2.py +860 -0
  63. sqlalchemy/dialects/postgresql/psycopg2cffi.py +62 -0
  64. sqlalchemy/dialects/postgresql/ranges.py +1002 -0
  65. sqlalchemy/dialects/postgresql/types.py +388 -0
  66. sqlalchemy/dialects/sqlite/__init__.py +59 -0
  67. sqlalchemy/dialects/sqlite/aiosqlite.py +375 -0
  68. sqlalchemy/dialects/sqlite/base.py +3103 -0
  69. sqlalchemy/dialects/sqlite/dml.py +314 -0
  70. sqlalchemy/dialects/sqlite/json.py +134 -0
  71. sqlalchemy/dialects/sqlite/provision.py +237 -0
  72. sqlalchemy/dialects/sqlite/pysqlcipher.py +166 -0
  73. sqlalchemy/dialects/sqlite/pysqlite.py +959 -0
  74. sqlalchemy/dialects/type_migration_guidelines.txt +145 -0
  75. sqlalchemy/engine/__init__.py +62 -0
  76. sqlalchemy/engine/_processors_cy.cp315-win32.pyd +0 -0
  77. sqlalchemy/engine/_processors_cy.py +92 -0
  78. sqlalchemy/engine/_result_cy.cp315-win32.pyd +0 -0
  79. sqlalchemy/engine/_result_cy.py +711 -0
  80. sqlalchemy/engine/_row_cy.cp315-win32.pyd +0 -0
  81. sqlalchemy/engine/_row_cy.py +232 -0
  82. sqlalchemy/engine/_util_cy.cp315-win32.pyd +0 -0
  83. sqlalchemy/engine/_util_cy.py +136 -0
  84. sqlalchemy/engine/base.py +3357 -0
  85. sqlalchemy/engine/characteristics.py +155 -0
  86. sqlalchemy/engine/create.py +877 -0
  87. sqlalchemy/engine/cursor.py +2425 -0
  88. sqlalchemy/engine/default.py +2627 -0
  89. sqlalchemy/engine/events.py +965 -0
  90. sqlalchemy/engine/interfaces.py +3636 -0
  91. sqlalchemy/engine/mock.py +133 -0
  92. sqlalchemy/engine/processors.py +83 -0
  93. sqlalchemy/engine/reflection.py +2141 -0
  94. sqlalchemy/engine/result.py +2012 -0
  95. sqlalchemy/engine/row.py +397 -0
  96. sqlalchemy/engine/strategies.py +16 -0
  97. sqlalchemy/engine/url.py +922 -0
  98. sqlalchemy/engine/util.py +164 -0
  99. sqlalchemy/event/__init__.py +26 -0
  100. sqlalchemy/event/api.py +220 -0
  101. sqlalchemy/event/attr.py +675 -0
  102. sqlalchemy/event/base.py +473 -0
  103. sqlalchemy/event/legacy.py +259 -0
  104. sqlalchemy/event/registry.py +391 -0
  105. sqlalchemy/events.py +17 -0
  106. sqlalchemy/exc.py +939 -0
  107. sqlalchemy/ext/__init__.py +10 -0
  108. sqlalchemy/ext/associationproxy.py +2073 -0
  109. sqlalchemy/ext/asyncio/__init__.py +29 -0
  110. sqlalchemy/ext/asyncio/base.py +281 -0
  111. sqlalchemy/ext/asyncio/engine.py +1487 -0
  112. sqlalchemy/ext/asyncio/exc.py +21 -0
  113. sqlalchemy/ext/asyncio/result.py +994 -0
  114. sqlalchemy/ext/asyncio/scoping.py +1679 -0
  115. sqlalchemy/ext/asyncio/session.py +2006 -0
  116. sqlalchemy/ext/automap.py +1702 -0
  117. sqlalchemy/ext/baked.py +558 -0
  118. sqlalchemy/ext/compiler.py +601 -0
  119. sqlalchemy/ext/declarative/__init__.py +65 -0
  120. sqlalchemy/ext/declarative/extensions.py +561 -0
  121. sqlalchemy/ext/horizontal_shard.py +481 -0
  122. sqlalchemy/ext/hybrid.py +1877 -0
  123. sqlalchemy/ext/indexable.py +364 -0
  124. sqlalchemy/ext/instrumentation.py +450 -0
  125. sqlalchemy/ext/mutable.py +1081 -0
  126. sqlalchemy/ext/orderinglist.py +440 -0
  127. sqlalchemy/ext/serializer.py +184 -0
  128. sqlalchemy/future/__init__.py +17 -0
  129. sqlalchemy/future/engine.py +15 -0
  130. sqlalchemy/inspection.py +188 -0
  131. sqlalchemy/log.py +279 -0
  132. sqlalchemy/orm/__init__.py +176 -0
  133. sqlalchemy/orm/_orm_constructors.py +2694 -0
  134. sqlalchemy/orm/_typing.py +180 -0
  135. sqlalchemy/orm/attributes.py +2868 -0
  136. sqlalchemy/orm/base.py +991 -0
  137. sqlalchemy/orm/bulk_persistence.py +2168 -0
  138. sqlalchemy/orm/clsregistry.py +630 -0
  139. sqlalchemy/orm/collections.py +1569 -0
  140. sqlalchemy/orm/context.py +3475 -0
  141. sqlalchemy/orm/decl_api.py +2283 -0
  142. sqlalchemy/orm/decl_base.py +2320 -0
  143. sqlalchemy/orm/dependency.py +1306 -0
  144. sqlalchemy/orm/descriptor_props.py +1183 -0
  145. sqlalchemy/orm/dynamic.py +306 -0
  146. sqlalchemy/orm/evaluator.py +378 -0
  147. sqlalchemy/orm/events.py +3387 -0
  148. sqlalchemy/orm/exc.py +237 -0
  149. sqlalchemy/orm/identity.py +302 -0
  150. sqlalchemy/orm/instrumentation.py +749 -0
  151. sqlalchemy/orm/interfaces.py +1595 -0
  152. sqlalchemy/orm/loading.py +1712 -0
  153. sqlalchemy/orm/mapped_collection.py +557 -0
  154. sqlalchemy/orm/mapper.py +4465 -0
  155. sqlalchemy/orm/path_registry.py +907 -0
  156. sqlalchemy/orm/persistence.py +1790 -0
  157. sqlalchemy/orm/properties.py +972 -0
  158. sqlalchemy/orm/query.py +3528 -0
  159. sqlalchemy/orm/relationships.py +3608 -0
  160. sqlalchemy/orm/scoping.py +2233 -0
  161. sqlalchemy/orm/session.py +5468 -0
  162. sqlalchemy/orm/state.py +1175 -0
  163. sqlalchemy/orm/state_changes.py +196 -0
  164. sqlalchemy/orm/strategies.py +3552 -0
  165. sqlalchemy/orm/strategy_options.py +2648 -0
  166. sqlalchemy/orm/sync.py +164 -0
  167. sqlalchemy/orm/unitofwork.py +797 -0
  168. sqlalchemy/orm/util.py +2461 -0
  169. sqlalchemy/orm/writeonly.py +701 -0
  170. sqlalchemy/pool/__init__.py +41 -0
  171. sqlalchemy/pool/base.py +1540 -0
  172. sqlalchemy/pool/events.py +375 -0
  173. sqlalchemy/pool/impl.py +583 -0
  174. sqlalchemy/py.typed +0 -0
  175. sqlalchemy/schema.py +75 -0
  176. sqlalchemy/sql/__init__.py +156 -0
  177. sqlalchemy/sql/_annotated_cols.py +402 -0
  178. sqlalchemy/sql/_cache_key_cy.cp315-win32.pyd +0 -0
  179. sqlalchemy/sql/_cache_key_cy.py +363 -0
  180. sqlalchemy/sql/_dml_constructors.py +132 -0
  181. sqlalchemy/sql/_elements_constructors.py +2190 -0
  182. sqlalchemy/sql/_orm_types.py +19 -0
  183. sqlalchemy/sql/_selectable_constructors.py +840 -0
  184. sqlalchemy/sql/_typing.py +500 -0
  185. sqlalchemy/sql/_util_cy.cp315-win32.pyd +0 -0
  186. sqlalchemy/sql/_util_cy.pxd +11 -0
  187. sqlalchemy/sql/_util_cy.py +127 -0
  188. sqlalchemy/sql/annotation.py +590 -0
  189. sqlalchemy/sql/base.py +2702 -0
  190. sqlalchemy/sql/cache_key.py +915 -0
  191. sqlalchemy/sql/coercions.py +1373 -0
  192. sqlalchemy/sql/compiler.py +8453 -0
  193. sqlalchemy/sql/crud.py +1816 -0
  194. sqlalchemy/sql/ddl.py +1962 -0
  195. sqlalchemy/sql/default_comparator.py +660 -0
  196. sqlalchemy/sql/dml.py +2018 -0
  197. sqlalchemy/sql/elements.py +6057 -0
  198. sqlalchemy/sql/events.py +458 -0
  199. sqlalchemy/sql/expression.py +171 -0
  200. sqlalchemy/sql/functions.py +2380 -0
  201. sqlalchemy/sql/lambdas.py +1442 -0
  202. sqlalchemy/sql/naming.py +204 -0
  203. sqlalchemy/sql/operators.py +2909 -0
  204. sqlalchemy/sql/roles.py +332 -0
  205. sqlalchemy/sql/schema.py +7075 -0
  206. sqlalchemy/sql/selectable.py +7634 -0
  207. sqlalchemy/sql/sqltypes.py +4130 -0
  208. sqlalchemy/sql/traversals.py +1041 -0
  209. sqlalchemy/sql/type_api.py +2450 -0
  210. sqlalchemy/sql/util.py +1496 -0
  211. sqlalchemy/sql/visitors.py +1153 -0
  212. sqlalchemy/testing/__init__.py +97 -0
  213. sqlalchemy/testing/assertions.py +1007 -0
  214. sqlalchemy/testing/assertsql.py +519 -0
  215. sqlalchemy/testing/asyncio.py +128 -0
  216. sqlalchemy/testing/cancellation.py +237 -0
  217. sqlalchemy/testing/config.py +440 -0
  218. sqlalchemy/testing/engines.py +482 -0
  219. sqlalchemy/testing/entities.py +117 -0
  220. sqlalchemy/testing/exclusions.py +501 -0
  221. sqlalchemy/testing/fixtures/__init__.py +30 -0
  222. sqlalchemy/testing/fixtures/base.py +426 -0
  223. sqlalchemy/testing/fixtures/mypy.py +247 -0
  224. sqlalchemy/testing/fixtures/orm.py +227 -0
  225. sqlalchemy/testing/fixtures/sql.py +538 -0
  226. sqlalchemy/testing/pickleable.py +155 -0
  227. sqlalchemy/testing/plugin/__init__.py +6 -0
  228. sqlalchemy/testing/plugin/bootstrap.py +50 -0
  229. sqlalchemy/testing/plugin/plugin_base.py +828 -0
  230. sqlalchemy/testing/plugin/pytestplugin.py +896 -0
  231. sqlalchemy/testing/profiles_file.py +350 -0
  232. sqlalchemy/testing/profiling.py +294 -0
  233. sqlalchemy/testing/provision.py +633 -0
  234. sqlalchemy/testing/requirements.py +1971 -0
  235. sqlalchemy/testing/schema.py +198 -0
  236. sqlalchemy/testing/suite/__init__.py +19 -0
  237. sqlalchemy/testing/suite/test_cte.py +237 -0
  238. sqlalchemy/testing/suite/test_ddl.py +420 -0
  239. sqlalchemy/testing/suite/test_dialect.py +776 -0
  240. sqlalchemy/testing/suite/test_insert.py +630 -0
  241. sqlalchemy/testing/suite/test_reflection.py +3815 -0
  242. sqlalchemy/testing/suite/test_results.py +660 -0
  243. sqlalchemy/testing/suite/test_rowcount.py +258 -0
  244. sqlalchemy/testing/suite/test_select.py +2112 -0
  245. sqlalchemy/testing/suite/test_sequence.py +317 -0
  246. sqlalchemy/testing/suite/test_table_via_select.py +686 -0
  247. sqlalchemy/testing/suite/test_types.py +2271 -0
  248. sqlalchemy/testing/suite/test_unicode_ddl.py +189 -0
  249. sqlalchemy/testing/suite/test_update_delete.py +139 -0
  250. sqlalchemy/testing/util.py +575 -0
  251. sqlalchemy/testing/warnings.py +52 -0
  252. sqlalchemy/types.py +75 -0
  253. sqlalchemy/util/__init__.py +165 -0
  254. sqlalchemy/util/_collections.py +688 -0
  255. sqlalchemy/util/_collections_cy.cp315-win32.pyd +0 -0
  256. sqlalchemy/util/_collections_cy.pxd +8 -0
  257. sqlalchemy/util/_collections_cy.py +516 -0
  258. sqlalchemy/util/_has_cython.py +48 -0
  259. sqlalchemy/util/_immutabledict_cy.cp315-win32.pyd +0 -0
  260. sqlalchemy/util/_immutabledict_cy.py +240 -0
  261. sqlalchemy/util/compat.py +298 -0
  262. sqlalchemy/util/concurrency.py +272 -0
  263. sqlalchemy/util/cython.py +95 -0
  264. sqlalchemy/util/deprecations.py +401 -0
  265. sqlalchemy/util/langhelpers.py +2797 -0
  266. sqlalchemy/util/preloaded.py +153 -0
  267. sqlalchemy/util/queue.py +304 -0
  268. sqlalchemy/util/tool_support.py +202 -0
  269. sqlalchemy/util/topological.py +120 -0
  270. sqlalchemy/util/typing.py +709 -0
  271. sqlalchemy-2.1.0rc1.dist-info/METADATA +270 -0
  272. sqlalchemy-2.1.0rc1.dist-info/RECORD +276 -0
  273. sqlalchemy-2.1.0rc1.dist-info/WHEEL +5 -0
  274. sqlalchemy-2.1.0rc1.dist-info/licenses/AUTHORS +30 -0
  275. sqlalchemy-2.1.0rc1.dist-info/licenses/LICENSE +19 -0
  276. sqlalchemy-2.1.0rc1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,907 @@
1
+ # orm/path_registry.py
2
+ # Copyright (C) 2005-2026 the SQLAlchemy authors and contributors
3
+ # <see AUTHORS file>
4
+ #
5
+ # This module is part of SQLAlchemy and is released under
6
+ # the MIT License: https://www.opensource.org/licenses/mit-license.php
7
+ """Path tracking utilities, representing mapper graph traversals."""
8
+
9
+ from __future__ import annotations
10
+
11
+ from functools import reduce
12
+ from itertools import chain
13
+ import logging
14
+ import operator
15
+ from typing import Any
16
+ from typing import cast
17
+ from typing import Dict
18
+ from typing import Final
19
+ from typing import Iterator
20
+ from typing import List
21
+ from typing import Literal
22
+ from typing import Mapping
23
+ from typing import Optional
24
+ from typing import overload
25
+ from typing import Sequence
26
+ from typing import Tuple
27
+ from typing import TYPE_CHECKING
28
+ from typing import Union
29
+
30
+ from . import base as orm_base
31
+ from ._typing import insp_is_mapper_property
32
+ from .. import exc
33
+ from .. import inspection
34
+ from .. import util
35
+ from ..sql import visitors
36
+ from ..sql.cache_key import HasCacheKey
37
+
38
+ if TYPE_CHECKING:
39
+ from typing import TypeGuard
40
+
41
+ from ._typing import _InternalEntityType
42
+ from .interfaces import StrategizedProperty
43
+ from .mapper import Mapper
44
+ from .relationships import RelationshipProperty
45
+ from .util import AliasedInsp
46
+ from ..sql.cache_key import _CacheKeyTraversalType
47
+ from ..sql.elements import BindParameter
48
+ from ..sql.visitors import anon_map
49
+ from ..util.typing import _LiteralStar
50
+
51
+ def is_root(path: PathRegistry) -> TypeGuard[RootRegistry]: ...
52
+
53
+ def is_entity(
54
+ path: PathRegistry,
55
+ ) -> TypeGuard[_AbstractEntityRegistry]: ...
56
+
57
+ else:
58
+ is_root = operator.attrgetter("is_root")
59
+ is_entity = operator.attrgetter("is_entity")
60
+
61
+
62
+ _SerializedPath = List[Any]
63
+ _StrPathToken = str
64
+ _PathElementType = Union[
65
+ _StrPathToken, "_InternalEntityType[Any]", "StrategizedProperty[Any]"
66
+ ]
67
+
68
+ # the representation is in fact
69
+ # a tuple with alternating:
70
+ # [_InternalEntityType[Any], Union[str, StrategizedProperty[Any]],
71
+ # _InternalEntityType[Any], Union[str, StrategizedProperty[Any]], ...]
72
+ # this might someday be a tuple of 2-tuples instead, but paths can be
73
+ # chopped at odd intervals as well so this is less flexible
74
+ _PathRepresentation = Tuple[_PathElementType, ...]
75
+
76
+ # NOTE: these names are weird since the array is 0-indexed,
77
+ # the "_Odd" entries are at 0, 2, 4, etc
78
+ _OddPathRepresentation = Sequence["_InternalEntityType[Any]"]
79
+ _EvenPathRepresentation = Sequence[Union["StrategizedProperty[Any]", str]]
80
+
81
+
82
+ log = logging.getLogger(__name__)
83
+
84
+
85
+ def _unreduce_path(path: _SerializedPath) -> PathRegistry:
86
+ return PathRegistry.deserialize(path)
87
+
88
+
89
+ _WILDCARD_TOKEN: _LiteralStar = "*"
90
+ _DEFAULT_TOKEN = "_sa_default"
91
+
92
+ _RELATIONSHIP_TOKEN: Final[Literal["relationship"]] = "relationship"
93
+ _COLUMN_TOKEN: Final[Literal["column"]] = "column"
94
+
95
+ _UNPREFIXED_TOKENS = frozenset([_WILDCARD_TOKEN, _DEFAULT_TOKEN])
96
+ """the wildcard strings accepted from the user in a loader option.
97
+
98
+ these are prefixed with the target property's ``strategy_wildcard_key`` to
99
+ form the tokens in :data:`._PATH_TOKENS`, and are not themselves valid as an
100
+ element of a path.
101
+
102
+ """
103
+
104
+ _PATH_TOKENS = frozenset(
105
+ f"{wildcard_key}:{suffix}"
106
+ for wildcard_key in (_RELATIONSHIP_TOKEN, _COLUMN_TOKEN)
107
+ for suffix in (_WILDCARD_TOKEN, _DEFAULT_TOKEN)
108
+ )
109
+ """the complete set of tokens which may appear within a path.
110
+
111
+ :attr:`.PathToken._intern` is populated from this collection at module import
112
+ time, so that a token is present in every process, including one which has
113
+ not yet run any query.
114
+
115
+ """
116
+
117
+ _ACCEPTED_TOKENS = _UNPREFIXED_TOKENS | _PATH_TOKENS
118
+ """every string a loader option may accept in place of an attribute name.
119
+
120
+ this is the union of the bare wildcards the user writes and the prefixed
121
+ tokens which the loader option internals hand back to themselves; only the
122
+ latter may appear in a path.
123
+
124
+ """
125
+
126
+
127
+ @inspection._self_inspects
128
+ class PathRegistry(HasCacheKey):
129
+ """Represent query load paths and registry functions.
130
+
131
+ Basically represents structures like:
132
+
133
+ (<User mapper>, "orders", <Order mapper>, "items", <Item mapper>)
134
+
135
+ These structures are generated by things like
136
+ query options (joinedload(), subqueryload(), etc.) and are
137
+ used to compose keys stored in the query._attributes dictionary
138
+ for various options.
139
+
140
+ They are then re-composed at query compile/result row time as
141
+ the query is formed and as rows are fetched, where they again
142
+ serve to compose keys to look up options in the context.attributes
143
+ dictionary, which is copied from query._attributes.
144
+
145
+ The path structure has a limited amount of caching, where each
146
+ "root" ultimately pulls from a fixed registry associated with
147
+ the first mapper, that also contains elements for each of its
148
+ property keys. However paths longer than two elements, which
149
+ are the exception rather than the rule, are generated on an
150
+ as-needed basis.
151
+
152
+ """
153
+
154
+ __slots__ = ()
155
+
156
+ is_token = False
157
+ is_root = False
158
+ has_entity = False
159
+ is_property = False
160
+ is_entity = False
161
+
162
+ is_unnatural: bool
163
+
164
+ path: _PathRepresentation
165
+ natural_path: _PathRepresentation
166
+ parent: Optional[PathRegistry]
167
+ root: RootRegistry
168
+
169
+ _cache_key_traversal: _CacheKeyTraversalType = [
170
+ ("path", visitors.ExtendedInternalTraversal.dp_has_cache_key_list)
171
+ ]
172
+
173
+ def __eq__(self, other: Any) -> bool:
174
+ try:
175
+ return other is not None and self.path == other._path_for_compare
176
+ except AttributeError:
177
+ util.warn(
178
+ "Comparison of PathRegistry to %r is not supported"
179
+ % (type(other))
180
+ )
181
+ return False
182
+
183
+ def __ne__(self, other: Any) -> bool:
184
+ try:
185
+ return other is None or self.path != other._path_for_compare
186
+ except AttributeError:
187
+ util.warn(
188
+ "Comparison of PathRegistry to %r is not supported"
189
+ % (type(other))
190
+ )
191
+ return True
192
+
193
+ @property
194
+ def _path_for_compare(self) -> Optional[_PathRepresentation]:
195
+ return self.path
196
+
197
+ def odd_element(self, index: int) -> _InternalEntityType[Any]:
198
+ return self.path[index] # type: ignore[return-value]
199
+
200
+ def set(self, attributes: Dict[Any, Any], key: Any, value: Any) -> None:
201
+ log.debug("set '%s' on path '%s' to '%s'", key, self, value)
202
+ attributes[(key, self.natural_path)] = value
203
+
204
+ def setdefault(
205
+ self, attributes: Dict[Any, Any], key: Any, value: Any
206
+ ) -> None:
207
+ log.debug("setdefault '%s' on path '%s' to '%s'", key, self, value)
208
+ attributes.setdefault((key, self.natural_path), value)
209
+
210
+ def get(
211
+ self, attributes: Dict[Any, Any], key: Any, value: Optional[Any] = None
212
+ ) -> Any:
213
+ key = (key, self.natural_path)
214
+ if key in attributes:
215
+ return attributes[key]
216
+ else:
217
+ return value
218
+
219
+ def __len__(self) -> int:
220
+ return len(self.path)
221
+
222
+ def __hash__(self) -> int:
223
+ return id(self)
224
+
225
+ @overload
226
+ def __getitem__(self, entity: _StrPathToken) -> _TokenRegistry: ...
227
+
228
+ @overload
229
+ def __getitem__(self, entity: int) -> _PathElementType: ...
230
+
231
+ @overload
232
+ def __getitem__(self, entity: slice) -> _PathRepresentation: ...
233
+
234
+ @overload
235
+ def __getitem__(
236
+ self, entity: _InternalEntityType[Any]
237
+ ) -> _AbstractEntityRegistry: ...
238
+
239
+ @overload
240
+ def __getitem__(
241
+ self, entity: StrategizedProperty[Any]
242
+ ) -> _PropRegistry: ...
243
+
244
+ def __getitem__(
245
+ self,
246
+ entity: Union[
247
+ _StrPathToken,
248
+ int,
249
+ slice,
250
+ _InternalEntityType[Any],
251
+ StrategizedProperty[Any],
252
+ ],
253
+ ) -> Union[
254
+ _TokenRegistry,
255
+ _PathElementType,
256
+ _PathRepresentation,
257
+ _PropRegistry,
258
+ _AbstractEntityRegistry,
259
+ ]:
260
+ raise NotImplementedError()
261
+
262
+ # TODO: what are we using this for?
263
+ @property
264
+ def length(self) -> int:
265
+ return len(self.path)
266
+
267
+ def pairs(
268
+ self,
269
+ ) -> Iterator[
270
+ Tuple[_InternalEntityType[Any], Union[str, StrategizedProperty[Any]]]
271
+ ]:
272
+ odd_path = cast(_OddPathRepresentation, self.path)
273
+ even_path = cast(_EvenPathRepresentation, odd_path)
274
+ for i in range(0, len(odd_path), 2):
275
+ yield odd_path[i], even_path[i + 1]
276
+
277
+ def contains_mapper(self, mapper: Mapper[Any]) -> bool:
278
+ _m_path = cast(_OddPathRepresentation, self.path)
279
+ for path_mapper in [_m_path[i] for i in range(0, len(_m_path), 2)]:
280
+ if path_mapper.mapper.isa(mapper):
281
+ return True
282
+ else:
283
+ return False
284
+
285
+ def contains(self, attributes: Dict[Any, Any], key: Any) -> bool:
286
+ return (key, self.path) in attributes
287
+
288
+ def __reduce__(self) -> Any:
289
+ return _unreduce_path, (self.serialize(),)
290
+
291
+ @classmethod
292
+ def _serialize_path(cls, path: _PathRepresentation) -> _SerializedPath:
293
+ _m_path = cast(_OddPathRepresentation, path)
294
+ _p_path = cast(_EvenPathRepresentation, path)
295
+
296
+ return list(
297
+ zip(
298
+ tuple(
299
+ m.class_ if (m.is_mapper or m.is_aliased_class) else str(m)
300
+ for m in [_m_path[i] for i in range(0, len(_m_path), 2)]
301
+ ),
302
+ tuple(
303
+ p.key if insp_is_mapper_property(p) else str(p)
304
+ for p in [_p_path[i] for i in range(1, len(_p_path), 2)]
305
+ )
306
+ + (None,),
307
+ )
308
+ )
309
+
310
+ @classmethod
311
+ def _deserialize_path(cls, path: _SerializedPath) -> _PathRepresentation:
312
+ def _deserialize_mapper_token(mcls: Any) -> Any:
313
+ return (
314
+ # note: we likely dont want configure=True here however
315
+ # this is maintained at the moment for backwards compatibility
316
+ orm_base._inspect_mapped_class(mcls, configure=True)
317
+ if mcls not in PathToken._intern
318
+ else PathToken._intern[mcls]
319
+ )
320
+
321
+ def _deserialize_key_token(mcls: Any, key: Any) -> Any:
322
+ if key is None:
323
+ return None
324
+ elif key in PathToken._intern:
325
+ return PathToken._intern[key]
326
+ else:
327
+ mp = orm_base._inspect_mapped_class(mcls, configure=True)
328
+ assert mp is not None
329
+ return mp.attrs[key]
330
+
331
+ p = tuple(
332
+ chain(
333
+ *[
334
+ (
335
+ _deserialize_mapper_token(mcls),
336
+ _deserialize_key_token(mcls, key),
337
+ )
338
+ for mcls, key in path
339
+ ]
340
+ )
341
+ )
342
+ if p and p[-1] is None:
343
+ p = p[0:-1]
344
+ return p
345
+
346
+ def serialize(self) -> _SerializedPath:
347
+ path = self.path
348
+ return self._serialize_path(path)
349
+
350
+ @classmethod
351
+ def deserialize(cls, path: _SerializedPath) -> PathRegistry:
352
+ assert path is not None
353
+ p = cls._deserialize_path(path)
354
+ return cls.coerce(p)
355
+
356
+ @overload
357
+ @classmethod
358
+ def per_mapper(cls, mapper: Mapper[Any]) -> _CachingEntityRegistry: ...
359
+
360
+ @overload
361
+ @classmethod
362
+ def per_mapper(cls, mapper: AliasedInsp[Any]) -> _SlotsEntityRegistry: ...
363
+
364
+ @classmethod
365
+ def per_mapper(
366
+ cls, mapper: _InternalEntityType[Any]
367
+ ) -> _AbstractEntityRegistry:
368
+ if mapper.is_mapper:
369
+ return _CachingEntityRegistry(cls.root, mapper)
370
+ else:
371
+ return _SlotsEntityRegistry(cls.root, mapper)
372
+
373
+ @classmethod
374
+ def coerce(cls, raw: _PathRepresentation) -> PathRegistry:
375
+ def _red(prev: PathRegistry, next_: _PathElementType) -> PathRegistry:
376
+ return prev[next_]
377
+
378
+ # can't quite get mypy to appreciate this one :)
379
+ return reduce(_red, raw, cls.root) # type: ignore[arg-type]
380
+
381
+ def __add__(self, other: PathRegistry) -> PathRegistry:
382
+ def _red(prev: PathRegistry, next_: _PathElementType) -> PathRegistry:
383
+ return prev[next_]
384
+
385
+ return reduce(_red, other.path, self)
386
+
387
+ def __str__(self) -> str:
388
+ return f"ORM Path[{' -> '.join(str(elem) for elem in self.path)}]"
389
+
390
+ def __repr__(self) -> str:
391
+ return f"{self.__class__.__name__}({self.path!r})"
392
+
393
+ def path_string(self) -> str:
394
+ """Return a user-facing string representation of this path,
395
+ e.g. ``"User.orders -> Order.items"``.
396
+
397
+ """
398
+
399
+ raw = self.path
400
+ parts = []
401
+ lraw = len(raw)
402
+ for i in range(0, lraw - 1, 2):
403
+ entity = raw[i]
404
+ prop = raw[i + 1]
405
+ prop_key = getattr(prop, "key", str(prop))
406
+
407
+ if (
408
+ i < lraw - 2
409
+ and cast(
410
+ "_InternalEntityType[Any]", raw[i + 2]
411
+ ).is_aliased_class
412
+ ):
413
+ parts.append(
414
+ f"{orm_base.entity_str(entity)}.{prop_key}."
415
+ f"of_type({orm_base.entity_str(raw[i + 2])})"
416
+ )
417
+ else:
418
+ parts.append(f"{orm_base.entity_str(entity)}.{prop_key}")
419
+
420
+ return (
421
+ " -> ".join(parts)
422
+ if parts
423
+ else orm_base.entity_str(self.path[0]) if self.path else ""
424
+ )
425
+
426
+
427
+ class _CreatesToken(PathRegistry):
428
+ __slots__ = ()
429
+
430
+ is_aliased_class: bool
431
+ is_root: bool
432
+
433
+ def token(self, token: _StrPathToken) -> _TokenRegistry:
434
+ if token not in PathToken._intern:
435
+ raise exc.ArgumentError(f"invalid token: {token}")
436
+ elif token.endswith(f":{_WILDCARD_TOKEN}"):
437
+ return _TokenRegistry(self, token)
438
+ else:
439
+ return _TokenRegistry(self.root, token)
440
+
441
+
442
+ class RootRegistry(_CreatesToken):
443
+ """Root registry, defers to mappers so that
444
+ paths are maintained per-root-mapper.
445
+
446
+ """
447
+
448
+ __slots__ = ()
449
+
450
+ inherit_cache = True
451
+
452
+ path = natural_path = ()
453
+ has_entity = False
454
+ is_aliased_class = False
455
+ is_root = True
456
+ is_unnatural = False
457
+
458
+ def _getitem(
459
+ self, entity: Any
460
+ ) -> Union[_TokenRegistry, _AbstractEntityRegistry]:
461
+ if entity in PathToken._intern:
462
+ if TYPE_CHECKING:
463
+ assert isinstance(entity, _StrPathToken)
464
+ return _TokenRegistry(self, PathToken._intern[entity])
465
+ else:
466
+ try:
467
+ return entity._path_registry # type: ignore[no-any-return]
468
+ except AttributeError:
469
+ raise IndexError(
470
+ f"invalid argument for RootRegistry.__getitem__: {entity}"
471
+ )
472
+
473
+ def _truncate_recursive(self) -> RootRegistry:
474
+ return self
475
+
476
+ if not TYPE_CHECKING:
477
+ __getitem__ = _getitem
478
+
479
+
480
+ PathRegistry.root = RootRegistry()
481
+
482
+
483
+ class PathToken(orm_base.InspectionAttr, HasCacheKey, str):
484
+ """cacheable string token"""
485
+
486
+ _intern: Mapping[str, PathToken]
487
+ """the :class:`.PathToken` for each of :data:`._PATH_TOKENS`.
488
+
489
+ this collection is fully populated below at module import time and is
490
+ never added to afterwards; it's typed as :class:`.Mapping` so that a
491
+ mutation is flagged by type checkers.
492
+ :meth:`.PathRegistry._deserialize_path` relies on it being complete,
493
+ distinguishing a token from a mapped attribute key by testing
494
+ membership here.
495
+
496
+ """
497
+
498
+ def _gen_cache_key(
499
+ self, anon_map: anon_map, bindparams: List[BindParameter[Any]]
500
+ ) -> Tuple[Any, ...]:
501
+ return (str(self),)
502
+
503
+ @property
504
+ def _path_for_compare(self) -> Optional[_PathRepresentation]:
505
+ return None
506
+
507
+
508
+ PathToken._intern = {token: PathToken(token) for token in _PATH_TOKENS}
509
+
510
+
511
+ class _TokenRegistry(PathRegistry):
512
+ __slots__ = ("token", "parent", "path", "natural_path")
513
+
514
+ inherit_cache = True
515
+
516
+ token: _StrPathToken
517
+ parent: _CreatesToken
518
+
519
+ def __init__(self, parent: _CreatesToken, token: _StrPathToken):
520
+ token = PathToken._intern[token]
521
+
522
+ self.token = token
523
+ self.parent = parent
524
+ self.path = parent.path + (token,)
525
+ self.natural_path = parent.natural_path + (token,)
526
+
527
+ has_entity = False
528
+
529
+ is_token = True
530
+
531
+ def generate_for_superclasses(self) -> Iterator[PathRegistry]:
532
+ # NOTE: this method is no longer used. consider removal
533
+ parent = self.parent
534
+ if is_root(parent):
535
+ yield self
536
+ return
537
+
538
+ if TYPE_CHECKING:
539
+ assert isinstance(parent, _AbstractEntityRegistry)
540
+ if not parent.is_aliased_class:
541
+ for mp_ent in parent.mapper.iterate_to_root():
542
+ yield _TokenRegistry(parent.parent[mp_ent], self.token)
543
+ elif (
544
+ parent.is_aliased_class
545
+ and cast(
546
+ "AliasedInsp[Any]",
547
+ parent.entity,
548
+ )._is_with_polymorphic
549
+ ):
550
+ yield self
551
+ for ent in cast(
552
+ "AliasedInsp[Any]", parent.entity
553
+ )._with_polymorphic_entities:
554
+ yield _TokenRegistry(parent.parent[ent], self.token)
555
+ else:
556
+ yield self
557
+
558
+ def _generate_natural_for_superclasses(
559
+ self,
560
+ ) -> Iterator[_PathRepresentation]:
561
+ parent = self.parent
562
+ if is_root(parent):
563
+ yield self.natural_path
564
+ return
565
+
566
+ if TYPE_CHECKING:
567
+ assert isinstance(parent, _AbstractEntityRegistry)
568
+ for mp_ent in parent.mapper.iterate_to_root():
569
+ yield _TokenRegistry(
570
+ parent.parent[mp_ent], self.token
571
+ ).natural_path
572
+ if (
573
+ parent.is_aliased_class
574
+ and cast(
575
+ "AliasedInsp[Any]",
576
+ parent.entity,
577
+ )._is_with_polymorphic
578
+ ):
579
+ yield self.natural_path
580
+ for ent in cast(
581
+ "AliasedInsp[Any]", parent.entity
582
+ )._with_polymorphic_entities:
583
+ yield (
584
+ _TokenRegistry(parent.parent[ent], self.token).natural_path
585
+ )
586
+ else:
587
+ yield self.natural_path
588
+
589
+ def _getitem(self, entity: Any) -> Any:
590
+ try:
591
+ return self.path[entity]
592
+ except TypeError as err:
593
+ raise IndexError(f"{entity}") from err
594
+
595
+ if not TYPE_CHECKING:
596
+ __getitem__ = _getitem
597
+
598
+
599
+ class _PropRegistry(PathRegistry):
600
+ __slots__ = (
601
+ "prop",
602
+ "parent",
603
+ "path",
604
+ "natural_path",
605
+ "has_entity",
606
+ "entity",
607
+ "mapper",
608
+ "_wildcard_path_loader_key",
609
+ "_default_path_loader_key",
610
+ "_loader_key",
611
+ "is_unnatural",
612
+ )
613
+ inherit_cache = True
614
+ is_property = True
615
+
616
+ prop: StrategizedProperty[Any]
617
+ mapper: Optional[Mapper[Any]]
618
+ entity: Optional[_InternalEntityType[Any]]
619
+ parent: _AbstractEntityRegistry
620
+
621
+ def __init__(
622
+ self, parent: _AbstractEntityRegistry, prop: StrategizedProperty[Any]
623
+ ):
624
+
625
+ # restate this path in terms of the
626
+ # given StrategizedProperty's parent.
627
+ insp = cast("_InternalEntityType[Any]", parent[-1])
628
+ natural_parent: _AbstractEntityRegistry = parent
629
+
630
+ # inherit "is_unnatural" from the parent
631
+ self.is_unnatural = parent.parent.is_unnatural or bool(
632
+ parent.mapper.inherits
633
+ )
634
+
635
+ if not insp.is_aliased_class or insp._use_mapper_path: # type: ignore[union-attr] # noqa: E501
636
+ parent = natural_parent = parent.parent[prop.parent]
637
+ elif (
638
+ insp.is_aliased_class
639
+ and insp.with_polymorphic_mappers
640
+ and prop.parent in insp.with_polymorphic_mappers
641
+ ):
642
+ subclass_entity: _InternalEntityType[Any] = parent[-1]._entity_for_mapper(prop.parent) # type: ignore[union-attr] # noqa: E501
643
+ parent = parent.parent[subclass_entity]
644
+
645
+ # when building a path where with_polymorphic() is in use,
646
+ # special logic to determine the "natural path" when subclass
647
+ # entities are used.
648
+ #
649
+ # here we are trying to distinguish between a path that starts
650
+ # on a with_polymorphic entity vs. one that starts on a
651
+ # normal entity that introduces a with_polymorphic() in the
652
+ # middle using of_type():
653
+ #
654
+ # # as in test_polymorphic_rel->
655
+ # # test_subqueryload_on_subclass_uses_path_correctly
656
+ # wp = with_polymorphic(RegularEntity, "*")
657
+ # sess.query(wp).options(someload(wp.SomeSubEntity.foos))
658
+ #
659
+ # vs
660
+ #
661
+ # # as in test_relationship->JoinedloadWPolyOfTypeContinued
662
+ # wp = with_polymorphic(SomeFoo, "*")
663
+ # sess.query(RegularEntity).options(
664
+ # someload(RegularEntity.foos.of_type(wp))
665
+ # .someload(wp.SubFoo.bar)
666
+ # )
667
+ #
668
+ # in the former case, the Query as it generates a path that we
669
+ # want to match will be in terms of the with_polymorphic at the
670
+ # beginning. in the latter case, Query will generate simple
671
+ # paths that don't know about this with_polymorphic, so we must
672
+ # use a separate natural path.
673
+ #
674
+ #
675
+ if parent.parent:
676
+ natural_parent = parent.parent[subclass_entity.mapper]
677
+ self.is_unnatural = True
678
+ else:
679
+ natural_parent = parent
680
+ elif (
681
+ natural_parent.parent
682
+ and insp.is_aliased_class
683
+ and prop.parent # this should always be the case here
684
+ is not insp.mapper
685
+ and insp.mapper.isa(prop.parent)
686
+ ):
687
+ natural_parent = parent.parent[prop.parent]
688
+
689
+ self.prop = prop
690
+ self.parent = parent
691
+ self.path = parent.path + (prop,)
692
+ self.natural_path = natural_parent.natural_path + (prop,)
693
+
694
+ self.has_entity = prop._links_to_entity
695
+ if prop._is_relationship:
696
+ if TYPE_CHECKING:
697
+ assert isinstance(prop, RelationshipProperty)
698
+ self.entity = prop.entity
699
+ self.mapper = prop.mapper
700
+ else:
701
+ self.entity = None
702
+ self.mapper = None
703
+
704
+ self._wildcard_path_loader_key = (
705
+ "loader",
706
+ parent.natural_path + self.prop._wildcard_token,
707
+ )
708
+ self._default_path_loader_key = self.prop._default_path_loader_key
709
+ self._loader_key = ("loader", self.natural_path)
710
+
711
+ def _truncate_recursive(self) -> _PropRegistry:
712
+ earliest = None
713
+ for i, token in enumerate(reversed(self.path[:-1])):
714
+ if token is self.prop:
715
+ earliest = i
716
+
717
+ if earliest is None:
718
+ return self
719
+ else:
720
+ return self.coerce(self.path[0 : -(earliest + 1)]) # type: ignore[return-value] # noqa: E501
721
+
722
+ @property
723
+ def entity_path(self) -> _AbstractEntityRegistry:
724
+ assert self.entity is not None
725
+ return self[self.entity]
726
+
727
+ def _getitem(
728
+ self, entity: Union[int, slice, _InternalEntityType[Any]]
729
+ ) -> Union[_AbstractEntityRegistry, _PathElementType, _PathRepresentation]:
730
+ if isinstance(entity, (int, slice)):
731
+ return self.path[entity]
732
+ else:
733
+ return _SlotsEntityRegistry(self, entity)
734
+
735
+ if not TYPE_CHECKING:
736
+ __getitem__ = _getitem
737
+
738
+
739
+ class _AbstractEntityRegistry(_CreatesToken):
740
+ __slots__ = (
741
+ "key",
742
+ "parent",
743
+ "is_aliased_class",
744
+ "path",
745
+ "entity",
746
+ "natural_path",
747
+ )
748
+
749
+ has_entity = True
750
+ is_entity = True
751
+
752
+ parent: Union[RootRegistry, _PropRegistry]
753
+ key: _InternalEntityType[Any]
754
+ entity: _InternalEntityType[Any]
755
+ is_aliased_class: bool
756
+
757
+ def __init__(
758
+ self,
759
+ parent: Union[RootRegistry, _PropRegistry],
760
+ entity: _InternalEntityType[Any],
761
+ ):
762
+ self.key = entity
763
+ self.parent = parent
764
+ self.is_aliased_class = entity.is_aliased_class
765
+ self.entity = entity
766
+ self.path = parent.path + (entity,)
767
+
768
+ # the "natural path" is the path that we get when Query is traversing
769
+ # from the lead entities into the various relationships; it corresponds
770
+ # to the structure of mappers and relationships. when we are given a
771
+ # path that comes from loader options, as of 1.3 it can have ac-hoc
772
+ # with_polymorphic() and other AliasedInsp objects inside of it, which
773
+ # are usually not present in mappings. So here we track both the
774
+ # "enhanced" path in self.path and the "natural" path that doesn't
775
+ # include those objects so these two traversals can be matched up.
776
+
777
+ # the test here for "(self.is_aliased_class or parent.is_unnatural)"
778
+ # are to avoid the more expensive conditional logic that follows if we
779
+ # know we don't have to do it. This conditional can just as well be
780
+ # "if parent.path:", it just is more function calls.
781
+ #
782
+ # This is basically the only place that the "is_unnatural" flag
783
+ # actually changes behavior.
784
+ if parent.path and (self.is_aliased_class or parent.is_unnatural):
785
+ # this is an infrequent code path used for loader strategies that
786
+ # also make use of of_type() or other intricate polymorphic
787
+ # base/subclass combinations
788
+ parent_natural_entity = parent.natural_path[-1]
789
+
790
+ if entity.mapper.isa(
791
+ parent_natural_entity.mapper # type: ignore[union-attr]
792
+ ) or parent_natural_entity.mapper.isa( # type: ignore[union-attr]
793
+ entity.mapper
794
+ ):
795
+ # when the entity mapper and parent mapper are in an
796
+ # inheritance relationship, use entity.mapper in natural_path.
797
+ # First case: entity.mapper inherits from parent mapper (e.g.,
798
+ # accessing a subclass mapper through parent path). Second case
799
+ # (issue #13193): parent mapper inherits from entity.mapper
800
+ # (e.g., parent path has Sub(Base) but we're accessing with
801
+ # Base where Base.related is declared, so use Base in
802
+ # natural_path).
803
+ self.natural_path = parent.natural_path + (entity.mapper,)
804
+ else:
805
+ self.natural_path = parent.natural_path + (
806
+ parent_natural_entity.entity, # type: ignore[operator, union-attr] # noqa: E501
807
+ )
808
+ # it seems to make sense that since these paths get mixed up
809
+ # with statements that are cached or not, we should make
810
+ # sure the natural path is cacheable across different occurrences
811
+ # of equivalent AliasedClass objects. however, so far this
812
+ # does not seem to be needed for whatever reason.
813
+ # elif not parent.path and self.is_aliased_class:
814
+ # self.natural_path = (self.entity._generate_cache_key()[0], )
815
+ else:
816
+ self.natural_path = self.path
817
+
818
+ def _truncate_recursive(self) -> _AbstractEntityRegistry:
819
+ return self.parent._truncate_recursive()[self.entity]
820
+
821
+ @property
822
+ def root_entity(self) -> _InternalEntityType[Any]:
823
+ return self.odd_element(0)
824
+
825
+ @property
826
+ def entity_path(self) -> PathRegistry:
827
+ return self
828
+
829
+ @property
830
+ def mapper(self) -> Mapper[Any]:
831
+ return self.entity.mapper
832
+
833
+ def __bool__(self) -> bool:
834
+ return True
835
+
836
+ def _getitem(
837
+ self, entity: Any
838
+ ) -> Union[_PathElementType, _PathRepresentation, PathRegistry]:
839
+ if isinstance(entity, (int, slice)):
840
+ return self.path[entity]
841
+ elif entity in PathToken._intern:
842
+ return _TokenRegistry(self, PathToken._intern[entity])
843
+ else:
844
+ return _PropRegistry(self, entity)
845
+
846
+ if not TYPE_CHECKING:
847
+ __getitem__ = _getitem
848
+
849
+
850
+ class _SlotsEntityRegistry(_AbstractEntityRegistry):
851
+ # for aliased class, return lightweight, no-cycles created
852
+ # version
853
+ inherit_cache = True
854
+
855
+
856
+ class _ERDict(Dict[Any, Any]):
857
+ def __init__(self, registry: _CachingEntityRegistry):
858
+ self.registry = registry
859
+
860
+ def __missing__(self, key: Any) -> _PropRegistry:
861
+ self[key] = item = _PropRegistry(self.registry, key)
862
+
863
+ return item
864
+
865
+
866
+ class _CachingEntityRegistry(_AbstractEntityRegistry):
867
+ # for long lived mapper, return dict based caching
868
+ # version that creates reference cycles
869
+
870
+ __slots__ = ("_cache",)
871
+
872
+ inherit_cache = True
873
+
874
+ def __init__(
875
+ self,
876
+ parent: Union[RootRegistry, _PropRegistry],
877
+ entity: _InternalEntityType[Any],
878
+ ):
879
+ super().__init__(parent, entity)
880
+ self._cache = _ERDict(self)
881
+
882
+ def pop(self, key: Any, default: Any) -> Any:
883
+ return self._cache.pop(key, default)
884
+
885
+ def _getitem(self, entity: Any) -> Any:
886
+ if isinstance(entity, (int, slice)):
887
+ return self.path[entity]
888
+ elif isinstance(entity, PathToken):
889
+ return _TokenRegistry(self, entity)
890
+ else:
891
+ return self._cache[entity]
892
+
893
+ if not TYPE_CHECKING:
894
+ __getitem__ = _getitem
895
+
896
+
897
+ if TYPE_CHECKING:
898
+
899
+ def path_is_entity(
900
+ path: PathRegistry,
901
+ ) -> TypeGuard[_AbstractEntityRegistry]: ...
902
+
903
+ def path_is_property(path: PathRegistry) -> TypeGuard[_PropRegistry]: ...
904
+
905
+ else:
906
+ path_is_entity = operator.attrgetter("is_entity")
907
+ path_is_property = operator.attrgetter("is_property")