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

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

396 statements  

1# orm/path_registry.py 

2# Copyright (C) 2005-2025 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 

9from __future__ import annotations 

10 

11from functools import reduce 

12from itertools import chain 

13import logging 

14import operator 

15from typing import Any 

16from typing import cast 

17from typing import Dict 

18from typing import Iterator 

19from typing import List 

20from typing import Optional 

21from typing import overload 

22from typing import Sequence 

23from typing import Tuple 

24from typing import TYPE_CHECKING 

25from typing import Union 

26 

27from . import base as orm_base 

28from ._typing import insp_is_mapper_property 

29from .. import exc 

30from .. import util 

31from ..sql import visitors 

32from ..sql.cache_key import HasCacheKey 

33 

34if TYPE_CHECKING: 

35 from ._typing import _InternalEntityType 

36 from .interfaces import StrategizedProperty 

37 from .mapper import Mapper 

38 from .relationships import RelationshipProperty 

39 from .util import AliasedInsp 

40 from ..sql.cache_key import _CacheKeyTraversalType 

41 from ..sql.elements import BindParameter 

42 from ..sql.visitors import anon_map 

43 from ..util.typing import _LiteralStar 

44 from ..util.typing import TypeGuard 

45 

46 def is_root(path: PathRegistry) -> TypeGuard[RootRegistry]: ... 

47 

48 def is_entity( 

49 path: PathRegistry, 

50 ) -> 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( 

492 parent.parent[mp_ent], self.token 

493 ).natural_path 

494 if ( 

495 parent.is_aliased_class 

496 and cast( 

497 "AliasedInsp[Any]", 

498 parent.entity, 

499 )._is_with_polymorphic 

500 ): 

501 yield self.natural_path 

502 for ent in cast( 

503 "AliasedInsp[Any]", parent.entity 

504 )._with_polymorphic_entities: 

505 yield ( 

506 _TokenRegistry(parent.parent[ent], self.token).natural_path 

507 ) 

508 else: 

509 yield self.natural_path 

510 

511 def _getitem(self, entity: Any) -> Any: 

512 try: 

513 return self.path[entity] 

514 except TypeError as err: 

515 raise IndexError(f"{entity}") from err 

516 

517 if not TYPE_CHECKING: 

518 __getitem__ = _getitem 

519 

520 

521class _PropRegistry(PathRegistry): 

522 __slots__ = ( 

523 "prop", 

524 "parent", 

525 "path", 

526 "natural_path", 

527 "has_entity", 

528 "entity", 

529 "mapper", 

530 "_wildcard_path_loader_key", 

531 "_default_path_loader_key", 

532 "_loader_key", 

533 "is_unnatural", 

534 ) 

535 inherit_cache = True 

536 is_property = True 

537 

538 prop: StrategizedProperty[Any] 

539 mapper: Optional[Mapper[Any]] 

540 entity: Optional[_InternalEntityType[Any]] 

541 

542 def __init__( 

543 self, parent: _AbstractEntityRegistry, prop: StrategizedProperty[Any] 

544 ): 

545 

546 # restate this path in terms of the 

547 # given StrategizedProperty's parent. 

548 insp = cast("_InternalEntityType[Any]", parent[-1]) 

549 natural_parent: _AbstractEntityRegistry = parent 

550 

551 # inherit "is_unnatural" from the parent 

552 self.is_unnatural = parent.parent.is_unnatural or bool( 

553 parent.mapper.inherits 

554 ) 

555 

556 if not insp.is_aliased_class or insp._use_mapper_path: # type: ignore 

557 parent = natural_parent = parent.parent[prop.parent] 

558 elif ( 

559 insp.is_aliased_class 

560 and insp.with_polymorphic_mappers 

561 and prop.parent in insp.with_polymorphic_mappers 

562 ): 

563 subclass_entity: _InternalEntityType[Any] = parent[-1]._entity_for_mapper(prop.parent) # type: ignore # noqa: E501 

564 parent = parent.parent[subclass_entity] 

565 

566 # when building a path where with_polymorphic() is in use, 

567 # special logic to determine the "natural path" when subclass 

568 # entities are used. 

569 # 

570 # here we are trying to distinguish between a path that starts 

571 # on a with_polymorphic entity vs. one that starts on a 

572 # normal entity that introduces a with_polymorphic() in the 

573 # middle using of_type(): 

574 # 

575 # # as in test_polymorphic_rel-> 

