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

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

871 statements  

1# orm/util.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# mypy: allow-untyped-defs, allow-untyped-calls 

8 

9from __future__ import annotations 

10 

11import enum 

12import functools 

13import itertools 

14import re 

15import types 

16import typing 

17from typing import AbstractSet 

18from typing import Any 

19from typing import Callable 

20from typing import cast 

21from typing import Dict 

22from typing import FrozenSet 

23from typing import Generic 

24from typing import get_origin 

25from typing import Iterable 

26from typing import Iterator 

27from typing import List 

28from typing import Literal 

29from typing import Match 

30from typing import Optional 

31from typing import Protocol 

32from typing import Sequence 

33from typing import Tuple 

34from typing import Type 

35from typing import TYPE_CHECKING 

36from typing import TypeVar 

37from typing import Union 

38import weakref 

39 

40from . import attributes # noqa 

41from . import exc as orm_exc 

42from ._typing import _O 

43from ._typing import insp_is_aliased_class 

44from ._typing import insp_is_mapper 

45from ._typing import prop_is_relationship 

46from .base import _class_to_mapper as _class_to_mapper 

47from .base import _MappedAnnotationBase 

48from .base import _never_set as _never_set # noqa: F401 

49from .base import _none_only_set as _none_only_set # noqa: F401 

50from .base import _none_set as _none_set # noqa: F401 

51from .base import attribute_str as attribute_str # noqa: F401 

52from .base import class_mapper as class_mapper 

53from .base import DynamicMapped 

54from .base import InspectionAttr as InspectionAttr 

55from .base import instance_str as instance_str # noqa: F401 

56from .base import Mapped 

57from .base import object_mapper as object_mapper 

58from .base import object_state as object_state # noqa: F401 

59from .base import opt_manager_of_class 

60from .base import ORMDescriptor 

61from .base import state_attribute_str as state_attribute_str # noqa: F401 

62from .base import state_class_str as state_class_str # noqa: F401 

63from .base import state_str as state_str # noqa: F401 

64from .base import Unmapped 

65from .base import WriteOnlyMapped 

66from .interfaces import CriteriaOption 

67from .interfaces import MapperProperty as MapperProperty 

68from .interfaces import ORMColumnsClauseRole 

69from .interfaces import ORMEntityColumnsClauseRole 

70from .interfaces import ORMFromClauseRole 

71from .path_registry import PathRegistry as PathRegistry 

72from .. import event 

73from .. import exc as sa_exc 

74from .. import inspection 

75from .. import sql 

76from .. import util 

77from ..engine.result import result_tuple 

78from ..sql import coercions 

79from ..sql import expression 

80from ..sql import lambdas 

81from ..sql import roles 

82from ..sql import util as sql_util 

83from ..sql import visitors 

84from ..sql._typing import is_selectable 

85from ..sql.annotation import SupportsCloneAnnotations 

86from ..sql.base import WriteableColumnCollection 

87from ..sql.cache_key import HasCacheKey 

88from ..sql.cache_key import MemoizedHasCacheKey 

89from ..sql.elements import ColumnElement 

90from ..sql.elements import KeyedColumnElement 

91from ..sql.schema import MetaData 

92from ..sql.selectable import FromClause 

93from ..sql.selectable import GenerativeSelect 

94from ..util.langhelpers import MemoizedSlots 

95from ..util.typing import de_stringify_annotation as _de_stringify_annotation 

96from ..util.typing import eval_name_only as _eval_name_only 

97from ..util.typing import fixup_container_fwd_refs 

98from ..util.typing import GenericProtocol 

99from ..util.typing import is_origin_of_cls 

100from ..util.typing import TupleAny 

101from ..util.typing import Unpack 

102 

103if typing.TYPE_CHECKING: 

104 from ._typing import _EntityType 

105 from ._typing import _IdentityKeyType 

106 from ._typing import _InternalEntityType 

107 from ._typing import _ORMCOLEXPR 

108 from .context import _MapperEntity 

109 from .context import _ORMCompileState 

110 from .context import _ORMSelectCompileState 

111 from .decl_api import RegistryType 

112 from .mapper import Mapper 

113 from .path_registry import _AbstractEntityRegistry 

114 from .query import Query 

115 from .relationships import RelationshipProperty 

116 from ..engine import Row 

117 from ..engine import RowMapping 

118 from ..sql._typing import _CE 

119 from ..sql._typing import _ColumnExpressionArgument 

120 from ..sql._typing import _EquivalentColumnMap 

121 from ..sql._typing import _FromClauseArgument 

122 from ..sql._typing import _OnClauseArgument 

123 from ..sql._typing import _PropagateAttrsType 

124 from ..sql.annotation import _SA 

125 from ..sql.base import ReadOnlyColumnCollection 

126 from ..sql.elements import BindParameter 

127 from ..sql.selectable import _ColumnsClauseElement 

128 from ..sql.selectable import Select 

129 from ..sql.selectable import Selectable 

130 from ..sql.visitors import anon_map 

131 from ..util.typing import _AnnotationScanType 

132 from ..util.typing import _MatchedOnType 

133 

134_T = TypeVar("_T", bound=Any) 

135 

136all_cascades = frozenset( 

137 ( 

138 "delete", 

139 "delete-orphan", 

140 "all", 

141 "merge", 

142 "expunge", 

143 "save-update", 

144 "refresh-expire", 

145 "none", 

146 ) 

147) 

148 

149_de_stringify_partial = functools.partial( 

150 functools.partial, 

151 locals_=util.immutabledict( 

152 { 

153 "Mapped": Mapped, 

154 "WriteOnlyMapped": WriteOnlyMapped, 

155 "DynamicMapped": DynamicMapped, 

156 "Unmapped": Unmapped, 

157 } 

158 ), 

159) 

160 

161# partial is practically useless as we have to write out the whole 

162# function and maintain the signature anyway 

163 

164 

165class _DeStringifyAnnotation(Protocol): 

166 def __call__( 

167 self, 

168 cls: Type[Any], 

169 annotation: _AnnotationScanType, 

170 originating_module: str, 

171 *, 

172 str_cleanup_fn: Optional[Callable[[str, str], str]] = None, 

173 include_generic: bool = False, 

174 ) -> _MatchedOnType: ... 

175 

176 

177de_stringify_annotation = cast( 

178 _DeStringifyAnnotation, _de_stringify_partial(_de_stringify_annotation) 

179) 

180 

181 

182class _EvalNameOnly(Protocol): 

183 def __call__(self, name: str, module_name: str) -> Any: ... 

184 

185 

186eval_name_only = cast(_EvalNameOnly, _de_stringify_partial(_eval_name_only)) 

187 

188 

189class CascadeOptions(FrozenSet[str]): 

190 """Keeps track of the options sent to 

191 :paramref:`.relationship.cascade`""" 

192 

193 _add_w_all_cascades = all_cascades.difference( 

194 ["all", "none", "delete-orphan"] 

195 ) 

196 _allowed_cascades = all_cascades 

197 

198 _viewonly_cascades = ["expunge", "all", "none", "refresh-expire", "merge"] 

199 

200 __slots__ = ( 

201 "save_update", 

202 "delete", 

203 "refresh_expire", 

204 "merge", 

205 "expunge", 

206 "delete_orphan", 

207 ) 

208 

209 save_update: bool 

210 delete: bool 

211 refresh_expire: bool 

212 merge: bool 

213 expunge: bool 

214 delete_orphan: bool 

215 

216 def __new__( 

217 cls, value_list: Optional[Union[Iterable[str], str]] 

218 ) -> CascadeOptions: 

219 if isinstance(value_list, str) or value_list is None: 

220 return cls.from_string(value_list) # type: ignore[no-any-return] 

221 values = set(value_list) 

222 if values.difference(cls._allowed_cascades): 

223 raise sa_exc.ArgumentError( 

224 "Invalid cascade option(s): %s" 

225 % ", ".join( 

226 [ 

227 repr(x) 

228 for x in sorted( 

229 values.difference(cls._allowed_cascades) 

230 ) 

231 ] 

232 ) 

233 ) 

234 

235 if "all" in values: 

236 values.update(cls._add_w_all_cascades) 

237 if "none" in values: 

238 values.clear() 

239 values.discard("all") 

240 

241 self = super().__new__(cls, values) 

242 self.save_update = "save-update" in values 

243 self.delete = "delete" in values 

244 self.refresh_expire = "refresh-expire" in values 

245 self.merge = "merge" in values 

246 self.expunge = "expunge" in values 

247 self.delete_orphan = "delete-orphan" in values 

248 

249 if self.delete_orphan and not self.delete: 

250 util.warn("The 'delete-orphan' cascade option requires 'delete'.") 

251 return self 

252 

253 def __repr__(self): 

254 return "CascadeOptions(%r)" % (",".join([x for x in sorted(self)])) 

255 

256 @classmethod 

257 def from_string(cls, arg): 

258 values = [c for c in re.split(r"\s*,\s*", arg or "") if c] 

259 return cls(values) 

260 

261 

262def _metadata_for_cls(cls: Type[Any], registry: RegistryType) -> MetaData: 

263 meta = getattr(cls, "metadata", None) 

264 if meta is not None and isinstance(meta, MetaData): 

265 return meta 

266 return registry.metadata 

267 

268 

269def _validator_events(desc, key, validator, include_removes, include_backrefs): 

270 """Runs a validation method on an attribute value to be set or 

271 appended. 

272 """ 

273 

274 if not include_backrefs: 

275 

276 def detect_is_backref(state, initiator): 

277 impl = state.manager[key].impl 

278 return initiator.impl is not impl 

279 

280 if include_removes: 

281 

282 def append(state, value, initiator): 

283 if initiator.op is not attributes.OP_BULK_REPLACE and ( 

284 include_backrefs or not detect_is_backref(state, initiator) 

285 ): 

286 return validator(state.obj(), key, value, False) 

287 else: 

288 return value 

289 

290 def bulk_set(state, values, initiator): 

291 if include_backrefs or not detect_is_backref(state, initiator): 

292 obj = state.obj() 

293 values[:] = [ 

294 validator(obj, key, value, False) for value in values 

295 ] 

296 

297 def set_(state, value, oldvalue, initiator): 

298 if include_backrefs or not detect_is_backref(state, initiator): 

299 return validator(state.obj(), key, value, False) 

300 else: 

301 return value 

302 

303 def remove(state, value, initiator): 

304 if include_backrefs or not detect_is_backref(state, initiator): 

305 validator(state.obj(), key, value, True) 

306 

307 else: 

308 

309 def append(state, value, initiator): 

310 if initiator.op is not attributes.OP_BULK_REPLACE and ( 

311 include_backrefs or not detect_is_backref(state, initiator) 

312 ): 

313 return validator(state.obj(), key, value) 

314 else: 

315 return value 

316 

317 def bulk_set(state, values, initiator): 

318 if include_backrefs or not detect_is_backref(state, initiator): 

319 obj = state.obj() 

320 values[:] = [validator(obj, key, value) for value in values] 

321 

322 def set_(state, value, oldvalue, initiator): 

323 if include_backrefs or not detect_is_backref(state, initiator): 

324 return validator(state.obj(), key, value) 

325 else: 

326 return value 

327 

328 event.listen(desc, "append", append, raw=True, retval=True) 

329 event.listen(desc, "bulk_replace", bulk_set, raw=True) 

330 event.listen(desc, "set", set_, raw=True, retval=True) 

331 if include_removes: 

332 event.listen(desc, "remove", remove, raw=True, retval=True) 

333 

334 

335def polymorphic_union( 

336 table_map, typecolname, aliasname="p_union", cast_nulls=True 

337): 

