Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/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

404 statements  

1# orm/interfaces.py 

2# Copyright (C) 2005-2024 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 TypedDict 

41from typing import TypeVar 

42from typing import Union 

43 

44from . import exc as orm_exc 

45from . import path_registry 

46from .base import _MappedAttribute as _MappedAttribute 

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

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

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

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

51from .base import InspectionAttrInfo as InspectionAttrInfo 

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

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

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

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

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

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

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

59from .base import SQLORMOperations 

60from .. import ColumnElement 

61from .. import exc as sa_exc 

62from .. import inspection 

63from .. import util 

64from ..sql import operators 

65from ..sql import roles 

66from ..sql import visitors 

67from ..sql.base import _NoArg 

68from ..sql.base import ExecutableOption 

69from ..sql.cache_key import HasCacheKey 

70from ..sql.operators import ColumnOperators 

71from ..sql.schema import Column 

72from ..sql.type_api import TypeEngine 

73from ..util import warn_deprecated 

74from ..util.typing import RODescriptorReference 

75from ..util.typing import TupleAny 

76from ..util.typing import Unpack 

77 

78 

79if typing.TYPE_CHECKING: 

80 from ._typing import _EntityType 

81 from ._typing import _IdentityKeyType 

82 from ._typing import _InstanceDict 

83 from ._typing import _InternalEntityType 

84 from ._typing import _ORMAdapterProto 

85 from .attributes import InstrumentedAttribute 

86 from .base import Mapped 

87 from .context import _MapperEntity 

88 from .context import ORMCompileState 

89 from .context import QueryContext 

90 from .decl_api import RegistryType 

91 from .decl_base import _ClassScanMapperConfig 

92 from .loading import _PopulatorDict 

93 from .mapper import Mapper 

94 from .path_registry import AbstractEntityRegistry 

95 from .query import Query 

96 from .session import Session 

97 from .state import InstanceState 

98 from .strategy_options import _LoadElement 

99 from .util import AliasedInsp 

100 from .util import ORMAdapter 

101 from ..engine.result import Result 

102 from ..sql._typing import _ColumnExpressionArgument 

103 from ..sql._typing import _ColumnsClauseArgument 

104 from ..sql._typing import _DMLColumnArgument 

105 from ..sql._typing import _InfoType 

106 from ..sql.operators import OperatorType 

107 from ..sql.visitors import _TraverseInternalsType 

108 from ..util.typing import _AnnotationScanType 

109 

110_StrategyKey = Tuple[Any, ...] 

111 

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

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

114 

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

116 

117 

118class ORMStatementRole(roles.StatementRole): 

119 __slots__ = () 

120 _role_name = ( 

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

122 ) 

123 

124 

125class ORMColumnsClauseRole( 

126 roles.ColumnsClauseRole, roles.TypedColumnsClauseRole[_T] 

127): 

128 __slots__ = () 

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

130 

131 

132class ORMEntityColumnsClauseRole(ORMColumnsClauseRole[_T]): 

133 __slots__ = () 

134 _role_name = "ORM mapped or aliased entity" 

135 

136 

137class ORMFromClauseRole(roles.StrictFromClauseRole): 

138 __slots__ = () 

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

140 

141 

142class ORMColumnDescription(TypedDict): 

143 name: str 

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

145 # into "type" is a bad idea 

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

147 aliased: bool 

148 expr: _ColumnsClauseArgument[Any] 

149 entity: Optional[_ColumnsClauseArgument[Any]] 

150 

151 

152class _IntrospectsAnnotations: 

153 __slots__ = () 

154 

155 @classmethod 

156 def _mapper_property_name(cls) -> str: 

157 return cls.__name__ 

158 

159 def found_in_pep593_annotated(self) -> Any: 

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

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

162 

163 raise NotImplementedError( 

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

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

166 ) 

167 

