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

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

823 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 re 

14import types 

15import typing 

16from typing import AbstractSet 

17from typing import Any 

18from typing import Callable 

19from typing import cast 

20from typing import Dict 

21from typing import FrozenSet 

22from typing import Generic 

23from typing import Iterable 

24from typing import Iterator 

25from typing import List 

26from typing import Match 

27from typing import Optional 

28from typing import Sequence 

29from typing import Tuple 

30from typing import Type 

31from typing import TYPE_CHECKING 

32from typing import TypeVar 

33from typing import Union 

34import weakref 

35 

36from . import attributes # noqa 

37from . import exc 

38from . import exc as orm_exc 

39from ._typing import _O 

40from ._typing import insp_is_aliased_class 

41from ._typing import insp_is_mapper 

42from ._typing import prop_is_relationship 

43from .base import _class_to_mapper as _class_to_mapper 

44from .base import _MappedAnnotationBase 

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

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

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

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

49from .base import class_mapper as class_mapper 

50from .base import DynamicMapped 

51from .base import InspectionAttr as InspectionAttr 

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

53from .base import Mapped 

54from .base import object_mapper as object_mapper 

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

56from .base import opt_manager_of_class 

57from .base import ORMDescriptor 

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

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

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

61from .base import WriteOnlyMapped 

62from .interfaces import CriteriaOption 

63from .interfaces import MapperProperty as MapperProperty 

64from .interfaces import ORMColumnsClauseRole 

65from .interfaces import ORMEntityColumnsClauseRole 

66from .interfaces import ORMFromClauseRole 

67from .path_registry import PathRegistry as PathRegistry 

68from .. import event 

69from .. import exc as sa_exc 

70from .. import inspection 

71from .. import sql 

72from .. import util 

73from ..engine.result import result_tuple 

74from ..sql import coercions 

75from ..sql import expression 

76from ..sql import lambdas 

77from ..sql import roles 

78from ..sql import util as sql_util 

79from ..sql import visitors 

80from ..sql._typing import is_selectable 

81from ..sql.annotation import SupportsCloneAnnotations 

82from ..sql.base import ColumnCollection 

83from ..sql.cache_key import HasCacheKey 

84from ..sql.cache_key import MemoizedHasCacheKey 

85from ..sql.elements import ColumnElement 

86from ..sql.elements import KeyedColumnElement 

87from ..sql.selectable import FromClause 

88from ..sql.selectable import GenerativeSelect 

89from ..util.langhelpers import MemoizedSlots 

90from ..util.typing import de_stringify_annotation as _de_stringify_annotation 

91from ..util.typing import eval_name_only as _eval_name_only 

92from ..util.typing import fixup_container_fwd_refs 

93from ..util.typing import get_origin 

94from ..util.typing import is_origin_of_cls 

95from ..util.typing import Literal 

96from ..util.typing import Protocol 

97 

98if typing.TYPE_CHECKING: 

99 from ._typing import _EntityType 

100 from ._typing import _IdentityKeyType 

101 from ._typing import _InternalEntityType 

102 from ._typing import _ORMCOLEXPR 

103 from .context import _MapperEntity 

104 from .context import ORMCompileState 

105 from .mapper import Mapper 

106 from .path_registry import AbstractEntityRegistry 

107 from .query import Query 

108 from .relationships import RelationshipProperty 

109 from ..engine import Row 

110 from ..engine import RowMapping 

111 from ..sql._typing import _CE 

112 from ..sql._typing import _ColumnExpressionArgument 

113 from ..sql._typing import _EquivalentColumnMap 

114 from ..sql._typing import _FromClauseArgument 

115 from ..sql._typing import _OnClauseArgument 

116 from ..sql._typing import _PropagateAttrsType 

117 from ..sql.annotation import _SA 

118 from ..sql.base import ReadOnlyColumnCollection 

119 from ..sql.elements import BindParameter 

120 from ..sql.selectable import _ColumnsClauseElement 

121 from ..sql.selectable import Select 

122 from ..sql.selectable import Selectable 

123 from ..sql.visitors import anon_map 

124 from ..util.typing import _AnnotationScanType 

125 

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

127 

128all_cascades = frozenset( 

129 ( 

130 "delete", 

131 "delete-orphan", 

132 "all", 

133 "merge", 

134 "expunge", 

135 "save-update", 

136 "refresh-expire", 

137 "none", 

138 ) 

139) 

140 

141_de_stringify_partial = functools.partial( 

142 functools.partial, 

143 locals_=util.immutabledict( 

144 { 

145 "Mapped": Mapped, 

146 "WriteOnlyMapped": WriteOnlyMapped, 

147 "DynamicMapped": DynamicMapped, 

148 } 

149 ), 

150) 

151 

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

153# function and maintain the signature anyway 

154 

155 

156class _DeStringifyAnnotation(Protocol): 

157 def __call__( 

158 self, 

159 cls: Type[Any], 

160 annotation: _AnnotationScanType, 

161 originating_module: str, 

162 *, 

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

164 include_generic: bool = False, 

165 ) -> Type[Any]: ... 

166 

167 

168de_stringify_annotation = cast( 

169 _DeStringifyAnnotation, _de_stringify_partial(_de_stringify_annotation) 

170) 

171 

172 

173class _EvalNameOnly(Protocol): 

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

175 

176 

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

178 

179 

180class CascadeOptions(FrozenSet[str]): 

181 """Keeps track of the options sent to 

182 :paramref:`.relationship.cascade`""" 

183 

184 _add_w_all_cascades = all_cascades.difference( 

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

186 ) 

187 _allowed_cascades = all_cascades 

188 

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

190 

191 __slots__ = ( 

192 "save_update", 

193 "delete", 

194 "refresh_expire", 

195 "merge", 

196 "expunge", 

197 "delete_orphan", 

198 ) 

199 

200 save_update: bool 

201 delete: bool 

202 refresh_expire: bool 

203 merge: bool 

204 expunge: bool 

205 delete_orphan: bool 

206 

207 def __new__( 

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

209 ) -> CascadeOptions: 

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

211 return cls.from_string(value_list) # type: ignore 

212 values = set(value_list) 

213 if values.difference(cls._allowed_cascades): 

214 raise sa_exc.ArgumentError( 

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

216 % ", ".join( 

217 [ 

218 repr(x) 

219 for x in sorted( 

220 values.difference(cls._allowed_cascades) 

221 ) 

222 ] 

223 ) 

224 ) 

225 

226 if "all" in values: 

227 values.update(cls._add_w_all_cascades) 

228 if "none" in values: 

229 values.clear() 

230 values.discard("all") 

231 

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

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

234 self.delete = "delete" in values 

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

236 self.merge = "merge" in values 

237 self.expunge = "expunge" in values 

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

239 

240 if self.delete_orphan and not self.delete: 

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

242 return self 

243 

244 def __repr__(self): 

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

246 

247 @classmethod 

248 def from_string(cls, arg): 

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

250 return cls(values) 

251 

252 

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

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

255 appended. 

