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

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

452 statements  

1# orm/interfaces.py 

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

3# <see AUTHORS file> 

4# 

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

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

7 

8""" 

9 

10Contains various base classes used throughout the ORM. 

11 

12Defines some key base classes prominent within the internals. 

13 

14This module and the classes within are mostly private, though some attributes 

15are exposed when inspecting mappings. 

16 

17""" 

18 

19from __future__ import annotations 

20 

21import collections 

22import dataclasses 

23import typing 

24from typing import Any 

25from typing import Callable 

26from typing import cast 

27from typing import ClassVar 

28from typing import Dict 

29from typing import Generic 

30from typing import Iterator 

31from typing import List 

32from typing import Mapping 

33from typing import NamedTuple 

34from typing import NoReturn 

35from typing import Optional 

36from typing import Sequence 

37from typing import Set 

38from typing import Tuple 

39from typing import Type 

40from typing import TYPE_CHECKING 

41from typing import TypedDict 

42from typing import TypeVar 

43from typing import Union 

44 

45from . import exc as orm_exc 

46from . import path_registry 

47from .base import _MappedAttribute as _MappedAttribute 

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

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

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

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

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

53from .base import InspectionAttrInfo as InspectionAttrInfo 

54from .base import MANYTOMANY as MANYTOMANY # noqa: F401 

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

56from .base import NO_KEY as NO_KEY # noqa: F401 

57from .base import NO_VALUE as NO_VALUE # noqa: F401 

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

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

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

61from .base import SQLORMOperations 

62from .. import ColumnElement 

63from .. import exc as sa_exc 

64from .. import inspection 

65from .. import util 

66from ..sql import operators 

67from ..sql import roles 

68from ..sql import visitors 

69from ..sql.base import _NoArg 

70from ..sql.base import ExecutableOption 

71from ..sql.cache_key import HasCacheKey 

72from ..sql.operators import ColumnOperators 

73from ..sql.schema import Column 

74from ..sql.type_api import TypeEngine 

75from ..util import warn_deprecated 

76from ..util.typing import RODescriptorReference 

77from ..util.typing import TupleAny 

78from ..util.typing import Unpack 

79 

80 

81if typing.TYPE_CHECKING: 

82 from ._typing import _EntityType 

83 from ._typing import _IdentityKeyType 

84 from ._typing import _InstanceDict 

85 from ._typing import _InternalEntityType 

86 from ._typing import _ORMAdapterProto 

87 from .attributes import InstrumentedAttribute 

88 from .base import Mapped 

89 from .context import _MapperEntity 

90 from .context import _ORMCompileState 

91 from .context import QueryContext 

92 from .decl_api import RegistryType 

93 from .decl_base import _ClassScanMapperConfig 

94 from .loading import _PopulatorDict 

95 from .mapper import Mapper 

96 from .path_registry import _AbstractEntityRegistry 

97 from .query import Query 

98 from .session import Session 

99 from .state import InstanceState 

100 from .strategy_options import _LoadElement 

101 from .util import AliasedInsp 

102 from .util import ORMAdapter 

103 from ..engine.result import Result 

104 from ..sql._typing import _ColumnExpressionArgument 

105 from ..sql._typing import _ColumnsClauseArgument 

106 from ..sql._typing import _DMLColumnArgument 

107 from ..sql._typing import _InfoType 

108 from ..sql.operators import OperatorType 

109 from ..sql.visitors import _TraverseInternalsType 

110 from ..util.typing import _AnnotationScanType 

111 

112_StrategyKey = Tuple[Any, ...] 

113 

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

115_T_co = TypeVar("_T_co", bound=Any, covariant=True) 

116 

117_TLS = TypeVar("_TLS", bound="Type[LoaderStrategy]") 

118 

119 

120class ORMStatementRole(roles.StatementRole): 

121 __slots__ = () 

122 _role_name = ( 

123 "Executable SQL or text() construct, including ORM aware objects" 

124 ) 

125 

126 

127class ORMColumnsClauseRole( 

128 roles.ColumnsClauseRole, roles.TypedColumnsClauseRole[_T] 

129): 

130 __slots__ = () 

131 _role_name = "ORM mapped entity, aliased entity, or Column expression" 

132 

133 

134class ORMEntityColumnsClauseRole(ORMColumnsClauseRole[_T]): 

135 __slots__ = () 

136 _role_name = "ORM mapped or aliased entity" 

137 

138 

139class ORMFromClauseRole(roles.FromClauseRole): 

140 __slots__ = () 

141 _role_name = "ORM mapped entity, aliased entity, or FROM expression" 

142 

143 

144class ORMColumnDescription(TypedDict): 

145 name: str 

146 # TODO: add python_type and sql_type here; combining them 

147 # into "type" is a bad idea 

148 type: Union[Type[Any], TypeEngine[Any]] 

149 aliased: bool 

150 expr: _ColumnsClauseArgument[Any] 

151 entity: Optional[_ColumnsClauseArgument[Any]] 

152 

153 

154class _IntrospectsAnnotations: 

155 __slots__ = () 

156 

157 @classmethod 

158 def _mapper_property_name(cls) -> str: 

159 return cls.__name__ 

160 

161 def found_in_pep593_annotated(self) -> Any: 

162 """return a copy of this object to use in declarative when the 

163 object is found inside of an Annotated object.""" 

164 

165 raise NotImplementedError( 

166 f"Use of the {self._mapper_property_name()!r} " 

167 "construct inside of an Annotated object is not yet supported." 

168 ) 

169 

170 def declarative_scan( 

171 self, 

172 decl_scan: _ClassScanMapperConfig, 

173 registry: RegistryType, 

174 cls: Type[Any], 

175 originating_module: Optional[str], 

176 key: str, 

177 mapped_container: Optional[Type[Mapped[Any]]], 

178 annotation: Optional[_AnnotationScanType], 

179 extracted_mapped_annotation: Optional[_AnnotationScanType], 

180 is_dataclass_field: bool, 

181 ) -> None: 

182 """Perform class-specific initializaton at early declarative scanning 

183 time. 

184 

185 .. versionadded:: 2.0 

186 

187 """ 

188 

189 def _raise_for_required(self, key: str, cls: Type[Any]) -> NoReturn: 

190 raise sa_exc.ArgumentError( 

191 f"Python typing annotation is required for attribute " 

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

193 f'"{self._mapper_property_name()}" ' 

194 "construct are None or not present" 

195 ) 

196 

197 

198class _DataclassArguments(TypedDict): 

199 """define arguments that can be passed to ORM Annotated Dataclass 

200 class definitions. 

201 

202 """ 

203 

204 init: Union[_NoArg, bool] 

205 repr: Union[_NoArg, bool] 

206 eq: Union[_NoArg, bool] 

207 order: Union[_NoArg, bool] 

208 unsafe_hash: Union[_NoArg, bool] 

209 match_args: Union[_NoArg, bool] 

210 kw_only: Union[_NoArg, bool] 

211 dataclass_callable: Union[_NoArg, Callable[..., Type[Any]]] 

212 

213 