168 def declarative_scan( 

169 self, 

170 decl_scan: _ClassScanMapperConfig, 

171 registry: RegistryType, 

172 cls: Type[Any], 

173 originating_module: Optional[str], 

174 key: str, 

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

176 annotation: Optional[_AnnotationScanType], 

177 extracted_mapped_annotation: Optional[_AnnotationScanType], 

178 is_dataclass_field: bool, 

179 ) -> None: 

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

181 time. 

182 

183 .. versionadded:: 2.0 

184 

185 """ 

186 

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

188 raise sa_exc.ArgumentError( 

189 f"Python typing annotation is required for attribute " 

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

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

192 "construct are None or not present" 

193 ) 

194 

195 

196class _AttributeOptions(NamedTuple): 

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

198 :class:`.MapperProperty` objects. 

199 

200 Currently this includes dataclass-generation arguments. 

201 

202 .. versionadded:: 2.0 

203 

204 """ 

205 

206 dataclasses_init: Union[_NoArg, bool] 

207 dataclasses_repr: Union[_NoArg, bool] 

208 dataclasses_default: Union[_NoArg, Any] 

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

210 dataclasses_compare: Union[_NoArg, bool] 

211 dataclasses_kw_only: Union[_NoArg, bool] 

212 

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

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

215 

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

217 if self.dataclasses_default_factory is not _NoArg.NO_ARG: 

218 kw["default_factory"] = self.dataclasses_default_factory 

219 if self.dataclasses_default is not _NoArg.NO_ARG: 

220 kw["default"] = self.dataclasses_default 

221 if self.dataclasses_init is not _NoArg.NO_ARG: 

222 kw["init"] = self.dataclasses_init 

223 if self.dataclasses_repr is not _NoArg.NO_ARG: 

224 kw["repr"] = self.dataclasses_repr 

225 if self.dataclasses_compare is not _NoArg.NO_ARG: 

226 kw["compare"] = self.dataclasses_compare 

227 if self.dataclasses_kw_only is not _NoArg.NO_ARG: 

228 kw["kw_only"] = self.dataclasses_kw_only 

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) 

309 

310_DEFAULT_READONLY_ATTRIBUTE_OPTIONS = _AttributeOptions( 

311 False, 

312 _NoArg.NO_ARG, 

313 _NoArg.NO_ARG, 

314 _NoArg.NO_ARG, 

315 _NoArg.NO_ARG, 

316 _NoArg.NO_ARG, 

317) 

318 

319 

320class _DCAttributeOptions: 

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

322 field options. 

323 

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

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

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

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

328 

329 """ 

330 

331 __slots__ = () 

332 

333 _attribute_options: _AttributeOptions 

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

335 

336 .. versionadded:: 2.0 

337 

338 """ 

339 

340 _has_dataclass_arguments: bool 

341 

342 

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

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

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

346 """ 

347 

348 __slots__ = () 

349 

350 @property 

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

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

353 raise NotImplementedError() 

354 

355 @property 

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

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

358 new Table object. 

359 

360 """ 

361 raise NotImplementedError() 

362 

363 

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

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

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

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

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

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

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

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

372# by typing tools 

373@inspection._self_inspects 

374class MapperProperty( 

375 HasCacheKey, 

376 _DCAttributeOptions, 

377 _MappedAttribute[_T], 

378 InspectionAttrInfo, 

379 util.MemoizedSlots, 

380): 

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

382 

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

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

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

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

387 represented in the mapping as an instance of 

388 :class:`.Relationship`. 

389 

390 """ 

391 

392 __slots__ = ( 

393 "_configure_started", 

394 "_configure_finished", 

395 "_attribute_options", 

396 "_has_dataclass_arguments", 

397 "parent", 

398 "key", 

399 "info", 

400 "doc", 

401 ) 

402 

403 _cache_key_traversal: _TraverseInternalsType = [ 

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

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

406 ] 

407 

408 if not TYPE_CHECKING: 

409 cascade = None 

410 

411 is_property = True 

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

413 mapper property. 

414 

415 """ 

416 

417 comparator: PropComparator[_T] 

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

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

420 

421 key: str 

422 """name of class attribute""" 

423 

424 parent: Mapper[Any] 

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

426 

427 _is_relationship = False 

