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

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

417 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 NamedTuple 

33from typing import NoReturn 

34from typing import Optional 

35from typing import Sequence 

36from typing import Set 

37from typing import Tuple 

38from typing import Type 

39from typing import TYPE_CHECKING 

40from typing import TypeVar 

41from typing import Union 

42 

43from . import exc as orm_exc 

44from . import path_registry 

45from .base import _MappedAttribute as _MappedAttribute 

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

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

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

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

50from .base import InspectionAttrInfo as InspectionAttrInfo 

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

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

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

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

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

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

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

58from .base import SQLORMOperations 

59from .. import ColumnElement 

60from .. import exc as sa_exc 

61from .. import inspection 

62from .. import util 

63from ..sql import operators 

64from ..sql import roles 

65from ..sql import visitors 

66from ..sql.base import _NoArg 

67from ..sql.base import ExecutableOption 

68from ..sql.cache_key import HasCacheKey 

69from ..sql.operators import ColumnOperators 

70from ..sql.schema import Column 

71from ..sql.type_api import TypeEngine 

72from ..util import warn_deprecated 

73from ..util.typing import RODescriptorReference 

74from ..util.typing import TypedDict 

75 

76if typing.TYPE_CHECKING: 

77 from ._typing import _EntityType 

78 from ._typing import _IdentityKeyType 

79 from ._typing import _InstanceDict 

80 from ._typing import _InternalEntityType 

81 from ._typing import _ORMAdapterProto 

82 from .attributes import InstrumentedAttribute 

83 from .base import Mapped 

84 from .context import _MapperEntity 

85 from .context import ORMCompileState 

86 from .context import QueryContext 

87 from .decl_api import RegistryType 

88 from .decl_base import _ClassScanMapperConfig 

89 from .loading import _PopulatorDict 

90 from .mapper import Mapper 

91 from .path_registry import AbstractEntityRegistry 

92 from .query import Query 

93 from .session import Session 

94 from .state import InstanceState 

95 from .strategy_options import _LoadElement 

96 from .util import AliasedInsp 

97 from .util import ORMAdapter 

98 from ..engine.result import Result 

99 from ..sql._typing import _ColumnExpressionArgument 

100 from ..sql._typing import _ColumnsClauseArgument 

101 from ..sql._typing import _DMLColumnArgument 

102 from ..sql._typing import _InfoType 

103 from ..sql.operators import OperatorType 

104 from ..sql.visitors import _TraverseInternalsType 

105 from ..util.typing import _AnnotationScanType 

106 

107_StrategyKey = Tuple[Any, ...] 

108 

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

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

111 

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

113 

114 

115class ORMStatementRole(roles.StatementRole): 

116 __slots__ = () 

117 _role_name = ( 

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

119 ) 

120 

121 

122class ORMColumnsClauseRole( 

123 roles.ColumnsClauseRole, roles.TypedColumnsClauseRole[_T] 

124): 

125 __slots__ = () 

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

127 

128 

129class ORMEntityColumnsClauseRole(ORMColumnsClauseRole[_T]): 

130 __slots__ = () 

131 _role_name = "ORM mapped or aliased entity" 

132 

133 

134class ORMFromClauseRole(roles.StrictFromClauseRole): 

135 __slots__ = () 

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

137 

138 

139class ORMColumnDescription(TypedDict): 

140 name: str 

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

142 # into "type" is a bad idea 

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

144 aliased: bool 

145 expr: _ColumnsClauseArgument[Any] 

146 entity: Optional[_ColumnsClauseArgument[Any]] 

147 

148 

149class _IntrospectsAnnotations: 

150 __slots__ = () 

151 

152 @classmethod 

153 def _mapper_property_name(cls) -> str: 

154 return cls.__name__ 

155 

156 def found_in_pep593_annotated(self) -> Any: 

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

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

159 

160 raise NotImplementedError( 

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

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

163 ) 

164 

165 def declarative_scan( 

166 self, 

167 decl_scan: _ClassScanMapperConfig, 

168 registry: RegistryType, 

169 cls: Type[Any], 

170 originating_module: Optional[str], 

171 key: str, 

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

173 annotation: Optional[_AnnotationScanType], 

174 extracted_mapped_annotation: Optional[_AnnotationScanType], 

175 is_dataclass_field: bool, 

176 ) -> None: 

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

