1# orm/interfaces.py 2# Copyright (C) 2005-2021 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 absolute_import 20 21import collections 22 23from . import exc as orm_exc 24from . import path_registry 25from .base import _MappedAttribute # noqa 26from .base import EXT_CONTINUE 27from .base import EXT_SKIP 28from .base import EXT_STOP 29from .base import InspectionAttr # noqa 30from .base import InspectionAttrInfo # noqa 31from .base import MANYTOMANY 32from .base import MANYTOONE 33from .base import NOT_EXTENSION 34from .base import ONETOMANY 35from .. import inspect 36from .. import inspection 37from .. import util 38from ..sql import operators 39from ..sql import roles 40from ..sql import visitors 41from ..sql.base import ExecutableOption 42from ..sql.traversals import HasCacheKey 43 44 45__all__ = ( 46 "EXT_CONTINUE", 47 "EXT_STOP", 48 "EXT_SKIP", 49 "ONETOMANY", 50 "MANYTOMANY", 51 "MANYTOONE", 52 "NOT_EXTENSION", 53 "LoaderStrategy", 54 "MapperOption", 55 "LoaderOption", 56 "MapperProperty", 57 "PropComparator", 58 "StrategizedProperty", 59) 60 61 62class ORMStatementRole(roles.StatementRole): 63 _role_name = ( 64 "Executable SQL or text() construct, including ORM " "aware objects" 65 ) 66 67 68class ORMColumnsClauseRole(roles.ColumnsClauseRole): 69 _role_name = "ORM mapped entity, aliased entity, or Column expression" 70 71 72class ORMEntityColumnsClauseRole(ORMColumnsClauseRole): 73 _role_name = "ORM mapped or aliased entity" 74 75 76class ORMFromClauseRole(roles.StrictFromClauseRole): 77 _role_name = "ORM mapped entity, aliased entity, or FROM expression" 78 79 80@inspection._self_inspects 81class MapperProperty( 82 HasCacheKey, _MappedAttribute, InspectionAttr, util.MemoizedSlots 83): 84 """Represent a particular class attribute mapped by :class:`_orm.Mapper`. 85 86 The most common occurrences of :class:`.MapperProperty` are the 87 mapped :class:`_schema.Column`, which is represented in a mapping as 88 an instance of :class:`.ColumnProperty`, 89 and a reference to another class produced by :func:`_orm.relationship`, 90 represented in the mapping as an instance of 91 :class:`.RelationshipProperty`. 92 93 """ 94 95 __slots__ = ( 96 "_configure_started", 97 "_configure_finished", 98 "parent", 99 "key", 100 "info", 101 ) 102 103 _cache_key_traversal = [ 104 ("parent", visitors.ExtendedInternalTraversal.dp_has_cache_key), 105 ("key", visitors.ExtendedInternalTraversal.dp_string), 106 ] 107 108 cascade = frozenset() 109 """The set of 'cascade' attribute names. 110 111 This collection is checked before the 'cascade_iterator' method is called. 112 113 The collection typically only applies to a RelationshipProperty. 114 115 """ 116 117 is_property = True 118 """Part of the InspectionAttr interface; states this object is a 119 mapper property. 120 121 """ 122 123 @property 124 def _links_to_entity(self): 125 """True if this MapperProperty refers to a mapped entity. 126 127 Should only be True for RelationshipProperty, False for all others. 128 129 """ 130 raise NotImplementedError() 131 132 def _memoized_attr_info(self): 133 """Info dictionary associated with the object, allowing user-defined 134 data to be associated with this :class:`.InspectionAttr`. 135 136 The dictionary is generated when first accessed. Alternatively, 137 it can be specified as a constructor argument to the 138 :func:`.column_property`, :func:`_orm.relationship`, or 139 :func:`.composite` 140 functions. 141 142 .. versionchanged:: 1.0.0 :attr:`.MapperProperty.info` is also 143 available on extension types via the 144 :attr:`.InspectionAttrInfo.info` attribute, so that it can apply 145 to a wider variety of ORM and extension constructs. 146 147 .. seealso:: 148 149 :attr:`.QueryableAttribute.info` 150 151 :attr:`.SchemaItem.info` 152 153 """ 154 return {} 155 156 def setup(self, context, query_entity, path, adapter, **kwargs): 157 """Called by Query for the purposes of constructing a SQL statement. 158 159 Each MapperProperty associated with the target mapper processes the 160 statement referenced by the query context, adding columns and/or 161 criterion as appropriate. 162 163 """ 164 165 def create_row_processor( 166 self, context, query_entity, path, mapper, result, adapter, populators 167 ): 168 """Produce row processing functions and append to the given 169 set of populators lists. 170 171 """ 172 173 def cascade_iterator( 174 self, type_, state, dict_, visited_states, halt_on=None 175 ): 176 """Iterate through instances related to the given instance for 177 a particular 'cascade', starting with this MapperProperty. 178 179 Return an iterator3-tuples (instance, mapper, state). 180 181 Note that the 'cascade' collection on this MapperProperty is 182 checked first for the given type before cascade_iterator is called. 183 184 This method typically only applies to RelationshipProperty. 185 186 """ 187 188 return iter(()) 189 190 def set_parent(self, parent, init): 191 """Set the parent mapper that references this MapperProperty. 192 193 This method is overridden by some subclasses to perform extra 194 setup when the mapper is first known. 195 196 """ 197 self.parent = parent 198 199 def instrument_class(self, mapper): 200 """Hook called by the Mapper to the property to initiate 201 instrumentation of the class attribute managed by this 202 MapperProperty. 203 204 The MapperProperty here will typically call out to the 205 attributes module to set up an InstrumentedAttribute. 206 207 This step is the first of two steps to set up an InstrumentedAttribute, 208 and is called early in the mapper setup process. 209 210 The second step is typically the init_class_attribute step, 211 called from StrategizedProperty via the post_instrument_class() 212 hook. This step assigns additional state to the InstrumentedAttribute 213 (specifically the "impl") which has been determined after the 214 MapperProperty has determined what kind of persistence 215 management it needs to do (e.g. scalar, object, collection, etc). 216 217 """ 218 219 def __init__(self): 220 self._configure_started = False 221 self._configure_finished = False 222 223 def init(self): 224 """Called after all mappers are created to assemble 225 relationships between mappers and perform other post-mapper-creation 226 initialization steps. 227 228 229 """ 230 self._configure_started = True 231 self.do_init() 232 self._configure_finished = True 233 234 @property 235 def class_attribute(self): 236 """Return the class-bound descriptor corresponding to this 237 :class:`.MapperProperty`. 238 239 This is basically a ``getattr()`` call:: 240 241 return getattr(self.parent.class_, self.key) 242 243 I.e. if this :class:`.MapperProperty` were named ``addresses``, 244 and the class to which it is mapped is ``User``, this sequence 245 is possible:: 246 247 >>> from sqlalchemy import inspect 248 >>> mapper = inspect(User) 249 >>> addresses_property = mapper.attrs.addresses 250 >>> addresses_property.class_attribute is User.addresses 251 True 252 >>> User.addresses.property is addresses_property 253 True 254 255 256 """ 257 258 return getattr(self.parent.class_, self.key) 259 260 def do_init(self): 261 """Perform subclass-specific initialization post-mapper-creation 262 steps. 263 264 This is a template method called by the ``MapperProperty`` 265 object's init() method. 266 267 """ 268 269 def post_instrument_class(self, mapper): 270 """Perform instrumentation adjustments that need to occur 271 after init() has completed. 272 273 The given Mapper is the Mapper invoking the operation, which 274 may not be the same Mapper as self.parent in an inheritance 275 scenario; however, Mapper will always at least be a sub-mapper of 276 self.parent. 277 278 This method is typically used by StrategizedProperty, which delegates 279 it to LoaderStrategy.init_class_attribute() to perform final setup 280 on the class-bound InstrumentedAttribute. 281 282 """ 283 284 def merge( 285 self, 286 session, 287 source_state, 288 source_dict, 289 dest_state, 290 dest_dict, 291 load, 292 _recursive, 293 _resolve_conflict_map, 294 ): 295 """Merge the attribute represented by this ``MapperProperty`` 296 from source to destination object. 297 298 """ 299 300 def __repr__(self): 301 return "<%s at 0x%x; %s>" % ( 302 self.__class__.__name__, 303 id(self), 304 getattr(self, "key", "no key"), 305 ) 306 307 308@inspection._self_inspects 309class PropComparator(operators.ColumnOperators): 310 r"""Defines SQL operators for :class:`.MapperProperty` objects. 311 312 SQLAlchemy allows for operators to 313 be redefined at both the Core and ORM level. :class:`.PropComparator` 314 is the base class of operator redefinition for ORM-level operations, 315 including those of :class:`.ColumnProperty`, 316 :class:`.RelationshipProperty`, and :class:`.CompositeProperty`. 317 318 .. note:: With the advent of Hybrid properties introduced in SQLAlchemy 319 0.7, as well as Core-level operator redefinition in 320 SQLAlchemy 0.8, the use case for user-defined :class:`.PropComparator` 321 instances is extremely rare. See :ref:`hybrids_toplevel` as well 322 as :ref:`types_operators`. 323 324 User-defined subclasses of :class:`.PropComparator` may be created. The 325 built-in Python comparison and math operator methods, such as 326 :meth:`.operators.ColumnOperators.__eq__`, 327 :meth:`.operators.ColumnOperators.__lt__`, and 328 :meth:`.operators.ColumnOperators.__add__`, can be overridden to provide 329 new operator behavior. The custom :class:`.PropComparator` is passed to 330 the :class:`.MapperProperty` instance via the ``comparator_factory`` 331 argument. In each case, 332 the appropriate subclass of :class:`.PropComparator` should be used:: 333 334 # definition of custom PropComparator subclasses 335 336 from sqlalchemy.orm.properties import \ 337 ColumnProperty,\ 338 CompositeProperty,\ 339 RelationshipProperty 340 341 class MyColumnComparator(ColumnProperty.Comparator): 342 def __eq__(self, other): 343 return self.__clause_element__() == other 344 345 class MyRelationshipComparator(RelationshipProperty.Comparator): 346 def any(self, expression): 347 "define the 'any' operation" 348 # ... 349 350 class MyCompositeComparator(CompositeProperty.Comparator): 351 def __gt__(self, other): 352 "redefine the 'greater than' operation" 353 354 return sql.and_(*[a>b for a, b in 355 zip(self.__clause_element__().clauses, 356 other.__composite_values__())]) 357 358 359 # application of custom PropComparator subclasses 360 361 from sqlalchemy.orm import column_property, relationship, composite 362 from sqlalchemy import Column, String 363 364 class SomeMappedClass(Base): 365 some_column = column_property(Column("some_column", String), 366 comparator_factory=MyColumnComparator) 367 368 some_relationship = relationship(SomeOtherClass, 369 comparator_factory=MyRelationshipComparator) 370 371 some_composite = composite( 372 Column("a", String), Column("b", String), 373 comparator_factory=MyCompositeComparator 374 ) 375 376 Note that for column-level operator redefinition, it's usually 377 simpler to define the operators at the Core level, using the 378 :attr:`.TypeEngine.comparator_factory` attribute. See 379 :ref:`types_operators` for more detail. 380 381 .. seealso:: 382 383 :class:`.ColumnProperty.Comparator` 384 385 :class:`.RelationshipProperty.Comparator` 386 387 :class:`.CompositeProperty.Comparator` 388 389 :class:`.ColumnOperators` 390 391 :ref:`types_operators` 392 393 :attr:`.TypeEngine.comparator_factory` 394 395 """ 396 397 __slots__ = "prop", "property", "_parententity", "_adapt_to_entity" 398 399 __visit_name__ = "orm_prop_comparator" 400 401 def __init__( 402 self, 403 prop, 404 parentmapper, 405 adapt_to_entity=None, 406 ): 407 self.prop = self.property = prop 408 self._parententity = adapt_to_entity or parentmapper 409 self._adapt_to_entity = adapt_to_entity 410 411 def __clause_element__(self): 412 raise NotImplementedError("%r" % self) 413 414 def _bulk_update_tuples(self, value): 415 """Receive a SQL expression that represents a value in the SET 416 clause of an UPDATE statement. 417 418 Return a tuple that can be passed to a :class:`_expression.Update` 419 construct. 420 421 """ 422 423 return [(self.__clause_element__(), value)] 424 425 def adapt_to_entity(self, adapt_to_entity): 426 """Return a copy of this PropComparator which will use the given 427 :class:`.AliasedInsp` to produce corresponding expressions. 428 """ 429 return self.__class__(self.prop, self._parententity, adapt_to_entity) 430 431 @property 432 def _parentmapper(self): 433 """legacy; this is renamed to _parententity to be 434 compatible with QueryableAttribute.""" 435 return inspect(self._parententity).mapper 436 437 @property 438 def _propagate_attrs(self): 439 # this suits the case in coercions where we don't actually 440 # call ``__clause_element__()`` but still need to get 441 # resolved._propagate_attrs. See #6558. 442 return util.immutabledict( 443 { 444 "compile_state_plugin": "orm", 445 "plugin_subject": self._parentmapper, 446 } 447 ) 448 449 @property 450 def adapter(self): 451 """Produce a callable that adapts column expressions 452 to suit an aliased version of this comparator. 453 454 """ 455 if self._adapt_to_entity is None: 456 return None 457 else: 458 return self._adapt_to_entity._adapt_element 459 460 @property 461 def info(self): 462 return self.property.info 463 464 @staticmethod 465 def any_op(a, b, **kwargs): 466 return a.any(b, **kwargs) 467 468 @staticmethod 469 def has_op(a, b, **kwargs): 470 return a.has(b, **kwargs) 471 472 @staticmethod 473 def of_type_op(a, class_): 474 return a.of_type(class_) 475 476 def of_type(self, class_): 477 r"""Redefine this object in terms of a polymorphic subclass, 478 :func:`_orm.with_polymorphic` construct, or :func:`_orm.aliased` 479 construct. 480 481 Returns a new PropComparator from which further criterion can be 482 evaluated. 483 484 e.g.:: 485 486 query.join(Company.employees.of_type(Engineer)).\ 487 filter(Engineer.name=='foo') 488 489 :param \class_: a class or mapper indicating that criterion will be 490 against this specific subclass. 491 492 .. seealso:: 493 494 :ref:`queryguide_join_onclause` - in the :ref:`queryguide_toplevel` 495 496 :ref:`inheritance_of_type` 497 498 """ 499 500 return self.operate(PropComparator.of_type_op, class_) 501 502 def and_(self, *criteria): 503 """Add additional criteria to the ON clause that's represented by this 504 relationship attribute. 505 506 E.g.:: 507 508 509 stmt = select(User).join( 510 User.addresses.and_(Address.email_address != 'foo') 511 ) 512 513 stmt = select(User).options( 514 joinedload(User.addresses.and_(Address.email_address != 'foo')) 515 ) 516 517 .. versionadded:: 1.4 518 519 .. seealso:: 520 521 :ref:`orm_queryguide_join_on_augmented` 522 523 :ref:`loader_option_criteria` 524 525 :func:`.with_loader_criteria` 526 527 """ 528 return self.operate(operators.and_, *criteria) 529 530 def any(self, criterion=None, **kwargs): 531 r"""Return true if this collection contains any member that meets the 532 given criterion. 533 534 The usual implementation of ``any()`` is 535 :meth:`.RelationshipProperty.Comparator.any`. 536 537 :param criterion: an optional ClauseElement formulated against the 538 member class' table or attributes. 539 540 :param \**kwargs: key/value pairs corresponding to member class 541 attribute names which will be compared via equality to the 542 corresponding values. 543 544 """ 545 546 return self.operate(PropComparator.any_op, criterion, **kwargs) 547 548 def has(self, criterion=None, **kwargs): 549 r"""Return true if this element references a member which meets the 550 given criterion. 551 552 The usual implementation of ``has()`` is 553 :meth:`.RelationshipProperty.Comparator.has`. 554 555 :param criterion: an optional ClauseElement formulated against the 556 member class' table or attributes. 557 558 :param \**kwargs: key/value pairs corresponding to member class 559 attribute names which will be compared via equality to the 560 corresponding values. 561 562 """ 563 564 return self.operate(PropComparator.has_op, criterion, **kwargs) 565 566 567class StrategizedProperty(MapperProperty): 568 """A MapperProperty which uses selectable strategies to affect 569 loading behavior. 570 571 There is a single strategy selected by default. Alternate 572 strategies can be selected at Query time through the usage of 573 ``StrategizedOption`` objects via the Query.options() method. 574 575 The mechanics of StrategizedProperty are used for every Query 576 invocation for every mapped attribute participating in that Query, 577 to determine first how the attribute will be rendered in SQL 578 and secondly how the attribute will retrieve a value from a result 579 row and apply it to a mapped object. The routines here are very 580 performance-critical. 581 582 """ 583 584 __slots__ = ( 585 "_strategies", 586 "strategy", 587 "_wildcard_token", 588 "_default_path_loader_key", 589 ) 590 inherit_cache = True 591 strategy_wildcard_key = None 592 593 def _memoized_attr__wildcard_token(self): 594 return ( 595 "%s:%s" 596 % (self.strategy_wildcard_key, path_registry._WILDCARD_TOKEN), 597 ) 598 599 def _memoized_attr__default_path_loader_key(self): 600 return ( 601 "loader", 602 ( 603 "%s:%s" 604 % (self.strategy_wildcard_key, path_registry._DEFAULT_TOKEN), 605 ), 606 ) 607 608 def _get_context_loader(self, context, path): 609 load = None 610 611 search_path = path[self] 612 613 # search among: exact match, "attr.*", "default" strategy 614 # if any. 615 for path_key in ( 616 search_path._loader_key, 617 search_path._wildcard_path_loader_key, 618 search_path._default_path_loader_key, 619 ): 620 if path_key in context.attributes: 621 load = context.attributes[path_key] 622 break 623 624 return load 625 626 def _get_strategy(self, key): 627 try: 628 return self._strategies[key] 629 except KeyError: 630 pass 631 632 # run outside to prevent transfer of exception context 633 cls = self._strategy_lookup(self, *key) 634 # this previously was setting self._strategies[cls], that's 635 # a bad idea; should use strategy key at all times because every 636 # strategy has multiple keys at this point 637 self._strategies[key] = strategy = cls(self, key) 638 return strategy 639 640 def setup(self, context, query_entity, path, adapter, **kwargs): 641 loader = self._get_context_loader(context, path) 642 if loader and loader.strategy: 643 strat = self._get_strategy(loader.strategy) 644 else: 645 strat = self.strategy 646 strat.setup_query( 647 context, query_entity, path, loader, adapter, **kwargs 648 ) 649 650 def create_row_processor( 651 self, context, query_entity, path, mapper, result, adapter, populators 652 ): 653 loader = self._get_context_loader(context, path) 654 if loader and loader.strategy: 655 strat = self._get_strategy(loader.strategy) 656 else: 657 strat = self.strategy 658 strat.create_row_processor( 659 context, 660 query_entity, 661 path, 662 loader, 663 mapper, 664 result, 665 adapter, 666 populators, 667 ) 668 669 def do_init(self): 670 self._strategies = {} 671 self.strategy = self._get_strategy(self.strategy_key) 672 673 def post_instrument_class(self, mapper): 674 if ( 675 not self.parent.non_primary 676 and not mapper.class_manager._attr_has_impl(self.key) 677 ): 678 self.strategy.init_class_attribute(mapper) 679 680 _all_strategies = collections.defaultdict(dict) 681 682 @classmethod 683 def strategy_for(cls, **kw): 684 def decorate(dec_cls): 685 # ensure each subclass of the strategy has its 686 # own _strategy_keys collection 687 if "_strategy_keys" not in dec_cls.__dict__: 688 dec_cls._strategy_keys = [] 689 key = tuple(sorted(kw.items())) 690 cls._all_strategies[cls][key] = dec_cls 691 dec_cls._strategy_keys.append(key) 692 return dec_cls 693 694 return decorate 695 696 @classmethod 697 def _strategy_lookup(cls, requesting_property, *key): 698 requesting_property.parent._with_polymorphic_mappers 699 700 for prop_cls in cls.__mro__: 701 if prop_cls in cls._all_strategies: 702 strategies = cls._all_strategies[prop_cls] 703 try: 704 return strategies[key] 705 except KeyError: 706 pass 707 708 for property_type, strats in cls._all_strategies.items(): 709 if key in strats: 710 intended_property_type = property_type 711 actual_strategy = strats[key] 712 break 713 else: 714 intended_property_type = None 715 actual_strategy = None 716 717 raise orm_exc.LoaderStrategyException( 718 cls, 719 requesting_property, 720 intended_property_type, 721 actual_strategy, 722 key, 723 ) 724 725 726class ORMOption(ExecutableOption): 727 """Base class for option objects that are passed to ORM queries. 728 729 These options may be consumed by :meth:`.Query.options`, 730 :meth:`.Select.options`, or in a more general sense by any 731 :meth:`.Executable.options` method. They are interpreted at 732 statement compile time or execution time in modern use. The 733 deprecated :class:`.MapperOption` is consumed at ORM query construction 734 time. 735 736 .. versionadded:: 1.4 737 738 """ 739 740 __slots__ = () 741 742 _is_legacy_option = False 743 744 propagate_to_loaders = False 745 """if True, indicate this option should be carried along 746 to "secondary" SELECT statements that occur for relationship 747 lazy loaders as well as attribute load / refresh operations. 748 749 """ 750 751 _is_compile_state = False 752 753 _is_criteria_option = False 754 755 _is_strategy_option = False 756 757 758class LoaderOption(ORMOption): 759 """Describe a loader modification to an ORM statement at compilation time. 760 761 .. versionadded:: 1.4 762 763 """ 764 765 _is_compile_state = True 766 767 def process_compile_state_replaced_entities( 768 self, compile_state, mapper_entities 769 ): 770 """Apply a modification to a given :class:`.CompileState`, 771 given entities that were replaced by with_only_columns() or 772 with_entities(). 773 774 .. versionadded:: 1.4.19 775 776 """ 777 self.process_compile_state(compile_state) 778 779 def process_compile_state(self, compile_state): 780 """Apply a modification to a given :class:`.CompileState`.""" 781 782 783class CriteriaOption(ORMOption): 784 """Describe a WHERE criteria modification to an ORM statement at 785 compilation time. 786 787 .. versionadded:: 1.4 788 789 """ 790 791 _is_compile_state = True 792 _is_criteria_option = True 793 794 def process_compile_state(self, compile_state): 795 """Apply a modification to a given :class:`.CompileState`.""" 796 797 def get_global_criteria(self, attributes): 798 """update additional entity criteria options in the given 799 attributes dictionary. 800 801 """ 802 803 804class UserDefinedOption(ORMOption): 805 """Base class for a user-defined option that can be consumed from the 806 :meth:`.SessionEvents.do_orm_execute` event hook. 807 808 """ 809 810 _is_legacy_option = False 811 812 propagate_to_loaders = False 813 """if True, indicate this option should be carried along 814 to "secondary" Query objects produced during lazy loads 815 or refresh operations. 816 817 """ 818 819 def __init__(self, payload=None): 820 self.payload = payload 821 822 823@util.deprecated_cls( 824 "1.4", 825 "The :class:`.MapperOption class is deprecated and will be removed " 826 "in a future release. For " 827 "modifications to queries on a per-execution basis, use the " 828 ":class:`.UserDefinedOption` class to establish state within a " 829 ":class:`.Query` or other Core statement, then use the " 830 ":meth:`.SessionEvents.before_orm_execute` hook to consume them.", 831 constructor=None, 832) 833class MapperOption(ORMOption): 834 """Describe a modification to a Query""" 835 836 _is_legacy_option = True 837 838 propagate_to_loaders = False 839 """if True, indicate this option should be carried along 840 to "secondary" Query objects produced during lazy loads 841 or refresh operations. 842 843 """ 844 845 def process_query(self, query): 846 """Apply a modification to the given :class:`_query.Query`.""" 847 848 def process_query_conditionally(self, query): 849 """same as process_query(), except that this option may not 850 apply to the given query. 851 852 This is typically applied during a lazy load or scalar refresh 853 operation to propagate options stated in the original Query to the 854 new Query being used for the load. It occurs for those options that 855 specify propagate_to_loaders=True. 856 857 """ 858 859 self.process_query(query) 860 861 862class LoaderStrategy(object): 863 """Describe the loading behavior of a StrategizedProperty object. 864 865 The ``LoaderStrategy`` interacts with the querying process in three 866 ways: 867 868 * it controls the configuration of the ``InstrumentedAttribute`` 869 placed on a class to handle the behavior of the attribute. this 870 may involve setting up class-level callable functions to fire 871 off a select operation when the attribute is first accessed 872 (i.e. a lazy load) 873 874 * it processes the ``QueryContext`` at statement construction time, 875 where it can modify the SQL statement that is being produced. 876 For example, simple column attributes will add their represented 877 column to the list of selected columns, a joined eager loader 878 may establish join clauses to add to the statement. 879 880 * It produces "row processor" functions at result fetching time. 881 These "row processor" functions populate a particular attribute 882 on a particular mapped instance. 883 884 """ 885 886 __slots__ = ( 887 "parent_property", 888 "is_class_level", 889 "parent", 890 "key", 891 "strategy_key", 892 "strategy_opts", 893 ) 894 895 def __init__(self, parent, strategy_key): 896 self.parent_property = parent 897 self.is_class_level = False 898 self.parent = self.parent_property.parent 899 self.key = self.parent_property.key 900 self.strategy_key = strategy_key 901 self.strategy_opts = dict(strategy_key) 902 903 def init_class_attribute(self, mapper): 904 pass 905 906 def setup_query( 907 self, compile_state, query_entity, path, loadopt, adapter, **kwargs 908 ): 909 """Establish column and other state for a given QueryContext. 910 911 This method fulfills the contract specified by MapperProperty.setup(). 912 913 StrategizedProperty delegates its setup() method 914 directly to this method. 915 916 """ 917 918 def create_row_processor( 919 self, 920 context, 921 query_entity, 922 path, 923 loadopt, 924 mapper, 925 result, 926 adapter, 927 populators, 928 ): 929 """Establish row processing functions for a given QueryContext. 930 931 This method fulfills the contract specified by 932 MapperProperty.create_row_processor(). 933 934 StrategizedProperty delegates its create_row_processor() method 935 directly to this method. 936 937 """ 938 939 def __str__(self): 940 return str(self.parent_property) 941