428 

429 _links_to_entity: bool 

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

431 

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

433 

434 """ 

435 

436 doc: Optional[str] 

437 """optional documentation string""" 

438 

439 info: _InfoType 

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

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

442 

443 The dictionary is generated when first accessed. Alternatively, 

444 it can be specified as a constructor argument to the 

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

446 functions. 

447 

448 .. seealso:: 

449 

450 :attr:`.QueryableAttribute.info` 

451 

452 :attr:`.SchemaItem.info` 

453 

454 """ 

455 

456 def _memoized_attr_info(self) -> _InfoType: 

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

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

459 

460 The dictionary is generated when first accessed. Alternatively, 

461 it can be specified as a constructor argument to the 

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

463 :func:`.composite` 

464 functions. 

465 

466 .. seealso:: 

467 

468 :attr:`.QueryableAttribute.info` 

469 

470 :attr:`.SchemaItem.info` 

471 

472 """ 

473 return {} 

474 

475 def setup( 

476 self, 

477 context: ORMCompileState, 

478 query_entity: _MapperEntity, 

479 path: AbstractEntityRegistry, 

480 adapter: Optional[ORMAdapter], 

481 **kwargs: Any, 

482 ) -> None: 

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

484 

485 Each MapperProperty associated with the target mapper processes the 

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

487 criterion as appropriate. 

488 

489 """ 

490 

491 def create_row_processor( 

492 self, 

493 context: ORMCompileState, 

494 query_entity: _MapperEntity, 

495 path: AbstractEntityRegistry, 

496 mapper: Mapper[Any], 

497 result: Result[Unpack[TupleAny]], 

498 adapter: Optional[ORMAdapter], 

499 populators: _PopulatorDict, 

500 ) -> None: 

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

502 set of populators lists. 

503 

504 """ 

505 

506 def cascade_iterator( 

507 self, 

508 type_: str, 

509 state: InstanceState[Any], 

510 dict_: _InstanceDict, 

511 visited_states: Set[InstanceState[Any]], 

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

513 ) -> Iterator[ 

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

515 ]: 

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

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

518 

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

520 

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

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

523 

524 This method typically only applies to Relationship. 

525 

526 """ 

527 

528 return iter(()) 

529 

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

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

532 

533 This method is overridden by some subclasses to perform extra 

534 setup when the mapper is first known. 

535 

536 """ 

537 self.parent = parent 

538 

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

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

541 instrumentation of the class attribute managed by this 

542 MapperProperty. 

543 

544 The MapperProperty here will typically call out to the 

545 attributes module to set up an InstrumentedAttribute. 

546 

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

548 and is called early in the mapper setup process. 

549 

550 The second step is typically the init_class_attribute step, 

551 called from StrategizedProperty via the post_instrument_class() 

552 hook. This step assigns additional state to the InstrumentedAttribute 

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

554 MapperProperty has determined what kind of persistence 

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

556 

557 """ 

558 

559 def __init__( 

560 self, 

561 attribute_options: Optional[_AttributeOptions] = None, 

562 _assume_readonly_dc_attributes: bool = False, 

563 ) -> None: 

564 self._configure_started = False 

565 self._configure_finished = False 

566 

567 if _assume_readonly_dc_attributes: 

568 default_attrs = _DEFAULT_READONLY_ATTRIBUTE_OPTIONS 

569 else: 

570 default_attrs = _DEFAULT_ATTRIBUTE_OPTIONS 

571 

572 if attribute_options and attribute_options != default_attrs: 

573 self._has_dataclass_arguments = True 

574 self._attribute_options = attribute_options 

575 else: 

576 self._has_dataclass_arguments = False 

577 self._attribute_options = default_attrs 

578 

579 def init(self) -> None: 

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

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

582 initialization steps. 

583 

584 