256 """ 

257 

258 if not include_backrefs: 

259 

260 def detect_is_backref(state, initiator): 

261 impl = state.manager[key].impl 

262 return initiator.impl is not impl 

263 

264 if include_removes: 

265 

266 def append(state, value, initiator): 

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

268 include_backrefs or not detect_is_backref(state, initiator) 

269 ): 

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

271 else: 

272 return value 

273 

274 def bulk_set(state, values, initiator): 

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

276 obj = state.obj() 

277 values[:] = [ 

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

279 ] 

280 

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

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

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

284 else: 

285 return value 

286 

287 def remove(state, value, initiator): 

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

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

290 

291 else: 

292 

293 def append(state, value, initiator): 

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

295 include_backrefs or not detect_is_backref(state, initiator) 

296 ): 

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

298 else: 

299 return value 

300 

301 def bulk_set(state, values, initiator): 

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

303 obj = state.obj() 

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

305 

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

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

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

309 else: 

310 return value 

311 

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

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

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

315 if include_removes: 

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

317 

318 

319def polymorphic_union( 

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

321): 

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

323 

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

325 this is used. 

326 

327 :param table_map: mapping of polymorphic identities to 

328 :class:`_schema.Table` objects. 

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

330 derived from the query, producing the polymorphic identity for 

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

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

333 construct generated. 

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

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

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

337 can be set to False. 

338 

339 """ 

340 

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

342 colnamemaps = {} 

343 types = {} 

344 for key in table_map: 

345 table = table_map[key] 

346 

347 table = coercions.expect( 

348 roles.StrictFromClauseRole, table, allow_select=True 

349 ) 

350 table_map[key] = table 

351 

352 m = {} 

353 for c in table.c: 

354 if c.key == typecolname: 

355 raise sa_exc.InvalidRequestError( 

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

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

358 "'typecolname' " 

359 "argument; this is available on " 

360 "ConcreteBase as '_concrete_discriminator_name'" 

361 % (typecolname, c) 

362 ) 

363 colnames.add(c.key) 

364 m[c.key] = c 

365 types[c.key] = c.type 

366 colnamemaps[table] = m 

367 

368 def col(name, table): 

369 try: 

370 return colnamemaps[table][name] 

371 except KeyError: 

372 if cast_nulls: 

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

374 else: 

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

376 

377 result = [] 

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

379 if typecolname is not None: 

380 result.append( 

381 sql.select( 

382 *( 

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

384 + [ 

385 sql.literal_column( 

386 sql_util._quote_ddl_expr(type_) 

387 ).label(typecolname) 

388 ] 

389 ) 

390 ).select_from(table) 

391 ) 

392 else: 

393 result.append( 

394 sql.select( 

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

396 ).select_from(table) 

397 ) 

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

399 

400 

401def identity_key( 

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

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

404 *, 

405 instance: Optional[_T] = None, 

406 row: Optional[Union[Row[Any], RowMapping]] = None, 

407 identity_token: Optional[Any] = None, 

408) -> _IdentityKeyType[_T]: 

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

410 :attr:`.Session.identity_map` dictionary. 

411 

412 This function has several call styles: 

413 

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

415 

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

417 tuple as an argument. 

418 

419 E.g.:: 

420 

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

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

423 

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

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

426 :param identity_token: optional identity token 

427 

428 .. versionadded:: 1.2 added identity_token 

429 

430 

431 * ``identity_key(instance=instance)`` 

432 

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

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

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

436 values). 

437 

438 E.g.:: 

439 

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

441 >>> identity_key(instance=instance) 

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

443 

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

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

446 effect of performing a database check for the corresponding row 

447 if the object is expired. 

448 

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

450 

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

452 

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

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

455 

456 E.g.:: 

457 

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

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

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

461 

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

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

464 (must be given as a keyword arg) 

465 :param identity_token: optional identity token 

466 

467 .. versionadded:: 1.2 added identity_token 

468 

469 """ # noqa: E501 

470 if class_ is not None: 

471 mapper = class_mapper(class_) 

472 if row is None: 

473 if ident is None: 

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

475 return mapper.identity_key_from_primary_key( 

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

477 ) 

478 else: 

479 return mapper.identity_key_from_row( 

480 row, identity_token=identity_token 

481 ) 

482 elif instance is not None: 

483 mapper = object_mapper(instance) 

484 return mapper.identity_key_from_instance(instance) 

485 else: 

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

487 

488 

489class _TraceAdaptRole(enum.Enum): 

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

491 

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

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

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

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

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

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

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

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

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

501 particular ORMAdapter within runtime debugging. 

502 

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

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

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

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

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

508 however the adaption is applied in a narrower scope. 

509 

510 """ 

511 

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

513 # construction time 

514 ALIASED_INSP = enum.auto() 

515 

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

517 # join 

518 JOINEDLOAD_USER_DEFINED_ALIAS = enum.auto() 

519 JOINEDLOAD_PATH_WITH_POLYMORPHIC = enum.auto() 

520 JOINEDLOAD_MEMOIZED_ADAPTER = enum.auto() 

521 

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

523 # clauses, replacing tables with subqueries 

524 MAPPER_POLYMORPHIC_ADAPTER = enum.auto() 

525 WITH_POLYMORPHIC_ADAPTER = enum.auto() 

526 WITH_POLYMORPHIC_ADAPTER_RIGHT_JOIN = enum.auto() 

527 DEPRECATED_JOIN_ADAPT_RIGHT_SIDE = enum.auto() 

528 

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

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

531 # time. assigned to ORMCompileState._from_obj_alias 

532 ADAPT_FROM_STATEMENT = enum.auto() 

533 

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

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

536 # joinedloads are then placed on the outside. 

537 # assigned to ORMCompileState.compound_eager_adapter 

538 COMPOUND_EAGER_STATEMENT = enum.auto() 

539 

540 # the legacy Query._set_select_from() case. 

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

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

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

544 # still does full statement traversal 

545 # assigned to ORMCompileState._from_obj_alias 

546 LEGACY_SELECT_FROM_ALIAS = enum.auto() 

547 

548 

549class ORMStatementAdapter(sql_util.ColumnAdapter): 

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

551 

552 __slots__ = ("role",) 

553 

554 def __init__( 

555 self, 

556 role: _TraceAdaptRole, 

557 selectable: Selectable, 

558 *, 

559 equivalents: Optional[_EquivalentColumnMap] = None, 

560 adapt_required: bool = False, 

561 allow_label_resolve: bool = True, 

562 anonymize_labels: bool = False, 

563 adapt_on_names: bool = False, 

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

565 ): 

566 self.role = role 

567 super().__init__( 

568 selectable, 

569 equivalents=equivalents, 

570 adapt_required=adapt_required, 

571 allow_label_resolve=allow_label_resolve, 

572 anonymize_labels=anonymize_labels, 

573 adapt_on_names=adapt_on_names, 

574 adapt_from_selectables=adapt_from_selectables, 

575 ) 

576 

577 

578class ORMAdapter(sql_util.ColumnAdapter): 

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

580 non-matching mappers. 

581 

582 """ 

583 

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

585 

586 is_aliased_class: bool 

587 aliased_insp: Optional[AliasedInsp[Any]] 

588 

589 def __init__( 

590 self, 

591 role: _TraceAdaptRole, 

592 entity: _InternalEntityType[Any], 

593 *, 

594 equivalents: Optional[_EquivalentColumnMap] = None, 

595 adapt_required: bool = False, 

596 allow_label_resolve: bool = True, 

597 anonymize_labels: bool = False, 

598 selectable: Optional[Selectable] = None, 

599 limit_on_entity: bool = True, 

600 adapt_on_names: bool = False, 

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

602 ): 

603 self.role = role 

604 self.mapper = entity.mapper 

605 if selectable is None: 

606 selectable = entity.selectable 

607 if insp_is_aliased_class(entity): 

608 self.is_aliased_class = True 

609 self.aliased_insp = entity 

610 else: 

611 self.is_aliased_class = False 

612 self.aliased_insp = None 

613 

614 super().__init__( 

615 selectable, 

616 equivalents, 

617 adapt_required=adapt_required, 

618 allow_label_resolve=allow_label_resolve, 

619 anonymize_labels=anonymize_labels, 

620 include_fn=self._include_fn if limit_on_entity else None, 

621 adapt_on_names=adapt_on_names, 

622 adapt_from_selectables=adapt_from_selectables, 

623 ) 

624 

625 def _include_fn(self, elem): 

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

627 

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

629 

630 

631class AliasedClass( 

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

633): 

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

635 

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

637 construct, this object mimics the mapped class using a 

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

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

640 

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

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

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

644 

645 # find all pairs of users with the same name 

646 user_alias = aliased(User) 

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

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

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

650 

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

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

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

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

655 See the links below for examples. 

656 

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

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

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

660 

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

662 This object implements an attribute scheme which produces the 

663 same attribute and method interface as the original mapped 

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

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

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

667 

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

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

670 using :func:`_sa.inspect`:: 

671 

672 from sqlalchemy import inspect 

673 

674 my_alias = aliased(MyClass) 

675 insp = inspect(my_alias) 

676 

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

678 

679 

680 .. seealso:: 

681 

682 :func:`.aliased` 

683 

684 :func:`.with_polymorphic` 

685 

686 :ref:`relationship_aliased_class` 

687 

688 :ref:`relationship_to_window_function` 

689 

690 

691 """ 