338 """Create a ``UNION`` statement used by a polymorphic mapper. 

339 

340 See :ref:`concrete_inheritance` for an example of how 

341 this is used. 

342 

343 :param table_map: mapping of polymorphic identities to 

344 :class:`_schema.Table` objects. 

345 :param typecolname: string name of a "discriminator" column, which will be 

346 derived from the query, producing the polymorphic identity for 

347 each row. If ``None``, no polymorphic discriminator is generated. 

348 :param aliasname: name of the :func:`~sqlalchemy.sql.expression.alias()` 

349 construct generated. 

350 :param cast_nulls: if True, non-existent columns, which are represented 

351 as labeled NULLs, will be passed into CAST. This is a legacy behavior 

352 that is problematic on some backends such as Oracle - in which case it 

353 can be set to False. 

354 

355 """ 

356 

357 colnames: util.OrderedSet[str] = util.OrderedSet() 

358 colnamemaps = {} 

359 types = {} 

360 for key in table_map: 

361 table = table_map[key] 

362 

363 table = coercions.expect(roles.FromClauseRole, table) 

364 table_map[key] = table 

365 

366 m = {} 

367 for c in table.c: 

368 if c.key == typecolname: 

369 raise sa_exc.InvalidRequestError( 

370 "Polymorphic union can't use '%s' as the discriminator " 

371 "column due to mapped column %r; please apply the " 

372 "'typecolname' " 

373 "argument; this is available on " 

374 "ConcreteBase as '_concrete_discriminator_name'" 

375 % (typecolname, c) 

376 ) 

377 colnames.add(c.key) 

378 m[c.key] = c 

379 types[c.key] = c.type 

380 colnamemaps[table] = m 

381 

382 def col(name, table): 

383 try: 

384 return colnamemaps[table][name] 

385 except KeyError: 

386 if cast_nulls: 

387 return sql.cast(sql.null(), types[name]).label(name) 

388 else: 

389 return sql.type_coerce(sql.null(), types[name]).label(name) 

390 

391 result = [] 

392 for type_, table in table_map.items(): 

393 if typecolname is not None: 

394 result.append( 

395 sql.select( 

396 *( 

397 [col(name, table) for name in colnames] 

398 + [ 

399 sql.literal_column( 

400 sql_util._quote_ddl_expr(type_) 

401 ).label(typecolname) 

402 ] 

403 ) 

404 ).select_from(table) 

405 ) 

406 else: 

407 result.append( 

408 sql.select( 

409 *[col(name, table) for name in colnames] 

410 ).select_from(table) 

411 ) 

412 return sql.union_all(*result).alias(aliasname) 

413 

414 

415def identity_key( 

416 class_: Optional[Type[_T]] = None, 

417 ident: Union[Any, Tuple[Any, ...]] = None, 

418 *, 

419 instance: Optional[_T] = None, 

420 row: Optional[Union[Row[Unpack[TupleAny]], RowMapping]] = None, 

421 identity_token: Optional[Any] = None, 

422) -> _IdentityKeyType[_T]: 

423 r"""Generate "identity key" tuples, as are used as keys in the 

424 :attr:`.Session.identity_map` dictionary. 

425 

426 This function has several call styles: 

427 

428 * ``identity_key(class, ident, identity_token=token)`` 

429 

430 This form receives a mapped class and a primary key scalar or 

431 tuple as an argument. 

432 

433 E.g.:: 

434 

435 >>> identity_key(MyClass, (1, 2)) 

436 (<class '__main__.MyClass'>, (1, 2), None) 

437 

438 :param class: mapped class (must be a positional argument) 

439 :param ident: primary key, may be a scalar or tuple argument. 

440 :param identity_token: optional identity token 

441 

442 * ``identity_key(instance=instance)`` 

443 

444 This form will produce the identity key for a given instance. The 

445 instance need not be persistent, only that its primary key attributes 

446 are populated (else the key will contain ``None`` for those missing 

447 values). 

448 

449 E.g.:: 

450 

451 >>> instance = MyClass(1, 2) 

452 >>> identity_key(instance=instance) 

453 (<class '__main__.MyClass'>, (1, 2), None) 

454 

455 In this form, the given instance is ultimately run though 

456 :meth:`_orm.Mapper.identity_key_from_instance`, which will have the 

457 effect of performing a database check for the corresponding row 

458 if the object is expired. 

459 

460 :param instance: object instance (must be given as a keyword arg) 

461 

462 * ``identity_key(class, row=row, identity_token=token)`` 

463 

464 This form is similar to the class/tuple form, except is passed a 

465 database result row as a :class:`.Row` or :class:`.RowMapping` object. 

466 

467 E.g.:: 

468 

469 >>> row = engine.execute(text("select * from table where a=1 and b=2")).first() 

470 >>> identity_key(MyClass, row=row) 

471 (<class '__main__.MyClass'>, (1, 2), None) 

472 

473 :param class: mapped class (must be a positional argument) 

474 :param row: :class:`.Row` row returned by a :class:`_engine.CursorResult` 

475 (must be given as a keyword arg) 

476 :param identity_token: optional identity token 

477 

478 """ # noqa: E501 

479 if class_ is not None: 

480 mapper = class_mapper(class_) 

481 if row is None: 

482 if ident is None: 

483 raise sa_exc.ArgumentError("ident or row is required") 

484 return mapper.identity_key_from_primary_key( 

485 tuple(util.to_list(ident)), identity_token=identity_token 

486 ) 

487 else: 

488 return mapper.identity_key_from_row( 

489 row, identity_token=identity_token 

490 ) 

491 elif instance is not None: 

492 mapper = object_mapper(instance) 

493 return mapper.identity_key_from_instance(instance) 

494 else: 

495 raise sa_exc.ArgumentError("class or instance is required") 

496 

497 

498class _TraceAdaptRole(enum.Enum): 

499 """Enumeration of all the use cases for ORMAdapter. 

500 

501 ORMAdapter remains one of the most complicated aspects of the ORM, as it is 

502 used for in-place adaption of column expressions to be applied to a SELECT, 

503 replacing :class:`.Table` and other objects that are mapped to classes with 

504 aliases of those tables in the case of joined eager loading, or in the case 

505 of polymorphic loading as used with concrete mappings or other custom "with 

506 polymorphic" parameters, with whole user-defined subqueries. The 

507 enumerations provide an overview of all the use cases used by ORMAdapter, a 

508 layer of formality as to the introduction of new ORMAdapter use cases (of 

509 which none are anticipated), as well as a means to trace the origins of a 

510 particular ORMAdapter within runtime debugging. 

511 

512 SQLAlchemy 2.0 has greatly scaled back ORM features which relied heavily on 

513 open-ended statement adaption, including the ``Query.with_polymorphic()`` 

514 method and the ``Query.select_from_entity()`` methods, favoring 

515 user-explicit aliasing schemes using the ``aliased()`` and 

516 ``with_polymorphic()`` standalone constructs; these still use adaption, 

517 however the adaption is applied in a narrower scope. 

518 

519 """ 

520 

521 # aliased() use that is used to adapt individual attributes at query 

522 # construction time 

523 ALIASED_INSP = enum.auto() 

524 

525 # joinedload cases; typically adapt an ON clause of a relationship 

526 # join 

527 JOINEDLOAD_USER_DEFINED_ALIAS = enum.auto() 

528 JOINEDLOAD_PATH_WITH_POLYMORPHIC = enum.auto() 

529 JOINEDLOAD_MEMOIZED_ADAPTER = enum.auto() 

530 

531 # polymorphic cases - these are complex ones that replace FROM 

532 # clauses, replacing tables with subqueries 

533 MAPPER_POLYMORPHIC_ADAPTER = enum.auto() 

534 WITH_POLYMORPHIC_ADAPTER = enum.auto() 

535 WITH_POLYMORPHIC_ADAPTER_RIGHT_JOIN = enum.auto() 

536 DEPRECATED_JOIN_ADAPT_RIGHT_SIDE = enum.auto() 

537 

538 # the from_statement() case, used only to adapt individual attributes 

539 # from a given statement to local ORM attributes at result fetching 

540 # time. assigned to ORMCompileState._from_obj_alias 

541 ADAPT_FROM_STATEMENT = enum.auto() 

542 

543 # the joinedload for queries that have LIMIT/OFFSET/DISTINCT case; 

544 # the query is placed inside of a subquery with the LIMIT/OFFSET/etc., 

545 # joinedloads are then placed on the outside. 

546 # assigned to ORMCompileState.compound_eager_adapter 

547 COMPOUND_EAGER_STATEMENT = enum.auto() 

548 

549 # the legacy Query._set_select_from() case. 

550 # this is needed for Query's set operations (i.e. UNION, etc. ) 

551 # as well as "legacy from_self()", which while removed from 2.0 as 

552 # public API, is used for the Query.count() method. this one 

553 # still does full statement traversal 

554 # assigned to ORMCompileState._from_obj_alias 

555 LEGACY_SELECT_FROM_ALIAS = enum.auto() 

556 

557 

558class ORMStatementAdapter(sql_util.ColumnAdapter): 

559 """ColumnAdapter which includes a role attribute.""" 

560 

561 __slots__ = ("role",) 

562 

563 def __init__( 

564 self, 

565 role: _TraceAdaptRole, 

566 selectable: Selectable, 

567 *, 

568 equivalents: Optional[_EquivalentColumnMap] = None, 

569 adapt_required: bool = False, 

570 allow_label_resolve: bool = True, 

571 anonymize_labels: bool = False, 

572 adapt_on_names: bool = False, 

573 adapt_from_selectables: Optional[AbstractSet[FromClause]] = None, 

574 ): 

575 self.role = role 

576 super().__init__( 

577 selectable, 

578 equivalents=equivalents, 

579 adapt_required=adapt_required, 

580 allow_label_resolve=allow_label_resolve, 

581 anonymize_labels=anonymize_labels, 

582 adapt_on_names=adapt_on_names, 

583 adapt_from_selectables=adapt_from_selectables, 

584 ) 

585 

586 

587class ORMAdapter(sql_util.ColumnAdapter): 

588 """ColumnAdapter subclass which excludes adaptation of entities from 

589 non-matching mappers. 

590 

591 """ 

592 

593 __slots__ = ("role", "mapper", "is_aliased_class", "aliased_insp") 

594 

595 is_aliased_class: bool 

596 aliased_insp: Optional[AliasedInsp[Any]] 

597 

598 def __init__( 

599 self, 

600 role: _TraceAdaptRole, 

601 entity: _InternalEntityType[Any], 

602 *, 

603 equivalents: Optional[_EquivalentColumnMap] = None, 

604 adapt_required: bool = False, 

605 allow_label_resolve: bool = True, 

606 anonymize_labels: bool = False, 

607 selectable: Optional[Selectable] = None, 

608 limit_on_entity: bool = True, 

609 adapt_on_names: bool = False, 

610 adapt_from_selectables: Optional[AbstractSet[FromClause]] = None, 

611 ): 

612 self.role = role 

613 self.mapper = entity.mapper 

614 if selectable is None: 

615 selectable = entity.selectable 

616 if insp_is_aliased_class(entity): 

617 self.is_aliased_class = True 

618 self.aliased_insp = entity 

619 else: 

620 self.is_aliased_class = False 

621 self.aliased_insp = None 

622 

623 super().__init__( 

624 selectable, 

625 equivalents, 

626 adapt_required=adapt_required, 

627 allow_label_resolve=allow_label_resolve, 

628 anonymize_labels=anonymize_labels, 

629 include_fn=self._include_fn if limit_on_entity else None, 

630 adapt_on_names=adapt_on_names, 

631 adapt_from_selectables=adapt_from_selectables, 

632 ) 

633 

634 def _include_fn(self, elem): 

635 entity = elem._annotations.get("parentmapper", None) 

636 

637 return not entity or entity.isa(self.mapper) or self.mapper.isa(entity) 

638 

639 

640def _is_alias_of_selectable( 

641 selectable: FromClause, target: FromClause 

642) -> bool: 

643 """Return True if ``selectable`` is ``target``, or a plain alias of it, 

644 including a flat alias of a join of the same structure and join types.""" 

645 

646 if selectable is target or ( 

647 isinstance(selectable, expression.Alias) 

648 and selectable.element is target 

649 ): 

650 return True 

651 