178 time. 

179 

180 .. versionadded:: 2.0 

181 

182 """ 

183 

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

185 raise sa_exc.ArgumentError( 

186 f"Python typing annotation is required for attribute " 

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

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

189 "construct are None or not present" 

190 ) 

191 

192 

193class _AttributeOptions(NamedTuple): 

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

195 :class:`.MapperProperty` objects. 

196 

197 Currently this includes dataclass-generation arguments. 

198 

199 .. versionadded:: 2.0 

200 

201 """ 

202 

203 dataclasses_init: Union[_NoArg, bool] 

204 dataclasses_repr: Union[_NoArg, bool] 

205 dataclasses_default: Union[_NoArg, Any] 

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

207 dataclasses_compare: Union[_NoArg, bool] 

208 dataclasses_kw_only: Union[_NoArg, bool] 

209 dataclasses_hash: Union[_NoArg, bool, None] 

210 

211 def _as_dataclass_field(self, key: str) -> Any: 

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

213 

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

215 if self.dataclasses_default_factory is not _NoArg.NO_ARG: 

216 kw["default_factory"] = self.dataclasses_default_factory 

217 if self.dataclasses_default is not _NoArg.NO_ARG: 

218 kw["default"] = self.dataclasses_default 

219 if self.dataclasses_init is not _NoArg.NO_ARG: 

220 kw["init"] = self.dataclasses_init 

221 if self.dataclasses_repr is not _NoArg.NO_ARG: 

222 kw["repr"] = self.dataclasses_repr 

223 if self.dataclasses_compare is not _NoArg.NO_ARG: 

224 kw["compare"] = self.dataclasses_compare 

225 if self.dataclasses_kw_only is not _NoArg.NO_ARG: 

226 kw["kw_only"] = self.dataclasses_kw_only 

227 if self.dataclasses_hash is not _NoArg.NO_ARG: 

228 kw["hash"] = self.dataclasses_hash 

229 

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

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

232 # insert_default or default_factory. #9936 

233 warn_deprecated( 

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

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

236 "ambiguous, " 

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

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

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

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

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

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

243 "``default_factory`` dataclasses parameter.", 

244 "2.0", 

245 ) 

246 

247 if ( 

248 "init" in kw 

249 and not kw["init"] 

250 and "default" in kw 

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

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

253 ): 

254 # fix for #9879 

255 default = kw.pop("default") 

256 kw["default_factory"] = lambda: default 

257 

258 return dataclasses.field(**kw) 

259 

260 @classmethod 

261 def _get_arguments_for_make_dataclass( 

262 cls, 

263 key: str, 

264 annotation: _AnnotationScanType, 

265 mapped_container: Optional[Any], 

266 elem: _T, 

267 ) -> Union[ 

268 Tuple[str, _AnnotationScanType], 

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

270 ]: 

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

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

273 for this attribute. 

274 

275 """ 

276 if isinstance(elem, _DCAttributeOptions): 

277 dc_field = elem._attribute_options._as_dataclass_field(key) 

278 

279 return (key, annotation, dc_field) 

280 elif elem is not _NoArg.NO_ARG: 

281 # why is typing not erroring on this? 

282 return (key, annotation, elem) 

283 elif mapped_container is not None: 

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

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

286 # happen. 

287 # previously, this would occur because _scan_attributes would 

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

289 # would still include it in the annotations, leading 

290 # to issue #8718 

291 

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

293 

294 else: 

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

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

297 # problems... 

298 return (key, annotation) 

299 

300 

301_DEFAULT_ATTRIBUTE_OPTIONS = _AttributeOptions( 

302 _NoArg.NO_ARG, 

303 _NoArg.NO_ARG, 

304 _NoArg.NO_ARG, 

305 _NoArg.NO_ARG, 

306 _NoArg.NO_ARG, 

307 _NoArg.NO_ARG, 

308 _NoArg.NO_ARG, 

309) 

310 

311_DEFAULT_READONLY_ATTRIBUTE_OPTIONS = _AttributeOptions( 

312 False, 

313 _NoArg.NO_ARG, 

314 _NoArg.NO_ARG, 

315 _NoArg.NO_ARG, 

316 _NoArg.NO_ARG, 

317 _NoArg.NO_ARG, 

318 _NoArg.NO_ARG, 

319) 

320 

321 

322class _DCAttributeOptions: 

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

324 field options. 

325 

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

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

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

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

330 

331 """ 

