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

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

404 statements  

1# orm/path_registry.py 

2# Copyright (C) 2005-2026 the SQLAlchemy authors and contributors 

3# <see AUTHORS file> 

4# 

5# This module is part of SQLAlchemy and is released under 

6# the MIT License: https://www.opensource.org/licenses/mit-license.php 

7"""Path tracking utilities, representing mapper graph traversals.""" 

8 

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 Mapping 

21from typing import Optional 

22from typing import overload 

23from typing import Sequence 

24from typing import Tuple 

25from typing import TYPE_CHECKING 

26from typing import Union 

27 

28from . import base as orm_base 

29from ._typing import insp_is_mapper_property 

30from .. import exc 

31from .. import util 

32from ..sql import visitors 

33from ..sql.cache_key import HasCacheKey 

34from ..util.typing import Final 

35from ..util.typing import Literal 

36 

37if TYPE_CHECKING: 

38 from ._typing import _InternalEntityType 

39 from .interfaces import StrategizedProperty 

40 from .mapper import Mapper 

41 from .relationships import RelationshipProperty 

42 from .util import AliasedInsp 

43 from ..sql.cache_key import _CacheKeyTraversalType 

44 from ..sql.elements import BindParameter 

45 from ..sql.visitors import anon_map 

46 from ..util.typing import _LiteralStar 

47 from ..util.typing import TypeGuard 

48 

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

50 

51 def is_entity(path: PathRegistry) -> 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_RELATIONSHIP_TOKEN: Final[Literal["relationship"]] = "relationship" 

89_COLUMN_TOKEN: Final[Literal["column"]] = "column" 

90 

91_UNPREFIXED_TOKENS = frozenset([_WILDCARD_TOKEN, _DEFAULT_TOKEN]) 

92"""the wildcard strings accepted from the user in a loader option. 

93 

94these are prefixed with the target property's ``strategy_wildcard_key`` to 

95form the tokens in :data:`._PATH_TOKENS`, and are not themselves valid as an 

96element of a path. 

97 

98""" 

99 

100_PATH_TOKENS = frozenset( 

101 f"{wildcard_key}:{suffix}" 

102 for wildcard_key in (_RELATIONSHIP_TOKEN, _COLUMN_TOKEN) 

103 for suffix in (_WILDCARD_TOKEN, _DEFAULT_TOKEN) 

104) 

105"""the complete set of tokens which may appear within a path. 

106 

107:attr:`.PathToken._intern` is populated from this collection at module import 

108time, so that a token is present in every process, including one which has 

109not yet run any query. 

110 

111""" 

112 

113_ACCEPTED_TOKENS = _UNPREFIXED_TOKENS | _PATH_TOKENS 

114"""every string a loader option may accept in place of an attribute name. 

115 

116this is the union of the bare wildcards the user writes and the prefixed 

117tokens which the loader option internals hand back to themselves; only the 

118latter may appear in a path. 

119 

120""" 

121 

122 

123class PathRegistry(HasCacheKey): 

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

125 

126 Basically represents structures like: 

127 

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

129 

130 These structures are generated by things like 

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

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

133 for various options. 

134 

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

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

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

138 dictionary, which is copied from query._attributes. 

139 

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

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

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

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

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

145 as-needed basis. 

146 