214class _AttributeOptions(NamedTuple): 

215 """define Python-local attribute behavior options common to all 

216 :class:`.MapperProperty` objects. 

217 

218 Currently this includes dataclass-generation arguments. 

219 

220 .. versionadded:: 2.0 

221 

222 """ 

223 

224 dataclasses_init: Union[_NoArg, bool] 

225 dataclasses_repr: Union[_NoArg, bool] 

226 dataclasses_default: Union[_NoArg, Any] 

227 dataclasses_default_factory: Union[_NoArg, Callable[[], Any]] 

228 dataclasses_compare: Union[_NoArg, bool] 

229 dataclasses_kw_only: Union[_NoArg, bool] 

230 dataclasses_hash: Union[_NoArg, bool, None] 

231 dataclasses_dataclass_metadata: Union[_NoArg, Mapping[Any, Any], None] 

232 

233 def _as_dataclass_field( 

234 self, key: str, dataclass_setup_arguments: _DataclassArguments 

235 ) -> Any: 

236 """Return a ``dataclasses.Field`` object given these arguments.""" 

237 

238 kw: Dict[str, Any] = {} 

239 if self.dataclasses_default_factory is not _NoArg.NO_ARG: 

240 kw["default_factory"] = self.dataclasses_default_factory 

241 if self.dataclasses_default is not _NoArg.NO_ARG: 

242 kw["default"] = self.dataclasses_default 

243 if self.dataclasses_init is not _NoArg.NO_ARG: 

244 kw["init"] = self.dataclasses_init 

245 if self.dataclasses_repr is not _NoArg.NO_ARG: 

246 kw["repr"] = self.dataclasses_repr 

247 if self.dataclasses_compare is not _NoArg.NO_ARG: 

248 kw["compare"] = self.dataclasses_compare 

249 if self.dataclasses_kw_only is not _NoArg.NO_ARG: 

250 kw["kw_only"] = self.dataclasses_kw_only 

251 if self.dataclasses_hash is not _NoArg.NO_ARG: 

252 kw["hash"] = self.dataclasses_hash 

253 if self.dataclasses_dataclass_metadata is not _NoArg.NO_ARG: 

254 kw["metadata"] = self.dataclasses_dataclass_metadata 

255 

256 if "default" in kw and callable(kw["default"]): 

257 # callable defaults are ambiguous. deprecate them in favour of 

258 # insert_default or default_factory. #9936 

259 warn_deprecated( 

260 f"Callable object passed to the ``default`` parameter for " 

261 f"attribute {key!r} in a ORM-mapped Dataclasses context is " 

262 "ambiguous, " 

263 "and this use will raise an error in a future release. " 

264 "If this callable is intended to produce Core level INSERT " 

265 "default values for an underlying ``Column``, use " 

266 "the ``mapped_column.insert_default`` parameter instead. " 

267 "To establish this callable as providing a default value " 

268 "for instances of the dataclass itself, use the " 

269 "``default_factory`` dataclasses parameter.", 

270 "2.0", 

271 ) 

272 

273 if ( 

274 "init" in kw 

275 and not kw["init"] 

276 and "default" in kw 

277 and not callable(kw["default"]) # ignore callable defaults. #9936 

278 and "default_factory" not in kw # illegal but let dc.field raise 

279 ): 

280 # fix for #9879 

281 default = kw.pop("default") 

282 kw["default_factory"] = lambda: default 

283 

284 return dataclasses.field(**kw) 

285 

286 @classmethod 

287 def _get_arguments_for_make_dataclass( 

288 cls, 

289 decl_scan: _ClassScanMapperConfig, 

290 key: str, 

291 annotation: _AnnotationScanType, 

292 mapped_container: Optional[Any], 

293 elem: Any, 

294 dataclass_setup_arguments: _DataclassArguments, 

295 ) -> Union[ 

296 Tuple[str, _AnnotationScanType], 

297 Tuple[str, _AnnotationScanType, dataclasses.Field[Any]], 

298 ]: 

299 """given attribute key, annotation, and value from a class, return 

300 the argument tuple we would pass to dataclasses.make_dataclass() 

301 for this attribute. 

302 

303 """ 

304 if isinstance(elem, _DCAttributeOptions): 

305 attribute_options = elem._get_dataclass_setup_options( 

306 decl_scan, key, dataclass_setup_arguments 

307 ) 

308 dc_field = attribute_options._as_dataclass_field( 

309 key, dataclass_setup_arguments 

310 ) 

311 

312 return (key, annotation, dc_field) 

313 elif elem is not _NoArg.NO_ARG: 

314 # why is typing not erroring on this? 

315 return (key, annotation, elem) 

316 elif mapped_container is not None: 

317 # it's Mapped[], but there's no "element", which means declarative 

318 # did not actually do anything for this field. this shouldn't 

319 # happen. 

320 # previously, this would occur because _scan_attributes would 

321 # skip a field that's on an already mapped superclass, but it 

322 # would still include it in the annotations, leading 

323 # to issue #8718 

324 

325 assert False, "Mapped[] received without a mapping declaration" 

326 

327 else: 

328 # plain dataclass field, not mapped. Is only possible 

329 # if __allow_unmapped__ is set up. I can see this mode causing 

330 # problems... 

331 return (key, annotation) 

332 

333 

334_DEFAULT_ATTRIBUTE_OPTIONS = _AttributeOptions( 

335 _NoArg.NO_ARG, 

336 _NoArg.NO_ARG, 

337 _NoArg.NO_ARG, 

338 _NoArg.NO_ARG, 

339 _NoArg.NO_ARG, 

340 _NoArg.NO_ARG, 

341 _NoArg.NO_ARG, 

342 _NoArg.NO_ARG, 

343) 

344 

345_DEFAULT_READONLY_ATTRIBUTE_OPTIONS = _AttributeOptions( 

346 False, 

347 _NoArg.NO_ARG, 

348 _NoArg.NO_ARG, 

349 _NoArg.NO_ARG, 

350 _NoArg.NO_ARG, 

351 _NoArg.NO_ARG, 

352 _NoArg.NO_ARG, 

353 _NoArg.NO_ARG, 

354) 

355 

356 

357class _DCAttributeOptions: 

358 """mixin for descriptors or configurational objects that include dataclass 

359 field options. 

360 

361 This includes :class:`.MapperProperty`, :class:`._MapsColumn` within 

362 the ORM, but also includes :class:`.AssociationProxy` within ext. 

363 Can in theory be used for other descriptors that serve a similar role 

364 as association proxy. (*maybe* hybrids, not sure yet.) 

365 

366 """ 

367 

368 __slots__ = () 

369 

370 _attribute_options: _AttributeOptions 

371 """behavioral options for ORM-enabled Python attributes 

372 

373 .. versionadded:: 2.0 

374 

375 """ 

376 

377 _has_dataclass_arguments: bool 

378 

379 def _get_dataclass_setup_options( 

380 self, 

381 decl_scan: _ClassScanMapperConfig, 

382 key: str, 

383 dataclass_setup_arguments: _DataclassArguments, 

384 ) -> _AttributeOptions: 

