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