147 """ 

148 

149 __slots__ = () 

150 

151 is_token = False 

152 is_root = False 

153 has_entity = False 

154 is_property = False 

155 is_entity = False 

156 

157 is_unnatural: bool 

158 

159 path: _PathRepresentation 

160 natural_path: _PathRepresentation 

161 parent: Optional[PathRegistry] 

162 root: RootRegistry 

163 

164 _cache_key_traversal: _CacheKeyTraversalType = [ 

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

166 ] 

167 

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

169 try: 

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

171 except AttributeError: 

172 util.warn( 

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

174 % (type(other)) 

175 ) 

176 return False 

177 

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

179 try: 

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

181 except AttributeError: 

182 util.warn( 

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

184 % (type(other)) 

185 ) 

186 return True 

187 

188 @property 

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

190 return self.path 

191 

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

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

194 

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

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

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

198 

199 def setdefault( 

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

201 ) -> None: 

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

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

204 

205 def get( 

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

207 ) -> Any: 

208 key = (key, self.natural_path) 

209 if key in attributes: 

210 return attributes[key] 

211 else: 

212 return value 

213 

214 def __len__(self) -> int: 

215 return len(self.path) 

216 

217 def __hash__(self) -> int: 

218 return id(self) 

219 

220 @overload 

221 def __getitem__(self, entity: _StrPathToken) -> TokenRegistry: ... 

222 

223 @overload 

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

225 

226 @overload 

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

228 

229 @overload 

230 def __getitem__( 

231 self, entity: _InternalEntityType[Any] 

232 ) -> AbstractEntityRegistry: ... 

233 

234 @overload 

235 def __getitem__( 

236 self, entity: StrategizedProperty[Any] 

237 ) -> PropRegistry: ... 

238 

239 def __getitem__( 

240 self, 

241 entity: Union[ 

242 _StrPathToken, 

243 int, 

244 slice, 

245 _InternalEntityType[Any], 

246 StrategizedProperty[Any], 

247 ], 

248 ) -> Union[ 

249 TokenRegistry, 

250 _PathElementType, 

251 _PathRepresentation, 

252 PropRegistry, 

253 AbstractEntityRegistry, 

254 ]: 

255 raise NotImplementedError() 

256 

257 # TODO: what are we using this for? 

258 @property 

259 def length(self) -> int: 

260 return len(self.path) 

261 

262 def pairs( 

263 self, 

264 ) -> Iterator[ 

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

266 ]: 

267 odd_path = cast(_OddPathRepresentation, self.path) 

268 even_path = cast(_EvenPathRepresentation, odd_path) 

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

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

271 

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

273 _m_path = cast(_OddPathRepresentation, self.path) 

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

275 if path_mapper.mapper.isa(mapper): 

276 return True 

277 else: 

278 return False 

279 

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

281 return (key, self.path) in attributes 

282 

283 def __reduce__(self) -> Any: 

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

285 

286 @classmethod 

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

288 _m_path = cast(_OddPathRepresentation, path) 

289 _p_path = cast(_EvenPathRepresentation, path) 

290 

291 return list( 

292 zip( 

293 tuple( 

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

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

296 ), 

297 tuple( 

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

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

300 ) 

301 + (None,), 

302 ) 

303 ) 

304 

305 @classmethod 

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

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

308 return ( 

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

310 # this is maintained at the moment for backwards compatibility 

311 orm_base._inspect_mapped_class(mcls, configure=True) 

312 if mcls not in PathToken._intern 

313 else PathToken._intern[mcls] 

314 ) 

315 

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

317 if key is None: 

318 return None 

319 elif key in PathToken._intern: 

320 return PathToken._intern[key] 

321 else: 

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

323 assert mp is not None 

324 return mp.attrs[key] 

325 

326 p = tuple( 

327 chain( 

328 *[ 

329 ( 

330 _deserialize_mapper_token(mcls), 

331 _deserialize_key_token(mcls, key), 

332 ) 

333 for mcls, key in path 

334 ] 

335 ) 

336 ) 

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

338 p = p[0:-1] 

339 return p 

340 

341 def serialize(self) -> _SerializedPath: 

342 path = self.path 

343 return self._serialize_path(path) 

344 

345 @classmethod 

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

347 assert path is not None 

348 p = cls._deserialize_path(path) 

349 return cls.coerce(p) 

350 

351 @overload 

352 @classmethod 

353 def per_mapper(cls, mapper: Mapper[Any]) -> CachingEntityRegistry: ... 

354 

355 @overload 

356 @classmethod 

357 def per_mapper(cls, mapper: AliasedInsp[Any]) -> SlotsEntityRegistry: ... 

358 

359 @classmethod 

360 def per_mapper( 

361 cls, mapper: _InternalEntityType[Any] 

362 ) -> AbstractEntityRegistry: 

363 if mapper.is_mapper: 

364 return CachingEntityRegistry(cls.root, mapper) 

365 else: 

366 return SlotsEntityRegistry(cls.root, mapper) 

367 

368 @classmethod 

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

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

371 return prev[next_] 

372 

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

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

375 

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

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

378 return prev[next_] 

379 

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

381 

382 def __str__(self) -> str: 

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

384 

385 def __repr__(self) -> str: 

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

387 

388 

389class CreatesToken(PathRegistry): 

390 __slots__ = () 

391 

392 is_aliased_class: bool 

393 is_root: bool 

394 

395 def token(self, token: _StrPathToken) -> TokenRegistry: 

396 if token not in PathToken._intern: 

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

398 elif token.endswith(f":{_WILDCARD_TOKEN}"): 

399 return TokenRegistry(self, token) 

400 else: 

401 return TokenRegistry(self.root, token) 

402 

403 

404class RootRegistry(CreatesToken): 

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

406 paths are maintained per-root-mapper. 

407 

408 """ 