385 return self._attribute_options 

386 

387 

388class _DataclassDefaultsDontSet(_DCAttributeOptions): 

389 __slots__ = () 

390 

391 _default_scalar_value: Any 

392 

393 _disable_dataclass_default_factory: bool = False 

394 

395 def _get_dataclass_setup_options( 

396 self, 

397 decl_scan: _ClassScanMapperConfig, 

398 key: str, 

399 dataclass_setup_arguments: _DataclassArguments, 

400 ) -> _AttributeOptions: 

401 

402 disable_descriptor_defaults = getattr( 

403 decl_scan.cls, "_sa_disable_descriptor_defaults", False 

404 ) 

405 

406 dataclasses_default = self._attribute_options.dataclasses_default 

407 dataclasses_default_factory = ( 

408 self._attribute_options.dataclasses_default_factory 

409 ) 

410 

411 if ( 

412 dataclasses_default is not _NoArg.NO_ARG 

413 and not callable(dataclasses_default) 

414 and not disable_descriptor_defaults 

415 ): 

416 self._default_scalar_value = ( 

417 self._attribute_options.dataclasses_default 

418 ) 

419 return self._attribute_options._replace( 

420 dataclasses_default=DONT_SET, 

421 ) 

422 elif ( 

423 self._disable_dataclass_default_factory 

424 and dataclasses_default_factory is not _NoArg.NO_ARG 

425 and not disable_descriptor_defaults 

426 ): 

427 return self._attribute_options._replace( 

428 dataclasses_default=DONT_SET, 

429 dataclasses_default_factory=_NoArg.NO_ARG, 

430 ) 

431 return self._attribute_options 

432 

433 

434class _MapsColumns(_DCAttributeOptions, _MappedAttribute[_T]): 

435 """interface for declarative-capable construct that delivers one or more 

436 Column objects to the declarative process to be part of a Table. 

437 """ 

438 

439 __slots__ = () 

440 

441 @property 

442 def mapper_property_to_assign(self) -> Optional[MapperProperty[_T]]: 

443 """return a MapperProperty to be assigned to the declarative mapping""" 

444 raise NotImplementedError() 

445 

446 @property 

447 def columns_to_assign(self) -> List[Tuple[Column[_T], int]]: 

448 """A list of Column objects that should be declaratively added to the 

449 new Table object. 

450 

451 """ 

452 raise NotImplementedError() 

453 

454 

455# NOTE: MapperProperty needs to extend _MappedAttribute so that declarative 

456# typing works, i.e. "Mapped[A] = relationship()". This introduces an 

457# inconvenience which is that all the MapperProperty objects are treated 

458# as descriptors by typing tools, which are misled by this as assignment / 

459# access to a descriptor attribute wants to move through __get__. 

460# Therefore, references to MapperProperty as an instance variable, such 

461# as in PropComparator, may have some special typing workarounds such as the 

462# use of sqlalchemy.util.typing.DescriptorReference to avoid mis-interpretation 

463# by typing tools 

464@inspection._self_inspects 

465class MapperProperty( 

466 HasCacheKey, 

467 _DCAttributeOptions, 

468 _MappedAttribute[_T], 

469 InspectionAttrInfo, 

470 util.MemoizedSlots, 

471): 

472 """Represent a particular class attribute mapped by :class:`_orm.Mapper`. 

473 

474 The most common occurrences of :class:`.MapperProperty` are the 

475 mapped :class:`_schema.Column`, which is represented in a mapping as 

476 an instance of :class:`.ColumnProperty`, 

477 and a reference to another class produced by :func:`_orm.relationship`, 

478 represented in the mapping as an instance of 

479 :class:`.Relationship`. 

480 

481 """ 

482 

483 __slots__ = ( 

484 "_configure_started", 

485 "_configure_finished", 

486 "_attribute_options", 

487 "_has_dataclass_arguments", 

488 "parent", 

489 "key", 

490 "info", 

491 "doc", 

492 ) 

493 

494 _cache_key_traversal: _TraverseInternalsType = [ 

495 ("parent", visitors.ExtendedInternalTraversal.dp_has_cache_key), 

496 ("key", visitors.ExtendedInternalTraversal.dp_string), 

497 ] 

498 

499 if not TYPE_CHECKING: 

500 cascade = None 

501 

502 is_property = True 

503 """Part of the InspectionAttr interface; states this object is a 

504 mapper property. 

505 

506 """ 

507 

508 comparator: PropComparator[_T] 

509 """The :class:`_orm.PropComparator` instance that implements SQL 

510 expression construction on behalf of this mapped attribute.""" 

511 

512 key: str 

513 """name of class attribute""" 

514 

515 parent: Mapper[Any] 

516 """the :class:`.Mapper` managing this property.""" 

517 

518 _is_relationship = False 

519 

520 _links_to_entity: bool 

521 """True if this MapperProperty refers to a mapped entity. 

522 

523 Should only be True for Relationship, False for all others. 

524 

525 """ 

526 

527 doc: Optional[str] 

528 """optional documentation string""" 

529 

530 info: _InfoType 

531 """Info dictionary associated with the object, allowing user-defined 

532 data to be associated with this :class:`.InspectionAttr`. 

533 

534 The dictionary is generated when first accessed. Alternatively, 

535 it can be specified as a constructor argument to the 

536 :func:`.column_property`, :func:`_orm.relationship`, or :func:`.composite` 

537 functions. 

538 

539 .. seealso:: 

540 

541 :attr:`.QueryableAttribute.info` 

542 

543 :attr:`.SchemaItem.info` 

544 

545 """ 

546 

547 def _memoized_attr_info(self) -> _InfoType: 

548 """Info dictionary associated with the object, allowing user-defined 

549 data to be associated with this :class:`.InspectionAttr`. 

550 

551 The dictionary is generated when first accessed. Alternatively, 

552 it can be specified as a constructor argument to the 

553 :func:`.column_property`, :func:`_orm.relationship`, or 

554 :func:`.composite` 

555 functions. 

556 

557 .. seealso:: 

558 

559 :attr:`.QueryableAttribute.info` 

560 

561 :attr:`.SchemaItem.info` 

562 

563 """ 

564 return {} 

565 

566 def setup( 

567 self, 

568 context: _ORMCompileState, 

569 query_entity: _MapperEntity, 

570 path: _AbstractEntityRegistry, 

571 adapter: Optional[ORMAdapter], 

572 **kwargs: Any, 

573 ) -> None: 

574 """Called by Query for the purposes of constructing a SQL statement. 

575 

576 Each MapperProperty associated with the target mapper processes the 

577 statement referenced by the query context, adding columns and/or 

578 criterion as appropriate. 

579 

580 """ 

581 

582 def create_row_processor( 

583 self, 

584 context: _ORMCompileState, 

585 query_entity: _MapperEntity, 

586 path: _AbstractEntityRegistry, 

587 mapper: Mapper[Any], 

588 result: Result[Unpack[TupleAny]], 

589 adapter: Optional[ORMAdapter], 

590 populators: _PopulatorDict, 

591 ) -> None: 