692 

693 __name__: str 

694 

695 def __init__( 

696 self, 

697 mapped_class_or_ac: _EntityType[_O], 

698 alias: Optional[FromClause] = None, 

699 name: Optional[str] = None, 

700 flat: bool = False, 

701 adapt_on_names: bool = False, 

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

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

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

705 use_mapper_path: bool = False, 

706 represents_outer_join: bool = False, 

707 ): 

708 insp = cast( 

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

710 ) 

711 mapper = insp.mapper 

712 

713 nest_adapters = False 

714 

715 if alias is None: 

716 if insp.is_aliased_class and insp.selectable._is_subquery: 

717 alias = insp.selectable.alias() 

718 else: 

719 alias = ( 

720 mapper._with_polymorphic_selectable._anonymous_fromclause( 

721 name=name, 

722 flat=flat, 

723 ) 

724 ) 

725 elif insp.is_aliased_class: 

726 nest_adapters = True 

727 

728 assert alias is not None 

729 self._aliased_insp = AliasedInsp( 

730 self, 

731 insp, 

732 alias, 

733 name, 

734 ( 

735 with_polymorphic_mappers 

736 if with_polymorphic_mappers 

737 else mapper.with_polymorphic_mappers 

738 ), 

739 ( 

740 with_polymorphic_discriminator 

741 if with_polymorphic_discriminator is not None 

742 else mapper.polymorphic_on 

743 ), 

744 base_alias, 

745 use_mapper_path, 

746 adapt_on_names, 

747 represents_outer_join, 

748 nest_adapters, 

749 ) 

750 

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

752 

753 @classmethod 

754 def _reconstitute_from_aliased_insp( 

755 cls, aliased_insp: AliasedInsp[_O] 

756 ) -> AliasedClass[_O]: 

757 obj = cls.__new__(cls) 

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

759 obj._aliased_insp = aliased_insp 

760 

761 if aliased_insp._is_with_polymorphic: 

762 for sub_aliased_insp in aliased_insp._with_polymorphic_entities: 

763 if sub_aliased_insp is not aliased_insp: 

764 ent = AliasedClass._reconstitute_from_aliased_insp( 

765 sub_aliased_insp 

766 ) 

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

768 

769 return obj 

770 

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

772 try: 

773 _aliased_insp = self.__dict__["_aliased_insp"] 

774 except KeyError: 

775 raise AttributeError() 

776 else: 

777 target = _aliased_insp._target 

778 # maintain all getattr mechanics 

779 attr = getattr(target, key) 

780 

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

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

783 # new self 

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

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

786 

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

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

789 if hasattr(attr, "__get__"): 

790 attr = attr.__get__(None, self) 

791 

792 # attributes within the QueryableAttribute system will want this 

793 # to be invoked so the object can be adapted 

794 if hasattr(attr, "adapt_to_entity"): 

795 attr = attr.adapt_to_entity(_aliased_insp) 

796 setattr(self, key, attr) 

797 

798 return attr 

799 

800 def _get_from_serialized( 

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

802 ) -> Any: 

803 # this method is only used in terms of the 

804 # sqlalchemy.ext.serializer extension 

805 attr = getattr(mapped_class, key) 

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

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

808 

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

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

811 if hasattr(attr, "__get__"): 

812 attr = attr.__get__(None, self) 

813 

814 # attributes within the QueryableAttribute system will want this 

815 # to be invoked so the object can be adapted 

816 if hasattr(attr, "adapt_to_entity"): 

817 aliased_insp._weak_entity = weakref.ref(self) 

818 attr = attr.adapt_to_entity(aliased_insp) 

819 setattr(self, key, attr) 

820 

821 return attr 

822 

823 def __repr__(self) -> str: 

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

825 id(self), 

826 self._aliased_insp._target.__name__, 

827 ) 

828 

829 def __str__(self) -> str: 

830 return str(self._aliased_insp) 

831 

832 

833@inspection._self_inspects 

834class AliasedInsp( 

835 ORMEntityColumnsClauseRole[_O], 

836 ORMFromClauseRole, 

837 HasCacheKey, 

838 InspectionAttr, 

839 MemoizedSlots, 

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

841 Generic[_O], 

842): 

843 """Provide an inspection interface for an 

844 :class:`.AliasedClass` object. 

845 

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

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

848 :func:`_sa.inspect` function:: 

849 

850 from sqlalchemy import inspect 

851 from sqlalchemy.orm import aliased 

852 

853 my_alias = aliased(MyMappedClass) 

854 insp = inspect(my_alias) 

855 

856 Attributes on :class:`.AliasedInsp` 

857 include: 

858 

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

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

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

862 construct which ultimately 

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

864 :class:`_expression.Select` 

865 construct. 

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

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

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

869 objects 

870 indicating all those mappers expressed in the select construct 

871 for the :class:`.AliasedClass`. 

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

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

874 

875 .. seealso:: 

876 

877 :ref:`inspection_toplevel` 

878 

879 """ 

880 

881 __slots__ = ( 

882 "__weakref__", 

883 "_weak_entity", 

884 "mapper", 

885 "selectable", 

886 "name", 

887 "_adapt_on_names", 

888 "with_polymorphic_mappers", 

889 "polymorphic_on", 

890 "_use_mapper_path", 

891 "_base_alias", 

892 "represents_outer_join", 

893 "persist_selectable", 

894 "local_table", 

895 "_is_with_polymorphic", 

896 "_with_polymorphic_entities", 

897 "_adapter", 

898 "_target", 

899 "__clause_element__", 

900 "_memoized_values", 

901 "_all_column_expressions", 

902 "_nest_adapters", 

903 ) 

904 

905 _cache_key_traversal = [ 

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

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

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

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

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

911 ( 

912 "with_polymorphic_mappers", 

913 visitors.InternalTraversal.dp_has_cache_key_list, 

914 ), 

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

916 ] 

917 

918 mapper: Mapper[_O] 

919 selectable: FromClause 

920 _adapter: ORMAdapter 

921 with_polymorphic_mappers: Sequence[Mapper[Any]] 

922 _with_polymorphic_entities: Sequence[AliasedInsp[Any]] 

923 

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

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

926 

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

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

929 

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

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

932 

