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 TypeGuard 

36 

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 

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

48 

49 def is_entity( 

50 path: PathRegistry, 

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

52 

53else: 

54 is_root = operator.attrgetter("is_root") 

55 is_entity = operator.attrgetter("is_entity") 

56 

57 

58_SerializedPath = List[Any] 

59_StrPathToken = str 

60_PathElementType = Union[ 

61 _StrPathToken, "_InternalEntityType[Any]", "StrategizedProperty[Any]" 

62] 

63 

64# the representation is in fact 

65# a tuple with alternating: 

66# [_InternalEntityType[Any], Union[str, StrategizedProperty[Any]], 

67# _InternalEntityType[Any], Union[str, StrategizedProperty[Any]], ...] 

68# this might someday be a tuple of 2-tuples instead, but paths can be 

69# chopped at odd intervals as well so this is less flexible 

70_PathRepresentation = Tuple[_PathElementType, ...] 

71 

72# NOTE: these names are weird since the array is 0-indexed, 

73# the "_Odd" entries are at 0, 2, 4, etc 

74_OddPathRepresentation = Sequence["_InternalEntityType[Any]"] 

75_EvenPathRepresentation = Sequence[Union["StrategizedProperty[Any]", str]] 

76 

77 

78log = logging.getLogger(__name__) 

79 

80 

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

82 return PathRegistry.deserialize(path) 

83 

84 

85_WILDCARD_TOKEN: _LiteralStar = "*" 

86_DEFAULT_TOKEN = "_sa_default" 

87 

88 

89class PathRegistry(HasCacheKey): 

90 """Represent query load paths and registry functions. 

91 

92 Basically represents structures like: 

93 

94 (<User mapper>, "orders", <Order mapper>, "items", <Item mapper>) 

95 

96 These structures are generated by things like 

97 query options (joinedload(), subqueryload(), etc.) and are 

98 used to compose keys stored in the query._attributes dictionary 

99 for various options. 

100 

101 They are then re-composed at query compile/result row time as 

102 the query is formed and as rows are fetched, where they again 

103 serve to compose keys to look up options in the context.attributes 

104 dictionary, which is copied from query._attributes. 

105 

106 The path structure has a limited amount of caching, where each 

107 "root" ultimately pulls from a fixed registry associated with 

108 the first mapper, that also contains elements for each of its 

109 property keys. However paths longer than two elements, which 

110 are the exception rather than the rule, are generated on an 

111 as-needed basis. 

112 

113 """ 

114 

115 __slots__ = () 

116 

117 is_token = False 

118 is_root = False 

119 has_entity = False 

120 is_property = False 

121 is_entity = False 

122 

123 is_unnatural: bool 

124 

125 path: _PathRepresentation 

126 natural_path: _PathRepresentation 

127 parent: Optional[PathRegistry] 

128 root: RootRegistry 

129 

130 _cache_key_traversal: _CacheKeyTraversalType = [ 

131 ("path", visitors.ExtendedInternalTraversal.dp_has_cache_key_list) 

132 ] 

133 

134 def __eq__(self, other: Any) -> bool: 

135 try: 

136 return other is not None and self.path == other._path_for_compare 

137 except AttributeError: 

138 util.warn( 

139 "Comparison of PathRegistry to %r is not supported" 

140 % (type(other)) 

141 ) 

142 return False 

143 

144 def __ne__(self, other: Any) -> bool: 

145 try: 

146 return other is None or self.path != other._path_for_compare 

147 except AttributeError: 

148 util.warn( 

149 "Comparison of PathRegistry to %r is not supported" 

150 % (type(other)) 

151 ) 

152 return True 

153 

154 @property 

155 def _path_for_compare(self) -> Optional[_PathRepresentation]: 

156 return self.path 

157 

158 def odd_element(self, index: int) -> _InternalEntityType[Any]: 

159 return self.path[index] # type: ignore 

160 

161 def set(self, attributes: Dict[Any, Any], key: Any, value: Any) -> None: 

162 log.debug("set '%s' on path '%s' to '%s'", key, self, value) 

163 attributes[(key, self.natural_path)] = value 

164 

165 def setdefault( 

166 self, attributes: Dict[Any, Any], key: Any, value: Any 

167 ) -> None: 

168 log.debug("setdefault '%s' on path '%s' to '%s'", key, self, value) 

169 attributes.setdefault((key, self.natural_path), value) 

170 

171 def get( 

172 self, attributes: Dict[Any, Any], key: Any, value: Optional[Any] = None 

173 ) -> Any: 

174 key = (key, self.natural_path) 

175 if key in attributes: 

176 return attributes[key] 

177 else: 

178 return value 

179 

180 def __len__(self) -> int: 

181 return len(self.path) 

182 

183 def __hash__(self) -> int: 

184 return id(self) 

185 

186 @overload 

187 def __getitem__(self, entity: _StrPathToken) -> _TokenRegistry: ... 

188 

189 @overload 

190 def __getitem__(self, entity: int) -> _PathElementType: ... 

191 

192 @overload 

193 def __getitem__(self, entity: slice) -> _PathRepresentation: ... 

194 

195 @overload 

196 def __getitem__( 

197 self, entity: _InternalEntityType[Any] 

198 ) -> _AbstractEntityRegistry: ... 

199 

200 @overload 

201 def __getitem__( 

202 self, entity: StrategizedProperty[Any] 

203 ) -> _PropRegistry: ... 

204 

205 def __getitem__( 

206 self, 

207 entity: Union[ 

208 _StrPathToken, 

209 int, 

210 slice, 

211 _InternalEntityType[Any], 

212 StrategizedProperty[Any], 

213 ], 

214 ) -> Union[ 

215 _TokenRegistry, 

216 _PathElementType, 

217 _PathRepresentation, 

218 _PropRegistry, 

219 _AbstractEntityRegistry, 

220 ]: 

221 raise NotImplementedError() 

222 

223 # TODO: what are we using this for? 

224 @property 

225 def length(self) -> int: 

226 return len(self.path) 

227 

228 def pairs( 

229 self, 

230 ) -> Iterator[ 

231 Tuple[_InternalEntityType[Any], Union[str, StrategizedProperty[Any]]] 

232 ]: 

233 odd_path = cast(_OddPathRepresentation, self.path) 

234 even_path = cast(_EvenPathRepresentation, odd_path) 

235 for i in range(0, len(odd_path), 2): 

236 yield odd_path[i], even_path[i + 1] 

237 

238 def contains_mapper(self, mapper: Mapper[Any]) -> bool: 

239 _m_path = cast(_OddPathRepresentation, self.path) 

240 for path_mapper in [_m_path[i] for i in range(0, len(_m_path), 2)]: 

241 if path_mapper.mapper.isa(mapper): 

242 return True 

243 else: 

244 return False 

245 

246 def contains(self, attributes: Dict[Any, Any], key: Any) -> bool: 

247 return (key, self.path) in attributes 

248 

249 def __reduce__(self) -> Any: 

250 return _unreduce_path, (self.serialize(),) 

251 

252 @classmethod 

253 def _serialize_path(cls, path: _PathRepresentation) -> _SerializedPath: 

254 _m_path = cast(_OddPathRepresentation, path) 

255 _p_path = cast(_EvenPathRepresentation, path) 

256 

257 return list( 

258 zip( 

259 tuple( 

260 m.class_ if (m.is_mapper or m.is_aliased_class) else str(m) 

261 for m in [_m_path[i] for i in range(0, len(_m_path), 2)] 

262 ), 

263 tuple( 

264 p.key if insp_is_mapper_property(p) else str(p) 

265 for p in [_p_path[i] for i in range(1, len(_p_path), 2)] 

266 ) 

267 + (None,), 

268 ) 

269 ) 

270 

271 @classmethod 

272 def _deserialize_path(cls, path: _SerializedPath) -> _PathRepresentation: 

273 def _deserialize_mapper_token(mcls: Any) -> Any: 

274 return ( 

275 # note: we likely dont want configure=True here however 

276 # this is maintained at the moment for backwards compatibility 

277 orm_base._inspect_mapped_class(mcls, configure=True) 

278 if mcls not in PathToken._intern 

279 else PathToken._intern[mcls] 

280 ) 

281 

282 def _deserialize_key_token(mcls: Any, key: Any) -> Any: 

283 if key is None: 

284 return None 

285 elif key in PathToken._intern: 

286 return PathToken._intern[key] 

287 else: 

288 mp = orm_base._inspect_mapped_class(mcls, configure=True) 

289 assert mp is not None 

290 return mp.attrs[key] 

291 

292 p = tuple( 

293 chain( 

294 *[ 

295 ( 

296 _deserialize_mapper_token(mcls), 

297 _deserialize_key_token(mcls, key), 

298 ) 

299 for mcls, key in path 

300 ] 

301 ) 

302 ) 

303 if p and p[-1] is None: 

304 p = p[0:-1] 

305 return p 

306 

307 def serialize(self) -> _SerializedPath: 

308 path = self.path 

309 return self._serialize_path(path) 

310 

311 @classmethod 

312 def deserialize(cls, path: _SerializedPath) -> PathRegistry: 

313 assert path is not None 

314 p = cls._deserialize_path(path) 

315 return cls.coerce(p) 

316 

317 @overload 

318 @classmethod 

319 def per_mapper(cls, mapper: Mapper[Any]) -> _CachingEntityRegistry: ... 

320 

321 @overload 

322 @classmethod 

323 def per_mapper(cls, mapper: AliasedInsp[Any]) -> _SlotsEntityRegistry: ... 

324 

325 @classmethod 

326 def per_mapper( 

327 cls, mapper: _InternalEntityType[Any] 

328 ) -> _AbstractEntityRegistry: 

329 if mapper.is_mapper: 

330 return _CachingEntityRegistry(cls.root, mapper) 

331 else: 

332 return _SlotsEntityRegistry(cls.root, mapper) 

333 

334 @classmethod 

335 def coerce(cls, raw: _PathRepresentation) -> PathRegistry: 

336 def _red(prev: PathRegistry, next_: _PathElementType) -> PathRegistry: 

337 return prev[next_] 

338 

339 # can't quite get mypy to appreciate this one :) 

340 return reduce(_red, raw, cls.root) # type: ignore 

341 

342 def __add__(self, other: PathRegistry) -> PathRegistry: 

343 def _red(prev: PathRegistry, next_: _PathElementType) -> PathRegistry: 

344 return prev[next_] 

345 

346 return reduce(_red, other.path, self) 

347 

348 def __str__(self) -> str: 

349 return f"ORM Path[{' -> '.join(str(elem) for elem in self.path)}]" 

350 

351 def __repr__(self) -> str: 

352 return f"{self.__class__.__name__}({self.path!r})" 

353 

354 

355class _CreatesToken(PathRegistry): 

356 __slots__ = () 

357 

358 is_aliased_class: bool 

359 is_root: bool 

360 

361 def token(self, token: _StrPathToken) -> _TokenRegistry: 

362 if token.endswith(f":{_WILDCARD_TOKEN}"): 

363 return _TokenRegistry(self, token) 

364 elif token.endswith(f":{_DEFAULT_TOKEN}"): 

365 return _TokenRegistry(self.root, token) 

366 else: 

367 raise exc.ArgumentError(f"invalid token: {token}") 

368 

369 

370class RootRegistry(_CreatesToken): 

371 """Root registry, defers to mappers so that 

372 paths are maintained per-root-mapper. 

373 

374 """ 

375 

376 __slots__ = () 

377 

378 inherit_cache = True 

379 

380 path = natural_path = () 

381 has_entity = False 

382 is_aliased_class = False 

383 is_root = True 

384 is_unnatural = False 

385 

386 def _getitem( 

387 self, entity: Any 

388 ) -> Union[_TokenRegistry, _AbstractEntityRegistry]: 

389 if entity in PathToken._intern: 

390 if TYPE_CHECKING: 

391 assert isinstance(entity, _StrPathToken) 

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

393 else: 

394 try: 

395 return entity._path_registry # type: ignore 

396 except AttributeError: 

397 raise IndexError( 

398 f"invalid argument for RootRegistry.__getitem__: {entity}" 

399 ) 

400 

401 def _truncate_recursive(self) -> RootRegistry: 

402 return self 

403 

404 if not TYPE_CHECKING: 

405 __getitem__ = _getitem 

406 

407 

408PathRegistry.root = RootRegistry() 

409 

410 

411class PathToken(orm_base.InspectionAttr, HasCacheKey, str): 

412 """cacheable string token""" 

413 

414 _intern: Dict[str, PathToken] = {} 

415 

416 def _gen_cache_key( 

417 self, anon_map: anon_map, bindparams: List[BindParameter[Any]] 

418 ) -> Tuple[Any, ...]: 

419 return (str(self),) 

420 

421 @property 

422 def _path_for_compare(self) -> Optional[_PathRepresentation]: 

423 return None 

424 

425 @classmethod 

426 def intern(cls, strvalue: str) -> PathToken: 

427 if strvalue in cls._intern: 

428 return cls._intern[strvalue] 

429 else: 

430 cls._intern[strvalue] = result = PathToken(strvalue) 

431 return result 

432 

433 

434class _TokenRegistry(PathRegistry): 

435 __slots__ = ("token", "parent", "path", "natural_path") 

436 

437 inherit_cache = True 

438 

439 token: _StrPathToken 

440 parent: _CreatesToken 

441 

442 def __init__(self, parent: _CreatesToken, token: _StrPathToken): 

443 token = PathToken.intern(token) 

444 

445 self.token = token 

446 self.parent = parent 

447 self.path = parent.path + (token,) 

448 self.natural_path = parent.natural_path + (token,) 

449 

450 has_entity = False 

451 

452 is_token = True 

453 

454 def generate_for_superclasses(self) -> Iterator[PathRegistry]: 

455 # NOTE: this method is no longer used. consider removal 

456 parent = self.parent 

457 if is_root(parent): 

458 yield self 

459 return 

460 

461 if TYPE_CHECKING: 

462 assert isinstance(parent, _AbstractEntityRegistry) 

463 if not parent.is_aliased_class: 

464 for mp_ent in parent.mapper.iterate_to_root(): 

465 yield _TokenRegistry(parent.parent[mp_ent], self.token) 

466 elif ( 

467 parent.is_aliased_class 

468 and cast( 

469 "AliasedInsp[Any]", 

470 parent.entity, 

471 )._is_with_polymorphic 

472 ): 

473 yield self 

474 for ent in cast( 

475 "AliasedInsp[Any]", parent.entity 

476 )._with_polymorphic_entities: 

477 yield _TokenRegistry(parent.parent[ent], self.token) 

478 else: 

479 yield self 

480 

481 def _generate_natural_for_superclasses( 

482 self, 

483 ) -> Iterator[_PathRepresentation]: 

484 parent = self.parent 

485 if is_root(parent): 

486 yield self.natural_path 

487 return 

488 

489 if TYPE_CHECKING: 

490 assert isinstance(parent, _AbstractEntityRegistry) 

491 for mp_ent in parent.mapper.iterate_to_root(): 

492 yield _TokenRegistry( 

493 parent.parent[mp_ent], self.token 

494 ).natural_path 

495 if ( 

496 parent.is_aliased_class 

497 and cast( 

498 "AliasedInsp[Any]", 

499 parent.entity, 

500 )._is_with_polymorphic 

501 ): 

502 yield self.natural_path 

503 for ent in cast( 

504 "AliasedInsp[Any]", parent.entity 

505 )._with_polymorphic_entities: 

506 yield ( 

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

508 ) 

509 else: 

510 yield self.natural_path 

511 

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

513 try: 

514 return self.path[entity] 

515 except TypeError as err: 

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

517 

518 if not TYPE_CHECKING: 

519 __getitem__ = _getitem 

520 

521 

522class _PropRegistry(PathRegistry): 

523 __slots__ = ( 

524 "prop", 

525 "parent", 

526 "path", 

527 "natural_path", 

528 "has_entity", 

529 "entity", 

530 "mapper", 

531 "_wildcard_path_loader_key", 

532 "_default_path_loader_key", 

533 "_loader_key", 

534 "is_unnatural", 

535 ) 

536 inherit_cache = True 

537 is_property = True 

538 

539 prop: StrategizedProperty[Any] 

540 mapper: Optional[Mapper[Any]] 

541 entity: Optional[_InternalEntityType[Any]] 

542 

543 def __init__( 

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

545 ): 

546 

547 # restate this path in terms of the 

548 # given StrategizedProperty's parent. 

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

550 natural_parent: _AbstractEntityRegistry = parent 

551 

552 # inherit "is_unnatural" from the parent 

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

554 parent.mapper.inherits 

555 ) 