652 return all( 

653 elem is tgt 

654 or (isinstance(elem, expression.Alias) and elem.element is tgt) 

655 or ( 

656 # zip_longest() pads the shorter of the two with None 

657 elem is not None 

658 and tgt is not None 

659 and elem._is_join 

660 and tgt._is_join 

661 and elem.isouter == tgt.isouter 

662 and elem.full == tgt.full 

663 ) 

664 or ( 

665 isinstance(elem, expression.FromGrouping) 

666 and isinstance(tgt, expression.FromGrouping) 

667 ) 

668 for elem, tgt in itertools.zip_longest( 

669 sql_util.surface_selectables(selectable), 

670 sql_util.surface_selectables(target), 

671 ) 

672 ) 

673 

674 

675class AliasedClass( 

676 inspection.Inspectable["AliasedInsp[_O]"], ORMColumnsClauseRole[_O] 

677): 

678 r"""Represents an "aliased" form of a mapped class for usage with Query. 

679 

680 The ORM equivalent of a :func:`~sqlalchemy.sql.expression.alias` 

681 construct, this object mimics the mapped class using a 

682 ``__getattr__`` scheme and maintains a reference to a 

683 real :class:`~sqlalchemy.sql.expression.Alias` object. 

684 

685 A primary purpose of :class:`.AliasedClass` is to serve as an alternate 

686 within a SQL statement generated by the ORM, such that an existing 

687 mapped entity can be used in multiple contexts. A simple example:: 

688 

689 # find all pairs of users with the same name 

690 user_alias = aliased(User) 

691 session.query(User, user_alias).join( 

692 (user_alias, User.id > user_alias.id) 

693 ).filter(User.name == user_alias.name) 

694 

695 :class:`.AliasedClass` is also capable of mapping an existing mapped 

696 class to an entirely new selectable, provided this selectable is column- 

697 compatible with the existing mapped selectable, and it can also be 

698 configured in a mapping as the target of a :func:`_orm.relationship`. 

699 See the links below for examples. 

700 

701 The :class:`.AliasedClass` object is constructed typically using the 

702 :func:`_orm.aliased` function. It also is produced with additional 

703 configuration when using the :func:`_orm.with_polymorphic` function. 

704 

705 The resulting object is an instance of :class:`.AliasedClass`. 

706 This object implements an attribute scheme which produces the 

707 same attribute and method interface as the original mapped 

708 class, allowing :class:`.AliasedClass` to be compatible 

709 with any attribute technique which works on the original class, 

710 including hybrid attributes (see :ref:`hybrids_toplevel`). 

711 

712 The :class:`.AliasedClass` can be inspected for its underlying 

713 :class:`_orm.Mapper`, aliased selectable, and other information 

714 using :func:`_sa.inspect`:: 

715 

716 from sqlalchemy import inspect 

717 

718 my_alias = aliased(MyClass) 

719 insp = inspect(my_alias) 

720 

721 The resulting inspection object is an instance of :class:`.AliasedInsp`. 

722 

723 

724 .. seealso:: 

725 

726 :func:`.aliased` 

727 

728 :func:`.with_polymorphic` 

729 

730 :ref:`relationship_aliased_class` 

731 

732 :ref:`relationship_to_window_function` 

733 

734 

735 """ 

736 

737 __name__: str 

738 

739 def __init__( 

740 self, 

741 mapped_class_or_ac: _EntityType[_O], 

742 alias: Optional[FromClause] = None, 

743 name: Optional[str] = None, 

744 flat: bool = False, 

745 adapt_on_names: bool = False, 

746 with_polymorphic_mappers: Optional[Sequence[Mapper[Any]]] = None, 

747 with_polymorphic_discriminator: Optional[ColumnElement[Any]] = None, 

748 base_alias: Optional[AliasedInsp[Any]] = None, 

749 use_mapper_path: bool = False, 

750 represents_outer_join: bool = False, 

751 ): 

752 insp = cast( 

753 "_InternalEntityType[_O]", inspection.inspect(mapped_class_or_ac) 

754 ) 

755 mapper = insp.mapper 

756 

757 nest_adapters = False 

758 

759 if alias is None: 

760 if insp_is_aliased_class(insp): 

761 if insp._is_with_polymorphic: 

762 # polymorphic arguments are only passed along with an 

763 # explicit selectable, by with_polymorphic() and 

764 # AliasedInsp._merge_with() 

765 assert ( 

766 not with_polymorphic_mappers 

767 and with_polymorphic_discriminator is None 

768 and not represents_outer_join 

769 ) 

770 

771 # aliased() of a with_polymorphic() carries along its 

772 # polymorphic configuration, #13584 

773 with_polymorphic_mappers = insp.with_polymorphic_mappers 

774 with_polymorphic_discriminator = insp.polymorphic_on 

775 represents_outer_join = insp.represents_outer_join 

776 

777 if not _is_alias_of_selectable( 

778 insp.selectable, mapper._with_polymorphic_selectable 

779 ): 

780 # aliased() of an aliased() that refers to a subquery, 

781 # CTE, with_polymorphic() or other selectable; alias 

782 # that selectable rather than the mapped table, 

783 # #13583, #13584 

784 alias = insp.selectable._anonymous_fromclause( 

785 name=name, flat=flat 

786 ) 

787 adapt_on_names = adapt_on_names or insp._adapt_on_names 

788 elif insp.selectable._is_subquery: 

789 alias = insp.selectable.alias() 

790 

791 if alias is None: 

792 alias = ( 

793 mapper._with_polymorphic_selectable._anonymous_fromclause( 

794 name=name, 

795 flat=flat, 

796 ) 

797 ) 

798 elif insp.is_aliased_class: 

799 nest_adapters = True 

800 

801 assert alias is not None 

802 self._aliased_insp = AliasedInsp( 

803 self, 

804 insp, 

805 alias, 

806 name, 

807 ( 

808 with_polymorphic_mappers 

809 if with_polymorphic_mappers 

810 else mapper.with_polymorphic_mappers 

811 ), 

812 ( 

813 with_polymorphic_discriminator 

814 if with_polymorphic_discriminator is not None 

815 else mapper.polymorphic_on 

816 ), 

817 base_alias, 

818 use_mapper_path, 

819 adapt_on_names, 

820 represents_outer_join, 

821 nest_adapters, 

822 ) 

823 

824 self.__name__ = f"aliased({mapper.class_.__name__})" 

825 

826 @classmethod 

827 def _reconstitute_from_aliased_insp( 

828 cls, aliased_insp: AliasedInsp[_O] 

829 ) -> AliasedClass[_O]: 

830 obj = cls.__new__(cls) 

831 obj.__name__ = f"aliased({aliased_insp.mapper.class_.__name__})" 

832 obj._aliased_insp = aliased_insp 

833 

834 if aliased_insp._is_with_polymorphic: 

835 for sub_aliased_insp in aliased_insp._with_polymorphic_entities: 

836 if sub_aliased_insp is not aliased_insp: 

837 ent = AliasedClass._reconstitute_from_aliased_insp( 

838 sub_aliased_insp 

839 ) 

840 setattr(obj, sub_aliased_insp.class_.__name__, ent) 

841 

842 return obj 

843 

844 def __getattr__(self, key: str) -> Any: 

845 try: 

846 _aliased_insp = self.__dict__["_aliased_insp"] 

847 except KeyError: 

848 raise AttributeError() 

849 else: 

850 target = _aliased_insp._target 

851 # maintain all getattr mechanics 

852 attr = getattr(target, key) 

853 

854 # attribute is a method, that will be invoked against a 

855 # "self"; so just return a new method with the same function and 

856 # new self 

857 if hasattr(attr, "__call__") and hasattr(attr, "__self__"): 

858 return types.MethodType(attr.__func__, self) 

859 

860 # attribute is a descriptor, that will be invoked against a 

861 # "self"; so invoke the descriptor against this self 

862 if hasattr(attr, "__get__"): 

863 attr = attr.__get__(None, self) 

864 

865 # attributes within the QueryableAttribute system will want this 

866 # to be invoked so the object can be adapted 

867 if hasattr(attr, "adapt_to_entity"): 

868 attr = attr.adapt_to_entity(_aliased_insp) 

869 setattr(self, key, attr) 

870 

871 return attr 

872 

873 def _get_from_serialized( 

874 self, key: str, mapped_class: _O, aliased_insp: AliasedInsp[_O] 

875 ) -> Any: 

876 # this method is only used in terms of the 

877 # sqlalchemy.ext.serializer extension 

878 attr = getattr(mapped_class, key) 

879 if hasattr(attr, "__call__") and hasattr(attr, "__self__"): 

880 return types.MethodType(attr.__func__, self) 

881 

882 # attribute is a descriptor, that will be invoked against a 

883 # "self"; so invoke the descriptor against this self 

884 if hasattr(attr, "__get__"): 

885 attr = attr.__get__(None, self) 

886 

887 # attributes within the QueryableAttribute system will want this 

888 # to be invoked so the object can be adapted 

889 if hasattr(attr, "adapt_to_entity"): 

890 aliased_insp._weak_entity = weakref.ref(self) 

891 attr = attr.adapt_to_entity(aliased_insp) 

892 setattr(self, key, attr) 

893 

894 return attr 

895 

896 def __repr__(self) -> str: 

897 return "<AliasedClass at 0x%x; %s>" % ( 

898 id(self), 

899 self._aliased_insp._target.__name__, 

900 ) 

901 

902 def __str__(self) -> str: 

903 return str(self._aliased_insp) 

904 

905 

906@inspection._self_inspects 

907class AliasedInsp( 

908 ORMEntityColumnsClauseRole[_O], 

909 ORMFromClauseRole, 

910 HasCacheKey, 

911 InspectionAttr, 

912 MemoizedSlots, 

913 inspection.Inspectable["AliasedInsp[_O]"], 

914 Generic[_O], 

915): 

916 """Provide an inspection interface for an 

917 :class:`.AliasedClass` object. 

918 

919 The :class:`.AliasedInsp` object is returned 

920 given an :class:`.AliasedClass` using the 

921 :func:`_sa.inspect` function:: 

922 

923 from sqlalchemy import inspect 

924 from sqlalchemy.orm import aliased 

925 

926 my_alias = aliased(MyMappedClass) 

927 insp = inspect(my_alias) 

928 

929 Attributes on :class:`.AliasedInsp` 

930 include: 

931 

932 * ``entity`` - the :class:`.AliasedClass` represented. 

933 * ``mapper`` - the :class:`_orm.Mapper` mapping the underlying class. 

934 * ``selectable`` - the :class:`_expression.Alias` 

935 construct which ultimately 

936 represents an aliased :class:`_schema.Table` or 

937 :class:`_expression.Select` 

938 construct. 

939 * ``name`` - the name of the alias. Also is used as the attribute 

940 name when returned in a result tuple from :class:`_query.Query`. 

941 * ``with_polymorphic_mappers`` - collection of :class:`_orm.Mapper` 

942 objects 

943 indicating all those mappers expressed in the select construct 

944 for the :class:`.AliasedClass`. 

945 * ``polymorphic_on`` - an alternate column or SQL expression which 

946 will be used as the "discriminator" for a polymorphic load. 

947 

948 .. seealso:: 

949 

950 :ref:`inspection_toplevel` 

951 

952 """ 

953 

954 __slots__ = ( 

955 "__weakref__", 

956 "_weak_entity", 

957 "mapper", 

958 "selectable", 

959 "name", 

960 "_adapt_on_names", 

961 "with_polymorphic_mappers", 

962 "polymorphic_on", 

963 "_use_mapper_path", 

964 "_base_alias", 

965 "represents_outer_join", 

966 "persist_selectable", 

967 "local_table", 

968 "_is_with_polymorphic", 

969 "_with_polymorphic_entities", 

970 "_adapter", 

971 "_target", 

972 "__clause_element__", 

973 "_memoized_values", 

974 "_all_column_expressions", 

975 "_nest_adapters", 

976 ) 

977 