576 # # test_subqueryload_on_subclass_uses_path_correctly 

577 # wp = with_polymorphic(RegularEntity, "*") 

578 # sess.query(wp).options(someload(wp.SomeSubEntity.foos)) 

579 # 

580 # vs 

581 # 

582 # # as in test_relationship->JoinedloadWPolyOfTypeContinued 

583 # wp = with_polymorphic(SomeFoo, "*") 

584 # sess.query(RegularEntity).options( 

585 # someload(RegularEntity.foos.of_type(wp)) 

586 # .someload(wp.SubFoo.bar) 

587 # ) 

588 # 

589 # in the former case, the Query as it generates a path that we 

590 # want to match will be in terms of the with_polymorphic at the 

591 # beginning. in the latter case, Query will generate simple 

592 # paths that don't know about this with_polymorphic, so we must 

593 # use a separate natural path. 

594 # 

595 # 

596 if parent.parent: 

597 natural_parent = parent.parent[subclass_entity.mapper] 

598 self.is_unnatural = True 

599 else: 

600 natural_parent = parent 

601 elif ( 

602 natural_parent.parent 

603 and insp.is_aliased_class 

604 and prop.parent # this should always be the case here 

605 is not insp.mapper 

606 and insp.mapper.isa(prop.parent) 

607 ): 

608 natural_parent = parent.parent[prop.parent] 

609 

610 self.prop = prop 

611 self.parent = parent 

612 self.path = parent.path + (prop,) 

613 self.natural_path = natural_parent.natural_path + (prop,) 

614 

615 self.has_entity = prop._links_to_entity 

616 if prop._is_relationship: 

617 if TYPE_CHECKING: 

618 assert isinstance(prop, RelationshipProperty) 

619 self.entity = prop.entity 

620 self.mapper = prop.mapper 

621 else: 

622 self.entity = None 

623 self.mapper = None 

624 

625 self._wildcard_path_loader_key = ( 

626 "loader", 

627 parent.natural_path + self.prop._wildcard_token, 

628 ) 

629 self._default_path_loader_key = self.prop._default_path_loader_key 

630 self._loader_key = ("loader", self.natural_path) 

631 

632 def _truncate_recursive(self) -> _PropRegistry: 

633 earliest = None 

634 for i, token in enumerate(reversed(self.path[:-1])): 

635 if token is self.prop: 

636 earliest = i 

637 

638 if earliest is None: 

639 return self 

640 else: 

641 return self.coerce(self.path[0 : -(earliest + 1)]) # type: ignore 

642 

643 @property 

644 def entity_path(self) -> _AbstractEntityRegistry: 

645 assert self.entity is not None 

646 return self[self.entity] 

647 

648 def _getitem( 

649 self, entity: Union[int, slice, _InternalEntityType[Any]] 

650 ) -> Union[_AbstractEntityRegistry, _PathElementType, _PathRepresentation]: 

651 if isinstance(entity, (int, slice)): 

652 return self.path[entity] 

653 else: 

654 return _SlotsEntityRegistry(self, entity) 

655 

656 if not TYPE_CHECKING: 

657 __getitem__ = _getitem 

658 

659 

660class _AbstractEntityRegistry(_CreatesToken): 

661 __slots__ = ( 

662 "key", 

663 "parent", 

664 "is_aliased_class", 

665 "path", 

666 "entity", 

667 "natural_path", 

668 ) 

669 

670 has_entity = True 

671 is_entity = True 

672 

673 parent: Union[RootRegistry, _PropRegistry] 

674 key: _InternalEntityType[Any] 

675 entity: _InternalEntityType[Any] 

676 is_aliased_class: bool 

677 

678 def __init__( 

679 self, 

680 parent: Union[RootRegistry, _PropRegistry], 

681 entity: _InternalEntityType[Any], 

682 ): 

683 self.key = entity 

684 self.parent = parent 

685 self.is_aliased_class = entity.is_aliased_class 

686 self.entity = entity 

687 self.path = parent.path + (entity,) 

688 

689 # the "natural path" is the path that we get when Query is traversing 

690 # from the lead entities into the various relationships; it corresponds 

691 # to the structure of mappers and relationships. when we are given a 

692 # path that comes from loader options, as of 1.3 it can have ac-hoc 

693 # with_polymorphic() and other AliasedInsp objects inside of it, which 

694 # are usually not present in mappings. So here we track both the 