556 

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

558 parent = natural_parent = parent.parent[prop.parent] 

559 elif ( 

560 insp.is_aliased_class 

561 and insp.with_polymorphic_mappers 

562 and prop.parent in insp.with_polymorphic_mappers 

563 ): 

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

565 parent = parent.parent[subclass_entity] 

566 

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

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

569 # entities are used. 

570 # 

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

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

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

574 # middle using of_type(): 

575 # 

576 # # as in test_polymorphic_rel-> 

577 # # test_subqueryload_on_subclass_uses_path_correctly 

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

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

580 # 

581 # vs 

582 # 

583 # # as in test_relationship->JoinedloadWPolyOfTypeContinued 

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

585 # sess.query(RegularEntity).options( 

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

587 # .someload(wp.SubFoo.bar) 

588 # ) 

589 # 

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

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

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

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

594 # use a separate natural path. 

595 # 

596 # 

597 if parent.parent: 

598 natural_parent = parent.parent[subclass_entity.mapper] 

599 self.is_unnatural = True 

600 else: 

601 natural_parent = parent 

602 elif ( 

603 natural_parent.parent 

604 and insp.is_aliased_class 

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

606 is not insp.mapper 

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

608 ): 

609 natural_parent = parent.parent[prop.parent] 

610 

611 self.prop = prop 