978 _cache_key_traversal = [ 

979 ("name", visitors.ExtendedInternalTraversal.dp_string), 

980 ("_adapt_on_names", visitors.ExtendedInternalTraversal.dp_boolean), 

981 ("_use_mapper_path", visitors.ExtendedInternalTraversal.dp_boolean), 

982 ("_target", visitors.ExtendedInternalTraversal.dp_inspectable), 

983 ("selectable", visitors.ExtendedInternalTraversal.dp_clauseelement), 

984 ( 

985 "with_polymorphic_mappers", 

986 visitors.InternalTraversal.dp_has_cache_key_list, 

987 ), 

988 ("polymorphic_on", visitors.InternalTraversal.dp_clauseelement), 

989 ] 

990 

991 mapper: Mapper[_O] 

992 selectable: FromClause 

993 _adapter: ORMAdapter 

994 with_polymorphic_mappers: Sequence[Mapper[Any]] 

995 _with_polymorphic_entities: Sequence[AliasedInsp[Any]] 

996 _adapt_on_names: bool 

997 

998 _weak_entity: weakref.ref[AliasedClass[_O]] 

999 """the AliasedClass that refers to this AliasedInsp""" 

1000 

1001 _target: Union[Type[_O], AliasedClass[_O]] 

1002 """the thing referenced by the AliasedClass/AliasedInsp. 

1003 

1004 In the vast majority of cases, this is the mapped class. However 

1005 it may also be another AliasedClass (alias of alias). 

1006 

1007 """ 

1008 

1009 def __init__( 

1010 self, 

1011 entity: AliasedClass[_O], 

1012 inspected: _InternalEntityType[_O], 

1013 selectable: FromClause, 

1014 name: Optional[str], 

1015 with_polymorphic_mappers: Optional[Sequence[Mapper[Any]]], 

1016 polymorphic_on: Optional[ColumnElement[Any]], 

1017 _base_alias: Optional[AliasedInsp[Any]], 

1018 _use_mapper_path: bool, 

1019 adapt_on_names: bool, 

1020 represents_outer_join: bool, 

1021 nest_adapters: bool, 

1022 ): 

1023 mapped_class_or_ac = inspected.entity 

1024 mapper = inspected.mapper 

1025 

1026 self._weak_entity = weakref.ref(entity) 

1027 self.mapper = mapper 

1028 self.selectable = self.persist_selectable = self.local_table = ( 

1029 selectable 

1030 ) 

1031 self.name = name 

1032 self.polymorphic_on = polymorphic_on 

1033 self._base_alias = weakref.ref(_base_alias or self) 

1034 self._use_mapper_path = _use_mapper_path 

1035 self.represents_outer_join = represents_outer_join 

1036 self._nest_adapters = nest_adapters 

1037 

1038 if with_polymorphic_mappers: 

1039 self._is_with_polymorphic = True 

1040 self.with_polymorphic_mappers = with_polymorphic_mappers 

1041 self._with_polymorphic_entities = [] 

1042 for poly in self.with_polymorphic_mappers: 

1043 if poly is not mapper: 

1044 ent = AliasedClass( 

1045 poly.class_, 

1046 selectable, 

1047 base_alias=self, 

1048 adapt_on_names=adapt_on_names, 

1049 use_mapper_path=_use_mapper_path, 

1050 ) 

1051 

1052 setattr(self.entity, poly.class_.__name__, ent) 

1053 self._with_polymorphic_entities.append(ent._aliased_insp) 

1054 

1055 else: 

1056 self._is_with_polymorphic = False 

1057 self.with_polymorphic_mappers = [mapper] 

1058 

1059 self._adapter = ORMAdapter( 

1060 _TraceAdaptRole.ALIASED_INSP, 

1061 mapper, 

1062 selectable=selectable, 

1063 equivalents=mapper._equivalent_columns, 

1064 adapt_on_names=adapt_on_names, 

1065 anonymize_labels=True, 

1066 # make sure the adapter doesn't try to grab other tables that 

1067 # are not even the thing we are mapping, such as embedded 

1068 # selectables in subqueries or CTEs. See issue #6060 

1069 adapt_from_selectables={ 

1070 m.selectable 

1071 for m in self.with_polymorphic_mappers 

1072 if not adapt_on_names 

1073 }, 

1074 limit_on_entity=False, 

1075 ) 

1076 

1077 if nest_adapters: 

1078 # supports "aliased class of aliased class" use case 

1079 assert isinstance(inspected, AliasedInsp) 

1080 self._adapter = inspected._adapter.wrap(self._adapter) 

1081 

1082 self._adapt_on_names = adapt_on_names 

1083 self._target = mapped_class_or_ac 

1084 

1085 @property 

1086 def _post_inspect(self): # type: ignore[override] 

1087 self.mapper._check_configure() 

1088 

1089 @classmethod 

1090 def _alias_factory( 

1091 cls, 

1092 element: Union[_EntityType[_O], FromClause], 

1093 alias: Optional[FromClause] = None, 

1094 name: Optional[str] = None, 

1095 flat: bool = False, 

1096 adapt_on_names: bool = False, 

1097 ) -> Union[AliasedClass[_O], FromClause]: 

1098 if isinstance(element, GenerativeSelect): 

1099 return coercions.expect(roles.FromClauseRole, element, flat=flat) 

1100 elif isinstance(element, FromClause): 

1101 if adapt_on_names: 

1102 raise sa_exc.ArgumentError( 

1103 "adapt_on_names only applies to ORM elements" 

1104 ) 

1105 if name: 

1106 return element.alias(name=name, flat=flat) 

1107 else: 

1108 # see selectable.py->Alias._factory() for similar 

1109 # mypy issue. Cannot get the overload to see this 

1110 # in mypy (works fine in pyright) 

1111 return coercions.expect( # type: ignore[no-any-return] 

1112 roles.AnonymizedFromClauseRole, element, flat=flat 

1113 ) 

1114 else: 

1115 return AliasedClass( 

1116 element, 

1117 alias=alias, 

1118 flat=flat, 

1119 name=name, 

1120 adapt_on_names=adapt_on_names, 

1121 ) 

1122 

1123 @classmethod 

1124 def _with_polymorphic_factory( 

1125 cls, 

1126 base: Union[Type[_O], Mapper[_O]], 

1127 classes: Union[Literal["*"], Iterable[_EntityType[Any]]], 

1128 selectable: Union[Literal[False, None], FromClause] = False, 

1129 flat: bool = False, 

1130 polymorphic_on: Optional[ColumnElement[Any]] = None, 

1131 aliased: bool = False, 

1132 innerjoin: bool = False, 

1133 adapt_on_names: bool = False, 

1134 name: Optional[str] = None, 

1135 _use_mapper_path: bool = False, 

1136 ) -> AliasedClass[_O]: 

1137 primary_mapper = _class_to_mapper(base) 

1138 

1139 if selectable not in (None, False) and flat: 

1140 raise sa_exc.ArgumentError( 

1141 "the 'flat' and 'selectable' arguments cannot be passed " 

1142 "simultaneously to with_polymorphic()" 

1143 ) 

1144 

1145 mappers, selectable = primary_mapper._with_polymorphic_args( 

1146 classes, selectable, innerjoin=innerjoin 

1147 ) 

1148 if aliased or flat: 

1149 assert selectable is not None 

1150 selectable = selectable._anonymous_fromclause(flat=flat) 

1151 

1152 return AliasedClass( 

1153 base, 

1154 selectable, 

1155 name=name, 

1156 with_polymorphic_mappers=mappers, 

1157 adapt_on_names=adapt_on_names, 

1158 with_polymorphic_discriminator=polymorphic_on, 

1159 use_mapper_path=_use_mapper_path, 

1160 represents_outer_join=not innerjoin, 

1161 ) 

1162 

1163 @property 

1164 def entity(self) -> AliasedClass[_O]: 

1165 # to eliminate reference cycles, the AliasedClass is held weakly. 

1166 # this produces some situations where the AliasedClass gets lost, 

1167 # particularly when one is created internally and only the AliasedInsp 

1168 # is passed around. 

1169 # to work around this case, we just generate a new one when we need 

1170 # it, as it is a simple class with very little initial state on it. 

1171 ent = self._weak_entity() 

1172 if ent is None: 

1173 ent = AliasedClass._reconstitute_from_aliased_insp(self) 

1174 self._weak_entity = weakref.ref(ent) 

1175 return ent 

1176 

1177 is_aliased_class = True 

1178 "always returns True" 

1179 

1180 def _memoized_method___clause_element__(self) -> FromClause: 

1181 return self.selectable._annotate( 

1182 { 

1183 "parentmapper": self.mapper, 

1184 "parententity": self, 

1185 "entity_namespace": self, 

1186 } 

1187 )._set_propagate_attrs( 

1188 {"compile_state_plugin": "orm", "plugin_subject": self} 

1189 ) 

1190 

1191 @property 

1192 def entity_namespace(self) -> AliasedClass[_O]: 

1193 return self.entity 

1194 

1195 @property 

1196 def class_(self) -> Type[_O]: 

1197 """Return the mapped class ultimately represented by this 

1198 :class:`.AliasedInsp`.""" 

1199 return self.mapper.class_ 

1200 

1201 @property 

1202 def _path_registry(self) -> _AbstractEntityRegistry: 

1203 if self._use_mapper_path: 

1204 return self.mapper._path_registry 

1205 else: 

1206 return PathRegistry.per_mapper(self) 

1207 

1208 def __getstate__(self) -> Dict[str, Any]: 

1209 return { 

1210 "entity": self.entity, 

1211 "mapper": self.mapper, 

1212 "alias": self.selectable, 

1213 "name": self.name, 

1214 "adapt_on_names": self._adapt_on_names, 

1215 "with_polymorphic_mappers": self.with_polymorphic_mappers, 

1216 "with_polymorphic_discriminator": self.polymorphic_on, 

1217 "base_alias": self._base_alias(), 

1218 "use_mapper_path": self._use_mapper_path, 

1219 "represents_outer_join": self.represents_outer_join, 

1220 "nest_adapters": self._nest_adapters, 

1221 } 

1222 

1223 def __setstate__(self, state: Dict[str, Any]) -> None: 

1224 self.__init__( # type: ignore[misc] 

1225 state["entity"], 

1226 state["mapper"], 

1227 state["alias"], 

1228 state["name"], 

1229 state["with_polymorphic_mappers"], 

1230 state["with_polymorphic_discriminator"], 

1231 state["base_alias"], 

1232 state["use_mapper_path"], 

1233 state["adapt_on_names"], 

1234 state["represents_outer_join"], 

1235 state["nest_adapters"], 

1236 ) 

1237 

1238 def _merge_with(self, other: AliasedInsp[_O]) -> AliasedInsp[_O]: 

1239 # assert self._is_with_polymorphic 

1240 # assert other._is_with_polymorphic 

1241 

1242 primary_mapper = other.mapper 

1243 

1244 assert self.mapper is primary_mapper 

1245 

1246 our_classes = util.to_set( 

1247 mp.class_ for mp in self.with_polymorphic_mappers 

1248 ) 

1249 new_classes = {mp.class_ for mp in other.with_polymorphic_mappers} 

1250 if our_classes == new_classes: 

1251 return other 

1252 else: 

1253 classes = our_classes.union(new_classes) 

1254 

1255 mappers, selectable = primary_mapper._with_polymorphic_args( 

1256 classes, None, innerjoin=not other.represents_outer_join 

1257 ) 

1258 selectable = selectable._anonymous_fromclause(flat=True) 

1259 return AliasedClass( 

1260 primary_mapper, 

1261 selectable, 

1262 with_polymorphic_mappers=mappers, 

1263 with_polymorphic_discriminator=other.polymorphic_on, 

1264 use_mapper_path=other._use_mapper_path, 

1265 represents_outer_join=other.represents_outer_join, 

1266 )._aliased_insp 

1267 

1268 def _adapt_element( 

1269 self, expr: _ORMCOLEXPR, key: Optional[str] = None 

1270 ) -> _ORMCOLEXPR: 

1271 assert isinstance(expr, ColumnElement) 