332 

333 __slots__ = () 

334 

335 _attribute_options: _AttributeOptions 

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

337 

338 .. versionadded:: 2.0 

339 

340 """ 

341 

342 _has_dataclass_arguments: bool 

343 

344 

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

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

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

348 """ 

349 

350 __slots__ = () 

351 

352 @property 

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

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

355 raise NotImplementedError() 

356 

357 @property 

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

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

360 new Table object. 

361 

362 """ 

363 raise NotImplementedError() 

364 

365 

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

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

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

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

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

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

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

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

374# by typing tools 

375@inspection._self_inspects 

376class MapperProperty( 

377 HasCacheKey, 

378 _DCAttributeOptions, 

379 _MappedAttribute[_T], 

380 InspectionAttrInfo, 

381 util.MemoizedSlots, 

382): 

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

384 

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

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

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

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

389 represented in the mapping as an instance of 

390 :class:`.Relationship`. 

391 

392 """ 

393 

394 __slots__ = ( 

395 "_configure_started", 

396 "_configure_finished", 

397 "_attribute_options", 

398 "_has_dataclass_arguments", 

399 "parent", 

400 "key", 

401 "info", 

402 "doc", 

403 ) 

404 

405 _cache_key_traversal: _TraverseInternalsType = [ 

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

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

408 ] 

409 

410 if not TYPE_CHECKING: 

411 cascade = None 

412 

413 is_property = True 

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

415 mapper property. 

416 

417 """ 

418 

419 comparator: PropComparator[_T] 

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

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

422 

423 key: str 

424 """name of class attribute""" 

425 

426 parent: Mapper[Any] 

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

428 

429 _is_relationship = False 

430 

431 _links_to_entity: bool 

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

433 

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

435 

436 """ 

437 

438 doc: Optional[str] 

439 """optional documentation string""" 

440 

441 info: _InfoType 

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

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

444 

445 The dictionary is generated when first accessed. Alternatively, 

446 it can be specified as a constructor argument to the 

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

448 functions. 

449 

450 .. seealso:: 

451 

452 :attr:`.QueryableAttribute.info` 

453 

454 :attr:`.SchemaItem.info` 

455 

456 """ 

457 

458 def _memoized_attr_info(self) -> _InfoType: 

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

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

461 

462 The dictionary is generated when first accessed. Alternatively, 

463 it can be specified as a constructor argument to the 

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

465 :func:`.composite` 

466 functions. 

467 

468 .. seealso:: 

469 

470 :attr:`.QueryableAttribute.info` 

471 

472 :attr:`.SchemaItem.info` 

473 

474 """ 

475 return {} 

476 

477 def setup( 

478 self, 

479 context: ORMCompileState, 

480 query_entity: _MapperEntity, 

481 path: AbstractEntityRegistry, 

482 adapter: Optional[ORMAdapter], 

483 **kwargs: Any, 

484 ) -> None: 

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

486 

487 Each MapperProperty associated with the target mapper processes the 

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

489 criterion as appropriate. 

490 

491 """ 

492 

493 def create_row_processor( 

494 self, 

495 context: ORMCompileState, 

496 query_entity: _MapperEntity, 

497 path: AbstractEntityRegistry, 

498 mapper: Mapper[Any], 

499 result: Result[Any], 

500 adapter: Optional[ORMAdapter], 

501 populators: _PopulatorDict, 

502 ) -> None: 

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

504 set of populators lists. 

505 

506 """ 

507 

508 def cascade_iterator( 

509 self, 

510 type_: str, 

511 state: InstanceState[Any], 

512 dict_: _InstanceDict, 

513 visited_states: Set[InstanceState[Any]], 

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

515 ) -> Iterator[ 

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

517 ]: 

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

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

520 

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

522 

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

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

525 

526 This method typically only applies to Relationship. 

527 

528 """ 

529 

530 return iter(()) 

531 

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

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

534 

535 This method is overridden by some subclasses to perform extra 

536 setup when the mapper is first known. 

537 

538 """ 

539 self.parent = parent 

540 

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

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

543 instrumentation of the class attribute managed by this 

544 MapperProperty. 

545 

546 The MapperProperty here will typically call out to the 

547 attributes module to set up an InstrumentedAttribute. 

