path_registry.py 25 KB

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