409 

410 __slots__ = () 

411 

412 inherit_cache = True 

413 

414 path = natural_path = () 

415 has_entity = False 

416 is_aliased_class = False 

417 is_root = True 

418 is_unnatural = False 

419 

420 def _getitem( 

421 self, entity: Any 

422 ) -> Union[TokenRegistry, AbstractEntityRegistry]: 

423 if entity in PathToken._intern: 

424 if TYPE_CHECKING: 

425 assert isinstance(entity, _StrPathToken) 

426 return TokenRegistry(self, PathToken._intern[entity]) 

427 else: 

428 try: 

429 return entity._path_registry # type: ignore 

430 except AttributeError: 

431 raise IndexError( 

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

433 ) 

434 

435 def _truncate_recursive(self) -> RootRegistry: 

436 return self 

437 

438 if not TYPE_CHECKING: 

439 __getitem__ = _getitem 

440 

441 

442PathRegistry.root = RootRegistry() 

443 

444 

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

446 """cacheable string token""" 

447 

448 _intern: Mapping[str, PathToken] 

449 """the :class:`.PathToken` for each of :data:`._PATH_TOKENS`. 

450 

451 this collection is fully populated below at module import time and is 

452 never added to afterwards; it's typed as :class:`.Mapping` so that a 

453 mutation is flagged by type checkers. 

454 :meth:`.PathRegistry._deserialize_path` relies on it being complete, 

455 distinguishing a token from a mapped attribute key by testing 

456 membership here. 

457 

458 """ 

459 

460 def _gen_cache_key( 

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

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

463 return (str(self),) 

464 

465 @property 

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

467 return None 

468 

469 

470PathToken._intern = {token: PathToken(token) for token in _PATH_TOKENS} 

471 

472 

473class TokenRegistry(PathRegistry): 

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

475 

476 inherit_cache = True 

477 

478 token: _StrPathToken 

479 parent: CreatesToken 

480 

481 def __init__(self, parent: CreatesToken, token: _StrPathToken): 

482 token = PathToken._intern[token] 

483 

484 self.token = token 

485 self.parent = parent 

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

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

488 

489 has_entity = False 

490 

491 is_token = True 

492 

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

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

495 parent = self.parent 

496 if is_root(parent): 

497 yield self 

498 return 

499 

500 if TYPE_CHECKING: 

501 assert isinstance(parent, AbstractEntityRegistry) 

502 if not parent.is_aliased_class: 

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

504 yield TokenRegistry(parent.parent[mp_ent], self.token) 

505 elif ( 

506 parent.is_aliased_class 

507 and cast( 

508 "AliasedInsp[Any]", 

509 parent.entity, 

510 )._is_with_polymorphic 

511 ): 

512 yield self 

513 for ent in cast( 

514 "AliasedInsp[Any]", parent.entity 

515 )._with_polymorphic_entities: 

516 yield TokenRegistry(parent.parent[ent], self.token) 

517 else: 

518 yield self 

519 

520 def _generate_natural_for_superclasses( 

521 self, 

522 ) -> Iterator[_PathRepresentation]: 

523 parent = self.parent 

524 if is_root(parent): 

525 yield self.natural_path 

526 return 

527 

528 if TYPE_CHECKING: 

529 assert isinstance(parent, AbstractEntityRegistry) 

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

531 yield TokenRegistry(parent.parent[mp_ent], self.token).natural_path 

532 if ( 

533 parent.is_aliased_class 

534 and cast( 

535 "AliasedInsp[Any]", 

536 parent.entity, 

537 )._is_with_polymorphic 

538 ): 

539 yield self.natural_path 

540 for ent in cast( 

541 "AliasedInsp[Any]", parent.entity 

542 )._with_polymorphic_entities: 

543 yield ( 

544 TokenRegistry(parent.parent[ent], self.token).natural_path 

545 ) 

546 else: 

547 yield self.natural_path 

548 

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

550 try: 

551 return self.path[entity] 

552 except TypeError as err: 

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

554 

555 if not TYPE_CHECKING: 

556 __getitem__ = _getitem 

557 

558 

559class PropRegistry(PathRegistry): 

560 __slots__ = ( 

561 "prop", 

562 "parent", 

563 "path", 

564 "natural_path", 

565 "has_entity", 

566 "entity", 

567 "mapper", 

568 "_wildcard_path_loader_key", 

569 "_default_path_loader_key", 

570 "_loader_key", 

571 "is_unnatural", 

572 ) 

573 inherit_cache = True 

574 is_property = True 

575 

576 prop: StrategizedProperty[Any] 

577 mapper: Optional[Mapper[Any]] 

578 entity: Optional[_InternalEntityType[Any]] 

579 

580 def __init__( 

581 self, parent: AbstractEntityRegistry, prop: StrategizedProperty[Any] 

582 ): 

583 

584 # restate this path in terms of the 

585 # given StrategizedProperty's parent. 

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

587 natural_parent: AbstractEntityRegistry = parent 

588 

589 # inherit "is_unnatural" from the parent 

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

591 parent.mapper.inherits 

592 ) 