592 """Produce row processing functions and append to the given 

593 set of populators lists. 

594 

595 """ 

596 

597 def cascade_iterator( 

598 self, 

599 type_: str, 

600 state: InstanceState[Any], 

601 dict_: _InstanceDict, 

602 visited_states: Set[InstanceState[Any]], 

603 halt_on: Optional[Callable[[InstanceState[Any]], bool]] = None, 

604 ) -> Iterator[ 

605 Tuple[object, Mapper[Any], InstanceState[Any], _InstanceDict] 

606 ]: 

607 """Iterate through instances related to the given instance for 

608 a particular 'cascade', starting with this MapperProperty. 

609 

610 Return an iterator3-tuples (instance, mapper, state). 

611 

612 Note that the 'cascade' collection on this MapperProperty is 

613 checked first for the given type before cascade_iterator is called. 

614 

615 This method typically only applies to Relationship. 

616 

617 """ 

618 

619 return iter(()) 

620 

621 def set_parent(self, parent: Mapper[Any], init: bool) -> None: 

622 """Set the parent mapper that references this MapperProperty. 

623 

624 This method is overridden by some subclasses to perform extra 

625 setup when the mapper is first known. 

626 

627 """ 

628 self.parent = parent 

629 

630 def instrument_class(self, mapper: Mapper[Any]) -> None: 

631 """Hook called by the Mapper to the property to initiate 

632 instrumentation of the class attribute managed by this 

633 MapperProperty. 

634 

635 The MapperProperty here will typically call out to the 

636 attributes module to set up an InstrumentedAttribute. 

637 

638 This step is the first of two steps to set up an InstrumentedAttribute, 

639 and is called early in the mapper setup process. 

640 

641 The second step is typically the init_class_attribute step, 

642 called from StrategizedProperty via the post_instrument_class() 

643 hook. This step assigns additional state to the InstrumentedAttribute 

644 (specifically the "impl") which has been determined after the 

645 MapperProperty has determined what kind of persistence 

646 management it needs to do (e.g. scalar, object, collection, etc). 

647 

648 """ 

649 

650 def __init__( 

651 self, 

652 attribute_options: Optional[_AttributeOptions] = None, 

653 _assume_readonly_dc_attributes: bool = False, 

654 ) -> None: 

655 self._configure_started = False 

656 self._configure_finished = False 

657 

658 if _assume_readonly_dc_attributes: 

659 default_attrs = _DEFAULT_READONLY_ATTRIBUTE_OPTIONS 

660 else: 

661 default_attrs = _DEFAULT_ATTRIBUTE_OPTIONS 

662 

663 if attribute_options and attribute_options != default_attrs: 

664 self._has_dataclass_arguments = True 

665 self._attribute_options = attribute_options 

666 else: 

667 self._has_dataclass_arguments = False 

668 self._attribute_options = default_attrs 

669 

670 def init(self) -> None: 

671 """Called after all mappers are created to assemble 

672 relationships between mappers and perform other post-mapper-creation 

673 initialization steps. 

674 

675 

676 """ 

677 self._configure_started = True 

678 self.do_init() 

679 self._configure_finished = True 

680 

681 @property 

682 def class_attribute(self) -> InstrumentedAttribute[_T]: 

683 """Return the class-bound descriptor corresponding to this 

684 :class:`.MapperProperty`. 

685 

686 This is basically a ``getattr()`` call:: 

687 

688 return getattr(self.parent.class_, self.key) 

689 

690 I.e. if this :class:`.MapperProperty` were named ``addresses``, 

691 and the class to which it is mapped is ``User``, this sequence 

692 is possible:: 

693 

694 >>> from sqlalchemy import inspect 

695 >>> mapper = inspect(User) 

696 >>> addresses_property = mapper.attrs.addresses 

697 >>> addresses_property.class_attribute is User.addresses 

698 True 

699 >>> User.addresses.property is addresses_property 

700 True 

701 

702 

703 """ 

704 

705 return getattr(self.parent.class_, self.key) # type: ignore 

706 

707 def do_init(self) -> None: 

708 """Perform subclass-specific initialization post-mapper-creation 

709 steps. 

710 

711 This is a template method called by the ``MapperProperty`` 

712 object's init() method. 

713 

714 """ 

715 

716 def post_instrument_class(self, mapper: Mapper[Any]) -> None: 

717 """Perform instrumentation adjustments that need to occur 

718 after init() has completed. 

719 

720 The given Mapper is the Mapper invoking the operation, which 

721 may not be the same Mapper as self.parent in an inheritance 

722 scenario; however, Mapper will always at least be a sub-mapper of 

723 self.parent. 

724 

725 This method is typically used by StrategizedProperty, which delegates 

726 it to LoaderStrategy.init_class_attribute() to perform final setup 

727 on the class-bound InstrumentedAttribute. 

728 

729 """ 

730 

731 def merge( 

732 self, 

733 session: Session, 

734 source_state: InstanceState[Any], 

735 source_dict: _InstanceDict, 

736 dest_state: InstanceState[Any], 

737 dest_dict: _InstanceDict, 

738 load: bool, 

739 _recursive: Dict[Any, object], 

740 _resolve_conflict_map: Dict[_IdentityKeyType[Any], object], 

741 ) -> None: 

742 """Merge the attribute represented by this ``MapperProperty`` 

743 from source to destination object. 

744 

745 """ 

746 

747 def __repr__(self) -> str: 

748 return "<%s at 0x%x; %s>" % ( 

749 self.__class__.__name__, 

750 id(self), 

751 getattr(self, "key", "no key"), 

752 ) 

753 

754 

755@inspection._self_inspects 

756class PropComparator(SQLORMOperations[_T_co], Generic[_T_co], ColumnOperators): 