612 self.parent = parent 

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

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

615 

616 self.has_entity = prop._links_to_entity 

617 if prop._is_relationship: 

618 if TYPE_CHECKING: 

619 assert isinstance(prop, RelationshipProperty) 

620 self.entity = prop.entity 

621 self.mapper = prop.mapper 

622 else: 

623 self.entity = None 

624 self.mapper = None 

625 

626 self._wildcard_path_loader_key = ( 

627 "loader", 

628 parent.natural_path + self.prop._wildcard_token, 

629 ) 

630 self._default_path_loader_key = self.prop._default_path_loader_key 

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

632 

633 def _truncate_recursive(self) -> _PropRegistry: 

634 earliest = None 

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

636 if token is self.prop: 

637 earliest = i 

638 

639 if earliest is None: 

640 return self 

641 else: 

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

643 

644 @property 

645 def entity_path(self) -> _AbstractEntityRegistry: 

646 assert self.entity is not None 

647 return self[self.entity] 

648 

649 def _getitem( 

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

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

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

653 return self.path[entity] 

654 else: 

655 return _SlotsEntityRegistry(self, entity) 

656 

657 if not TYPE_CHECKING: 

658 __getitem__ = _getitem 

659 

660 

661class _AbstractEntityRegistry(_CreatesToken): 

662 __slots__ = ( 

663 "key", 

664 "parent", 

665 "is_aliased_class", 

666 "path", 

667 "entity", 

668 "natural_path", 

669 ) 

670 

671 has_entity = True 

672 is_entity = True 

673 

674 parent: Union[RootRegistry, _PropRegistry] 

675 key: _InternalEntityType[Any] 

676 entity: _InternalEntityType[Any] 

677 is_aliased_class: bool 

678 

679 def __init__( 

680 self, 

681 parent: Union[RootRegistry, _PropRegistry], 

682 entity: _InternalEntityType[Any], 

683 ): 

684 self.key = entity 

685 self.parent = parent 

686 self.is_aliased_class = entity.is_aliased_class 

687 self.entity = entity 

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

689 

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

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

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

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

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

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

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

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

698 

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

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

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

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

703 # 

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

705 # actually changes behavior. 

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

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

708 # that also make use of of_type(). 

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

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

711 else: 

712 self.natural_path = parent.natural_path + ( 

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

714 ) 

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

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

717 # sure the natural path is cacheable across different occurrences 

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

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

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

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

722 else: 

723 self.natural_path = self.path 

724 

725 def _truncate_recursive(self) -> _AbstractEntityRegistry: 

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

727 

728 @property 

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

730 return self.odd_element(0) 

731 

732 @property 

733 def entity_path(self) -> PathRegistry: 

734 return self 

735 

736 @property 

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

738 return self.entity.mapper 

739 

740 def __bool__(self) -> bool: 

741 return True 

742 

743 def _getitem( 

744 self, entity: Any 

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

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

747 return self.path[entity] 

748 elif entity in PathToken._intern: 

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

750 else: 

751 return _PropRegistry(self, entity) 

752 

753 if not TYPE_CHECKING: 

754 __getitem__ = _getitem 

755 

756 

757class _SlotsEntityRegistry(_AbstractEntityRegistry): 

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

759 # version 

760 inherit_cache = True 

761 

762 

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

764 def __init__(self, registry: _CachingEntityRegistry): 

765 self.registry = registry 

766 

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

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

769 

770 return item 

771 

772 

773class _CachingEntityRegistry(_AbstractEntityRegistry): 

774 # for long lived mapper, return dict based caching 

775 # version that creates reference cycles 

776 

777 __slots__ = ("_cache",) 

778 

779 inherit_cache = True 

780 

781 def __init__( 

782 self, 

783 parent: Union[RootRegistry, _PropRegistry], 

784 entity: _InternalEntityType[Any], 

785 ): 

786 super().__init__(parent, entity) 

787 self._cache = _ERDict(self) 

788 

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

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

791 

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

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

794 return self.path[entity] 

795 elif isinstance(entity, PathToken): 

796 return _TokenRegistry(self, entity) 

797 else: 

798 return self._cache[entity] 

799 

800 if not TYPE_CHECKING: 

801 __getitem__ = _getitem 

802 

803 

804if TYPE_CHECKING: 

805 

806 def path_is_entity( 

807 path: PathRegistry, 

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

809 

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

811 

812else: 

813 path_is_entity = operator.attrgetter("is_entity") 

814 path_is_property = operator.attrgetter("is_property")