548 

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

550 and is called early in the mapper setup process. 

551 

552 The second step is typically the init_class_attribute step, 

553 called from StrategizedProperty via the post_instrument_class() 

554 hook. This step assigns additional state to the InstrumentedAttribute 

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

556 MapperProperty has determined what kind of persistence 

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

558 

559 """ 

560 

561 def __init__( 

562 self, 

563 attribute_options: Optional[_AttributeOptions] = None, 

564 _assume_readonly_dc_attributes: bool = False, 

565 ) -> None: 

566 self._configure_started = False 

567 self._configure_finished = False 

568 

569 if _assume_readonly_dc_attributes: 

570 default_attrs = _DEFAULT_READONLY_ATTRIBUTE_OPTIONS 

571 else: 

572 default_attrs = _DEFAULT_ATTRIBUTE_OPTIONS 

573 

574 if attribute_options and attribute_options != default_attrs: 

575 self._has_dataclass_arguments = True 

576 self._attribute_options = attribute_options 

577 else: 

578 self._has_dataclass_arguments = False 

579 self._attribute_options = default_attrs 

580 

581 def init(self) -> None: 

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

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

584 initialization steps. 

585 

586 

587 """ 

588 self._configure_started = True 

589 self.do_init() 

590 self._configure_finished = True 

591 

592 @property 

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

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

595 :class:`.MapperProperty`. 

596 

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

598 

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

600 

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

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

603 is possible:: 

604 

605 >>> from sqlalchemy import inspect 

606 >>> mapper = inspect(User) 

607 >>> addresses_property = mapper.attrs.addresses 

608 >>> addresses_property.class_attribute is User.addresses 

609 True 

610 >>> User.addresses.property is addresses_property 

611 True 

612 

613 

614 """ 

615 

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

617 

618 def do_init(self) -> None: 

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

620 steps. 

621 

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

623 object's init() method. 

624 

625 """ 

626 

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

628 """Perform instrumentation adjustments that need to occur 

629 after init() has completed. 

630 

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

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

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

634 self.parent. 

635 

636 This method is typically used by StrategizedProperty, which delegates 

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

638 on the class-bound InstrumentedAttribute. 

639 

640 """ 

641 

642 def merge( 

643 self, 

644 session: Session, 

645 source_state: InstanceState[Any], 

646 source_dict: _InstanceDict, 

647 dest_state: InstanceState[Any], 

648 dest_dict: _InstanceDict, 

649 load: bool, 

650 _recursive: Dict[Any, object], 

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

652 ) -> None: 

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

654 from source to destination object. 

655 

656 """ 

657 

658 def __repr__(self) -> str: 

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

660 self.__class__.__name__, 

661 id(self), 

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

663 ) 

664 

665 

666@inspection._self_inspects 

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

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

669 

670 SQLAlchemy allows for operators to 

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

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

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

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

675 

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

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

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

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

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

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

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

683 argument. In each case, 

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

685 

686 # definition of custom PropComparator subclasses 

687 

688 from sqlalchemy.orm.properties import ( 

689 ColumnProperty, 

690 Composite, 

691 Relationship, 

692 ) 

693 

694 

695 class MyColumnComparator(ColumnProperty.Comparator): 

696 def __eq__(self, other): 

697 return self.__clause_element__() == other 

698 

699 

700 class MyRelationshipComparator(Relationship.Comparator): 

701 def any(self, expression): 

702 "define the 'any' operation" 

703 # ... 

704 

705 

706 class MyCompositeComparator(Composite.Comparator): 

707 def __gt__(self, other): 

708 "redefine the 'greater than' operation" 

709 

710 return sql.and_( 

711 *[ 

712 a > b 

713 for a, b in zip( 

714 self.__clause_element__().clauses, 

715 other.__composite_values__(), 

716 ) 

717 ] 

718 ) 

719 

720 

721 # application of custom PropComparator subclasses 

722 

723 from sqlalchemy.orm import column_property, relationship, composite 

724 from sqlalchemy import Column, String 

725 

726 

727 class SomeMappedClass(Base): 

728 some_column = column_property( 

729 Column("some_column", String), 

730 comparator_factory=MyColumnComparator, 

731 ) 

732 

733 some_relationship = relationship( 

734 SomeOtherClass, comparator_factory=MyRelationshipComparator 

735 ) 

736 

737 some_composite = composite( 

738 Column("a", String), 

739 Column("b", String), 

740 comparator_factory=MyCompositeComparator, 

741 ) 

742 

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

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

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

746 :ref:`types_operators` for more detail. 

747 

748 .. seealso:: 

749 

750 :class:`.ColumnProperty.Comparator` 

751 

752 :class:`.Relationship.Comparator` 

753 

754 :class:`.Composite.Comparator` 

755 

756 :class:`.ColumnOperators` 

757 

758 :ref:`types_operators` 

759 

760 :attr:`.TypeEngine.comparator_factory` 

761 

762 """ 