757 r"""Defines SQL operations for ORM mapped attributes. 

758 

759 SQLAlchemy allows for operators to 

760 be redefined at both the Core and ORM level. :class:`.PropComparator` 

761 is the base class of operator redefinition for ORM-level operations, 

762 including those of :class:`.ColumnProperty`, 

763 :class:`.Relationship`, and :class:`.Composite`. 

764 

765 User-defined subclasses of :class:`.PropComparator` may be created. The 

766 built-in Python comparison and math operator methods, such as 

767 :meth:`.operators.ColumnOperators.__eq__`, 

768 :meth:`.operators.ColumnOperators.__lt__`, and 

769 :meth:`.operators.ColumnOperators.__add__`, can be overridden to provide 

770 new operator behavior. The custom :class:`.PropComparator` is passed to 

771 the :class:`.MapperProperty` instance via the ``comparator_factory`` 

772 argument. In each case, 

773 the appropriate subclass of :class:`.PropComparator` should be used:: 

774 

775 # definition of custom PropComparator subclasses 

776 

777 from sqlalchemy.orm.properties import ( 

778 ColumnProperty, 

779 Composite, 

780 Relationship, 

781 ) 

782 

783 

784 class MyColumnComparator(ColumnProperty.Comparator): 

785 def __eq__(self, other): 

786 return self.__clause_element__() == other 

787 

788 

789 class MyRelationshipComparator(Relationship.Comparator): 

790 def any(self, expression): 

791 "define the 'any' operation" 

792 # ... 

793 

794 

795 class MyCompositeComparator(Composite.Comparator): 

796 def __gt__(self, other): 

797 "redefine the 'greater than' operation" 

798 

799 return sql.and_( 

800 *[ 

801 a > b 

802 for a, b in zip( 

803 self.__clause_element__().clauses, 

804 other.__composite_values__(), 

805 ) 

806 ] 

807 ) 

808 

809 

810 # application of custom PropComparator subclasses 

811 

812 from sqlalchemy.orm import column_property, relationship, composite 

813 from sqlalchemy import Column, String 

814 

815 

816 class SomeMappedClass(Base): 

817 some_column = column_property( 

818 Column("some_column", String), 

819 comparator_factory=MyColumnComparator, 

820 ) 

821 

822 some_relationship = relationship( 

823 SomeOtherClass, comparator_factory=MyRelationshipComparator 

824 ) 

825 

826 some_composite = composite( 

827 Column("a", String), 

828 Column("b", String), 

829 comparator_factory=MyCompositeComparator, 

830 ) 

831 

832 Note that for column-level operator redefinition, it's usually 

833 simpler to define the operators at the Core level, using the 

834 :attr:`.TypeEngine.comparator_factory` attribute. See 

835 :ref:`types_operators` for more detail. 

836 

837 .. seealso:: 

838 

839 :class:`.ColumnProperty.Comparator` 

840 

841 :class:`.Relationship.Comparator` 

842 

843 :class:`.Composite.Comparator` 

844 

845 :class:`.ColumnOperators` 

846 

847 :ref:`types_operators` 

848 

849 :attr:`.TypeEngine.comparator_factory` 

850 

851 """ 

852 

853 __slots__ = "prop", "_parententity", "_adapt_to_entity" 

854 

855 __visit_name__ = "orm_prop_comparator" 

856 

857 _parententity: _InternalEntityType[Any] 

858 _adapt_to_entity: Optional[AliasedInsp[Any]] 

859 prop: RODescriptorReference[MapperProperty[_T_co]] 

860 

861 def __init__( 

862 self, 

863 prop: MapperProperty[_T], 

864 parentmapper: _InternalEntityType[Any], 

865 adapt_to_entity: Optional[AliasedInsp[Any]] = None, 

866 ): 

867 self.prop = prop 

868 self._parententity = adapt_to_entity or parentmapper 

869 self._adapt_to_entity = adapt_to_entity 

870 

871 @util.non_memoized_property 

872 def property(self) -> MapperProperty[_T_co]: 

873 """Return the :class:`.MapperProperty` associated with this 

874 :class:`.PropComparator`. 

875 

876 

877 Return values here will commonly be instances of 

878 :class:`.ColumnProperty` or :class:`.Relationship`. 

879 

880 

881 """ 

882 return self.prop 

883 

884 def __clause_element__(self) -> roles.ColumnsClauseRole: 

885 raise NotImplementedError("%r" % self) 

886 

887 def _bulk_update_tuples( 

888 self, value: Any 

889 ) -> Sequence[Tuple[_DMLColumnArgument, Any]]: 

890 """Receive a SQL expression that represents a value in the SET 

891 clause of an UPDATE statement. 

892 

893 Return a tuple that can be passed to a :class:`_expression.Update` 

894 construct. 

895 

896 """ 

897 

898 return [(cast("_DMLColumnArgument", self.__clause_element__()), value)] 

899 

900 def _bulk_dml_setter(self, key: str) -> Optional[Callable[..., Any]]: 

901 """return a callable that will process a bulk INSERT value""" 

902 

903 return None 

904 

905 def adapt_to_entity( 

906 self, adapt_to_entity: AliasedInsp[Any] 

907 ) -> PropComparator[_T_co]: 

908 """Return a copy of this PropComparator which will use the given 

909 :class:`.AliasedInsp` to produce corresponding expressions. 

910 """ 

911 return self.__class__(self.prop, self._parententity, adapt_to_entity) 

912 

913 @util.ro_non_memoized_property 

914 def _parentmapper(self) -> Mapper[Any]: 

915 """legacy; this is renamed to _parententity to be 

916 compatible with QueryableAttribute.""" 

917 return self._parententity.mapper 

918 

919 def _criterion_exists( 

920 self, 

921 criterion: Optional[_ColumnExpressionArgument[bool]] = None, 

922 **kwargs: Any, 

923 ) -> ColumnElement[Any]: 

924 return self.prop.comparator._criterion_exists(criterion, **kwargs) 

925 

926 @util.ro_non_memoized_property 

927 def adapter(self) -> Optional[_ORMAdapterProto]: 

928 """Produce a callable that adapts column expressions 

929 to suit an aliased version of this comparator. 

930 

931 """ 

932 if self._adapt_to_entity is None: 

933 return None 

934 else: 

935 return self._adapt_to_entity._orm_adapt_element 

936 

937 @util.ro_non_memoized_property 

938 def info(self) -> _InfoType: 

939 return self.prop.info 

940 

941 @staticmethod 

942 def _any_op(a: Any, b: Any, **kwargs: Any) -> Any: 

943 return a.any(b, **kwargs) 

944 

945 @staticmethod 

946 def _has_op(left: Any, other: Any, **kwargs: Any) -> Any: 

947 return left.has(other, **kwargs) 

948 

949 @staticmethod 

950 def _of_type_op(a: Any, class_: Any) -> Any: 

951 return a.of_type(class_) 

952 

953 any_op = cast(operators.OperatorType, _any_op) 

954 has_op = cast(operators.OperatorType, _has_op) 

955 of_type_op = cast(operators.OperatorType, _of_type_op) 

956 

957 if typing.TYPE_CHECKING: 

958 

959 def operate( 

960 self, op: OperatorType, *other: Any, **kwargs: Any 

961 ) -> ColumnElement[Any]: ... 

962 

963 def reverse_operate( 

964 self, op: OperatorType, other: Any, **kwargs: Any 

965 ) -> ColumnElement[Any]: ... 

966 

967 def of_type(self, class_: _EntityType[Any]) -> PropComparator[_T_co]: 

968 r"""Redefine this object in terms of a polymorphic subclass, 

969 :func:`_orm.with_polymorphic` construct, or :func:`_orm.aliased` 

970 construct. 

971 

972 Returns a new PropComparator from which further criterion can be 

973 evaluated. 

974 

975 e.g.:: 

976 

977 query.join(Company.employees.of_type(Engineer)).filter( 

978 Engineer.name == "foo" 

979 ) 

980 

981 :param \class_: a class or mapper indicating that criterion will be 

982 against this specific subclass. 

983 

984 .. seealso:: 

985 

986 :ref:`orm_queryguide_joining_relationships_aliased` - in the 

987 :ref:`queryguide_toplevel` 

988 

989 :ref:`inheritance_of_type` 

990 

991 """ 

992 