695 # "enhanced" path in self.path and the "natural" path that doesn't 

696 # include those objects so these two traversals can be matched up. 

697 

698 # the test here for "(self.is_aliased_class or parent.is_unnatural)" 

699 # are to avoid the more expensive conditional logic that follows if we 

700 # know we don't have to do it. This conditional can just as well be 

701 # "if parent.path:", it just is more function calls. 

702 # 

703 # This is basically the only place that the "is_unnatural" flag 

704 # actually changes behavior. 

705 if parent.path and (self.is_aliased_class or parent.is_unnatural): 

706 # this is an infrequent code path used only for loader strategies 

707 # that also make use of of_type(). 

708 if entity.mapper.isa(parent.natural_path[-1].mapper): # type: ignore # noqa: E501 

709 self.natural_path = parent.natural_path + (entity.mapper,) 

710 else: 

711 self.natural_path = parent.natural_path + ( 

712 parent.natural_path[-1].entity, # type: ignore 

713 ) 

714 # it seems to make sense that since these paths get mixed up 

715 # with statements that are cached or not, we should make 

716 # sure the natural path is cacheable across different occurrences 

717 # of equivalent AliasedClass objects. however, so far this 

718 # does not seem to be needed for whatever reason. 

719 # elif not parent.path and self.is_aliased_class: 

720 # self.natural_path = (self.entity._generate_cache_key()[0], ) 

721 else: 

722 self.natural_path = self.path 

723 

724 def _truncate_recursive(self) -> _AbstractEntityRegistry: 

725 return self.parent._truncate_recursive()[self.entity] 

726 

727 @property 

728 def root_entity(self) -> _InternalEntityType[Any]: 

729 return self.odd_element(0) 

730 

731 @property 

732 def entity_path(self) -> PathRegistry: 

733 return self 

734 

735 @property 

736 def mapper(self) -> Mapper[Any]: 

737 return self.entity.mapper 

738 

739 def __bool__(self) -> bool: 

740 return True 

741 

742 def _getitem( 

743 self, entity: Any 

744 ) -> Union[_PathElementType, _PathRepresentation, PathRegistry]: 

745 if isinstance(entity, (int, slice)): 

746 return self.path[entity] 

747 elif entity in PathToken._intern: 

748 return _TokenRegistry(self, PathToken._intern[entity]) 

749 else: 

750 return _PropRegistry(self, entity) 

751 

752 if not TYPE_CHECKING: 

753 __getitem__ = _getitem 

754 

755 

756class _SlotsEntityRegistry(_AbstractEntityRegistry): 

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

758 # version 

759 inherit_cache = True 

760 

761 

762class _ERDict(Dict[Any, Any]): 

763 def __init__(self, registry: _CachingEntityRegistry): 

764 self.registry = registry 

765 

766 def __missing__(self, key: Any) -> _PropRegistry: 

767 self[key] = item = _PropRegistry(self.registry, key) 

768 

769 return item 

770 

771 

772class _CachingEntityRegistry(_AbstractEntityRegistry): 

773 # for long lived mapper, return dict based caching 

774 # version that creates reference cycles 

775 

776 __slots__ = ("_cache",) 

777 

778 inherit_cache = True 

779 

780 def __init__( 

781 self, 

782 parent: Union[RootRegistry, _PropRegistry], 

783 entity: _InternalEntityType[Any], 

784 ): 

785 super().__init__(parent, entity) 

786 self._cache = _ERDict(self) 

787 

788 def pop(self, key: Any, default: Any) -> Any: 

789 return self._cache.pop(key, default) 

790 

791 def _getitem(self, entity: Any) -> Any: 

792 if isinstance(entity, (int, slice)): 

793 return self.path[entity] 

794 elif isinstance(entity, PathToken): 

795 return _TokenRegistry(self, entity) 

796 else: 

797 return self._cache[entity] 

798 

799 if not TYPE_CHECKING: 

800 __getitem__ = _getitem 

801 

802 

803if TYPE_CHECKING: 

804 

805 def path_is_entity( 

806 path: PathRegistry, 

807 ) -> TypeGuard[_AbstractEntityRegistry]: ... 

808 

809 def path_is_property(path: PathRegistry) -> TypeGuard[_PropRegistry]: ... 

810 

811else: 

812 path_is_entity = operator.attrgetter("is_entity") 

813 path_is_property = operator.attrgetter("is_property")