933 """ 

934 

935 def __init__( 

936 self, 

937 entity: AliasedClass[_O], 

938 inspected: _InternalEntityType[_O], 

939 selectable: FromClause, 

940 name: Optional[str], 

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

942 polymorphic_on: Optional[ColumnElement[Any]], 

943 _base_alias: Optional[AliasedInsp[Any]], 

944 _use_mapper_path: bool, 

945 adapt_on_names: bool, 

946 represents_outer_join: bool, 

947 nest_adapters: bool, 

948 ): 

949 mapped_class_or_ac = inspected.entity 

950 mapper = inspected.mapper 

951 

952 self._weak_entity = weakref.ref(entity) 

953 self.mapper = mapper 

954 self.selectable = self.persist_selectable = self.local_table = ( 

955 selectable 

956 ) 

957 self.name = name 

958 self.polymorphic_on = polymorphic_on 

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

960 self._use_mapper_path = _use_mapper_path 

961 self.represents_outer_join = represents_outer_join 

962 self._nest_adapters = nest_adapters 

963 

964 if with_polymorphic_mappers: 

965 self._is_with_polymorphic = True 

966 self.with_polymorphic_mappers = with_polymorphic_mappers 

967 self._with_polymorphic_entities = [] 

968 for poly in self.with_polymorphic_mappers: 

969 if poly is not mapper: 

970 ent = AliasedClass( 

971 poly.class_, 

972 selectable, 

973 base_alias=self, 

974 adapt_on_names=adapt_on_names, 

975 use_mapper_path=_use_mapper_path, 

976 ) 

977 

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

979 self._with_polymorphic_entities.append(ent._aliased_insp) 

980 

981 else: 

982 self._is_with_polymorphic = False 

983 self.with_polymorphic_mappers = [mapper] 

984 

985 self._adapter = ORMAdapter( 

986 _TraceAdaptRole.ALIASED_INSP, 

987 mapper, 

988 selectable=selectable, 

989 equivalents=mapper._equivalent_columns, 

990 adapt_on_names=adapt_on_names, 

991 anonymize_labels=True, 

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

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

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

995 adapt_from_selectables={ 

996 m.selectable 

997 for m in self.with_polymorphic_mappers 

998 if not adapt_on_names 

999 }, 

1000 limit_on_entity=False, 

1001 ) 

1002 

1003 if nest_adapters: 

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

1005 assert isinstance(inspected, AliasedInsp) 

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

1007 

1008 self._adapt_on_names = adapt_on_names 

1009 self._target = mapped_class_or_ac 

1010 

1011 @property 

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

1013 self.mapper._check_configure() 

1014 

1015 @classmethod 

1016 def _alias_factory( 

1017 cls, 

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

1019 alias: Optional[FromClause] = None, 

1020 name: Optional[str] = None, 

1021 flat: bool = False, 

1022 adapt_on_names: bool = False, 

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

1024 if isinstance(element, GenerativeSelect): 

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

1026 elif isinstance(element, FromClause): 

1027 if adapt_on_names: 

1028 raise sa_exc.ArgumentError( 

1029 "adapt_on_names only applies to ORM elements" 

1030 ) 

1031 if name: 

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

1033 else: 

1034 return coercions.expect( 

1035 roles.AnonymizedFromClauseRole, element, flat=flat 

1036 ) 

1037 else: 

1038 return AliasedClass( 

1039 element, 

1040 alias=alias, 

1041 flat=flat, 

1042 name=name, 

1043 adapt_on_names=adapt_on_names, 

1044 ) 

1045 

1046 @classmethod 

1047 def _with_polymorphic_factory( 

1048 cls, 

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

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

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

1052 flat: bool = False, 

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

1054 aliased: bool = False, 

1055 innerjoin: bool = False, 

1056 adapt_on_names: bool = False, 

1057 name: Optional[str] = None, 

1058 _use_mapper_path: bool = False, 

1059 ) -> AliasedClass[_O]: 

1060 primary_mapper = _class_to_mapper(base) 

1061 

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

1063 raise sa_exc.ArgumentError( 

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

1065 "simultaneously to with_polymorphic()" 

1066 ) 

1067 

1068 mappers, selectable = primary_mapper._with_polymorphic_args( 

1069 classes, selectable, innerjoin=innerjoin 

1070 ) 

1071 if aliased or flat: 

1072 assert selectable is not None 

1073 selectable = selectable._anonymous_fromclause(flat=flat) 

1074 

1075 return AliasedClass( 

1076 base, 

1077 selectable, 

1078 name=name, 

1079 with_polymorphic_mappers=mappers, 

1080 adapt_on_names=adapt_on_names, 

1081 with_polymorphic_discriminator=polymorphic_on, 

1082 use_mapper_path=_use_mapper_path, 

1083 represents_outer_join=not innerjoin, 

1084 ) 

1085 

1086 @property 

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

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

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

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

1091 # is passed around. 

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

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

1094 ent = self._weak_entity() 

1095 if ent is None: 

1096 ent = AliasedClass._reconstitute_from_aliased_insp(self) 

1097 self._weak_entity = weakref.ref(ent) 

1098 return ent 

1099 

1100 is_aliased_class = True 

1101 "always returns True" 

1102 

1103 def _memoized_method___clause_element__(self) -> FromClause: 

1104 return self.selectable._annotate( 

1105 { 

1106 "parentmapper": self.mapper, 

1107 "parententity": self, 

1108 "entity_namespace": self, 

1109 } 

1110 )._set_propagate_attrs( 

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

1112 ) 

1113 

1114 @property 

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

1116 return self.entity 

1117 

1118 @property 

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

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

1121 :class:`.AliasedInsp`.""" 

1122 return self.mapper.class_ 

1123 

1124 @property 

1125 def _path_registry(self) -> AbstractEntityRegistry: 

1126 if self._use_mapper_path: 

1127 return self.mapper._path_registry 

1128 else: 

1129 return PathRegistry.per_mapper(self) 

1130 

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

1132 return { 

1133 "entity": self.entity, 

1134 "mapper": self.mapper, 

1135 "alias": self.selectable, 

1136 "name": self.name, 

1137 "adapt_on_names": self._adapt_on_names, 

1138 "with_polymorphic_mappers": self.with_polymorphic_mappers, 

1139 "with_polymorphic_discriminator": self.polymorphic_on, 

1140 "base_alias": self._base_alias(), 

1141 "use_mapper_path": self._use_mapper_path, 

1142 "represents_outer_join": self.represents_outer_join, 

1143 "nest_adapters": self._nest_adapters, 

1144 } 

1145 

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

1147 self.__init__( # type: ignore 

1148 state["entity"], 

1149 state["mapper"], 

1150 state["alias"], 

1151 state["name"], 

1152 state["with_polymorphic_mappers"], 

1153 state["with_polymorphic_discriminator"], 

1154 state["base_alias"], 

1155 state["use_mapper_path"], 

1156 state["adapt_on_names"], 

1157 state["represents_outer_join"], 

1158 state["nest_adapters"], 

1159 ) 

1160 

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

1162 # assert self._is_with_polymorphic 

1163 # assert other._is_with_polymorphic 

1164 

1165 primary_mapper = other.mapper 

1166 

1167 assert self.mapper is primary_mapper 

1168 

1169 our_classes = util.to_set( 

1170 mp.class_ for mp in self.with_polymorphic_mappers 

1171 ) 

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

1173 if our_classes == new_classes: 

1174 return other 

1175 else: 

1176 classes = our_classes.union(new_classes) 

1177 

1178 mappers, selectable = primary_mapper._with_polymorphic_args( 

1179 classes, None, innerjoin=not other.represents_outer_join 

1180 ) 

1181 selectable = selectable._anonymous_fromclause(flat=True) 