763 

764 __slots__ = "prop", "_parententity", "_adapt_to_entity" 

765 

766 __visit_name__ = "orm_prop_comparator" 

767 

768 _parententity: _InternalEntityType[Any] 

769 _adapt_to_entity: Optional[AliasedInsp[Any]] 

770 prop: RODescriptorReference[MapperProperty[_T_co]] 

771 

772 def __init__( 

773 self, 

774 prop: MapperProperty[_T], 

775 parentmapper: _InternalEntityType[Any], 

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

777 ): 

778 self.prop = prop 

779 self._parententity = adapt_to_entity or parentmapper 

780 self._adapt_to_entity = adapt_to_entity 

781 

782 @util.non_memoized_property 

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

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

785 :class:`.PropComparator`. 

786 

787 

788 Return values here will commonly be instances of 

789 :class:`.ColumnProperty` or :class:`.Relationship`. 

790 

791 

792 """ 

793 return self.prop 

794 

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

796 raise NotImplementedError("%r" % self) 

797 

798 def _bulk_update_tuples( 

799 self, value: Any 

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

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

802 clause of an UPDATE statement. 

803 

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

805 construct. 

806 

807 """ 

808 

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

810 

811 def adapt_to_entity( 

812 self, adapt_to_entity: AliasedInsp[Any] 

813 ) -> PropComparator[_T_co]: 

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

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

816 """ 

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

818 

819 @util.ro_non_memoized_property 

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

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

822 compatible with QueryableAttribute.""" 

823 return self._parententity.mapper 

824 

825 def _criterion_exists( 

826 self, 

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

828 **kwargs: Any, 

829 ) -> ColumnElement[Any]: 

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

831 

832 @util.ro_non_memoized_property 

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

834 """Produce a callable that adapts column expressions 

835 to suit an aliased version of this comparator. 

836 

837 """ 

838 if self._adapt_to_entity is None: 

839 return None 

840 else: 

841 return self._adapt_to_entity._orm_adapt_element 

842 

843 @util.ro_non_memoized_property 

844 def info(self) -> _InfoType: 

845 return self.prop.info 

846 

847 @staticmethod 

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

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

850 

851 @staticmethod 

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

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

854 

855 @staticmethod 

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

857 return a.of_type(class_) 

858 

859 any_op = cast(operators.OperatorType, _any_op) 

860 has_op = cast(operators.OperatorType, _has_op) 

861 of_type_op = cast(operators.OperatorType, _of_type_op) 

862 

863 if typing.TYPE_CHECKING: 

864 