1272 d: Dict[str, Any] = { 

1273 "parententity": self, 

1274 "parentmapper": self.mapper, 

1275 } 

1276 if key: 

1277 d["proxy_key"] = key 

1278 

1279 # userspace adapt of an attribute from AliasedClass; validate that 

1280 # it actually was present 

1281 adapted = self._adapter.adapt_check_present(expr) 

1282 if adapted is None: 

1283 adapted = expr 

1284 if self._adapter.adapt_on_names: 

1285 util.warn_limited( 

1286 "Did not locate an expression in selectable for " 

1287 "attribute %r; ensure name is correct in expression", 

1288 (key,), 

1289 ) 

1290 else: 

1291 util.warn_limited( 

1292 "Did not locate an expression in selectable for " 

1293 "attribute %r; to match by name, use the " 

1294 "adapt_on_names parameter", 

1295 (key,), 

1296 ) 

1297 

1298 return adapted._annotate(d)._set_propagate_attrs( 

1299 {"compile_state_plugin": "orm", "plugin_subject": self} 

1300 ) 

1301 

1302 if TYPE_CHECKING: 

1303 # establish compatibility with the _ORMAdapterProto protocol, 

1304 # which in turn is compatible with _CoreAdapterProto. 

1305 

1306 def _orm_adapt_element( 

1307 self, 

1308 obj: _CE, 

1309 key: Optional[str] = None, 

1310 ) -> _CE: ... 

1311 

1312 else: 

1313 _orm_adapt_element = _adapt_element 

1314 

1315 def _entity_for_mapper(self, mapper): 

1316 self_poly = self.with_polymorphic_mappers 

1317 if mapper in self_poly: 

1318 if mapper is self.mapper: 

1319 return self 

1320 else: 

1321 return getattr( 

1322 self.entity, mapper.class_.__name__ 

1323 )._aliased_insp 

1324 elif mapper.isa(self.mapper): 

1325 return self 

1326 else: 

1327 assert False, "mapper %s doesn't correspond to %s" % (mapper, self) 

1328 

1329 def _memoized_attr__get_clause(self): 

1330 onclause, replacemap = self.mapper._get_clause 

1331 return ( 

1332 self._adapter.traverse(onclause), 

1333 { 

1334 self._adapter.traverse(col): param 

1335 for col, param in replacemap.items() 

1336 }, 

1337 ) 

1338 

1339 def _memoized_attr__memoized_values(self): 

1340 return {} 

1341 

1342 def _memoized_attr__all_column_expressions(self): 

1343 if self._is_with_polymorphic: 

1344 cols_plus_keys = self.mapper._columns_plus_keys( 

1345 [ent.mapper for ent in self._with_polymorphic_entities] 

1346 ) 

1347 else: 

1348 cols_plus_keys = self.mapper._columns_plus_keys() 

1349 

1350 cols_plus_keys = [ 

1351 (key, self._adapt_element(col)) for key, col in cols_plus_keys 

1352 ] 

1353 

1354 return WriteableColumnCollection(cols_plus_keys) 

1355 

1356 def _memo(self, key, callable_, *args, **kw): 

1357 if key in self._memoized_values: 

1358 return self._memoized_values[key] 

1359 else: 

1360 self._memoized_values[key] = value = callable_(*args, **kw) 

1361 return value 

1362 

1363 def __repr__(self): 

1364 if self.with_polymorphic_mappers: 

1365 with_poly = "(%s)" % ", ".join( 

1366 mp.class_.__name__ for mp in self.with_polymorphic_mappers 

1367 ) 

1368 else: 

1369 with_poly = "" 

1370 return "<AliasedInsp at 0x%x; %s%s>" % ( 

1371 id(self), 

1372 self.class_.__name__, 

1373 with_poly, 

1374 ) 

1375 

1376 def __str__(self): 

1377 return self.path_string() 

1378 

1379 def path_string(self) -> str: 

1380 """Return a user-facing name for this :class:`.AliasedInsp`, 

1381 for use in a :class:`_orm.PathRegistry` string representation. 

1382 

1383 """ 

1384 if self._is_with_polymorphic: 

1385 return "with_polymorphic(%s, [%s])" % ( 

1386 self._target.__name__, 

1387 ", ".join( 

1388 mp.class_.__name__ 

1389 for mp in self.with_polymorphic_mappers 

1390 if mp is not self.mapper 

1391 ), 

1392 ) 

1393 else: 

1394 return "aliased(%s)" % (self._target.__name__,) 

1395 

1396 

1397class _WrapUserEntity: 

1398 """A wrapper used within the loader_criteria lambda caller so that 

1399 we can bypass declared_attr descriptors on unmapped mixins, which 

1400 normally emit a warning for such use. 

1401 

1402 might also be useful for other per-lambda instrumentations should 

1403 the need arise. 

1404 

1405 """ 

1406 

1407 __slots__ = ("subject",) 

1408 

1409 def __init__(self, subject): 

1410 self.subject = subject 

1411 

1412 @util.preload_module("sqlalchemy.orm.decl_api") 

1413 def __getattribute__(self, name): 

1414 decl_api = util.preloaded.orm.decl_api 

1415 

1416 subject = object.__getattribute__(self, "subject") 

1417 if name in subject.__dict__ and isinstance( 

1418 subject.__dict__[name], decl_api.declared_attr 

1419 ): 

1420 return subject.__dict__[name].fget(subject) 

1421 else: 

1422 return getattr(subject, name) 

1423 

1424 

1425class LoaderCriteriaOption(CriteriaOption): 

1426 """Add additional WHERE criteria to the load for all occurrences of 

1427 a particular entity. 

1428 

1429 :class:`_orm.LoaderCriteriaOption` is invoked using the 

1430 :func:`_orm.with_loader_criteria` function; see that function for 

1431 details. 

1432 

1433 .. versionadded:: 1.4 

1434 

1435 """ 

1436 

1437 __slots__ = ( 

1438 "root_entity", 

1439 "entity", 

1440 "deferred_where_criteria", 

1441 "where_criteria", 

1442 "_where_crit_orig", 

1443 "include_aliases", 

1444 "propagate_to_loaders", 

1445 ) 

1446 

1447 _traverse_internals = [ 

1448 ("root_entity", visitors.ExtendedInternalTraversal.dp_plain_obj), 

1449 ("entity", visitors.ExtendedInternalTraversal.dp_has_cache_key), 

1450 ("where_criteria", visitors.InternalTraversal.dp_clauseelement), 

1451 ("include_aliases", visitors.InternalTraversal.dp_boolean), 

1452 ("propagate_to_loaders", visitors.InternalTraversal.dp_boolean), 

1453 ] 

1454 

1455 root_entity: Optional[Type[Any]] 

1456 entity: Optional[_InternalEntityType[Any]] 

1457 where_criteria: Union[ColumnElement[bool], lambdas.DeferredLambdaElement] 

1458 deferred_where_criteria: bool 

1459 include_aliases: bool 

1460 propagate_to_loaders: bool 

1461 

1462 _where_crit_orig: Any 

1463 

1464 def __init__( 

1465 self, 

1466 entity_or_base: _EntityType[Any], 

1467 where_criteria: Union[ 

1468 _ColumnExpressionArgument[bool], 

1469 Callable[[Any], _ColumnExpressionArgument[bool]], 

1470 ], 

1471 loader_only: bool = False, 

1472 include_aliases: bool = False, 

1473 propagate_to_loaders: bool = True, 

1474 track_closure_variables: bool = True, 

1475 ): 

1476 entity = cast( 

1477 "_InternalEntityType[Any]", 

1478 inspection.inspect(entity_or_base, False), 

1479 ) 

1480 if entity is None: 

1481 self.root_entity = cast("Type[Any]", entity_or_base) 

1482 self.entity = None 

1483 else: 

1484 self.root_entity = None 

1485 self.entity = entity 

1486 

1487 self._where_crit_orig = where_criteria 

1488 if callable(where_criteria): 

1489 if self.root_entity is not None: 

1490 wrap_entity = self.root_entity 

1491 else: 

1492 assert entity is not None 

1493 wrap_entity = entity.entity 

1494 

1495 self.deferred_where_criteria = True 

1496 self.where_criteria = lambdas.DeferredLambdaElement( 

1497 where_criteria, 

1498 roles.WhereHavingRole, 

1499 lambda_args=(_WrapUserEntity(wrap_entity),), 

1500 opts=lambdas.LambdaOptions( 

1501 track_closure_variables=track_closure_variables 

1502 ), 

1503 ) 

1504 else: 

1505 self.deferred_where_criteria = False 

1506 self.where_criteria = coercions.expect( 

1507 roles.WhereHavingRole, where_criteria 

1508 ) 

1509 

1510 self.include_aliases = include_aliases 

1511 self.propagate_to_loaders = propagate_to_loaders 

1512 

1513 @classmethod 

1514 def _unreduce( 

1515 cls, entity, where_criteria, include_aliases, propagate_to_loaders 

1516 ): 

1517 return LoaderCriteriaOption( 

1518 entity, 

1519 where_criteria, 

1520 include_aliases=include_aliases, 

1521 propagate_to_loaders=propagate_to_loaders, 

1522 ) 

1523 

1524 def __reduce__(self): 

1525 return ( 

1526 LoaderCriteriaOption._unreduce, 

1527 ( 

1528 self.entity.class_ if self.entity else self.root_entity, 

1529 self._where_crit_orig, 

1530 self.include_aliases, 

1531 self.propagate_to_loaders, 

1532 ), 

1533 ) 

1534 

1535 def _all_mappers(self) -> Iterator[Mapper[Any]]: 

1536 if self.entity: 

1537 yield from self.entity.mapper.self_and_descendants 

1538 else: 

1539 assert self.root_entity 

1540 stack = list(self.root_entity.__subclasses__()) 

1541 while stack: 

1542 subclass = stack.pop(0) 

1543 ent = cast( 

1544 "_InternalEntityType[Any]", 

1545 inspection.inspect(subclass, raiseerr=False), 

1546 ) 

1547 if ent: 

1548 yield from ent.mapper.self_and_descendants 

1549 else: 

1550 stack.extend(subclass.__subclasses__()) 

1551 

1552 def _should_include(self, compile_state: _ORMSelectCompileState) -> bool: 

1553 # don't apply the criteria to a SELECT that's part of the criteria 

1554 # itself, either directly or nested at any level within other 

1555 # SELECTs, such as another option's criteria that in turn refers 

1556 # back to our entity; this would otherwise recurse endlessly (#13714) 

1557 return self not in compile_state._containing_loader_criteria 

1558 

1559 def _resolve_where_criteria( 

1560 self, ext_info: _InternalEntityType[Any] 

1561 ) -> ColumnElement[bool]: 

1562 if self.deferred_where_criteria: 

1563 crit = cast( 

1564 "ColumnElement[bool]", 

1565 self.where_criteria._resolve_with_args(ext_info.entity), 

1566 ) 

1567 else: 

1568 crit = self.where_criteria # type: ignore[assignment] 

1569 assert isinstance(crit, ColumnElement) 

1570 return sql_util._deep_annotate( 

1571 crit, 

1572 {"for_loader_criteria": self}, 

1573 detect_subquery_cols=True, 

1574 ind_cols_on_fromclause=True, 

1575 ) 

1576 

1577 def process_compile_state_replaced_entities( 

1578 self, 

1579 compile_state: _ORMCompileState, 

1580 mapper_entities: Iterable[_MapperEntity], 

1581 ) -> None: 

1582 self.process_compile_state(compile_state) 

1583 

1584 def process_compile_state(self, compile_state: _ORMCompileState) -> None: 

1585 """Apply a modification to a given :class:`.CompileState`.""" 

1586 

1587 # if options to limit the criteria to immediate query only, 

1588 # use compile_state.attributes instead 

1589 

1590 self.get_global_criteria(compile_state.global_attributes) 

1591 

1592 def get_global_criteria(self, attributes: Dict[Any, Any]) -> None: 

1593 for mp in self._all_mappers(): 