585 """ 

586 self._configure_started = True 

587 self.do_init() 

588 self._configure_finished = True 

589 

590 @property 

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

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

593 :class:`.MapperProperty`. 

594 

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

596 

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

598 

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

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

601 is possible:: 

602 

603 >>> from sqlalchemy import inspect 

604 >>> mapper = inspect(User) 

605 >>> addresses_property = mapper.attrs.addresses 

606 >>> addresses_property.class_attribute is User.addresses 

607 True 

608 >>> User.addresses.property is addresses_property 

609 True 

610 

611 

612 """ 

613 

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

615 

616 def do_init(self) -> None: 

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

618 steps. 

619 

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

621 object's init() method. 

622 

623 """ 

624 

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

626 """Perform instrumentation adjustments that need to occur 

627 after init() has completed. 

628 

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

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

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

632 self.parent. 

633 

634 This method is typically used by StrategizedProperty, which delegates 

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

636 on the class-bound InstrumentedAttribute. 

637 

638 """ 

639 

640 def merge( 

641 self, 

642 session: Session, 

643 source_state: InstanceState[Any], 

644 source_dict: _InstanceDict, 

645 dest_state: InstanceState[Any], 

646 dest_dict: _InstanceDict, 

647 load: bool, 

648 _recursive: Dict[Any, object], 

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

650 ) -> None: 

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

652 from source to destination object. 

653 

654 """ 

655 

656 def __repr__(self) -> str: 

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

658 self.__class__.__name__, 

659 id(self), 

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

661 ) 

662 

663 

664@inspection._self_inspects 

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

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

667 

668 SQLAlchemy allows for operators to 

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

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

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

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

673 

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

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

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

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

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

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

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

681 argument. In each case, 

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

683 

684 # definition of custom PropComparator subclasses 

685 

686 from sqlalchemy.orm.properties import \ 

687 ColumnProperty,\ 

688 Composite,\ 

689 Relationship 

690 

691 class MyColumnComparator(ColumnProperty.Comparator): 

692 def __eq__(self, other): 

693 return self.__clause_element__() == other 

694 

695 class MyRelationshipComparator(Relationship.Comparator): 

696 def any(self, expression): 

697 "define the 'any' operation" 

698 # ... 

699 

700 class MyCompositeComparator(Composite.Comparator): 

701 def __gt__(self, other): 

702 "redefine the 'greater than' operation" 

703 

704 return sql.and_(*[a>b for a, b in 

705 zip(self.__clause_element__().clauses, 

706 other.__composite_values__())]) 

707 

708 

709 # application of custom PropComparator subclasses 

710 

711 from sqlalchemy.orm import column_property, relationship, composite 

712 from sqlalchemy import Column, String 

713 

714 class SomeMappedClass(Base): 

715 some_column = column_property(Column("some_column", String), 

716 comparator_factory=MyColumnComparator) 

717 

718 some_relationship = relationship(SomeOtherClass, 

719 comparator_factory=MyRelationshipComparator) 

720 

721 some_composite = composite( 

722 Column("a", String), Column("b", String), 

723 comparator_factory=MyCompositeComparator 

724 ) 

725 

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

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

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

729 :ref:`types_operators` for more detail. 

730 

731 .. seealso:: 

732 

733 :class:`.ColumnProperty.Comparator` 

734 

735 :class:`.Relationship.Comparator` 

736 

737 :class:`.Composite.Comparator` 

738 

739 :class:`.ColumnOperators` 

740 

741 :ref:`types_operators` 

742 

743 :attr:`.TypeEngine.comparator_factory` 

744 

745 """ 

746 

747 __slots__ = "prop", "_parententity", "_adapt_to_entity" 

748 

749 __visit_name__ = "orm_prop_comparator" 

750 

751 _parententity: _InternalEntityType[Any] 

752 _adapt_to_entity: Optional[AliasedInsp[Any]] 

753 prop: RODescriptorReference[MapperProperty[_T_co]] 

754 

755 def __init__( 

756 self, 

757 prop: MapperProperty[_T], 

758 parentmapper: _InternalEntityType[Any], 

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

760 ): 

761 self.prop = prop 

762 self._parententity = adapt_to_entity or parentmapper 

763 self._adapt_to_entity = adapt_to_entity 

764 

765 @util.non_memoized_property 

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

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

768 :class:`.PropComparator`. 

769 

770 

771 Return values here will commonly be instances of 

772 :class:`.ColumnProperty` or :class:`.Relationship`. 

773 

774 

775 """ 