993 return self.operate(PropComparator.of_type_op, class_) # type: ignore 

994 

995 def and_( 

996 self, *criteria: _ColumnExpressionArgument[bool] 

997 ) -> PropComparator[bool]: 

998 """Add additional criteria to the ON clause that's represented by this 

999 relationship attribute. 

1000 

1001 E.g.:: 

1002 

1003 

1004 stmt = select(User).join( 

1005 User.addresses.and_(Address.email_address != "foo") 

1006 ) 

1007 

1008 stmt = select(User).options( 

1009 joinedload(User.addresses.and_(Address.email_address != "foo")) 

1010 ) 

1011 

1012 .. versionadded:: 1.4 

1013 

1014 .. seealso:: 

1015 

1016 :ref:`orm_queryguide_join_on_augmented` 

1017 

1018 :ref:`loader_option_criteria` 

1019 

1020 :func:`.with_loader_criteria` 

1021 

1022 """ 

1023 return self.operate(operators.and_, *criteria) # type: ignore 

1024 

1025 def any( 

1026 self, 

1027 criterion: Optional[_ColumnExpressionArgument[bool]] = None, 

1028 **kwargs: Any, 

1029 ) -> ColumnElement[bool]: 

1030 r"""Return a SQL expression representing true if this element 

1031 references a member which meets the given criterion. 

1032 

1033 The usual implementation of ``any()`` is 

1034 :meth:`.Relationship.Comparator.any`. 

1035 

1036 :param criterion: an optional ClauseElement formulated against the 

1037 member class' table or attributes. 

1038 

1039 :param \**kwargs: key/value pairs corresponding to member class 

1040 attribute names which will be compared via equality to the 

1041 corresponding values. 

1042 

1043 """ 

1044 

1045 return self.operate(PropComparator.any_op, criterion, **kwargs) 

1046 

1047 def has( 

1048 self, 

1049 criterion: Optional[_ColumnExpressionArgument[bool]] = None, 

1050 **kwargs: Any, 

1051 ) -> ColumnElement[bool]: 

1052 r"""Return a SQL expression representing true if this element 

1053 references a member which meets the given criterion. 

1054 

1055 The usual implementation of ``has()`` is 

1056 :meth:`.Relationship.Comparator.has`. 

1057 

1058 :param criterion: an optional ClauseElement formulated against the 

1059 member class' table or attributes. 

1060 

1061 :param \**kwargs: key/value pairs corresponding to member class 

1062 attribute names which will be compared via equality to the 

1063 corresponding values. 

1064 

1065 """ 

1066 

1067 return self.operate(PropComparator.has_op, criterion, **kwargs) 

1068 

1069 

1070class StrategizedProperty(MapperProperty[_T]): 

1071 """A MapperProperty which uses selectable strategies to affect 

1072 loading behavior. 

1073 

1074 There is a single strategy selected by default. Alternate 

1075 strategies can be selected at Query time through the usage of 

1076 ``StrategizedOption`` objects via the Query.options() method. 

1077 

1078 The mechanics of StrategizedProperty are used for every Query 

1079 invocation for every mapped attribute participating in that Query, 

1080 to determine first how the attribute will be rendered in SQL 

1081 and secondly how the attribute will retrieve a value from a result 

1082 row and apply it to a mapped object. The routines here are very 

1083 performance-critical. 

1084 

1085 """ 

1086 

1087 __slots__ = ( 

1088 "_strategies", 

1089 "strategy", 

1090 "_wildcard_token", 

1091 "_default_path_loader_key", 

1092 "strategy_key", 

1093 ) 

1094 inherit_cache = True 

1095 strategy_wildcard_key: ClassVar[str] 

1096 

1097 strategy_key: _StrategyKey 

1098 

1099 _strategies: Dict[_StrategyKey, LoaderStrategy] 

1100 

1101 def _memoized_attr__wildcard_token(self) -> Tuple[str]: 

1102 return ( 

1103 f"{self.strategy_wildcard_key}:{path_registry._WILDCARD_TOKEN}", 

1104 ) 

1105 

1106 def _memoized_attr__default_path_loader_key( 

1107 self, 

1108 ) -> Tuple[str, Tuple[str]]: 

1109 return ( 

1110 "loader", 

1111 (f"{self.strategy_wildcard_key}:{path_registry._DEFAULT_TOKEN}",), 

1112 ) 

1113 

1114 def _get_context_loader( 

1115 self, context: _ORMCompileState, path: _AbstractEntityRegistry 

1116 ) -> Optional[_LoadElement]: 

1117 load: Optional[_LoadElement] = None 

1118 

1119 search_path = path[self] 

1120 

1121 # search among: exact match, "attr.*", "default" strategy 

1122 # if any. 

1123 for path_key in ( 

1124 search_path._loader_key, 

1125 search_path._wildcard_path_loader_key, 

1126 search_path._default_path_loader_key, 

1127 ): 

1128 if path_key in context.attributes: 

1129 load = context.attributes[path_key] 

1130 break 

1131 

1132 # note that if strategy_options.Load is placing non-actionable 

1133 # objects in the context like defaultload(), we would 

1134 # need to continue the loop here if we got such an 

1135 # option as below. 

1136 # if load.strategy or load.local_opts: 

1137 # break 

1138 

1139 return load 

1140 

1141 def _get_strategy(self, key: _StrategyKey) -> LoaderStrategy: 

1142 try: 

1143 return self._strategies[key] 

1144 except KeyError: 

1145 pass 

1146 

1147 # run outside to prevent transfer of exception context 

1148 cls = self._strategy_lookup(self, *key) 

1149 # this previously was setting self._strategies[cls], that's 

1150 # a bad idea; should use strategy key at all times because every 

1151 # strategy has multiple keys at this point 

1152 self._strategies[key] = strategy = cls(self, key) 

1153 return strategy 

1154 

1155 def setup( 

1156 self, 

1157 context: _ORMCompileState, 

1158 query_entity: _MapperEntity, 

1159 path: _AbstractEntityRegistry, 

1160 adapter: Optional[ORMAdapter], 

1161 **kwargs: Any, 

1162 ) -> None: 

1163 loader = self._get_context_loader(context, path) 

1164 if loader and loader.strategy: 

1165 strat = self._get_strategy(loader.strategy) 

1166 else: 

1167 strat = self.strategy 

1168 strat.setup_query( 

1169 context, query_entity, path, loader, adapter, **kwargs 

1170 ) 

1171 

1172 def create_row_processor( 

1173 self, 

1174 context: _ORMCompileState, 

1175 query_entity: _MapperEntity, 

1176 path: _AbstractEntityRegistry, 

1177 mapper: Mapper[Any], 

1178 result: Result[Unpack[TupleAny]], 

1179 adapter: Optional[ORMAdapter], 

1180 populators: _PopulatorDict, 

1181 ) -> None: 

1182 loader = self._get_context_loader(context, path) 

1183 if loader and loader.strategy: 

1184 strat = self._get_strategy(loader.strategy) 

1185 else: 

1186 strat = self.strategy 