1594 load_criteria = attributes.setdefault( 

1595 ("additional_entity_criteria", mp), [] 

1596 ) 

1597 

1598 load_criteria.append(self) 

1599 

1600 

1601inspection._inspects(AliasedClass)(lambda target: target._aliased_insp) 

1602 

1603 

1604@inspection._inspects(type) 

1605def _inspect_mc( 

1606 class_: Type[_O], 

1607) -> Optional[Mapper[_O]]: 

1608 try: 

1609 class_manager = opt_manager_of_class(class_) 

1610 if class_manager is None or not class_manager.is_mapped: 

1611 return None 

1612 mapper = class_manager.mapper 

1613 except orm_exc.NO_STATE: 

1614 return None 

1615 else: 

1616 return mapper 

1617 

1618 

1619GenericAlias = type(List[Any]) 

1620 

1621 

1622@inspection._inspects(GenericAlias) 

1623def _inspect_generic_alias( 

1624 class_: Type[_O], 

1625) -> Optional[Mapper[_O]]: 

1626 origin = cast("Type[_O]", get_origin(class_)) 

1627 return _inspect_mc(origin) 

1628 

1629 

1630@inspection._self_inspects 

1631class Bundle( 

1632 ORMColumnsClauseRole[_T], 

1633 SupportsCloneAnnotations, 

1634 MemoizedHasCacheKey, 

1635 inspection.Inspectable["Bundle[_T]"], 

1636 InspectionAttr, 

1637): 

1638 """A grouping of SQL expressions that are returned by a :class:`.Query` 

1639 under one namespace. 

1640 

1641 The :class:`.Bundle` essentially allows nesting of the tuple-based 

1642 results returned by a column-oriented :class:`_query.Query` object. 

1643 It also 

1644 is extensible via simple subclassing, where the primary capability 

1645 to override is that of how the set of expressions should be returned, 

1646 allowing post-processing as well as custom return types, without 

1647 involving ORM identity-mapped classes. 

1648 

1649 .. seealso:: 

1650 

1651 :ref:`bundles` 

1652 

1653 :class:`.DictBundle` 

1654 

1655 """ 

1656 

1657 single_entity = False 

1658 """If True, queries for a single Bundle will be returned as a single 

1659 entity, rather than an element within a keyed tuple.""" 

1660 

1661 is_clause_element = False 

1662 

1663 is_mapper = False 

1664 

1665 is_aliased_class = False 

1666 

1667 is_bundle = True 

1668 

1669 _propagate_attrs: _PropagateAttrsType = util.immutabledict() 

1670 

1671 proxy_set = util.EMPTY_SET 

1672 

1673 exprs: List[_ColumnsClauseElement] 

1674 

1675 def __init__( 

1676 self, name: str, *exprs: _ColumnExpressionArgument[Any], **kw: Any 

1677 ) -> None: 

1678 r"""Construct a new :class:`.Bundle`. 

1679 

1680 e.g.:: 

1681 

1682 bn = Bundle("mybundle", MyClass.x, MyClass.y) 

1683 

1684 for row in session.query(bn).filter(bn.c.x == 5).filter(bn.c.y == 4): 

1685 print(row.mybundle.x, row.mybundle.y) 

1686 

1687 :param name: name of the bundle. 

1688 :param \*exprs: columns or SQL expressions comprising the bundle. 

1689 :param single_entity=False: if True, rows for this :class:`.Bundle` 

1690 can be returned as a "single entity" outside of any enclosing tuple 

1691 in the same manner as a mapped entity. 

1692 

1693 """ # noqa: E501 

1694 self.name = self._label = name 

1695 coerced_exprs = [ 

1696 coercions.expect( 

1697 roles.ColumnsClauseRole, expr, apply_propagate_attrs=self 

1698 ) 

1699 for expr in exprs 

1700 ] 

1701 self.exprs = coerced_exprs 

1702 

1703 self.c = self.columns = WriteableColumnCollection( 

1704 (getattr(col, "key", col._label), col) 

1705 for col in [e._annotations.get("bundle", e) for e in coerced_exprs] 

1706 ).as_readonly() 

1707 self.single_entity = kw.pop("single_entity", self.single_entity) 

1708 