776 return self.prop 

777 

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

779 raise NotImplementedError("%r" % self) 

780 

781 def _bulk_update_tuples( 

782 self, value: Any 

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

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

785 clause of an UPDATE statement. 

786 

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

788 construct. 

789 

790 """ 

791 

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

793 

794 def adapt_to_entity( 

795 self, adapt_to_entity: AliasedInsp[Any] 

796 ) -> PropComparator[_T_co]: 

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

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

799 """ 

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

801 

802 @util.ro_non_memoized_property 

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

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

805 compatible with QueryableAttribute.""" 

806 return self._parententity.mapper 

807 

808 def _criterion_exists( 

809 self, 

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

811 **kwargs: Any, 

812 ) -> ColumnElement[Any]: 

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

814 

815 @util.ro_non_memoized_property 

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

817 """Produce a callable that adapts column expressions 

818 to suit an aliased version of this comparator. 

819 

820 """ 

821 if self._adapt_to_entity is None: 

822 return None 

823 else: 

824 return self._adapt_to_entity._orm_adapt_element 

825 

826 @util.ro_non_memoized_property 

827 def info(self) -> _InfoType: 

828 return self.prop.info 

829 

830 @staticmethod 

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

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

833 

834 @staticmethod 

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

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

837 

838 @staticmethod 

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

840 return a.of_type(class_) 

841 

842 any_op = cast(operators.OperatorType, _any_op) 

843 has_op = cast(operators.OperatorType, _has_op) 

844 of_type_op = cast(operators.OperatorType, _of_type_op) 

845 

846 if typing.TYPE_CHECKING: 

847 

848 def operate( 

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

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

851 

852 def reverse_operate( 

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

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

855 

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

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

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

859 construct. 

860 

861 Returns a new PropComparator from which further criterion can be 

862 evaluated. 

863 

864 e.g.:: 

865 

866 query.join(Company.employees.of_type(Engineer)).\ 

867 filter(Engineer.name=='foo') 

868 

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

870 against this specific subclass. 

871 

872 .. seealso:: 

873 

874 :ref:`orm_queryguide_joining_relationships_aliased` - in the 

875 :ref:`queryguide_toplevel` 

876 

877 :ref:`inheritance_of_type` 

878 

879 """ 

880 

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

882 

883 def and_( 

884 self, *criteria: _ColumnExpressionArgument[bool] 

885 ) -> PropComparator[bool]: 

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

887 relationship attribute. 

888 

889 E.g.:: 

890 

891 

892 stmt = select(User).join( 

893 User.addresses.and_(Address.email_address != 'foo') 

894 ) 

895 

896 stmt = select(User).options( 

897 joinedload(User.addresses.and_(Address.email_address != 'foo')) 

898 ) 

899 

900 .. versionadded:: 1.4 

901 

902 .. seealso:: 

903 

904 :ref:`orm_queryguide_join_on_augmented` 

905 

906 :ref:`loader_option_criteria` 

907 

908 :func:`.with_loader_criteria` 

909 

910 """ 

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

912 

913 def any( 

914 self, 

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

916 **kwargs: Any, 

917 ) -> ColumnElement[bool]: 

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

919 references a member which meets the given criterion. 

920 

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

922 :meth:`.Relationship.Comparator.any`. 

923 

924 :param criterion: an optional ClauseElement formulated against the 

925 member class' table or attributes. 

926 

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

928 attribute names which will be compared via equality to the 

929 corresponding values. 

930 

931 """ 

932 

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

934 

935 def has( 

936 self, 

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

938 **kwargs: Any, 

939 ) -> ColumnElement[bool]: 

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

941 references a member which meets the given criterion. 

942 

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

944 :meth:`.Relationship.Comparator.has`. 

945 

946 :param criterion: an optional ClauseElement formulated against the 

947 member class' table or attributes. 

948 

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

950 attribute names which will be compared via equality to the 

951 corresponding values. 

952 

953 """ 

954 

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

956 

957 

958class StrategizedProperty(MapperProperty[_T]): 

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

960 loading behavior. 

961 

962 There is a single strategy selected by default. Alternate 

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

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

965 

966 The mechanics of StrategizedProperty are used for every Query 

967 invocation for every mapped attribute participating in that Query, 

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

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

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

971 performance-critical. 

972 

973 """ 

974 

975 __slots__ = ( 

976 "_strategies", 

977 "strategy", 

978 "_wildcard_token", 

979 "_default_path_loader_key", 

980 "strategy_key", 

981 ) 

982 inherit_cache = True 

983 strategy_wildcard_key: ClassVar[str] 

984 

985 strategy_key: _StrategyKey 

986 

987 _strategies: Dict[_StrategyKey, LoaderStrategy] 

988 

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

990 return ( 

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

992 ) 

993 

994 def _memoized_attr__default_path_loader_key( 

995 self, 

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

997 return ( 

998 "loader", 

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

1000 ) 

1001 

1002 def _get_context_loader( 

1003 self, context: ORMCompileState, path: AbstractEntityRegistry 

1004 ) -> Optional[_LoadElement]: 

1005 load: Optional[_LoadElement] = None 

1006 

1007 search_path = path[self] 

1008 

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

1010 # if any. 

1011 for path_key in ( 

1012 search_path._loader_key, 

1013 search_path._wildcard_path_loader_key, 

1014 search_path._default_path_loader_key, 

1015 ): 

1016 if path_key in context.attributes: 

1017 load = context.attributes[path_key] 

1018 break 

1019 

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

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

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

1023 # option as below. 

1024 # if load.strategy or load.local_opts: 

1025 # break 

1026 

1027 return load 

1028 

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

1030 try: 

1031 return self._strategies[key] 

1032 except KeyError: 

1033 pass 

1034 

1035 # run outside to prevent transfer of exception context 

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

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

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

1039 # strategy has multiple keys at this point 

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

1041 return strategy 

1042 

1043 def setup( 

1044 self, 

1045 context: ORMCompileState, 

1046 query_entity: _MapperEntity, 

1047 path: AbstractEntityRegistry, 

1048 adapter: Optional[ORMAdapter], 

1049 **kwargs: Any, 

1050 ) -> None: 

1051 loader = self._get_context_loader(context, path) 

1052 if loader and loader.strategy: 

1053 strat = self._get_strategy(loader.strategy) 

1054 else: 

1055 strat = self.strategy 

1056 strat.setup_query( 

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

1058 ) 

1059 

1060 def create_row_processor( 

1061 self, 

1062 context: ORMCompileState, 

1063 query_entity: _MapperEntity, 

1064 path: AbstractEntityRegistry, 

1065 mapper: Mapper[Any], 

1066 result: Result[Unpack[TupleAny]], 

1067 adapter: Optional[ORMAdapter], 

1068 populators: _PopulatorDict, 

1069 ) -> None: 

1070 loader = self._get_context_loader(context, path) 

1071 if loader and loader.strategy: 

1072 strat = self._get_strategy(loader.strategy) 

1073 else: 

1074 strat = self.strategy 

1075 strat.create_row_processor( 

1076 context, 

1077 query_entity, 

1078 path, 

1079 loader, 

1080 mapper, 

1081 result, 

1082 adapter, 

1083 populators, 

1084 ) 

1085 

1086 def do_init(self) -> None: 

1087 self._strategies = {} 

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

1089 

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

1091 if ( 

1092 not self.parent.non_primary 

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

1094 ): 

1095 self.strategy.init_class_attribute(mapper) 

1096 

1097 _all_strategies: collections.defaultdict[ 

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

1099 ] = collections.defaultdict(dict) 

1100 

1101 @classmethod 

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

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

1104 # ensure each subclass of the strategy has its 

1105 # own _strategy_keys collection 

1106 if "_strategy_keys" not in dec_cls.__dict__: 

1107 dec_cls._strategy_keys = [] 

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

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

1110 dec_cls._strategy_keys.append(key) 

1111 return dec_cls 

1112 

1113 return decorate 

1114 

1115 @classmethod 

1116 def _strategy_lookup( 

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

1118 ) -> Type[LoaderStrategy]: 

1119 requesting_property.parent._with_polymorphic_mappers 

1120 

1121 for prop_cls in cls.__mro__: 

1122 if prop_cls in cls._all_strategies: 

1123 if TYPE_CHECKING: 

1124 assert issubclass(prop_cls, MapperProperty) 

1125 strategies = cls._all_strategies[prop_cls] 

1126 try: 

1127 return strategies[key] 

1128 except KeyError: 

1129 pass 

1130 

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

1132 if key in strats: 

1133 intended_property_type = property_type 

1134 actual_strategy = strats[key] 

1135 break 

1136 else: 

1137 intended_property_type = None 

1138 actual_strategy = None 

1139 

1140 raise orm_exc.LoaderStrategyException( 

1141 cls, 

1142 requesting_property, 

1143 intended_property_type, 

1144 actual_strategy, 

1145 key, 

1146 ) 

1147 

1148 

1149class ORMOption(ExecutableOption): 

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

1151 

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

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

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

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

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

1157 time. 

1158 

1159 .. versionadded:: 1.4 

1160 

1161 """ 

1162 

1163 __slots__ = () 

1164 

1165 _is_legacy_option = False 

1166 

1167 propagate_to_loaders = False 

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

1169 to "secondary" SELECT statements that occur for relationship 

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

1171 

1172 """ 

1173 

1174 _is_core = False 

1175 

1176 _is_user_defined = False 

1177 

1178 _is_compile_state = False 

1179 

1180 _is_criteria_option = False 

1181 

1182 _is_strategy_option = False 

1183 

1184 def _adapt_cached_option_to_uncached_option( 

1185 self, context: QueryContext, uncached_opt: ORMOption 

1186 ) -> ORMOption: 

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

1188 loader strategy context. 

1189 

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

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

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

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

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

1195 entities passed to us from the cached query. 

1196 

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

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

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

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

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

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

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

1204 

1205 This routine could in the future involve 

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

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

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

1209 

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

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

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

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

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

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

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

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

1218 like an md5 key. 

1219 

1220 .. versionadded:: 1.4.41 

1221 

1222 """ 

1223 if uncached_opt is not None: 

1224 return uncached_opt 

1225 else: 

1226 return self 

1227 

1228 

1229class CompileStateOption(HasCacheKey, ORMOption): 

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

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

1232 

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

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

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

1236 interact with ORM compilation internals or caching. 

1237 

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

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

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

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

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

1243 structures. 

1244 

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

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

1247 

1248 .. versionadded:: 1.4.28 

1249 

1250 

1251 """ 

1252 

1253 __slots__ = () 

1254 

1255 _is_compile_state = True 

1256 

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

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

1259 

1260 This method is part of the implementation of a particular 

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

1262 when an ORM query is compiled. 

1263 

1264 """ 

1265 

1266 def process_compile_state_replaced_entities( 

1267 self, 

1268 compile_state: ORMCompileState, 

1269 mapper_entities: Sequence[_MapperEntity], 

1270 ) -> None: 

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

1272 given entities that were replaced by with_only_columns() or 

1273 with_entities(). 

1274 

1275 This method is part of the implementation of a particular 

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

1277 when an ORM query is compiled. 

1278 

1279 .. versionadded:: 1.4.19 

1280 

1281 """ 

1282 

1283 

1284class LoaderOption(CompileStateOption): 

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

1286 

1287 .. versionadded:: 1.4 

1288 

1289 """ 

1290 

1291 __slots__ = () 

1292 

1293 def process_compile_state_replaced_entities( 

1294 self, 

1295 compile_state: ORMCompileState, 

1296 mapper_entities: Sequence[_MapperEntity], 

1297 ) -> None: 

1298 self.process_compile_state(compile_state) 

1299 

1300 

1301class CriteriaOption(CompileStateOption): 

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

1303 compilation time. 

1304 

1305 .. versionadded:: 1.4 

1306 

1307 """ 

1308 

1309 __slots__ = () 

1310 

1311 _is_criteria_option = True 

1312 

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

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

1315 attributes dictionary. 

1316 

1317 """ 

1318 

1319 

1320class UserDefinedOption(ORMOption): 

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

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

1323 

1324 """ 

1325 

1326 __slots__ = ("payload",) 

1327 

1328 _is_legacy_option = False 

1329 

1330 _is_user_defined = True 

1331 

1332 propagate_to_loaders = False 

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

1334 to "secondary" Query objects produced during lazy loads 

1335 or refresh operations. 

1336 

1337 """ 

1338 

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

1340 self.payload = payload 

1341 

1342 

1343@util.deprecated_cls( 

1344 "1.4", 

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

1346 "in a future release. For " 

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

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

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

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

1351 constructor=None, 

1352) 

1353class MapperOption(ORMOption): 

1354 """Describe a modification to a Query""" 

1355 

1356 __slots__ = () 

1357 

1358 _is_legacy_option = True 

1359 

1360 propagate_to_loaders = False 

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

1362 to "secondary" Query objects produced during lazy loads 

1363 or refresh operations. 

1364 

1365 """ 

1366 

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

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

1369 

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

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

1372 apply to the given query. 

1373 

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

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

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

1377 specify propagate_to_loaders=True. 

1378 

1379 """ 

1380 

1381 self.process_query(query) 

1382 

1383 

1384class LoaderStrategy: 

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

1386 

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

1388 ways: 

1389 

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

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

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

1393 off a select operation when the attribute is first accessed 

1394 (i.e. a lazy load) 

1395 

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

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

1398 For example, simple column attributes will add their represented 

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

1400 may establish join clauses to add to the statement. 

1401 

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

1403 These "row processor" functions populate a particular attribute 

1404 on a particular mapped instance. 

1405 

1406 """ 

1407 

1408 __slots__ = ( 

1409 "parent_property", 

1410 "is_class_level", 

1411 "parent", 

1412 "key", 

1413 "strategy_key", 

1414 "strategy_opts", 

1415 ) 

1416 

1417 _strategy_keys: ClassVar[List[_StrategyKey]] 

1418 

1419 def __init__( 

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

1421 ): 

1422 self.parent_property = parent 

1423 self.is_class_level = False 

1424 self.parent = self.parent_property.parent 

1425 self.key = self.parent_property.key 

1426 self.strategy_key = strategy_key 

1427 self.strategy_opts = dict(strategy_key) 

1428 

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

1430 pass 

1431 

1432 def setup_query( 

1433 self, 

1434 compile_state: ORMCompileState, 

1435 query_entity: _MapperEntity, 

1436 path: AbstractEntityRegistry, 

1437 loadopt: Optional[_LoadElement], 

1438 adapter: Optional[ORMAdapter], 

1439 **kwargs: Any, 

1440 ) -> None: 

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

1442 

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

1444 

1445 StrategizedProperty delegates its setup() method 

1446 directly to this method. 

1447 

1448 """ 

1449 

1450 def create_row_processor( 

1451 self, 

1452 context: ORMCompileState, 

1453 query_entity: _MapperEntity, 

1454 path: AbstractEntityRegistry, 

1455 loadopt: Optional[_LoadElement], 

1456 mapper: Mapper[Any], 

1457 result: Result[Unpack[TupleAny]], 

1458 adapter: Optional[ORMAdapter], 

1459 populators: _PopulatorDict, 

1460 ) -> None: 

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

1462 

1463 This method fulfills the contract specified by 

1464 MapperProperty.create_row_processor(). 

1465 

1466 StrategizedProperty delegates its create_row_processor() method 

1467 directly to this method. 

1468 

1469 """ 

1470 

1471 def __str__(self) -> str: 

1472 return str(self.parent_property)