1187 strat.create_row_processor( 

1188 context, 

1189 query_entity, 

1190 path, 

1191 loader, 

1192 mapper, 

1193 result, 

1194 adapter, 

1195 populators, 

1196 ) 

1197 

1198 def do_init(self) -> None: 

1199 self._strategies = {} 

1200 self.strategy = self._get_strategy(self.strategy_key) 

1201 

1202 def post_instrument_class(self, mapper: Mapper[Any]) -> None: 

1203 if not mapper.class_manager._attr_has_impl(self.key): 

1204 self.strategy.init_class_attribute(mapper) 

1205 

1206 _all_strategies: collections.defaultdict[ 

1207 Type[MapperProperty[Any]], Dict[_StrategyKey, Type[LoaderStrategy]] 

1208 ] = collections.defaultdict(dict) 

1209 

1210 @classmethod 

1211 def strategy_for(cls, **kw: Any) -> Callable[[_TLS], _TLS]: 

1212 def decorate(dec_cls: _TLS) -> _TLS: 

1213 # ensure each subclass of the strategy has its 

1214 # own _strategy_keys collection 

1215 if "_strategy_keys" not in dec_cls.__dict__: 

1216 dec_cls._strategy_keys = [] 

1217 key = tuple(sorted(kw.items())) 

1218 cls._all_strategies[cls][key] = dec_cls 

1219 dec_cls._strategy_keys.append(key) 

1220 return dec_cls 

1221 

1222 return decorate 

1223 

1224 @classmethod 

1225 def _strategy_lookup( 

1226 cls, requesting_property: MapperProperty[Any], *key: Any 

1227 ) -> Type[LoaderStrategy]: 

1228 requesting_property.parent._with_polymorphic_mappers 

1229 

1230 for prop_cls in cls.__mro__: 

1231 if prop_cls in cls._all_strategies: 

1232 if TYPE_CHECKING: 

1233 assert issubclass(prop_cls, MapperProperty) 

1234 strategies = cls._all_strategies[prop_cls] 

1235 try: 

1236 return strategies[key] 

1237 except KeyError: 

1238 pass 

1239 

1240 for property_type, strats in cls._all_strategies.items(): 

1241 if key in strats: 

1242 intended_property_type = property_type 

1243 actual_strategy = strats[key] 

1244 break 

1245 else: 

1246 intended_property_type = None 

1247 actual_strategy = None 

1248 

1249 raise orm_exc.LoaderStrategyException( 

1250 cls, 

1251 requesting_property, 

1252 intended_property_type, 

1253 actual_strategy, 

1254 key, 

1255 ) 

1256 

1257 

1258class ORMOption(ExecutableOption): 

1259 """Base class for option objects that are passed to ORM queries. 

1260 

1261 These options may be consumed by :meth:`.Query.options`, 

1262 :meth:`.Select.options`, or in a more general sense by any 

1263 :meth:`.Executable.options` method. They are interpreted at 

1264 statement compile time or execution time in modern use. The 

1265 deprecated :class:`.MapperOption` is consumed at ORM query construction 

1266 time. 

1267 

1268 .. versionadded:: 1.4 

1269 

1270 """ 

1271 

1272 __slots__ = () 

1273 

1274 _is_legacy_option = False 

1275 

1276 propagate_to_loaders = False 

1277 """if True, indicate this option should be carried along 

1278 to "secondary" SELECT statements that occur for relationship 

1279 lazy loaders as well as attribute load / refresh operations. 

1280 

1281 """ 

1282 

1283 _is_core = False 

1284 

1285 _is_user_defined = False 

1286 

1287 _is_compile_state = False 

1288 

1289 _is_criteria_option = False 

1290 

1291 _is_strategy_option = False 

1292 

1293 def _adapt_cached_option_to_uncached_option( 

1294 self, context: QueryContext, uncached_opt: ORMOption 

1295 ) -> ORMOption: 

1296 """adapt this option to the "uncached" version of itself in a 

1297 loader strategy context. 

1298 

1299 given "self" which is an option from a cached query, as well as the 

1300 corresponding option from the uncached version of the same query, 

1301 return the option we should use in a new query, in the context of a 

1302 loader strategy being asked to load related rows on behalf of that 

1303 cached query, which is assumed to be building a new query based on 

1304 entities passed to us from the cached query. 

1305 

1306 Currently this routine chooses between "self" and "uncached" without 

1307 manufacturing anything new. If the option is itself a loader strategy 

1308 option which has a path, that path needs to match to the entities being 

1309 passed to us by the cached query, so the :class:`_orm.Load` subclass 

1310 overrides this to return "self". For all other options, we return the 

1311 uncached form which may have changing state, such as a 

1312 with_loader_criteria() option which will very often have new state. 

1313 

1314 This routine could in the future involve 

1315 generating a new option based on both inputs if use cases arise, 

1316 such as if with_loader_criteria() needed to match up to 

1317 ``AliasedClass`` instances given in the parent query. 

1318 

1319 However, longer term it might be better to restructure things such that 

1320 ``AliasedClass`` entities are always matched up on their cache key, 

1321 instead of identity, in things like paths and such, so that this whole 

1322 issue of "the uncached option does not match the entities" goes away. 

1323 However this would make ``PathRegistry`` more complicated and difficult 

1324 to debug as well as potentially less performant in that it would be 

1325 hashing enormous cache keys rather than a simple AliasedInsp. UNLESS, 

1326 we could get cache keys overall to be reliably hashed into something 

1327 like an md5 key. 

1328 

1329 .. versionadded:: 1.4.41 

1330 

1331 """ 

1332 if uncached_opt is not None: 

1333 return uncached_opt 

1334 else: 

1335 return self 

1336 

1337 

1338class CompileStateOption(HasCacheKey, ORMOption): 

1339 """base for :class:`.ORMOption` classes that affect the compilation of 

1340 a SQL query and therefore need to be part of the cache key. 

1341 

1342 .. note:: :class:`.CompileStateOption` is generally non-public and 

1343 should not be used as a base class for user-defined options; instead, 

1344 use :class:`.UserDefinedOption`, which is easier to use as it does not 

1345 interact with ORM compilation internals or caching. 

1346 

1347 :class:`.CompileStateOption` defines an internal attribute 

1348 ``_is_compile_state=True`` which has the effect of the ORM compilation 

1349 routines for SELECT and other statements will call upon these options when 

1350 a SQL string is being compiled. As such, these classes implement 

1351 :class:`.HasCacheKey` and need to provide robust ``_cache_key_traversal`` 

1352 structures. 

1353 

1354 The :class:`.CompileStateOption` class is used to implement the ORM 

1355 :class:`.LoaderOption` and :class:`.CriteriaOption` classes. 

1356 

1357 .. versionadded:: 1.4.28 

1358 

1359 

1360 """ 

1361 

1362 __slots__ = () 

1363 

1364 _is_compile_state = True 

1365 

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

1367 """Apply a modification to a given :class:`.ORMCompileState`. 

1368 

1369 This method is part of the implementation of a particular 

1370 :class:`.CompileStateOption` and is only invoked internally 

1371 when an ORM query is compiled. 

1372 

1373 """ 