1709 def _gen_cache_key( 

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

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

1712 return (self.__class__, self.name, self.single_entity) + tuple( 

1713 [expr._gen_cache_key(anon_map, bindparams) for expr in self.exprs] 

1714 ) 

1715 

1716 @property 

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

1718 mp: Optional[Mapper[Any]] = self.exprs[0]._annotations.get( 

1719 "parentmapper", None 

1720 ) 

1721 return mp 

1722 

1723 @property 

1724 def entity(self) -> Optional[_InternalEntityType[Any]]: 

1725 ie: Optional[_InternalEntityType[Any]] = self.exprs[ 

1726 0 

1727 ]._annotations.get("parententity", None) 

1728 return ie 

1729 

1730 @property 

1731 def entity_namespace( 

1732 self, 

1733 ) -> ReadOnlyColumnCollection[str, KeyedColumnElement[Any]]: 

1734 return self.c 

1735 

1736 columns: ReadOnlyColumnCollection[str, KeyedColumnElement[Any]] 

1737 

1738 """A namespace of SQL expressions referred to by this :class:`.Bundle`. 

1739 

1740 e.g.:: 

1741 

1742 bn = Bundle("mybundle", MyClass.x, MyClass.y) 

1743 

1744 q = sess.query(bn).filter(bn.c.x == 5) 

1745 

1746 Nesting of bundles is also supported:: 

1747 

1748 b1 = Bundle( 

1749 "b1", 

1750 Bundle("b2", MyClass.a, MyClass.b), 

1751 Bundle("b3", MyClass.x, MyClass.y), 

1752 ) 

1753 

1754 q = sess.query(b1).filter(b1.c.b2.c.a == 5).filter(b1.c.b3.c.y == 9) 

1755 

1756 .. seealso:: 

1757 

1758 :attr:`.Bundle.c` 

1759 

1760 """ # noqa: E501 

1761 

1762 c: ReadOnlyColumnCollection[str, KeyedColumnElement[Any]] 

1763 """An alias for :attr:`.Bundle.columns`.""" 

1764 

1765 def _clone(self, **kw): 

1766 cloned = self.__class__.__new__(self.__class__) 

1767 cloned.__dict__.update(self.__dict__) 

1768 return cloned 

1769 

1770 def __clause_element__(self): 

1771 # ensure existing entity_namespace remains 

1772 annotations = {"bundle": self, "entity_namespace": self} 

1773 annotations.update(self._annotations) 

1774 

1775 plugin_subject = self.exprs[0]._propagate_attrs.get( 

1776 "plugin_subject", self.entity 

1777 ) 

1778 return ( 

1779 expression.ClauseList( 

1780 _literal_as_text_role=roles.ColumnsClauseRole, 

1781 group=False, 

1782 *[e._annotations.get("bundle", e) for e in self.exprs], 

1783 ) 

1784 ._annotate(annotations) 

1785 ._set_propagate_attrs( 

1786 # the Bundle *must* use the orm plugin no matter what. the 

1787 # subject can be None but it's much better if it's not. 

1788 { 

1789 "compile_state_plugin": "orm", 

1790 "plugin_subject": plugin_subject, 

1791 } 

1792 ) 

1793 ) 

1794 

1795 @property 

1796 def clauses(self): 

1797 return self.__clause_element__().clauses 

1798 

1799 def label(self, name): 

1800 """Provide a copy of this :class:`.Bundle` passing a new label.""" 

1801 

1802 cloned = self._clone() 

1803 cloned.name = name 

1804 return cloned 

1805 

1806 def create_row_processor( 

1807 self, 

1808 query: Select[Unpack[TupleAny]], 

1809 procs: Sequence[Callable[[Row[Unpack[TupleAny]]], Any]], 

1810 labels: Sequence[str], 

1811 ) -> Callable[[Row[Unpack[TupleAny]]], Any]: 

1812 """Produce the "row processing" function for this :class:`.Bundle`. 

1813 

1814 May be overridden by subclasses to provide custom behaviors when 

1815 results are fetched. The method is passed the statement object and a 

1816 set of "row processor" functions at query execution time; these 

1817 processor functions when given a result row will return the individual 

1818 attribute value, which can then be adapted into any kind of return data 

1819 structure. 

1820 

1821 The example below illustrates replacing the usual :class:`.Row` 

1822 return structure with a straight Python dictionary:: 

1823 

1824 from sqlalchemy.orm import Bundle 

1825 

1826 

1827 class DictBundle(Bundle): 

1828 def create_row_processor(self, query, procs, labels): 

1829 "Override create_row_processor to return values as dictionaries" 

1830 

1831 def proc(row): 

1832 return dict(zip(labels, (proc(row) for proc in procs))) 

1833 

1834 return proc 

1835 

1836 A result from the above :class:`_orm.Bundle` will return dictionary 

1837 values:: 

1838 

1839 bn = DictBundle("mybundle", MyClass.data1, MyClass.data2) 

1840 for row in session.execute(select(bn)).where(bn.c.data1 == "d1"): 

1841 print(row.mybundle["data1"], row.mybundle["data2"]) 

1842 

1843 The above example is available natively using :class:`.DictBundle` 

1844 

1845 .. seealso:: 

1846 

1847 :class:`.DictBundle` 

1848 

1849 """ # noqa: E501 

1850 keyed_tuple = result_tuple(labels, [() for l in labels]) 

1851 

1852 def proc(row: Row[Unpack[TupleAny]]) -> Any: 

1853 return keyed_tuple([proc(row) for proc in procs]) 

1854 

1855 return proc 

1856 

1857 

1858class DictBundle(Bundle[_T]): 

1859 """Like :class:`.Bundle` but returns ``dict`` instances instead of 

1860 named tuple like objects:: 

1861 

1862 bn = DictBundle("mybundle", MyClass.data1, MyClass.data2) 

1863 for row in session.execute(select(bn)).where(bn.c.data1 == "d1"): 

1864 print(row.mybundle["data1"], row.mybundle["data2"]) 

1865 

1866 Differently from :class:`.Bundle`, multiple columns with the same name are 

1867 not supported. 

1868 

1869 .. versionadded:: 2.1 

1870 

1871 .. seealso:: 

1872 

1873 :ref:`bundles` 

1874 

1875 :class:`.Bundle` 

1876 """ 

1877 

1878 def __init__( 

1879 self, name: str, *exprs: _ColumnExpressionArgument[Any], **kw: Any 

1880 ) -> None: 

1881 super().__init__(name, *exprs, **kw) 

1882 if len(set(self.c.keys())) != len(self.c): 

1883 raise sa_exc.ArgumentError( 

1884 "DictBundle does not support duplicate column names" 

1885 ) 

1886 

1887 def create_row_processor( 

1888 self, 

1889 query: Select[Unpack[TupleAny]], 

1890 procs: Sequence[Callable[[Row[Unpack[TupleAny]]], Any]], 

1891 labels: Sequence[str], 

1892 ) -> Callable[[Row[Unpack[TupleAny]]], dict[str, Any]]: 

1893 def proc(row: Row[Unpack[TupleAny]]) -> dict[str, Any]: 

1894 return dict(zip(labels, (proc(row) for proc in procs))) 

1895 

1896 return proc 

1897 

1898 

1899def _orm_full_deannotate(element: _SA) -> _SA: 

1900 return sql_util._deep_deannotate(element) 

1901 

1902 

1903class _ORMJoin(expression.Join): 

1904 """Extend Join to support ORM constructs as input.""" 

1905 

1906 __visit_name__ = expression.Join.__visit_name__ 

1907 

1908 inherit_cache = True 

1909 

1910 def __init__( 

1911 self, 

1912 left: _FromClauseArgument, 

1913 right: _FromClauseArgument, 

1914 onclause: Optional[_OnClauseArgument] = None, 

1915 isouter: bool = False, 

1916 full: bool = False, 

1917 _left_memo: Optional[Any] = None, 

1918 _right_memo: Optional[Any] = None, 

1919 _extra_criteria: Tuple[ColumnElement[bool], ...] = (), 

1920 ): 

1921 left_info = cast( 

1922 "Union[FromClause, _InternalEntityType[Any]]", 

1923 inspection.inspect(left), 

1924 ) 

1925 

1926 right_info = cast( 

1927 "Union[FromClause, _InternalEntityType[Any]]", 

1928 inspection.inspect(right), 

1929 ) 

1930 adapt_to = right_info.selectable 

1931 

1932 # used by joined eager loader 

1933 self._left_memo = _left_memo 

1934 self._right_memo = _right_memo 

1935 

1936 if isinstance(onclause, attributes.QueryableAttribute): 

1937 if TYPE_CHECKING: 

1938 assert isinstance( 

1939 onclause.comparator, RelationshipProperty.Comparator 

1940 ) 

1941 on_selectable = onclause.comparator._source_selectable() 

1942 prop = onclause.property 

1943 _extra_criteria += onclause._extra_criteria 

1944 elif isinstance(onclause, MapperProperty): 

1945 # used internally by joined eager loader...possibly not ideal 

1946 prop = onclause 

1947 on_selectable = prop.parent.selectable 

1948 else: 

1949 prop = None 

1950 on_selectable = None 

1951 

1952 left_selectable = left_info.selectable 

1953 if prop: 

1954 adapt_from: Optional[FromClause] 

1955 if sql_util.clause_is_present(on_selectable, left_selectable): 

1956 adapt_from = on_selectable 

1957 else: 

1958 assert isinstance(left_selectable, FromClause) 

1959 adapt_from = left_selectable 

1960 

1961 ( 

1962 pj, 

1963 sj, 

1964 source, 

1965 dest, 

1966 secondary, 

1967 target_adapter, 

1968 ) = prop._create_joins( 

1969 source_selectable=adapt_from, 

1970 dest_selectable=adapt_to, 

1971 source_polymorphic=True, 

1972 of_type_entity=right_info, 

1973 alias_secondary=True, 

1974 extra_criteria=_extra_criteria, 

1975 ) 

1976 

1977 if sj is not None: 

1978 if isouter: 

1979 # note this is an inner join from secondary->right 

1980 right = sql.join(secondary, right, sj) 

1981 onclause = pj 

1982 else: 

1983 left = sql.join(left, secondary, pj, isouter) 

1984 onclause = sj 

1985 else: 

1986 onclause = pj 

1987 

1988 self._target_adapter = target_adapter 

1989 

1990 # we don't use the normal coercions logic for _ORMJoin 

1991 # (probably should), so do some gymnastics to get the entity. 

1992 # logic here is for #8721, which was a major bug in 1.4 

1993 # for almost two years, not reported/fixed until 1.4.43 (!) 

1994 if is_selectable(left_info): 

1995 parententity = left_selectable._annotations.get( 

1996 "parententity", None 

1997 ) 

1998 elif insp_is_mapper(left_info) or insp_is_aliased_class(left_info): 

1999 parententity = left_info 

2000 else: 

2001 parententity = None 

2002 

2003 if parententity is not None: 

2004 self._annotations = self._annotations.union( 

2005 {"parententity": parententity} 

2006 ) 

2007 

2008 augment_onclause = bool(_extra_criteria) and not prop 

2009 expression.Join.__init__(self, left, right, onclause, isouter, full) 

2010 

2011 assert self.onclause is not None 

2012 

2013 if augment_onclause: 

2014 self.onclause &= sql.and_(*_extra_criteria) 

2015 

2016 if ( 

2017 not prop 

2018 and getattr(right_info, "mapper", None) 

2019 and right_info.mapper.single # type: ignore[union-attr] 

2020 ): 

2021 right_info = cast("_InternalEntityType[Any]", right_info) 

2022 # if single inheritance target and we are using a manual 

2023 # or implicit ON clause, augment it the same way we'd augment the 

2024 # WHERE. 

2025 single_crit = right_info.mapper._single_table_criterion 

2026 if single_crit is not None: 

2027 if insp_is_aliased_class(right_info): 

2028 single_crit = right_info._adapter.traverse(single_crit) 

2029 self.onclause = self.onclause & single_crit 

2030 

2031 def _splice_into_center(self, other): 

2032 """Splice a join into the center. 

2033 

2034 Given join(a, b) and join(b, c), return join(a, b).join(c) 

2035 

2036 """ 

2037 leftmost = other 

2038 while isinstance(leftmost, sql.Join): 

2039 leftmost = leftmost.left 

2040 

2041 assert self.right is leftmost 

2042 

2043 left = _ORMJoin( 

2044 self.left, 

2045 other.left, 

2046 self.onclause, 

2047 isouter=self.isouter, 

2048 _left_memo=self._left_memo, 

2049 _right_memo=other._left_memo._path_registry, 

2050 ) 

2051 

2052 return _ORMJoin( 

2053 left, 

2054 other.right, 

2055 other.onclause, 

2056 isouter=other.isouter, 

2057 _right_memo=other._right_memo, 

2058 ) 

2059 

2060 def join( 

2061 self, 

2062 right: _FromClauseArgument, 

2063 onclause: Optional[_OnClauseArgument] = None, 

2064 isouter: bool = False, 

2065 full: bool = False, 

2066 ) -> _ORMJoin: 

2067 return _ORMJoin(self, right, onclause, full=full, isouter=isouter) 

2068 

2069 def outerjoin( 

2070 self, 

2071 right: _FromClauseArgument, 

2072 onclause: Optional[_OnClauseArgument] = None, 

2073 full: bool = False, 

2074 ) -> _ORMJoin: 

2075 return _ORMJoin(self, right, onclause, isouter=True, full=full) 

2076 

2077 

2078def with_parent( 

2079 instance: object, 

2080 prop: attributes.QueryableAttribute[Any], 

2081 from_entity: Optional[_EntityType[Any]] = None, 

2082) -> ColumnElement[bool]: 

2083 """Create filtering criterion that relates this query's primary entity 

2084 to the given related instance, using established 

2085 :func:`_orm.relationship()` 

2086 configuration. 

2087 

2088 E.g.:: 

2089 

2090 stmt = select(Address).where(with_parent(some_user, User.addresses)) 

2091 

2092 The SQL rendered is the same as that rendered when a lazy loader 

2093 would fire off from the given parent on that attribute, meaning 

2094 that the appropriate state is taken from the parent object in 

2095 Python without the need to render joins to the parent table 

2096 in the rendered statement. 

2097 

2098 The given property may also make use of :meth:`_orm.PropComparator.of_type` 

2099 to indicate the left side of the criteria:: 

2100 

2101 

2102 a1 = aliased(Address) 

2103 a2 = aliased(Address) 

2104 stmt = select(a1, a2).where(with_parent(u1, User.addresses.of_type(a2))) 

2105 

2106 The above use is equivalent to using the 

2107 :func:`_orm.with_parent.from_entity` argument:: 

2108 

2109 a1 = aliased(Address) 

2110 a2 = aliased(Address) 

2111 stmt = select(a1, a2).where( 

2112 with_parent(u1, User.addresses, from_entity=a2) 

2113 ) 

2114 

2115 :param instance: 

2116 An instance which has some :func:`_orm.relationship`. 

2117 

2118 :param property: 

2119 Class-bound attribute, which indicates 

2120 what relationship from the instance should be used to reconcile the 

2121 parent/child relationship. 

2122 

2123 :param from_entity: 

2124 Entity in which to consider as the left side. This defaults to the 

2125 "zero" entity of the :class:`_query.Query` itself. 

2126 

2127 """ # noqa: E501 

2128 prop_t: RelationshipProperty[Any] 

2129 

2130 if isinstance(prop, str): 

2131 raise sa_exc.ArgumentError( 

2132 "with_parent() accepts class-bound mapped attributes, not strings" 

2133 ) 

2134 elif isinstance(prop, attributes.QueryableAttribute): 

2135 if prop._of_type: 

2136 from_entity = prop._of_type 

2137 mapper_property = prop.property 

2138 if mapper_property is None or not prop_is_relationship( 

2139 mapper_property 

2140 ): 

2141 raise sa_exc.ArgumentError( 

2142 f"Expected relationship property for with_parent(), " 

2143 f"got {mapper_property}" 

2144 ) 

2145 prop_t = mapper_property 

2146 else: 

2147 prop_t = prop 

2148 

2149 return prop_t._with_parent(instance, from_entity=from_entity) 

2150 

2151 

2152def has_identity(object_: object) -> bool: 

2153 """Return True if the given object has a database 

2154 identity. 

2155 

2156 This typically corresponds to the object being 

2157 in either the persistent or detached state. 

2158 

2159 .. seealso:: 

2160 

2161 :func:`.was_deleted` 

2162 

2163 """ 

2164 state = attributes.instance_state(object_) 

2165 return state.has_identity 

2166 

2167 

2168def was_deleted(object_: object) -> bool: 

2169 """Return True if the given object was deleted 

2170 within a session flush. 

2171 

2172 This is regardless of whether or not the object is 

2173 persistent or detached. 

2174 

2175 .. seealso:: 

2176 

2177 :attr:`.InstanceState.was_deleted` 

2178 

2179 """ 

2180 

2181 state = attributes.instance_state(object_) 

2182 return state.was_deleted 

2183 

2184 

2185def _entity_corresponds_to( 

2186 given: _InternalEntityType[Any], entity: _InternalEntityType[Any] 

2187) -> bool: 

2188 """determine if 'given' corresponds to 'entity', in terms 

2189 of an entity passed to Query that would match the same entity 

2190 being referred to elsewhere in the query. 

2191 

2192 """ 

2193 if insp_is_aliased_class(entity): 

2194 if insp_is_aliased_class(given): 

2195 if entity._base_alias() is given._base_alias(): 

2196 return True 

2197 return False 

2198 elif insp_is_aliased_class(given): 

2199 if given._use_mapper_path: 

2200 return entity in given.with_polymorphic_mappers 

2201 else: 

2202 return entity is given 

2203 

2204 assert insp_is_mapper(given) 

2205 return entity.common_parent(given) 

2206 

2207 

2208def _entity_corresponds_to_use_path_impl( 

2209 given: _InternalEntityType[Any], entity: _InternalEntityType[Any] 

2210) -> bool: 

2211 """determine if 'given' corresponds to 'entity', in terms 

2212 of a path of loader options where a mapped attribute is taken to 

2213 be a member of a parent entity. 

2214 

2215 e.g.:: 

2216 

2217 someoption(A).someoption(A.b) # -> fn(A, A) -> True 

2218 someoption(A).someoption(C.d) # -> fn(A, C) -> False 

2219 

2220 a1 = aliased(A) 

2221 someoption(a1).someoption(A.b) # -> fn(a1, A) -> False 

2222 someoption(a1).someoption(a1.b) # -> fn(a1, a1) -> True 

2223 

2224 wp = with_polymorphic(A, [A1, A2]) 

2225 someoption(wp).someoption(A1.foo) # -> fn(wp, A1) -> False 

2226 someoption(wp).someoption(wp.A1.foo) # -> fn(wp, wp.A1) -> True 

2227 

2228 """ 

2229 if insp_is_aliased_class(given): 

2230 return ( 

2231 insp_is_aliased_class(entity) 

2232 and not entity._use_mapper_path 

2233 and (given is entity or entity in given._with_polymorphic_entities) 

2234 ) 

2235 elif not insp_is_aliased_class(entity): 

2236 return given.isa(entity.mapper) 

2237 else: 

2238 return ( 

2239 entity._use_mapper_path 

2240 and given in entity.with_polymorphic_mappers 

2241 ) 

2242 

2243 

2244def _entity_isa(given: _InternalEntityType[Any], mapper: Mapper[Any]) -> bool: 

2245 """determine if 'given' "is a" mapper, in terms of the given 

2246 would load rows of type 'mapper'. 

2247 

2248 """ 

2249 if given.is_aliased_class: 

2250 return mapper in given.with_polymorphic_mappers or given.mapper.isa( 

2251 mapper 

2252 ) 

2253 elif given.with_polymorphic_mappers: 

2254 return mapper in given.with_polymorphic_mappers or given.isa(mapper) 

2255 else: 

2256 return given.isa(mapper) 

2257 

2258 

2259def _getitem(iterable_query: Query[Any], item: Any) -> Any: 

2260 """calculate __getitem__ in terms of an iterable query object 

2261 that also has a slice() method. 

2262 

2263 """ 

2264 

2265 def _no_negative_indexes(): 

2266 raise IndexError( 

2267 "negative indexes are not accepted by SQL " 

2268 "index / slice operators" 

2269 ) 

2270 

2271 if isinstance(item, slice): 

2272 start, stop, step = util.decode_slice(item) 

2273 

2274 if ( 

2275 isinstance(stop, int) 

2276 and isinstance(start, int) 

2277 and stop - start <= 0 

2278 ): 

2279 return [] 

2280 

2281 elif (isinstance(start, int) and start < 0) or ( 

2282 isinstance(stop, int) and stop < 0 

2283 ): 

2284 _no_negative_indexes() 

2285 

2286 res = iterable_query.slice(start, stop) 

2287 if step is not None: 

2288 return list(res)[None : None : item.step] 

2289 else: 

2290 return list(res) 

2291 else: 

2292 if item == -1: 

2293 _no_negative_indexes() 

2294 else: 

2295 return list(iterable_query[item : item + 1])[0] 

2296 

2297 

2298def _is_mapped_annotation( 

2299 raw_annotation: _AnnotationScanType, 

2300 cls: Type[Any], 

2301 originating_cls: Type[Any], 

2302) -> bool: 

2303 try: 

2304 annotated = de_stringify_annotation( 

2305 cls, raw_annotation, originating_cls.__module__ 

2306 ) 

2307 except NameError: 

2308 # in most cases, at least within our own tests, we can raise 

2309 # here, which is more accurate as it prevents us from returning 

2310 # false negatives. However, in the real world, try to avoid getting 

2311 # involved with end-user annotations that have nothing to do with us. 

2312 # see issue #8888 where we bypass using this function in the case 

2313 # that we want to detect an unresolvable Mapped[] type. 

2314 return False 

2315 else: 

2316 return is_origin_of_cls(annotated, _MappedAnnotationBase) 

2317 

2318 

2319class _CleanupError(Exception): 

2320 pass 

2321 

2322 

2323def _cleanup_mapped_str_annotation( 

2324 annotation: str, originating_module: str 

2325) -> str: 

2326 # fix up an annotation that comes in as the form: 

2327 # 'Mapped[List[Address]]' so that it instead looks like: 

2328 # 'Mapped[List["Address"]]' , which will allow us to get 

2329 # "Address" as a string 

2330 

2331 # additionally, resolve symbols for these names since this is where 

2332 # we'd have to do it 

2333 

2334 inner: Optional[Match[str]] 

2335 

2336 mm = re.match(r"^([^ \|]+?)\[(.+)\]$", annotation) 

2337 

2338 if not mm: 

2339 return annotation 

2340 

2341 # ticket #8759. Resolve the Mapped name to a real symbol. 

2342 # originally this just checked the name. 

2343 try: 

2344 obj = eval_name_only(mm.group(1), originating_module) 

2345 except NameError as ne: 

2346 raise _CleanupError( 

2347 f'For annotation "{annotation}", could not resolve ' 

2348 f'container type "{mm.group(1)}". ' 

2349 "Please ensure this type is imported at the module level " 

2350 "outside of TYPE_CHECKING blocks" 

2351 ) from ne 

2352 

2353 if obj is typing.ClassVar: 

2354 real_symbol = "ClassVar" 

2355 else: 

2356 try: 

2357 if issubclass(obj, _MappedAnnotationBase): 

2358 real_symbol = obj.__name__ 

2359 else: 

2360 return annotation 

2361 except TypeError: 

2362 # avoid isinstance(obj, type) check, just catch TypeError 

2363 return annotation 

2364 

2365 # note: if one of the codepaths above didn't define real_symbol and 

2366 # then didn't return, real_symbol raises UnboundLocalError 

2367 # which is actually a NameError, and the calling routines don't 

2368 # notice this since they are catching NameError anyway. Just in case 

2369 # this is being modified in the future, something to be aware of. 

2370 

2371 stack = [] 

2372 inner = mm 

2373 while True: 

2374 stack.append(real_symbol if mm is inner else inner.group(1)) 

2375 g2 = inner.group(2) 

2376 inner = re.match(r"^([^ \|]+?)\[(.+)\]$", g2) 

2377 if inner is None: 

2378 stack.append(g2) 

2379 break 

2380 

2381 # stacks we want to rewrite, that is, quote the last entry which 

2382 # we think is a relationship class name: 

2383 # 

2384 # ['Mapped', 'List', 'Address'] 

2385 # ['Mapped', 'A'] 

2386 # 

2387 # stacks we dont want to rewrite, which are generally MappedColumn 

2388 # use cases: 

2389 # 

2390 # ['Mapped', "'Optional[Dict[str, str]]'"] 

2391 # ['Mapped', 'dict[str, str] | None'] 

2392 

2393 if ( 

2394 # avoid already quoted symbols such as 

2395 # ['Mapped', "'Optional[Dict[str, str]]'"] 

2396 not re.match(r"""^["'].*["']$""", stack[-1]) 

2397 # avoid further generics like Dict[] such as 

2398 # ['Mapped', 'dict[str, str] | None'], 

2399 # ['Mapped', 'list[int] | list[str]'], 

2400 # ['Mapped', 'Union[list[int], list[str]]'], 

2401 and not re.search(r"[\[\]]", stack[-1]) 

2402 ): 

2403 stripchars = "\"' " 

2404 stack[-1] = ", ".join( 

2405 f'"{elem.strip(stripchars)}"' for elem in stack[-1].split(",") 

2406 ) 

2407 

2408 annotation = "[".join(stack) + ("]" * (len(stack) - 1)) 

2409 

2410 return annotation 

2411 

2412 

2413_mapped_str_annotation = re.compile(r"(?:Mapped|Unmapped)\[") 

2414 

2415 

2416def _unresolved_annotation_is_mapped( 

2417 raw_annotation: _AnnotationScanType, 

2418) -> bool: 

2419 """given an un-resolvable annotation, guess if it's ``Mapped[]``. 

2420 

2421 Used only to decide whether a ``NameError`` raised while de-stringifying 

2422 is worth reporting as a mapping error. The annotation may be a plain 

2423 string (``__future__`` annotations / explicitly quoted), a ``ForwardRef`` 

2424 (:pep:`649` deferred annotations on Python 3.14 and above), or an 

2425 already-resolved object. 

2426 

2427 """ 

2428 if isinstance(raw_annotation, str): 

2429 return bool(_mapped_str_annotation.search(raw_annotation)) 

2430 elif isinstance(raw_annotation, typing.ForwardRef): 

2431 return bool( 

2432 _mapped_str_annotation.search(raw_annotation.__forward_arg__) 

2433 ) 

2434 else: 

2435 origin = typing.get_origin(raw_annotation) 

2436 return isinstance(origin, type) and issubclass( 

2437 origin, _MappedAnnotationBase 

2438 ) 

2439 

2440 

2441def _extract_mapped_subtype( 

2442 raw_annotation: Optional[_AnnotationScanType], 

2443 cls: type, 

2444 originating_module: str, 

2445 key: str, 

2446 attr_cls: Type[Any], 

2447 required: bool, 

2448 is_dataclass_field: bool, 

2449 expect_mapped: bool = True, 

2450 raiseerr: bool = True, 

2451) -> Optional[Tuple[Union[_AnnotationScanType, str], Optional[type]]]: 

2452 """given an annotation, figure out if it's ``Mapped[something]`` and if 

2453 so, return the ``something`` part. 

2454 

2455 Includes error raise scenarios and other options. 

2456 

2457 """ 

2458 

2459 if raw_annotation is None: 

2460 if required: 

2461 raise orm_exc.MappedAnnotationError( 

2462 f"Python typing annotation is required for attribute " 

2463 f'"{cls.__name__}.{key}" when primary argument(s) for ' 

2464 f'"{attr_cls.__name__}" construct are None or not present' 

2465 ) 

2466 return None 

2467 

2468 try: 

2469 # destringify the "outside" of the annotation. note we are not 

2470 # adding include_generic so it will *not* dig into generic contents, 

2471 # which will remain as ForwardRef or plain str under future annotations 

2472 # mode. The full destringify happens later when mapped_column goes 

2473 # to do a full lookup in the registry type_annotations_map. 

2474 annotated = de_stringify_annotation( 

2475 cls, 

2476 raw_annotation, 

2477 originating_module, 

2478 str_cleanup_fn=_cleanup_mapped_str_annotation, 

2479 ) 

2480 except _CleanupError as ce: 

2481 raise orm_exc.MappedAnnotationError( 

2482 f"Could not interpret annotation {raw_annotation}. " 

2483 "Check that it uses names that are correctly imported at the " 

2484 "module level. See chained stack trace for more hints." 

2485 ) from ce 

2486 except NameError as ne: 

2487 if raiseerr and _unresolved_annotation_is_mapped(raw_annotation): 

2488 raise orm_exc.MappedAnnotationError( 

2489 f"Could not interpret annotation {raw_annotation}. " 

2490 "Check that it uses names that are correctly imported at the " 

2491 "module level. See chained stack trace for more hints." 

2492 ) from ne 

2493 

2494 annotated = raw_annotation # type: ignore[assignment] 

2495 

2496 if is_dataclass_field: 

2497 return annotated, None 

2498 else: 

2499 if not hasattr(annotated, "__origin__") or not is_origin_of_cls( 

2500 annotated, _MappedAnnotationBase 

2501 ): 

2502 if expect_mapped: 

2503 if not raiseerr: 

2504 return None 

2505 

2506 origin = getattr(annotated, "__origin__", None) 

2507 if origin is typing.ClassVar: 

2508 return None 

2509 

2510 # check for other kind of ORM descriptor like AssociationProxy, 

2511 # don't raise for that (issue #9957) 

2512 elif isinstance(origin, type) and issubclass( 

2513 origin, ORMDescriptor 

2514 ): 

2515 return None 

2516 

2517 raise orm_exc.MappedAnnotationError( 

2518 f'Type annotation for "{cls.__name__}.{key}" ' 

2519 "can't be correctly interpreted for " 

2520 "Annotated Declarative Table form. ORM annotations " 

2521 "should normally make use of the ``Mapped[]`` generic " 

2522 "type, or other ORM-compatible generic type, as a " 

2523 "container for the actual type, which indicates the " 

2524 "intent that the attribute is mapped. " 

2525 "Class variables that are not intended to be mapped " 

2526 "by the ORM should use ClassVar[]. " 

2527 "To allow Annotated Declarative to disregard legacy " 

2528 "annotations which don't use Mapped[] to pass, set " 

2529 '"__allow_unmapped__ = True" on the class or a ' 

2530 "superclass this class.", 

2531 code="zlpr", 

2532 ) 

2533 

2534 else: 

2535 return annotated, None 

2536 

2537 generic_annotated = cast(GenericProtocol[Any], annotated) 

2538 if len(generic_annotated.__args__) != 1: 

2539 raise orm_exc.MappedAnnotationError( 

2540 "Expected sub-type for Mapped[] annotation" 

2541 ) 

2542 

2543 return ( 

2544 # fix dict/list/set args to be ForwardRef, see #11814 

2545 fixup_container_fwd_refs(generic_annotated.__args__[0]), 

2546 generic_annotated.__origin__, 

2547 ) 

2548 

2549 

2550def _mapper_property_as_plain_name(prop: Type[Any]) -> str: 

2551 if hasattr(prop, "_mapper_property_name"): 

2552 name = prop._mapper_property_name() 

2553 else: 

2554 name = None 

2555 return util.clsname_as_plain_name(prop, name)