865 def operate( 

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

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

868 

869 def reverse_operate( 

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

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

872 

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

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

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

876 construct. 

877 

878 Returns a new PropComparator from which further criterion can be 

879 evaluated. 

880 

881 e.g.:: 

882 

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

884 Engineer.name == "foo" 

885 ) 

886 

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

888 against this specific subclass. 

889 

890 .. seealso:: 

891 

892 :ref:`orm_queryguide_joining_relationships_aliased` - in the 

893 :ref:`queryguide_toplevel` 

894 

895 :ref:`inheritance_of_type` 

896 

897 """ 

898 

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

900 

901 def and_( 

902 self, *criteria: _ColumnExpressionArgument[bool] 

903 ) -> PropComparator[bool]: 

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

905 relationship attribute. 

906 

907 E.g.:: 

908 

909 

910 stmt = select(User).join( 

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

912 ) 

913 

914 stmt = select(User).options( 

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

916 ) 

917 

918 .. versionadded:: 1.4 

919 

920 .. seealso:: 

921 

922 :ref:`orm_queryguide_join_on_augmented` 

923 

924 :ref:`loader_option_criteria` 

925 

926 :func:`.with_loader_criteria` 

927 

928 """ 

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

930 

931 def any( 

932 self, 

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

934 **kwargs: Any, 

935 ) -> ColumnElement[bool]: 

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

937 references a member which meets the given criterion. 

938 

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

940 :meth:`.Relationship.Comparator.any`. 

941 

942 :param criterion: an optional ClauseElement formulated against the 

943 member class' table or attributes. 

944 

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

946 attribute names which will be compared via equality to the 

947 corresponding values. 

948 

949 """ 

950 

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

952 

953 def has( 

954 self, 

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

956 **kwargs: Any, 

957 ) -> ColumnElement[bool]: 

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

959 references a member which meets the given criterion. 

960 

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

962 :meth:`.Relationship.Comparator.has`. 

963 

964 :param criterion: an optional ClauseElement formulated against the 

965 member class' table or attributes. 

966 

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

968 attribute names which will be compared via equality to the 

969 corresponding values. 

970 

971 """ 

972 

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

974 

975 

976class StrategizedProperty(MapperProperty[_T]): 

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

978 loading behavior. 

979 

980 There is a single strategy selected by default. Alternate 

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

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

983 

984 The mechanics of StrategizedProperty are used for every Query 

985 invocation for every mapped attribute participating in that Query, 

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

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

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

989 performance-critical. 

990 

991 """ 

992 

993 __slots__ = ( 

994 "_strategies", 

995 "strategy", 

996 "_wildcard_token", 

997 "_default_path_loader_key", 

998 "strategy_key", 

999 ) 

1000 inherit_cache = True 

1001 strategy_wildcard_key: ClassVar[str] 

1002 

1003 strategy_key: _StrategyKey 

1004 

1005 _strategies: Dict[_StrategyKey, LoaderStrategy] 

1006 

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

1008 return ( 

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

1010 ) 

1011 

1012 def _memoized_attr__default_path_loader_key( 

1013 self, 

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

1015 return ( 

1016 "loader", 

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

1018 ) 

1019 

1020 def _get_context_loader( 

1021 self, context: ORMCompileState, path: AbstractEntityRegistry 

1022 ) -> Optional[_LoadElement]: 

1023 load: Optional[_LoadElement] = None 

1024 

1025 search_path = path[self] 

1026 

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

1028 # if any. 

1029 for path_key in ( 

1030 search_path._loader_key, 

1031 search_path._wildcard_path_loader_key, 

1032 search_path._default_path_loader_key, 

1033 ): 

1034 if path_key in context.attributes: 

1035 load = context.attributes[path_key] 

1036 break 

1037 

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

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

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

1041 # option as below. 

1042 # if load.strategy or load.local_opts: 

1043 # break 

1044 

1045 return load 

1046 

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

1048 try: 

1049 return self._strategies[key] 

1050 except KeyError: 

1051 pass 

1052 

1053 # run outside to prevent transfer of exception context 

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

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

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

1057 # strategy has multiple keys at this point 

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

1059 return strategy 

1060 

1061 def setup( 

1062 self, 

1063 context: ORMCompileState, 

1064 query_entity: _MapperEntity, 

1065 path: AbstractEntityRegistry, 

1066 adapter: Optional[ORMAdapter], 

1067 **kwargs: Any, 

1068 ) -> None: 

1069 loader = self._get_context_loader(context, path) 

1070 if loader and loader.strategy: 

1071 strat = self._get_strategy(loader.strategy) 

1072 else: 

1073 strat = self.strategy 

1074 strat.setup_query( 

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

1076 ) 

1077 

1078 def create_row_processor( 

1079 self, 

1080 context: ORMCompileState, 

1081 query_entity: _MapperEntity, 

1082 path: AbstractEntityRegistry, 

1083 mapper: Mapper[Any], 

1084 result: Result[Any], 

1085 adapter: Optional[ORMAdapter], 

1086 populators: _PopulatorDict, 

1087 ) -> None: 

1088 loader = self._get_context_loader(context, path) 

1089 if loader and loader.strategy: 

1090 strat = self._get_strategy(loader.strategy) 

1091 else: 

1092 strat = self.strategy 

1093 strat.create_row_processor( 

1094 context, 

1095 query_entity, 

1096 path, 

1097 loader, 

1098 mapper, 

1099 result, 

1100 adapter, 

1101 populators, 

1102 ) 

1103 

1104 def do_init(self) -> None: 

1105 self._strategies = {} 

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

1107 

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

1109 if ( 

1110 not self.parent.non_primary 

1111 and not mapper.class_manager._attr_has_impl(self.key) 

1112 ): 

1113 self.strategy.init_class_attribute(mapper) 

1114 

1115 _all_strategies: collections.defaultdict[ 

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

1117 ] = collections.defaultdict(dict) 

1118 

1119 @classmethod 

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

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

1122 # ensure each subclass of the strategy has its 

1123 # own _strategy_keys collection 

1124 if "_strategy_keys" not in dec_cls.__dict__: 

1125 dec_cls._strategy_keys = [] 

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

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

1128 dec_cls._strategy_keys.append(key) 

1129 return dec_cls 

1130 

1131 return decorate 

1132 

1133 @classmethod 

1134 def _strategy_lookup( 

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

1136 ) -> Type[LoaderStrategy]: 

1137 requesting_property.parent._with_polymorphic_mappers 

1138 

1139 for prop_cls in cls.__mro__: 

1140 if prop_cls in cls._all_strategies: 

1141 if TYPE_CHECKING: 

1142 assert issubclass(prop_cls, MapperProperty) 

1143 strategies = cls._all_strategies[prop_cls] 

1144 try: 

1145 return strategies[key] 

1146 except KeyError: 

1147 pass 

1148 

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

1150 if key in strats: 

1151 intended_property_type = property_type 

1152 actual_strategy = strats[key] 

1153 break 

1154 else: 

1155 intended_property_type = None 

1156 actual_strategy = None 

1157 

1158 raise orm_exc.LoaderStrategyException( 

1159 cls, 

1160 requesting_property, 

1161 intended_property_type, 

1162 actual_strategy, 

1163 key, 

1164 ) 

1165 

1166 

1167class ORMOption(ExecutableOption): 

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

1169 

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

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

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

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

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

1175 time. 

1176 

1177 .. versionadded:: 1.4 

1178 

1179 """ 

1180 

1181 __slots__ = () 

1182 

1183 _is_legacy_option = False 

1184 

1185 propagate_to_loaders = False 

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

1187 to "secondary" SELECT statements that occur for relationship 

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

1189 

1190 """ 

1191 

1192 _is_core = False 

1193 

1194 _is_user_defined = False 

1195 

1196 _is_compile_state = False 

1197 

1198 _is_criteria_option = False 

1199 

1200 _is_strategy_option = False 

1201 

1202 def _adapt_cached_option_to_uncached_option( 

1203 self, context: QueryContext, uncached_opt: ORMOption 

1204 ) -> ORMOption: 

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

1206 loader strategy context. 

1207 

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

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

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

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

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

1213 entities passed to us from the cached query. 

1214 

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

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

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

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

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

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

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

1222 

1223 This routine could in the future involve 

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

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

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

1227 

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

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

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

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

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

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

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

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

1236 like an md5 key. 

1237 

1238 .. versionadded:: 1.4.41 

1239 

1240 """ 

1241 if uncached_opt is not None: 

1242 return uncached_opt 

1243 else: 

1244 return self 

1245 

1246 

1247class CompileStateOption(HasCacheKey, ORMOption): 

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

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

1250 

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

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

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

1254 interact with ORM compilation internals or caching. 

1255 

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

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

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

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

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

1261 structures. 

1262 

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

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

1265 

1266 .. versionadded:: 1.4.28 

1267 

1268 

1269 """ 

1270 

1271 __slots__ = () 

1272 

1273 _is_compile_state = True 

1274 

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

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

1277 

1278 This method is part of the implementation of a particular 

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

1280 when an ORM query is compiled. 

1281 

1282 """ 

1283 

1284 def process_compile_state_replaced_entities( 

1285 self, 

1286 compile_state: ORMCompileState, 

1287 mapper_entities: Sequence[_MapperEntity], 

1288 ) -> None: 

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

1290 given entities that were replaced by with_only_columns() or 

1291 with_entities(). 

1292 

1293 This method is part of the implementation of a particular 

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

1295 when an ORM query is compiled. 

1296 

1297 .. versionadded:: 1.4.19 

1298 

1299 """ 

1300 

1301 

1302class LoaderOption(CompileStateOption): 

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

1304 

1305 .. versionadded:: 1.4 

1306 

1307 """ 

1308 

1309 __slots__ = () 

1310 

1311 def process_compile_state_replaced_entities( 

1312 self, 

1313 compile_state: ORMCompileState, 

1314 mapper_entities: Sequence[_MapperEntity], 

1315 ) -> None: 

1316 self.process_compile_state(compile_state) 

1317 

1318 

1319class CriteriaOption(CompileStateOption): 

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

1321 compilation time. 

1322 

1323 .. versionadded:: 1.4 

1324 

1325 """ 

1326 

1327 __slots__ = () 

1328 

1329 _is_criteria_option = True 

1330 

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

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

1333 attributes dictionary. 

1334 

1335 """ 

1336 

1337 

1338class UserDefinedOption(ORMOption): 

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

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

1341 

1342 """ 

1343 

1344 __slots__ = ("payload",) 

1345 

1346 _is_legacy_option = False 

1347 

1348 _is_user_defined = True 

1349 

1350 propagate_to_loaders = False 

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

1352 to "secondary" Query objects produced during lazy loads 

1353 or refresh operations. 

1354 

1355 """ 

1356 

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

1358 self.payload = payload 

1359 

1360 

1361@util.deprecated_cls( 

1362 "1.4", 

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

1364 "in a future release. For " 

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

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

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

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

1369 constructor=None, 

1370) 

1371class MapperOption(ORMOption): 

1372 """Describe a modification to a Query""" 

1373 

1374 __slots__ = () 

1375 

1376 _is_legacy_option = True 

1377 

1378 propagate_to_loaders = False 

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

1380 to "secondary" Query objects produced during lazy loads 

1381 or refresh operations. 

1382 

1383 """ 

1384 

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

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

1387 

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

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

1390 apply to the given query. 

1391 

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

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

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

1395 specify propagate_to_loaders=True. 

1396 

1397 """ 

1398 

1399 self.process_query(query) 

1400 

1401 

1402class LoaderStrategy: 

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

1404 

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

1406 ways: 

1407 

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

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

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

1411 off a select operation when the attribute is first accessed 

1412 (i.e. a lazy load) 

1413 

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

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

1416 For example, simple column attributes will add their represented 

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

1418 may establish join clauses to add to the statement. 

1419 

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

1421 These "row processor" functions populate a particular attribute 

1422 on a particular mapped instance. 

1423 

1424 """ 

1425 

1426 __slots__ = ( 

1427 "parent_property", 

1428 "is_class_level", 

1429 "parent", 

1430 "key", 

1431 "strategy_key", 

1432 "strategy_opts", 

1433 ) 

1434 

1435 _strategy_keys: ClassVar[List[_StrategyKey]] 

1436 

1437 def __init__( 

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

1439 ): 

1440 self.parent_property = parent 

1441 self.is_class_level = False 

1442 self.parent = self.parent_property.parent 

1443 self.key = self.parent_property.key 

1444 self.strategy_key = strategy_key 

1445 self.strategy_opts = dict(strategy_key) 

1446 

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

1448 pass 

1449 

1450 def setup_query( 

1451 self, 

1452 compile_state: ORMCompileState, 

1453 query_entity: _MapperEntity, 

1454 path: AbstractEntityRegistry, 

1455 loadopt: Optional[_LoadElement], 

1456 adapter: Optional[ORMAdapter], 

1457 **kwargs: Any, 

1458 ) -> None: 

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

1460 

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

1462 

1463 StrategizedProperty delegates its setup() method 

1464 directly to this method. 

1465 

1466 """ 

1467 

1468 def create_row_processor( 

1469 self, 

1470 context: ORMCompileState, 

1471 query_entity: _MapperEntity, 

1472 path: AbstractEntityRegistry, 

1473 loadopt: Optional[_LoadElement], 

1474 mapper: Mapper[Any], 

1475 result: Result[Any], 

1476 adapter: Optional[ORMAdapter], 

1477 populators: _PopulatorDict, 

1478 ) -> None: 

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

1480 

1481 This method fulfills the contract specified by 

1482 MapperProperty.create_row_processor(). 

1483 

1484 StrategizedProperty delegates its create_row_processor() method 

1485 directly to this method. 

1486 

1487 """ 

1488 

1489 def __str__(self) -> str: 

1490 return str(self.parent_property)