1374 

1375 def process_compile_state_replaced_entities( 

1376 self, 

1377 compile_state: _ORMCompileState, 

1378 mapper_entities: Sequence[_MapperEntity], 

1379 ) -> None: 

1380 """Apply a modification to a given :class:`.ORMCompileState`, 

1381 given entities that were replaced by with_only_columns() or 

1382 with_entities(). 

1383 

1384 This method is part of the implementation of a particular 

1385 :class:`.CompileStateOption` and is only invoked internally 

1386 when an ORM query is compiled. 

1387 

1388 .. versionadded:: 1.4.19 

1389 

1390 """ 

1391 

1392 

1393class LoaderOption(CompileStateOption): 

1394 """Describe a loader modification to an ORM statement at compilation time. 

1395 

1396 .. versionadded:: 1.4 

1397 

1398 """ 

1399 

1400 __slots__ = () 

1401 

1402 def process_compile_state_replaced_entities( 

1403 self, 

1404 compile_state: _ORMCompileState, 

1405 mapper_entities: Sequence[_MapperEntity], 

1406 ) -> None: 

1407 self.process_compile_state(compile_state) 

1408 

1409 

1410class CriteriaOption(CompileStateOption): 

1411 """Describe a WHERE criteria modification to an ORM statement at 

1412 compilation time. 

1413 

1414 .. versionadded:: 1.4 

1415 

1416 """ 

1417 

1418 __slots__ = () 

1419 

1420 _is_criteria_option = True 

1421 

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

1423 """update additional entity criteria options in the given 

1424 attributes dictionary. 

1425 

1426 """ 

1427 

1428 

1429class UserDefinedOption(ORMOption): 

1430 """Base class for a user-defined option that can be consumed from the 

1431 :meth:`.SessionEvents.do_orm_execute` event hook. 

1432 

1433 """ 

1434 

1435 __slots__ = ("payload",) 

1436 

1437 _is_legacy_option = False 

1438 

1439 _is_user_defined = True 

1440 

1441 propagate_to_loaders = False 

1442 """if True, indicate this option should be carried along 

1443 to "secondary" Query objects produced during lazy loads 

1444 or refresh operations. 

1445 

1446 """ 

1447 

1448 def __init__(self, payload: Optional[Any] = None): 

1449 self.payload = payload 

1450 

1451 

1452@util.deprecated_cls( 

1453 "1.4", 

1454 "The :class:`.MapperOption class is deprecated and will be removed " 

1455 "in a future release. For " 

1456 "modifications to queries on a per-execution basis, use the " 

1457 ":class:`.UserDefinedOption` class to establish state within a " 

1458 ":class:`.Query` or other Core statement, then use the " 

1459 ":meth:`.SessionEvents.before_orm_execute` hook to consume them.", 

1460 constructor=None, 

1461) 

1462class MapperOption(ORMOption): 

1463 """Describe a modification to a Query""" 

1464 

1465 __slots__ = () 

1466 

1467 _is_legacy_option = True 

1468 

1469 propagate_to_loaders = False 

1470 """if True, indicate this option should be carried along 

1471 to "secondary" Query objects produced during lazy loads 

1472 or refresh operations. 

1473 

1474 """ 

1475 

1476 def process_query(self, query: Query[Any]) -> None: 

1477 """Apply a modification to the given :class:`_query.Query`.""" 

1478 

1479 def process_query_conditionally(self, query: Query[Any]) -> None: 

1480 """same as process_query(), except that this option may not 

1481 apply to the given query. 

1482 

1483 This is typically applied during a lazy load or scalar refresh 

1484 operation to propagate options stated in the original Query to the 

1485 new Query being used for the load. It occurs for those options that 

1486 specify propagate_to_loaders=True. 

1487 

1488 """ 

1489 

1490 self.process_query(query) 

1491 

1492 

1493class LoaderStrategy: 

1494 """Describe the loading behavior of a StrategizedProperty object. 

1495 

1496 The ``LoaderStrategy`` interacts with the querying process in three 

1497 ways: 

1498 

1499 * it controls the configuration of the ``InstrumentedAttribute`` 

1500 placed on a class to handle the behavior of the attribute. this 

1501 may involve setting up class-level callable functions to fire 

1502 off a select operation when the attribute is first accessed 

1503 (i.e. a lazy load) 

1504 

1505 * it processes the ``QueryContext`` at statement construction time, 

1506 where it can modify the SQL statement that is being produced. 

1507 For example, simple column attributes will add their represented 

1508 column to the list of selected columns, a joined eager loader 

1509 may establish join clauses to add to the statement. 

1510 

1511 * It produces "row processor" functions at result fetching time. 

1512 These "row processor" functions populate a particular attribute 

1513 on a particular mapped instance. 

1514 

1515 """ 

1516 

1517 __slots__ = ( 

1518 "parent_property", 

1519 "is_class_level", 

1520 "parent", 

1521 "key", 

1522 "strategy_key", 

1523 "strategy_opts", 

1524 ) 

1525 

1526 _strategy_keys: ClassVar[List[_StrategyKey]] 

1527 

1528 def __init__( 

1529 self, parent: MapperProperty[Any], strategy_key: _StrategyKey 

1530 ): 

1531 self.parent_property = parent 

1532 self.is_class_level = False 

1533 self.parent = self.parent_property.parent 

1534 self.key = self.parent_property.key 

1535 self.strategy_key = strategy_key 

1536 self.strategy_opts = dict(strategy_key) 

1537 

1538 def init_class_attribute(self, mapper: Mapper[Any]) -> None: 

1539 pass 

1540 

1541 def setup_query( 

1542 self, 

1543 compile_state: _ORMCompileState, 

1544 query_entity: _MapperEntity, 

1545 path: _AbstractEntityRegistry, 

1546 loadopt: Optional[_LoadElement], 

1547 adapter: Optional[ORMAdapter], 

1548 **kwargs: Any, 

1549 ) -> None: 

1550 """Establish column and other state for a given QueryContext. 

1551 

1552 This method fulfills the contract specified by MapperProperty.setup(). 

1553 

1554 StrategizedProperty delegates its setup() method 

1555 directly to this method. 

1556 

1557 """ 

1558 

1559 def create_row_processor( 

1560 self, 

1561 context: _ORMCompileState, 

1562 query_entity: _MapperEntity, 

1563 path: _AbstractEntityRegistry, 

1564 loadopt: Optional[_LoadElement], 

1565 mapper: Mapper[Any], 

1566 result: Result[Unpack[TupleAny]], 

1567 adapter: Optional[ORMAdapter], 

1568 populators: _PopulatorDict, 

1569 ) -> None: 

1570 """Establish row processing functions for a given QueryContext. 

1571 

1572 This method fulfills the contract specified by 

1573 MapperProperty.create_row_processor(). 

1574 

1575 StrategizedProperty delegates its create_row_processor() method 

1576 directly to this method. 

1577 

1578 """ 

1579 

1580 def __str__(self) -> str: 

1581 return str(self.parent_property)