1182 return AliasedClass( 

1183 primary_mapper, 

1184 selectable, 

1185 with_polymorphic_mappers=mappers, 

1186 with_polymorphic_discriminator=other.polymorphic_on, 

1187 use_mapper_path=other._use_mapper_path, 

1188 represents_outer_join=other.represents_outer_join, 

1189 )._aliased_insp 

1190 

1191 def _adapt_element( 

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

1193 ) -> _ORMCOLEXPR: 

1194 assert isinstance(expr, ColumnElement) 

1195 d: Dict[str, Any] = { 

1196 "parententity": self, 

1197 "parentmapper": self.mapper, 

1198 } 

1199 if key: 

1200 d["proxy_key"] = key 

1201 

1202 # IMO mypy should see this one also as returning the same type 

1203 # we put into it, but it's not 

1204 return ( 

1205 self._adapter.traverse(expr) 

1206 ._annotate(d) 

1207 ._set_propagate_attrs( 

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

1209 ) 

1210 ) 

1211 

1212 if TYPE_CHECKING: 

1213 # establish compatibility with the _ORMAdapterProto protocol, 

1214 # which in turn is compatible with _CoreAdapterProto. 

1215 

1216 def _orm_adapt_element( 

1217 self, 

1218 obj: _CE, 

1219 key: Optional[str] = None, 

1220 ) -> _CE: ... 

1221 

1222 else: 

1223 _orm_adapt_element = _adapt_element 

1224 

1225 def _entity_for_mapper(self, mapper): 

1226 self_poly = self.with_polymorphic_mappers 

1227 if mapper in self_poly: 

1228 if mapper is self.mapper: 

1229 return self 

1230 else: 

1231 return getattr( 

1232 self.entity, mapper.class_.__name__ 

1233 )._aliased_insp 

1234 elif mapper.isa(self.mapper): 

1235 return self 

1236 else: 

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

1238 

1239 def _memoized_attr__get_clause(self): 

1240 onclause, replacemap = self.mapper._get_clause 

1241 return ( 

1242 self._adapter.traverse(onclause), 

1243 { 

1244 self._adapter.traverse(col): param 

1245 for col, param in replacemap.items() 

1246 }, 

1247 ) 

1248 

1249 def _memoized_attr__memoized_values(self): 

1250 return {} 

1251 

1252 def _memoized_attr__all_column_expressions(self): 

1253 if self._is_with_polymorphic: 

1254 cols_plus_keys = self.mapper._columns_plus_keys( 

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

1256 ) 

1257 else: 

1258 cols_plus_keys = self.mapper._columns_plus_keys() 

1259 

1260 cols_plus_keys = [ 

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

1262 ] 

1263 

1264 return ColumnCollection(cols_plus_keys) 

1265 

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

1267 if key in self._memoized_values: 

1268 return self._memoized_values[key] 

1269 else: 

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

1271 return value 

1272 

1273 def __repr__(self): 

1274 if self.with_polymorphic_mappers: 

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

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

1277 ) 

1278 else: 

1279 with_poly = "" 

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

1281 id(self), 

1282 self.class_.__name__, 

1283 with_poly, 

1284 ) 

1285 

1286 def __str__(self): 

1287 if self._is_with_polymorphic: 

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

1289 self._target.__name__, 

1290 ", ".join( 

1291 mp.class_.__name__ 

1292 for mp in self.with_polymorphic_mappers 

1293 if mp is not self.mapper 

1294 ), 

1295 ) 

1296 else: 

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

1298 

1299 

1300class _WrapUserEntity: 

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

1302 we can bypass declared_attr descriptors on unmapped mixins, which 

1303 normally emit a warning for such use. 

1304 

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

1306 the need arise. 

1307 

1308 """ 

1309 

1310 __slots__ = ("subject",) 

1311 

1312 def __init__(self, subject): 

1313 self.subject = subject 

1314 

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

1316 def __getattribute__(self, name): 

1317 decl_api = util.preloaded.orm.decl_api 

1318 

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

1320 if name in subject.__dict__ and isinstance( 

1321 subject.__dict__[name], decl_api.declared_attr 

1322 ): 

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

1324 else: 

1325 return getattr(subject, name) 

1326 

1327 

1328class LoaderCriteriaOption(CriteriaOption): 

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

1330 a particular entity. 

1331 

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

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

1334 details. 

1335 

1336 .. versionadded:: 1.4 

1337 

1338 """ 

1339 

1340 __slots__ = ( 

1341 "root_entity", 

1342 "entity", 

1343 "deferred_where_criteria", 

1344 "where_criteria", 

1345 "_where_crit_orig", 

1346 "include_aliases", 

1347 "propagate_to_loaders", 

1348 ) 

1349 

1350 _traverse_internals = [ 

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

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

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

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

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

1356 ] 

1357 

1358 root_entity: Optional[Type[Any]] 

1359 entity: Optional[_InternalEntityType[Any]] 

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

1361 deferred_where_criteria: bool 

1362 include_aliases: bool 

1363 propagate_to_loaders: bool 

1364 

1365 _where_crit_orig: Any 

1366 

1367 def __init__( 

1368 self, 

1369 entity_or_base: _EntityType[Any], 

1370 where_criteria: Union[ 

1371 _ColumnExpressionArgument[bool], 

1372 Callable[[Any], _ColumnExpressionArgument[bool]], 

1373 ], 

1374 loader_only: bool = False, 

1375 include_aliases: bool = False, 

1376 propagate_to_loaders: bool = True, 

1377 track_closure_variables: bool = True, 

1378 ): 

1379 entity = cast( 

1380 "_InternalEntityType[Any]", 

1381 inspection.inspect(entity_or_base, False), 

1382 ) 

1383 if entity is None: 

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

1385 self.entity = None 

1386 else: 

1387 self.root_entity = None 

1388 self.entity = entity 

1389 

1390 self._where_crit_orig = where_criteria 

1391 if callable(where_criteria): 

1392 if self.root_entity is not None: 

1393 wrap_entity = self.root_entity 

1394 else: 

1395 assert entity is not None 

1396 wrap_entity = entity.entity 

1397 

1398 self.deferred_where_criteria = True 

1399 self.where_criteria = lambdas.DeferredLambdaElement( 

1400 where_criteria, 

1401 roles.WhereHavingRole, 

1402 lambda_args=(_WrapUserEntity(wrap_entity),), 

1403 opts=lambdas.LambdaOptions( 

1404 track_closure_variables=track_closure_variables 

1405 ), 

1406 ) 

1407 else: 

1408 self.deferred_where_criteria = False 

1409 self.where_criteria = coercions.expect( 

1410 roles.WhereHavingRole, where_criteria 

1411 ) 

1412 

1413 self.include_aliases = include_aliases 

1414 self.propagate_to_loaders = propagate_to_loaders 

1415 

1416 @classmethod 

1417 def _unreduce( 

1418 cls, entity, where_criteria, include_aliases, propagate_to_loaders 

1419 ): 

1420 return LoaderCriteriaOption( 

1421 entity, 

1422 where_criteria, 

1423 include_aliases=include_aliases, 

1424 propagate_to_loaders=propagate_to_loaders, 

1425 ) 

1426 

1427 def __reduce__(self): 

1428 return ( 

1429 LoaderCriteriaOption._unreduce, 

1430 ( 

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

1432 self._where_crit_orig, 

1433 self.include_aliases, 

1434 self.propagate_to_loaders, 

1435 ), 

1436 ) 

1437 

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

1439 if self.entity: 

1440 yield from self.entity.mapper.self_and_descendants 

1441 else: 

1442 assert self.root_entity 

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

1444 while stack: 

1445 subclass = stack.pop(0) 

1446 ent = cast( 

1447 "_InternalEntityType[Any]", 

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

1449 ) 

1450 if ent: 

