Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/sqlalchemy/orm/path_registry.py: 47%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

395 statements  

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 

11from __future__ import annotations 

12 

13from functools import reduce 

14from itertools import chain 

15import logging 

16import operator 

17from typing import Any 

18from typing import cast 

19from typing import Dict 

20from typing import Iterator 

21from typing import List 

22from typing import Optional 

23from typing import overload 

24from typing import Sequence 

25from typing import Tuple 

26from typing import TYPE_CHECKING 

27from typing import Union 

28 

29from . import base as orm_base 

30from ._typing import insp_is_mapper_property 

31from .. import exc 

32from .. import util 

33from ..sql import visitors 

34from ..sql.cache_key import HasCacheKey 

35 

36if 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 

52else: 

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 

77log = logging.getLogger(__name__) 

78 

79 

80def _unreduce_path(path: _SerializedPath) -> PathRegistry: 

81 return PathRegistry.deserialize(path) 

82 

83 

84_WILDCARD_TOKEN: _LiteralStar = "*" 

85_DEFAULT_TOKEN = "_sa_default" 

86 

87 

88class 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 

354class 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 

369class 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 

407PathRegistry.root = RootRegistry() 

408 

409 

410class 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 

433class 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 

519class 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 

658class 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 

754class SlotsEntityRegistry(AbstractEntityRegistry): 

755 # for aliased class, return lightweight, no-cycles created 

756 # version 

757 inherit_cache = True 

758 

759 

760class _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 

770class 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 

801if 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 

809else: 

810 path_is_entity = operator.attrgetter("is_entity") 

811 path_is_property = operator.attrgetter("is_property")