593 

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

595 parent = natural_parent = parent.parent[prop.parent] 

596 elif ( 

597 insp.is_aliased_class 

598 and insp.with_polymorphic_mappers 

599 and prop.parent in insp.with_polymorphic_mappers 

600 ): 

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

602 parent = parent.parent[subclass_entity] 

603 

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

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

606 # entities are used. 

607 # 

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

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

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

611 # middle using of_type(): 

612 # 

613 # # as in test_polymorphic_rel-> 

614 # # test_subqueryload_on_subclass_uses_path_correctly 

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

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

617 # 

618 # vs 

619 # 

620 # # as in test_relationship->JoinedloadWPolyOfTypeContinued 

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

622 # sess.query(RegularEntity).options( 

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

624 # .someload(wp.SubFoo.bar) 

625 # ) 

626 # 

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

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

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

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

631 # use a separate natural path. 

632 # 

633 # 

634 if parent.parent: 

635 natural_parent = parent.parent[subclass_entity.mapper] 

636 self.is_unnatural = True 

637 else: 

638 natural_parent = parent 

639 elif ( 

640 natural_parent.parent 

641 and insp.is_aliased_class 

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

643 is not insp.mapper 

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

645 ): 

646 natural_parent = parent.parent[prop.parent] 

647 

648 self.prop = prop 

649 self.parent = parent 

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

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

652 

653 self.has_entity = prop._links_to_entity 

654 if prop._is_relationship: 

655 if TYPE_CHECKING: 

656 assert isinstance(prop, RelationshipProperty) 

657 self.entity = prop.entity 

658 self.mapper = prop.mapper 

659 else: 

660 self.entity = None 

661 self.mapper = None 

662 

663 self._wildcard_path_loader_key = ( 

664 "loader", 

665 parent.natural_path + self.prop._wildcard_token, 

666 ) 

667 self._default_path_loader_key = self.prop._default_path_loader_key 

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

669 

670 def _truncate_recursive(self) -> PropRegistry: 

671 earliest = None 

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

673 if token is self.prop: 

674 earliest = i 

675 

676 if earliest is None: 

677 return self 

678 else: 

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

680 

681 @property 

682 def entity_path(self) -> AbstractEntityRegistry: 

683 assert self.entity is not None 

684 return self[self.entity] 

685 

686 def _getitem( 

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

688 ) -> Union[AbstractEntityRegistry, _PathElementType, _PathRepresentation]: 

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

690 return self.path[entity] 

691 else: 

692 return SlotsEntityRegistry(self, entity) 

693 

694 if not TYPE_CHECKING: 

695 __getitem__ = _getitem 

696 

697 

698class AbstractEntityRegistry(CreatesToken): 

699 __slots__ = ( 

700 "key", 

701 "parent", 

702 "is_aliased_class", 

703 "path", 

704 "entity", 

705 "natural_path", 

706 ) 