1451 yield from ent.mapper.self_and_descendants 

1452 else: 

1453 stack.extend(subclass.__subclasses__()) 

1454 

1455 def _should_include(self, compile_state: ORMCompileState) -> bool: 

1456 if ( 

1457 compile_state.select_statement._annotations.get( 

1458 "for_loader_criteria", None 

1459 ) 

1460 is self 

1461 ): 

1462 return False 

1463 return True 

1464 

1465 def _resolve_where_criteria( 

1466 self, ext_info: _InternalEntityType[Any] 

1467 ) -> ColumnElement[bool]: 

1468 if self.deferred_where_criteria: 

1469 crit = cast( 

1470 "ColumnElement[bool]", 

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

1472 ) 

1473 else: 

1474 crit = self.where_criteria # type: ignore 

1475 assert isinstance(crit, ColumnElement) 

1476 return sql_util._deep_annotate( 

1477 crit, 

1478 {"for_loader_criteria": self}, 

1479 detect_subquery_cols=True, 

1480 ind_cols_on_fromclause=True, 

1481 ) 

1482 

1483 def process_compile_state_replaced_entities( 

1484 self, 

1485 compile_state: ORMCompileState, 

1486 mapper_entities: Iterable[_MapperEntity], 

1487 ) -> None: 

1488 self.process_compile_state(compile_state) 

1489 

1490 def process_compile_state(self, compile_state: ORMCompileState) -> None: 

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

1492 

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

1494 # use compile_state.attributes instead 

1495 

1496 self.get_global_criteria(compile_state.global_attributes) 

1497 

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

1499 for mp in self._all_mappers(): 

1500 load_criteria = attributes.setdefault( 

1501 ("additional_entity_criteria", mp), [] 

1502 ) 

1503 

1504 load_criteria.append(self) 

1505 

1506 

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

1508 

1509 

1510@inspection._inspects(type) 

1511def _inspect_mc( 

1512 class_: Type[_O], 

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

1514 try: 

1515 class_manager = opt_manager_of_class(class_) 

1516 if class_manager is None or not class_manager.is_mapped: 

1517 return None 

1518 mapper = class_manager.mapper 

1519 except exc.NO_STATE: 

1520 return None 

1521 else: 

1522 return mapper 

1523 

1524 

1525GenericAlias = type(List[Any]) 

1526 

1527 

1528@inspection._inspects(GenericAlias) 

1529def _inspect_generic_alias( 

1530 class_: Type[_O], 

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

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

1533 return _inspect_mc(origin) 

1534 

1535 

1536@inspection._self_inspects 

1537class Bundle( 

1538 ORMColumnsClauseRole[_T], 

1539 SupportsCloneAnnotations, 

1540 MemoizedHasCacheKey, 

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

1542 InspectionAttr, 

1543): 

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

1545 under one namespace. 

1546 

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

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

1549 It also 

1550 is extensible via simple subclassing, where the primary capability 

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

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

1553 involving ORM identity-mapped classes. 

1554 

1555 .. seealso:: 

1556 

1557 :ref:`bundles` 

1558 

1559 

1560 """ 

1561 

1562 single_entity = False 

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

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

1565 

1566 is_clause_element = False 

1567 

1568 is_mapper = False 

1569 

1570 is_aliased_class = False 

1571 

1572 is_bundle = True 

1573 

1574 _propagate_attrs: _PropagateAttrsType = util.immutabledict() 

1575 

1576 proxy_set = util.EMPTY_SET 

1577 

1578 exprs: List[_ColumnsClauseElement] 

1579 

1580 def __init__( 

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

1582 ): 

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

1584 

1585 e.g.:: 

1586 

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

1588 

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

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

1591 

1592 :param name: name of the bundle. 

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

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

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

1596 in the same manner as a mapped entity. 

1597 

1598 """ # noqa: E501 

1599 self.name = self._label = name 

1600 coerced_exprs = [ 

1601 coercions.expect( 

1602 roles.ColumnsClauseRole, expr, apply_propagate_attrs=self 

1603 ) 

1604 for expr in exprs 

1605 ] 

1606 self.exprs = coerced_exprs 

1607 

1608 self.c = self.columns = ColumnCollection( 

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

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

1611 ).as_readonly() 

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

1613 

1614 def _gen_cache_key( 

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

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

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

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

1619 ) 

1620 

1621 @property 

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

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

1624 "parentmapper", None 

1625 ) 

1626 return mp 

1627 

1628 @property 

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

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

1631 0 

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

1633 return ie 

1634 

1635 @property 

1636 def entity_namespace( 

1637 self, 

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

1639 return self.c 

1640 

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

1642 

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

1644 

1645 e.g.:: 

1646 

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

1648 

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

1650 

1651 Nesting of bundles is also supported:: 

1652 

1653 b1 = Bundle( 

1654 "b1", 

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

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

1657 ) 

1658 

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

1660 

1661 .. seealso:: 

1662 

1663 :attr:`.Bundle.c` 

1664 

1665 """ # noqa: E501 

1666 

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

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

1669 

1670 def _clone(self, **kw): 

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

1672 cloned.__dict__.update(self.__dict__) 

1673 return cloned 

1674 

1675 def __clause_element__(self): 

1676 # ensure existing entity_namespace remains 

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

1678 annotations.update(self._annotations) 

1679 

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

1681 "plugin_subject", self.entity 

1682 ) 

1683 return ( 

1684 expression.ClauseList( 

1685 _literal_as_text_role=roles.ColumnsClauseRole, 

1686 group=False, 

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

1688 ) 

1689 ._annotate(annotations) 

1690 ._set_propagate_attrs( 

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

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

1693 { 

1694 "compile_state_plugin": "orm", 

1695 "plugin_subject": plugin_subject, 

1696 } 

1697 ) 

1698 ) 

1699 

1700 @property 

1701 def clauses(self): 

1702 return self.__clause_element__().clauses 

1703 

1704 def label(self, name): 

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

1706 

1707 cloned = self._clone() 

1708 cloned.name = name 

1709 return cloned 

1710 

1711 def create_row_processor( 

1712 self, 

1713 query: Select[Any], 

1714 procs: Sequence[Callable[[Row[Any]], Any]], 

1715 labels: Sequence[str], 

1716 ) -> Callable[[Row[Any]], Any]: 

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

1718 

1719 May be overridden by subclasses to provide custom behaviors when 

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

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

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

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

1724 structure. 

1725 

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

1727 return structure with a straight Python dictionary:: 

1728 

1729 from sqlalchemy.orm import Bundle 

1730 

1731 

1732 class DictBundle(Bundle): 

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

1734 "Override create_row_processor to return values as dictionaries" 

1735 

1736 def proc(row): 

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

1738 

1739 return proc 

1740 

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

1742 values:: 

1743 

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

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

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

1747 

1748 """ # noqa: E501 

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

1750 

1751 def proc(row: Row[Any]) -> Any: 

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

1753 

1754 return proc 

1755 

1756 

1757def _orm_annotate(element: _SA, exclude: Optional[Any] = None) -> _SA: 

1758 """Deep copy the given ClauseElement, annotating each element with the 

1759 "_orm_adapt" flag. 

1760 

1761 Elements within the exclude collection will be cloned but not annotated. 

1762 

1763 """ 

1764 return sql_util._deep_annotate(element, {"_orm_adapt": True}, exclude) 

1765 

1766 

1767def _orm_deannotate(element: _SA) -> _SA: 

1768 """Remove annotations that link a column to a particular mapping. 

1769 

1770 Note this doesn't affect "remote" and "foreign" annotations 

1771 passed by the :func:`_orm.foreign` and :func:`_orm.remote` 

1772 annotators. 

1773 

1774 """ 

1775 

1776 return sql_util._deep_deannotate( 

1777 element, values=("_orm_adapt", "parententity") 

1778 ) 

1779 

1780 

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

1782 return sql_util._deep_deannotate(element) 

1783 

1784 

1785class _ORMJoin(expression.Join): 

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

1787 

1788 __visit_name__ = expression.Join.__visit_name__ 

1789 

1790 inherit_cache = True 

1791 

1792 def __init__( 

1793 self, 

1794 left: _FromClauseArgument, 

1795 right: _FromClauseArgument, 

1796 onclause: Optional[_OnClauseArgument] = None, 

1797 isouter: bool = False, 

1798 full: bool = False, 

1799 _left_memo: Optional[Any] = None, 

1800 _right_memo: Optional[Any] = None, 

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

1802 ): 

1803 left_info = cast( 

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

1805 inspection.inspect(left), 

1806 ) 

1807 

1808 right_info = cast( 

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

1810 inspection.inspect(right), 

1811 ) 

1812 adapt_to = right_info.selectable 

1813 

1814 # used by joined eager loader 

1815 self._left_memo = _left_memo 

1816 self._right_memo = _right_memo 

1817 

1818 if isinstance(onclause, attributes.QueryableAttribute): 

1819 if TYPE_CHECKING: 

1820 assert isinstance( 

1821 onclause.comparator, RelationshipProperty.Comparator 

1822 ) 

1823 on_selectable = onclause.comparator._source_selectable() 

1824 prop = onclause.property 

1825 _extra_criteria += onclause._extra_criteria 

1826 elif isinstance(onclause, MapperProperty): 

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

1828 prop = onclause 

1829 on_selectable = prop.parent.selectable 

1830 else: 

1831 prop = None 

1832 on_selectable = None 

1833 

1834 left_selectable = left_info.selectable 

1835 if prop: 

1836 adapt_from: Optional[FromClause] 

1837 if sql_util.clause_is_present(on_selectable, left_selectable): 

1838 adapt_from = on_selectable 

1839 else: 

1840 assert isinstance(left_selectable, FromClause) 

1841 adapt_from = left_selectable 

1842 

1843 ( 

1844 pj, 

1845 sj, 

1846 source, 

1847 dest, 

1848 secondary, 

1849 target_adapter, 

1850 ) = prop._create_joins( 

1851 source_selectable=adapt_from, 

1852 dest_selectable=adapt_to, 

1853 source_polymorphic=True, 

1854 of_type_entity=right_info, 

1855 alias_secondary=True, 

1856 extra_criteria=_extra_criteria, 

1857 ) 

1858 

1859 if sj is not None: 

1860 if isouter: 

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

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

1863 onclause = pj 

1864 else: 

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

1866 onclause = sj 

1867 else: 

1868 onclause = pj 

1869 

1870 self._target_adapter = target_adapter 

1871 

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

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

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

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

1876 if is_selectable(left_info): 

1877 parententity = left_selectable._annotations.get( 

1878 "parententity", None 

1879 ) 

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

1881 parententity = left_info 

1882 else: 

1883 parententity = None 

1884 

1885 if parententity is not None: 

1886 self._annotations = self._annotations.union( 

1887 {"parententity": parententity} 

1888 ) 

1889 

1890 augment_onclause = bool(_extra_criteria) and not prop 

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

1892 

1893 assert self.onclause is not None 

1894 

1895 if augment_onclause: 

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

1897 

1898 if ( 

1899 not prop 

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

1901 and right_info.mapper.single # type: ignore 

1902 ): 

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

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

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

1906 # WHERE. 

1907 single_crit = right_info.mapper._single_table_criterion 

1908 if single_crit is not None: 

1909 if insp_is_aliased_class(right_info): 

1910 single_crit = right_info._adapter.traverse(single_crit) 

1911 self.onclause = self.onclause & single_crit 

1912 

1913 def _splice_into_center(self, other): 

1914 """Splice a join into the center. 

1915 

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

1917 

1918 """ 

1919 leftmost = other 

1920 while isinstance(leftmost, sql.Join): 

1921 leftmost = leftmost.left 

1922 

1923 assert self.right is leftmost 

1924 

1925 left = _ORMJoin( 

1926 self.left, 

1927 other.left, 

1928 self.onclause, 

1929 isouter=self.isouter, 

1930 _left_memo=self._left_memo, 

1931 _right_memo=other._left_memo._path_registry, 

1932 ) 

1933 

1934 return _ORMJoin( 

1935 left, 

1936 other.right, 

1937 other.onclause, 

1938 isouter=other.isouter, 

1939 _right_memo=other._right_memo, 

1940 ) 

1941 

1942 def join( 

1943 self, 

1944 right: _FromClauseArgument, 

1945 onclause: Optional[_OnClauseArgument] = None, 

1946 isouter: bool = False, 

1947 full: bool = False, 

1948 ) -> _ORMJoin: 

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

1950 

1951 def outerjoin( 

1952 self, 

1953 right: _FromClauseArgument, 

1954 onclause: Optional[_OnClauseArgument] = None, 

1955 full: bool = False, 

1956 ) -> _ORMJoin: 

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

1958 

1959 

1960def with_parent( 

1961 instance: object, 

1962 prop: attributes.QueryableAttribute[Any], 

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

1964) -> ColumnElement[bool]: 

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

1966 to the given related instance, using established 

1967 :func:`_orm.relationship()` 

1968 configuration. 

1969 

1970 E.g.:: 

1971 

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

1973 

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

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

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

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

1978 in the rendered statement. 

1979 

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

1981 to indicate the left side of the criteria:: 

1982 

1983 

1984 a1 = aliased(Address) 

1985 a2 = aliased(Address) 

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

1987 

1988 The above use is equivalent to using the 

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

1990 

1991 a1 = aliased(Address) 

1992 a2 = aliased(Address) 

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

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

1995 ) 

1996 

1997 :param instance: 

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

1999 

2000 :param property: 

2001 Class-bound attribute, which indicates 

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

2003 parent/child relationship. 

2004 

2005 :param from_entity: 

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

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

2008 

2009 .. versionadded:: 1.2 

2010 

2011 """ # noqa: E501 

2012 prop_t: RelationshipProperty[Any] 

2013 

2014 if isinstance(prop, str): 

2015 raise sa_exc.ArgumentError( 

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

2017 ) 

2018 elif isinstance(prop, attributes.QueryableAttribute): 

2019 if prop._of_type: 

2020 from_entity = prop._of_type 

2021 mapper_property = prop.property 

2022 if mapper_property is None or not prop_is_relationship( 

2023 mapper_property 

2024 ): 

2025 raise sa_exc.ArgumentError( 

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

2027 f"got {mapper_property}" 

2028 ) 

2029 prop_t = mapper_property 

2030 else: 

2031 prop_t = prop 

2032 

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

2034 

2035 

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

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

2038 identity. 

2039 

2040 This typically corresponds to the object being 

2041 in either the persistent or detached state. 

2042 

2043 .. seealso:: 

2044 

2045 :func:`.was_deleted` 

2046 

2047 """ 

2048 state = attributes.instance_state(object_) 

2049 return state.has_identity 

2050 

2051 

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

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

2054 within a session flush. 

2055 

2056 This is regardless of whether or not the object is 

2057 persistent or detached. 

2058 

2059 .. seealso:: 

2060 

2061 :attr:`.InstanceState.was_deleted` 

2062 

2063 """ 

2064 

2065 state = attributes.instance_state(object_) 

2066 return state.was_deleted 

2067 

2068 

2069def _entity_corresponds_to( 

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

2071) -> bool: 

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

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

2074 being referred to elsewhere in the query. 

2075 

2076 """ 

2077 if insp_is_aliased_class(entity): 

2078 if insp_is_aliased_class(given): 

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

2080 return True 

2081 return False 

2082 elif insp_is_aliased_class(given): 

2083 if given._use_mapper_path: 

2084 return entity in given.with_polymorphic_mappers 

2085 else: 

2086 return entity is given 

2087 

2088 assert insp_is_mapper(given) 

2089 return entity.common_parent(given) 

2090 

2091 

2092def _entity_corresponds_to_use_path_impl( 

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

2094) -> bool: 

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

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

2097 be a member of a parent entity. 

2098 

2099 e.g.:: 

2100 

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

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

2103 

2104 a1 = aliased(A) 

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

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

2107 

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

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

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

2111 

2112 """ 

2113 if insp_is_aliased_class(given): 

2114 return ( 

2115 insp_is_aliased_class(entity) 

2116 and not entity._use_mapper_path 

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

2118 ) 

2119 elif not insp_is_aliased_class(entity): 

2120 return given.isa(entity.mapper) 

2121 else: 

2122 return ( 

2123 entity._use_mapper_path 

2124 and given in entity.with_polymorphic_mappers 

2125 ) 

2126 

2127 

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

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

2130 would load rows of type 'mapper'. 

2131 

2132 """ 

2133 if given.is_aliased_class: 

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

2135 mapper 

2136 ) 