707 

708 has_entity = True 

709 is_entity = True 

710 

711 parent: Union[RootRegistry, PropRegistry] 

712 key: _InternalEntityType[Any] 

713 entity: _InternalEntityType[Any] 

714 is_aliased_class: bool 

715 

716 def __init__( 

717 self, 

718 parent: Union[RootRegistry, PropRegistry], 

719 entity: _InternalEntityType[Any], 

720 ): 

721 self.key = entity 

722 self.parent = parent 

723 self.is_aliased_class = entity.is_aliased_class 

724 self.entity = entity 

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

726 

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

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

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

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

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

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

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

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

735 

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

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

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

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

740 # 

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

742 # actually changes behavior. 

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

744 # this is an infrequent code path used for loader strategies that 

745 # also make use of of_type() or other intricate polymorphic 

746 # base/subclass combinations 

747 parent_natural_entity = parent.natural_path[-1] 

748 

749 if entity.mapper.isa( 

750 parent_natural_entity.mapper # type: ignore 

751 ) or parent_natural_entity.mapper.isa( # type: ignore 

752 entity.mapper 

753 ): 

754 # when the entity mapper and parent mapper are in an 

755 # inheritance relationship, use entity.mapper in natural_path. 

756 # First case: entity.mapper inherits from parent mapper (e.g., 

757 # accessing a subclass mapper through parent path). Second case 

758 # (issue #13193): parent mapper inherits from entity.mapper 

759 # (e.g., parent path has Sub(Base) but we're accessing with 

760 # Base where Base.related is declared, so use Base in 

761 # natural_path). 

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

763 else: 

764 self.natural_path = parent.natural_path + ( 

765 parent_natural_entity.entity, # type: ignore 

766 ) 

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

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

769 # sure the natural path is cacheable across different occurrences 

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

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

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

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

774 else: 

775 self.natural_path = self.path 

776 

777 def _truncate_recursive(self) -> AbstractEntityRegistry: 

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

779 

780 @property 

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

782 return self.odd_element(0) 

783 

784 @property 

785 def entity_path(self) -> PathRegistry: 

786 return self 

787 

788 @property 

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

790 return self.entity.mapper 

791 

792 def __bool__(self) -> bool: 

793 return True 

794 

795 def _getitem( 

796 self, entity: Any 

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

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

799 return self.path[entity] 

800 elif entity in PathToken._intern: 

801 return TokenRegistry(self, PathToken._intern[entity]) 

802 else: 

803 return PropRegistry(self, entity) 

804 

805 if not TYPE_CHECKING: 

806 __getitem__ = _getitem 

807 

808 

809class SlotsEntityRegistry(AbstractEntityRegistry): 

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

811 # version 

812 inherit_cache = True 

813 

814 

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

816 def __init__(self, registry: CachingEntityRegistry): 

817 self.registry = registry 

818 

819 def __missing__(self, key: Any) -> PropRegistry: 

820 self[key] = item = PropRegistry(self.registry, key) 

821 

822 return item 

823 

824 

825class CachingEntityRegistry(AbstractEntityRegistry): 

826 # for long lived mapper, return dict based caching 

827 # version that creates reference cycles 

828 

829 __slots__ = ("_cache",) 

830 

831 inherit_cache = True 

832 

833 def __init__( 

834 self, 

835 parent: Union[RootRegistry, PropRegistry], 

836 entity: _InternalEntityType[Any], 

837 ): 

838 super().__init__(parent, entity) 

839 self._cache = _ERDict(self) 

840 

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

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

843 

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

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

846 return self.path[entity] 

847 elif isinstance(entity, PathToken): 

848 return TokenRegistry(self, entity) 

849 else: 

850 return self._cache[entity] 

851 

852 if not TYPE_CHECKING: 

853 __getitem__ = _getitem 

854 

855 

856if TYPE_CHECKING: 

857 

858 def path_is_entity( 

859 path: PathRegistry, 

860 ) -> TypeGuard[AbstractEntityRegistry]: ... 

861 

862 def path_is_property(path: PathRegistry) -> TypeGuard[PropRegistry]: ... 

863 

864else: 

865 path_is_entity = operator.attrgetter("is_entity") 

866 path_is_property = operator.attrgetter("is_property")