2137 elif given.with_polymorphic_mappers: 

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

2139 else: 

2140 return given.isa(mapper) 

2141 

2142 

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

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

2145 that also has a slice() method. 

2146 

2147 """ 

2148 

2149 def _no_negative_indexes(): 

2150 raise IndexError( 

2151 "negative indexes are not accepted by SQL " 

2152 "index / slice operators" 

2153 ) 

2154 

2155 if isinstance(item, slice): 

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

2157 

2158 if ( 

2159 isinstance(stop, int) 

2160 and isinstance(start, int) 

2161 and stop - start <= 0 

2162 ): 

2163 return [] 

2164 

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

2166 isinstance(stop, int) and stop < 0 

2167 ): 

2168 _no_negative_indexes() 

2169 

2170 res = iterable_query.slice(start, stop) 

2171 if step is not None: 

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

2173 else: 

2174 return list(res) 

2175 else: 

2176 if item == -1: 

2177 _no_negative_indexes() 

2178 else: 

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

2180 

2181 

2182def _is_mapped_annotation( 

2183 raw_annotation: _AnnotationScanType, 

2184 cls: Type[Any], 

2185 originating_cls: Type[Any], 

2186) -> bool: 

2187 try: 

2188 annotated = de_stringify_annotation( 

2189 cls, raw_annotation, originating_cls.__module__ 

2190 ) 

2191 except NameError: 

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

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

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

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

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

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

2198 return False 

2199 else: 

2200 return is_origin_of_cls(annotated, _MappedAnnotationBase) 

2201 

2202 

2203class _CleanupError(Exception): 

2204 pass 

2205 

2206 

2207def _cleanup_mapped_str_annotation( 

2208 annotation: str, originating_module: str 

2209) -> str: 

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

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

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

2213 # "Address" as a string 

2214 

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

2216 # we'd have to do it 

2217 

2218 inner: Optional[Match[str]] 

2219 

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

2221 

2222 if not mm: 

2223 return annotation 

2224 

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

2226 # originally this just checked the name. 

2227 try: 

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

2229 except NameError as ne: 

2230 raise _CleanupError( 

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

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

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

2234 "outside of TYPE_CHECKING blocks" 

2235 ) from ne 

2236 

2237 if obj is typing.ClassVar: 

2238 real_symbol = "ClassVar" 

2239 else: 

2240 try: 

2241 if issubclass(obj, _MappedAnnotationBase): 

2242 real_symbol = obj.__name__ 

2243 else: 

2244 return annotation 

2245 except TypeError: 

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

2247 return annotation 

2248 

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

2250 # then didn't return, real_symbol raises UnboundLocalError 

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

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

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

2254 

2255 stack = [] 

2256 inner = mm 

2257 while True: 

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

2259 g2 = inner.group(2) 

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

2261 if inner is None: 

2262 stack.append(g2) 

2263 break 

2264 

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

2266 # we think is a relationship class name: 

2267 # 

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

2269 # ['Mapped', 'A'] 

2270 # 

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

2272 # use cases: 

2273 # 

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

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

2276 

2277 if ( 

2278 # avoid already quoted symbols such as 

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

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

2281 # avoid further generics like Dict[] such as 

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

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

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

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

2286 ): 

2287 stripchars = "\"' " 

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

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

2290 ) 

2291 

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

2293 

2294 return annotation 

2295 

2296 

2297def _extract_mapped_subtype( 

2298 raw_annotation: Optional[_AnnotationScanType], 

2299 cls: type, 

2300 originating_module: str, 

2301 key: str, 

2302 attr_cls: Type[Any], 

2303 required: bool, 

2304 is_dataclass_field: bool, 

2305 expect_mapped: bool = True, 

2306 raiseerr: bool = True, 

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

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

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

2310 

2311 Includes error raise scenarios and other options. 

2312 

2313 """ 

2314 

2315 if raw_annotation is None: 

2316 if required: 

2317 raise orm_exc.MappedAnnotationError( 

2318 f"Python typing annotation is required for attribute " 

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

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

2321 ) 

2322 return None 

2323 

2324 try: 

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

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

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

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

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

2330 annotated = de_stringify_annotation( 

2331 cls, 

2332 raw_annotation, 

2333 originating_module, 

2334 str_cleanup_fn=_cleanup_mapped_str_annotation, 

2335 ) 

2336 except _CleanupError as ce: 

2337 raise orm_exc.MappedAnnotationError( 

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

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

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

2341 ) from ce 

2342 except NameError as ne: 

2343 if raiseerr and "Mapped[" in raw_annotation: # type: ignore 

2344 raise orm_exc.MappedAnnotationError( 

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

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

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

2348 ) from ne 

2349 

2350 annotated = raw_annotation # type: ignore 

2351 

2352 if is_dataclass_field: 

2353 return annotated, None 

2354 else: 

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

2356 annotated, _MappedAnnotationBase 

2357 ): 

2358 if expect_mapped: 

2359 if not raiseerr: 

2360 return None 

2361 

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

2363 if origin is typing.ClassVar: 

2364 return None 

2365 

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

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

2368 elif isinstance(origin, type) and issubclass( 

2369 origin, ORMDescriptor 

2370 ): 

2371 return None 

2372 

2373 raise orm_exc.MappedAnnotationError( 

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

2375 "can't be correctly interpreted for " 

2376 "Annotated Declarative Table form. ORM annotations " 

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

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

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

2380 "intent that the attribute is mapped. " 

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

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

2383 "To allow Annotated Declarative to disregard legacy " 

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

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

2386 "superclass this class.", 

2387 code="zlpr", 

2388 ) 

2389 

2390 else: 

2391 return annotated, None 

2392 

2393 if len(annotated.__args__) != 1: 

2394 raise orm_exc.MappedAnnotationError( 

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

2396 ) 

2397 

2398 return ( 

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

2400 fixup_container_fwd_refs(annotated.__args__[0]), 

2401 annotated.__origin__, 

2402 ) 

2403 

2404 

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

2406 if hasattr(prop, "_mapper_property_name"): 

2407 name = prop._mapper_property_name() 

2408 else: 

2409 name = None 

2410 return util.clsname_as_plain_name(prop, name)