1# orm/interfaces.py 
    2# Copyright (C) 2005-2025 the SQLAlchemy authors and contributors 
    3# <see AUTHORS file> 
    4# 
    5# This module is part of SQLAlchemy and is released under 
    6# the MIT License: https://www.opensource.org/licenses/mit-license.php 
    7 
    8""" 
    9 
    10Contains various base classes used throughout the ORM. 
    11 
    12Defines some key base classes prominent within the internals. 
    13 
    14This module and the classes within are mostly private, though some attributes 
    15are exposed when inspecting mappings. 
    16 
    17""" 
    18 
    19from __future__ import annotations 
    20 
    21import collections 
    22import dataclasses 
    23import typing 
    24from typing import Any 
    25from typing import Callable 
    26from typing import cast 
    27from typing import ClassVar 
    28from typing import Dict 
    29from typing import Generic 
    30from typing import Iterator 
    31from typing import List 
    32from typing import Mapping 
    33from typing import NamedTuple 
    34from typing import NoReturn 
    35from typing import Optional 
    36from typing import Sequence 
    37from typing import Set 
    38from typing import Tuple 
    39from typing import Type 
    40from typing import TYPE_CHECKING 
    41from typing import TypedDict 
    42from typing import TypeVar 
    43from typing import Union 
    44 
    45from . import exc as orm_exc 
    46from . import path_registry 
    47from .base import _MappedAttribute as _MappedAttribute 
    48from .base import DONT_SET as DONT_SET  # noqa: F401 
    49from .base import EXT_CONTINUE as EXT_CONTINUE  # noqa: F401 
    50from .base import EXT_SKIP as EXT_SKIP  # noqa: F401 
    51from .base import EXT_STOP as EXT_STOP  # noqa: F401 
    52from .base import InspectionAttr as InspectionAttr  # noqa: F401 
    53from .base import InspectionAttrInfo as InspectionAttrInfo 
    54from .base import MANYTOMANY as MANYTOMANY  # noqa: F401 
    55from .base import MANYTOONE as MANYTOONE  # noqa: F401 
    56from .base import NO_KEY as NO_KEY  # noqa: F401 
    57from .base import NO_VALUE as NO_VALUE  # noqa: F401 
    58from .base import NotExtension as NotExtension  # noqa: F401 
    59from .base import ONETOMANY as ONETOMANY  # noqa: F401 
    60from .base import RelationshipDirection as RelationshipDirection  # noqa: F401 
    61from .base import SQLORMOperations 
    62from .. import ColumnElement 
    63from .. import exc as sa_exc 
    64from .. import inspection 
    65from .. import util 
    66from ..sql import operators 
    67from ..sql import roles 
    68from ..sql import visitors 
    69from ..sql.base import _NoArg 
    70from ..sql.base import ExecutableOption 
    71from ..sql.cache_key import HasCacheKey 
    72from ..sql.operators import ColumnOperators 
    73from ..sql.schema import Column 
    74from ..sql.type_api import TypeEngine 
    75from ..util import warn_deprecated 
    76from ..util.typing import RODescriptorReference 
    77from ..util.typing import TupleAny 
    78from ..util.typing import Unpack 
    79 
    80 
    81if typing.TYPE_CHECKING: 
    82    from ._typing import _EntityType 
    83    from ._typing import _IdentityKeyType 
    84    from ._typing import _InstanceDict 
    85    from ._typing import _InternalEntityType 
    86    from ._typing import _ORMAdapterProto 
    87    from .attributes import InstrumentedAttribute 
    88    from .base import Mapped 
    89    from .context import _MapperEntity 
    90    from .context import _ORMCompileState 
    91    from .context import QueryContext 
    92    from .decl_api import RegistryType 
    93    from .decl_base import _ClassScanAbstractConfig 
    94    from .decl_base import _DeclarativeMapperConfig 
    95    from .loading import _PopulatorDict 
    96    from .mapper import Mapper 
    97    from .path_registry import _AbstractEntityRegistry 
    98    from .query import Query 
    99    from .session import Session 
    100    from .state import InstanceState 
    101    from .strategy_options import _LoadElement 
    102    from .util import AliasedInsp 
    103    from .util import ORMAdapter 
    104    from ..engine.result import Result 
    105    from ..sql._typing import _ColumnExpressionArgument 
    106    from ..sql._typing import _ColumnsClauseArgument 
    107    from ..sql._typing import _DMLColumnArgument 
    108    from ..sql._typing import _InfoType 
    109    from ..sql.operators import OperatorType 
    110    from ..sql.visitors import _TraverseInternalsType 
    111    from ..util.typing import _AnnotationScanType 
    112 
    113_StrategyKey = Tuple[Any, ...] 
    114 
    115_T = TypeVar("_T", bound=Any) 
    116_T_co = TypeVar("_T_co", bound=Any, covariant=True) 
    117 
    118_TLS = TypeVar("_TLS", bound="Type[LoaderStrategy]") 
    119 
    120 
    121class ORMStatementRole(roles.StatementRole): 
    122    __slots__ = () 
    123    _role_name = ( 
    124        "Executable SQL or text() construct, including ORM aware objects" 
    125    ) 
    126 
    127 
    128class ORMColumnsClauseRole( 
    129    roles.ColumnsClauseRole, roles.TypedColumnsClauseRole[_T] 
    130): 
    131    __slots__ = () 
    132    _role_name = "ORM mapped entity, aliased entity, or Column expression" 
    133 
    134 
    135class ORMEntityColumnsClauseRole(ORMColumnsClauseRole[_T]): 
    136    __slots__ = () 
    137    _role_name = "ORM mapped or aliased entity" 
    138 
    139 
    140class ORMFromClauseRole(roles.FromClauseRole): 
    141    __slots__ = () 
    142    _role_name = "ORM mapped entity, aliased entity, or FROM expression" 
    143 
    144 
    145class ORMColumnDescription(TypedDict): 
    146    name: str 
    147    # TODO: add python_type and sql_type here; combining them 
    148    # into "type" is a bad idea 
    149    type: Union[Type[Any], TypeEngine[Any]] 
    150    aliased: bool 
    151    expr: _ColumnsClauseArgument[Any] 
    152    entity: Optional[_ColumnsClauseArgument[Any]] 
    153 
    154 
    155class _IntrospectsAnnotations: 
    156    __slots__ = () 
    157 
    158    @classmethod 
    159    def _mapper_property_name(cls) -> str: 
    160        return cls.__name__ 
    161 
    162    def found_in_pep593_annotated(self) -> Any: 
    163        """return a copy of this object to use in declarative when the 
    164        object is found inside of an Annotated object.""" 
    165 
    166        raise NotImplementedError( 
    167            f"Use of the {self._mapper_property_name()!r} " 
    168            "construct inside of an Annotated object is not yet supported." 
    169        ) 
    170 
    171    def declarative_scan( 
    172        self, 
    173        decl_scan: _DeclarativeMapperConfig, 
    174        registry: RegistryType, 
    175        cls: Type[Any], 
    176        originating_module: Optional[str], 
    177        key: str, 
    178        mapped_container: Optional[Type[Mapped[Any]]], 
    179        annotation: Optional[_AnnotationScanType], 
    180        extracted_mapped_annotation: Optional[_AnnotationScanType], 
    181        is_dataclass_field: bool, 
    182    ) -> None: 
    183        """Perform class-specific initializaton at early declarative scanning 
    184        time. 
    185 
    186        .. versionadded:: 2.0 
    187 
    188        """ 
    189 
    190    def _raise_for_required(self, key: str, cls: Type[Any]) -> NoReturn: 
    191        raise sa_exc.ArgumentError( 
    192            f"Python typing annotation is required for attribute " 
    193            f'"{cls.__name__}.{key}" when primary argument(s) for ' 
    194            f'"{self._mapper_property_name()}" ' 
    195            "construct are None or not present" 
    196        ) 
    197 
    198 
    199class _DataclassArguments(TypedDict): 
    200    """define arguments that can be passed to ORM Annotated Dataclass 
    201    class definitions. 
    202 
    203    """ 
    204 
    205    init: Union[_NoArg, bool] 
    206    repr: Union[_NoArg, bool] 
    207    eq: Union[_NoArg, bool] 
    208    order: Union[_NoArg, bool] 
    209    unsafe_hash: Union[_NoArg, bool] 
    210    match_args: Union[_NoArg, bool] 
    211    kw_only: Union[_NoArg, bool] 
    212    dataclass_callable: Union[_NoArg, Callable[..., Type[Any]]] 
    213 
    214 
    215class _AttributeOptions(NamedTuple): 
    216    """define Python-local attribute behavior options common to all 
    217    :class:`.MapperProperty` objects. 
    218 
    219    Currently this includes dataclass-generation arguments. 
    220 
    221    .. versionadded:: 2.0 
    222 
    223    """ 
    224 
    225    dataclasses_init: Union[_NoArg, bool] 
    226    dataclasses_repr: Union[_NoArg, bool] 
    227    dataclasses_default: Union[_NoArg, Any] 
    228    dataclasses_default_factory: Union[_NoArg, Callable[[], Any]] 
    229    dataclasses_compare: Union[_NoArg, bool] 
    230    dataclasses_kw_only: Union[_NoArg, bool] 
    231    dataclasses_hash: Union[_NoArg, bool, None] 
    232    dataclasses_dataclass_metadata: Union[_NoArg, Mapping[Any, Any], None] 
    233 
    234    def _as_dataclass_field( 
    235        self, key: str, dataclass_setup_arguments: _DataclassArguments 
    236    ) -> Any: 
    237        """Return a ``dataclasses.Field`` object given these arguments.""" 
    238 
    239        kw: Dict[str, Any] = {} 
    240        if self.dataclasses_default_factory is not _NoArg.NO_ARG: 
    241            kw["default_factory"] = self.dataclasses_default_factory 
    242        if self.dataclasses_default is not _NoArg.NO_ARG: 
    243            kw["default"] = self.dataclasses_default 
    244        if self.dataclasses_init is not _NoArg.NO_ARG: 
    245            kw["init"] = self.dataclasses_init 
    246        if self.dataclasses_repr is not _NoArg.NO_ARG: 
    247            kw["repr"] = self.dataclasses_repr 
    248        if self.dataclasses_compare is not _NoArg.NO_ARG: 
    249            kw["compare"] = self.dataclasses_compare 
    250        if self.dataclasses_kw_only is not _NoArg.NO_ARG: 
    251            kw["kw_only"] = self.dataclasses_kw_only 
    252        if self.dataclasses_hash is not _NoArg.NO_ARG: 
    253            kw["hash"] = self.dataclasses_hash 
    254        if self.dataclasses_dataclass_metadata is not _NoArg.NO_ARG: 
    255            kw["metadata"] = self.dataclasses_dataclass_metadata 
    256 
    257        if "default" in kw and callable(kw["default"]): 
    258            # callable defaults are ambiguous. deprecate them in favour of 
    259            # insert_default or default_factory. #9936 
    260            warn_deprecated( 
    261                f"Callable object passed to the ``default`` parameter for " 
    262                f"attribute {key!r} in a ORM-mapped Dataclasses context is " 
    263                "ambiguous, " 
    264                "and this use will raise an error in a future release.  " 
    265                "If this callable is intended to produce Core level INSERT " 
    266                "default values for an underlying ``Column``, use " 
    267                "the ``mapped_column.insert_default`` parameter instead.  " 
    268                "To establish this callable as providing a default value " 
    269                "for instances of the dataclass itself, use the " 
    270                "``default_factory`` dataclasses parameter.", 
    271                "2.0", 
    272            ) 
    273 
    274        if ( 
    275            "init" in kw 
    276            and not kw["init"] 
    277            and "default" in kw 
    278            and not callable(kw["default"])  # ignore callable defaults. #9936 
    279            and "default_factory" not in kw  # illegal but let dc.field raise 
    280        ): 
    281            # fix for #9879 
    282            default = kw.pop("default") 
    283            kw["default_factory"] = lambda: default 
    284 
    285        return dataclasses.field(**kw) 
    286 
    287    @classmethod 
    288    def _get_arguments_for_make_dataclass( 
    289        cls, 
    290        decl_scan: _ClassScanAbstractConfig, 
    291        key: str, 
    292        annotation: _AnnotationScanType, 
    293        mapped_container: Optional[Any], 
    294        elem: Any, 
    295        dataclass_setup_arguments: _DataclassArguments, 
    296        enable_descriptor_defaults: bool, 
    297    ) -> Union[ 
    298        Tuple[str, _AnnotationScanType], 
    299        Tuple[str, _AnnotationScanType, dataclasses.Field[Any] | None], 
    300    ]: 
    301        """given attribute key, annotation, and value from a class, return 
    302        the argument tuple we would pass to dataclasses.make_dataclass() 
    303        for this attribute. 
    304 
    305        """ 
    306        if isinstance(elem, _DCAttributeOptions): 
    307            attribute_options = elem._get_dataclass_setup_options( 
    308                decl_scan, 
    309                key, 
    310                dataclass_setup_arguments, 
    311                enable_descriptor_defaults, 
    312            ) 
    313            dc_field = attribute_options._as_dataclass_field( 
    314                key, dataclass_setup_arguments 
    315            ) 
    316 
    317            return (key, annotation, dc_field) 
    318        elif elem is not _NoArg.NO_ARG: 
    319            # why is typing not erroring on this? 
    320            return (key, annotation, elem) 
    321        elif mapped_container is not None: 
    322            # it's Mapped[], but there's no "element", which means declarative 
    323            # did not actually do anything for this field. 
    324            # prior to 2.1, this would never happen and we had a false 
    325            # assertion here, because the mapper _scan_attributes always 
    326            # generates a MappedColumn when one is not present 
    327            # (see issue #8718).  However, in 2.1 we handle this case for the 
    328            # non-mapped dataclass use case without the need to generate 
    329            # MappedColumn that gets thrown away anyway. 
    330            return (key, annotation) 
    331 
    332        else: 
    333            # plain dataclass field, not mapped.  Is only possible 
    334            # if __allow_unmapped__ is set up.  I can see this mode causing 
    335            # problems... 
    336            return (key, annotation) 
    337 
    338 
    339_DEFAULT_ATTRIBUTE_OPTIONS = _AttributeOptions( 
    340    _NoArg.NO_ARG, 
    341    _NoArg.NO_ARG, 
    342    _NoArg.NO_ARG, 
    343    _NoArg.NO_ARG, 
    344    _NoArg.NO_ARG, 
    345    _NoArg.NO_ARG, 
    346    _NoArg.NO_ARG, 
    347    _NoArg.NO_ARG, 
    348) 
    349 
    350_DEFAULT_READONLY_ATTRIBUTE_OPTIONS = _AttributeOptions( 
    351    False, 
    352    _NoArg.NO_ARG, 
    353    _NoArg.NO_ARG, 
    354    _NoArg.NO_ARG, 
    355    _NoArg.NO_ARG, 
    356    _NoArg.NO_ARG, 
    357    _NoArg.NO_ARG, 
    358    _NoArg.NO_ARG, 
    359) 
    360 
    361 
    362class _DCAttributeOptions: 
    363    """mixin for descriptors or configurational objects that include dataclass 
    364    field options. 
    365 
    366    This includes :class:`.MapperProperty`, :class:`._MapsColumn` within 
    367    the ORM, but also includes :class:`.AssociationProxy` within ext. 
    368    Can in theory be used for other descriptors that serve a similar role 
    369    as association proxy.   (*maybe* hybrids, not sure yet.) 
    370 
    371    """ 
    372 
    373    __slots__ = () 
    374 
    375    _attribute_options: _AttributeOptions 
    376    """behavioral options for ORM-enabled Python attributes 
    377 
    378    .. versionadded:: 2.0 
    379 
    380    """ 
    381 
    382    _has_dataclass_arguments: bool 
    383 
    384    def _get_dataclass_setup_options( 
    385        self, 
    386        decl_scan: _ClassScanAbstractConfig, 
    387        key: str, 
    388        dataclass_setup_arguments: _DataclassArguments, 
    389        enable_descriptor_defaults: bool, 
    390    ) -> _AttributeOptions: 
    391        return self._attribute_options 
    392 
    393 
    394class _DataclassDefaultsDontSet(_DCAttributeOptions): 
    395    __slots__ = () 
    396 
    397    _default_scalar_value: Any 
    398 
    399    _disable_dataclass_default_factory: bool = False 
    400 
    401    def _get_dataclass_setup_options( 
    402        self, 
    403        decl_scan: _ClassScanAbstractConfig, 
    404        key: str, 
    405        dataclass_setup_arguments: _DataclassArguments, 
    406        enable_descriptor_defaults: bool, 
    407    ) -> _AttributeOptions: 
    408 
    409        disable_descriptor_defaults = ( 
    410            not enable_descriptor_defaults 
    411            or getattr(decl_scan.cls, "_sa_disable_descriptor_defaults", False) 
    412        ) 
    413 
    414        if disable_descriptor_defaults: 
    415            return self._attribute_options 
    416 
    417        dataclasses_default = self._attribute_options.dataclasses_default 
    418        dataclasses_default_factory = ( 
    419            self._attribute_options.dataclasses_default_factory 
    420        ) 
    421 
    422        if dataclasses_default is not _NoArg.NO_ARG and not callable( 
    423            dataclasses_default 
    424        ): 
    425            self._default_scalar_value = ( 
    426                self._attribute_options.dataclasses_default 
    427            ) 
    428            return self._attribute_options._replace( 
    429                dataclasses_default=DONT_SET, 
    430            ) 
    431        elif ( 
    432            self._disable_dataclass_default_factory 
    433            and dataclasses_default_factory is not _NoArg.NO_ARG 
    434        ): 
    435            return self._attribute_options._replace( 
    436                dataclasses_default=DONT_SET, 
    437                dataclasses_default_factory=_NoArg.NO_ARG, 
    438            ) 
    439        return self._attribute_options 
    440 
    441 
    442class _MapsColumns(_DCAttributeOptions, _MappedAttribute[_T]): 
    443    """interface for declarative-capable construct that delivers one or more 
    444    Column objects to the declarative process to be part of a Table. 
    445    """ 
    446 
    447    __slots__ = () 
    448 
    449    @property 
    450    def mapper_property_to_assign(self) -> Optional[MapperProperty[_T]]: 
    451        """return a MapperProperty to be assigned to the declarative mapping""" 
    452        raise NotImplementedError() 
    453 
    454    @property 
    455    def columns_to_assign(self) -> List[Tuple[Column[_T], int]]: 
    456        """A list of Column objects that should be declaratively added to the 
    457        new Table object. 
    458 
    459        """ 
    460        raise NotImplementedError() 
    461 
    462 
    463# NOTE: MapperProperty needs to extend _MappedAttribute so that declarative 
    464# typing works, i.e. "Mapped[A] = relationship()".   This introduces an 
    465# inconvenience which is that all the MapperProperty objects are treated 
    466# as descriptors by typing tools, which are misled by this as assignment / 
    467# access to a descriptor attribute wants to move through __get__. 
    468# Therefore, references to MapperProperty as an instance variable, such 
    469# as in PropComparator, may have some special typing workarounds such as the 
    470# use of sqlalchemy.util.typing.DescriptorReference to avoid mis-interpretation 
    471# by typing tools 
    472@inspection._self_inspects 
    473class MapperProperty( 
    474    HasCacheKey, 
    475    _DCAttributeOptions, 
    476    _MappedAttribute[_T], 
    477    InspectionAttrInfo, 
    478    util.MemoizedSlots, 
    479): 
    480    """Represent a particular class attribute mapped by :class:`_orm.Mapper`. 
    481 
    482    The most common occurrences of :class:`.MapperProperty` are the 
    483    mapped :class:`_schema.Column`, which is represented in a mapping as 
    484    an instance of :class:`.ColumnProperty`, 
    485    and a reference to another class produced by :func:`_orm.relationship`, 
    486    represented in the mapping as an instance of 
    487    :class:`.Relationship`. 
    488 
    489    """ 
    490 
    491    __slots__ = ( 
    492        "_configure_started", 
    493        "_configure_finished", 
    494        "_attribute_options", 
    495        "_has_dataclass_arguments", 
    496        "parent", 
    497        "key", 
    498        "info", 
    499        "doc", 
    500    ) 
    501 
    502    _cache_key_traversal: _TraverseInternalsType = [ 
    503        ("parent", visitors.ExtendedInternalTraversal.dp_has_cache_key), 
    504        ("key", visitors.ExtendedInternalTraversal.dp_string), 
    505    ] 
    506 
    507    if not TYPE_CHECKING: 
    508        cascade = None 
    509 
    510    is_property = True 
    511    """Part of the InspectionAttr interface; states this object is a 
    512    mapper property. 
    513 
    514    """ 
    515 
    516    comparator: PropComparator[_T] 
    517    """The :class:`_orm.PropComparator` instance that implements SQL 
    518    expression construction on behalf of this mapped attribute.""" 
    519 
    520    key: str 
    521    """name of class attribute""" 
    522 
    523    parent: Mapper[Any] 
    524    """the :class:`.Mapper` managing this property.""" 
    525 
    526    _is_relationship = False 
    527 
    528    _links_to_entity: bool 
    529    """True if this MapperProperty refers to a mapped entity. 
    530 
    531    Should only be True for Relationship, False for all others. 
    532 
    533    """ 
    534 
    535    doc: Optional[str] 
    536    """optional documentation string""" 
    537 
    538    info: _InfoType 
    539    """Info dictionary associated with the object, allowing user-defined 
    540    data to be associated with this :class:`.InspectionAttr`. 
    541 
    542    The dictionary is generated when first accessed.  Alternatively, 
    543    it can be specified as a constructor argument to the 
    544    :func:`.column_property`, :func:`_orm.relationship`, or :func:`.composite` 
    545    functions. 
    546 
    547    .. seealso:: 
    548 
    549        :attr:`.QueryableAttribute.info` 
    550 
    551        :attr:`.SchemaItem.info` 
    552 
    553    """ 
    554 
    555    def _memoized_attr_info(self) -> _InfoType: 
    556        """Info dictionary associated with the object, allowing user-defined 
    557        data to be associated with this :class:`.InspectionAttr`. 
    558 
    559        The dictionary is generated when first accessed.  Alternatively, 
    560        it can be specified as a constructor argument to the 
    561        :func:`.column_property`, :func:`_orm.relationship`, or 
    562        :func:`.composite` 
    563        functions. 
    564 
    565        .. seealso:: 
    566 
    567            :attr:`.QueryableAttribute.info` 
    568 
    569            :attr:`.SchemaItem.info` 
    570 
    571        """ 
    572        return {} 
    573 
    574    def setup( 
    575        self, 
    576        context: _ORMCompileState, 
    577        query_entity: _MapperEntity, 
    578        path: _AbstractEntityRegistry, 
    579        adapter: Optional[ORMAdapter], 
    580        **kwargs: Any, 
    581    ) -> None: 
    582        """Called by Query for the purposes of constructing a SQL statement. 
    583 
    584        Each MapperProperty associated with the target mapper processes the 
    585        statement referenced by the query context, adding columns and/or 
    586        criterion as appropriate. 
    587 
    588        """ 
    589 
    590    def create_row_processor( 
    591        self, 
    592        context: _ORMCompileState, 
    593        query_entity: _MapperEntity, 
    594        path: _AbstractEntityRegistry, 
    595        mapper: Mapper[Any], 
    596        result: Result[Unpack[TupleAny]], 
    597        adapter: Optional[ORMAdapter], 
    598        populators: _PopulatorDict, 
    599    ) -> None: 
    600        """Produce row processing functions and append to the given 
    601        set of populators lists. 
    602 
    603        """ 
    604 
    605    def cascade_iterator( 
    606        self, 
    607        type_: str, 
    608        state: InstanceState[Any], 
    609        dict_: _InstanceDict, 
    610        visited_states: Set[InstanceState[Any]], 
    611        halt_on: Optional[Callable[[InstanceState[Any]], bool]] = None, 
    612    ) -> Iterator[ 
    613        Tuple[object, Mapper[Any], InstanceState[Any], _InstanceDict] 
    614    ]: 
    615        """Iterate through instances related to the given instance for 
    616        a particular 'cascade', starting with this MapperProperty. 
    617 
    618        Return an iterator3-tuples (instance, mapper, state). 
    619 
    620        Note that the 'cascade' collection on this MapperProperty is 
    621        checked first for the given type before cascade_iterator is called. 
    622 
    623        This method typically only applies to Relationship. 
    624 
    625        """ 
    626 
    627        return iter(()) 
    628 
    629    def set_parent(self, parent: Mapper[Any], init: bool) -> None: 
    630        """Set the parent mapper that references this MapperProperty. 
    631 
    632        This method is overridden by some subclasses to perform extra 
    633        setup when the mapper is first known. 
    634 
    635        """ 
    636        self.parent = parent 
    637 
    638    def instrument_class(self, mapper: Mapper[Any]) -> None: 
    639        """Hook called by the Mapper to the property to initiate 
    640        instrumentation of the class attribute managed by this 
    641        MapperProperty. 
    642 
    643        The MapperProperty here will typically call out to the 
    644        attributes module to set up an InstrumentedAttribute. 
    645 
    646        This step is the first of two steps to set up an InstrumentedAttribute, 
    647        and is called early in the mapper setup process. 
    648 
    649        The second step is typically the init_class_attribute step, 
    650        called from StrategizedProperty via the post_instrument_class() 
    651        hook.  This step assigns additional state to the InstrumentedAttribute 
    652        (specifically the "impl") which has been determined after the 
    653        MapperProperty has determined what kind of persistence 
    654        management it needs to do (e.g. scalar, object, collection, etc). 
    655 
    656        """ 
    657 
    658    def __init__( 
    659        self, 
    660        attribute_options: Optional[_AttributeOptions] = None, 
    661        _assume_readonly_dc_attributes: bool = False, 
    662    ) -> None: 
    663        self._configure_started = False 
    664        self._configure_finished = False 
    665 
    666        if _assume_readonly_dc_attributes: 
    667            default_attrs = _DEFAULT_READONLY_ATTRIBUTE_OPTIONS 
    668        else: 
    669            default_attrs = _DEFAULT_ATTRIBUTE_OPTIONS 
    670 
    671        if attribute_options and attribute_options != default_attrs: 
    672            self._has_dataclass_arguments = True 
    673            self._attribute_options = attribute_options 
    674        else: 
    675            self._has_dataclass_arguments = False 
    676            self._attribute_options = default_attrs 
    677 
    678    def init(self) -> None: 
    679        """Called after all mappers are created to assemble 
    680        relationships between mappers and perform other post-mapper-creation 
    681        initialization steps. 
    682 
    683 
    684        """ 
    685        self._configure_started = True 
    686        self.do_init() 
    687        self._configure_finished = True 
    688 
    689    @property 
    690    def class_attribute(self) -> InstrumentedAttribute[_T]: 
    691        """Return the class-bound descriptor corresponding to this 
    692        :class:`.MapperProperty`. 
    693 
    694        This is basically a ``getattr()`` call:: 
    695 
    696            return getattr(self.parent.class_, self.key) 
    697 
    698        I.e. if this :class:`.MapperProperty` were named ``addresses``, 
    699        and the class to which it is mapped is ``User``, this sequence 
    700        is possible:: 
    701 
    702            >>> from sqlalchemy import inspect 
    703            >>> mapper = inspect(User) 
    704            >>> addresses_property = mapper.attrs.addresses 
    705            >>> addresses_property.class_attribute is User.addresses 
    706            True 
    707            >>> User.addresses.property is addresses_property 
    708            True 
    709 
    710 
    711        """ 
    712 
    713        return getattr(self.parent.class_, self.key)  # type: ignore 
    714 
    715    def do_init(self) -> None: 
    716        """Perform subclass-specific initialization post-mapper-creation 
    717        steps. 
    718 
    719        This is a template method called by the ``MapperProperty`` 
    720        object's init() method. 
    721 
    722        """ 
    723 
    724    def post_instrument_class(self, mapper: Mapper[Any]) -> None: 
    725        """Perform instrumentation adjustments that need to occur 
    726        after init() has completed. 
    727 
    728        The given Mapper is the Mapper invoking the operation, which 
    729        may not be the same Mapper as self.parent in an inheritance 
    730        scenario; however, Mapper will always at least be a sub-mapper of 
    731        self.parent. 
    732 
    733        This method is typically used by StrategizedProperty, which delegates 
    734        it to LoaderStrategy.init_class_attribute() to perform final setup 
    735        on the class-bound InstrumentedAttribute. 
    736 
    737        """ 
    738 
    739    def merge( 
    740        self, 
    741        session: Session, 
    742        source_state: InstanceState[Any], 
    743        source_dict: _InstanceDict, 
    744        dest_state: InstanceState[Any], 
    745        dest_dict: _InstanceDict, 
    746        load: bool, 
    747        _recursive: Dict[Any, object], 
    748        _resolve_conflict_map: Dict[_IdentityKeyType[Any], object], 
    749    ) -> None: 
    750        """Merge the attribute represented by this ``MapperProperty`` 
    751        from source to destination object. 
    752 
    753        """ 
    754 
    755    def __repr__(self) -> str: 
    756        return "<%s at 0x%x; %s>" % ( 
    757            self.__class__.__name__, 
    758            id(self), 
    759            getattr(self, "key", "no key"), 
    760        ) 
    761 
    762 
    763@inspection._self_inspects 
    764class PropComparator(SQLORMOperations[_T_co], Generic[_T_co], ColumnOperators): 
    765    r"""Defines SQL operations for ORM mapped attributes. 
    766 
    767    SQLAlchemy allows for operators to 
    768    be redefined at both the Core and ORM level.  :class:`.PropComparator` 
    769    is the base class of operator redefinition for ORM-level operations, 
    770    including those of :class:`.ColumnProperty`, 
    771    :class:`.Relationship`, and :class:`.Composite`. 
    772 
    773    User-defined subclasses of :class:`.PropComparator` may be created. The 
    774    built-in Python comparison and math operator methods, such as 
    775    :meth:`.operators.ColumnOperators.__eq__`, 
    776    :meth:`.operators.ColumnOperators.__lt__`, and 
    777    :meth:`.operators.ColumnOperators.__add__`, can be overridden to provide 
    778    new operator behavior. The custom :class:`.PropComparator` is passed to 
    779    the :class:`.MapperProperty` instance via the ``comparator_factory`` 
    780    argument. In each case, 
    781    the appropriate subclass of :class:`.PropComparator` should be used:: 
    782 
    783        # definition of custom PropComparator subclasses 
    784 
    785        from sqlalchemy.orm.properties import ( 
    786            ColumnProperty, 
    787            Composite, 
    788            Relationship, 
    789        ) 
    790 
    791 
    792        class MyColumnComparator(ColumnProperty.Comparator): 
    793            def __eq__(self, other): 
    794                return self.__clause_element__() == other 
    795 
    796 
    797        class MyRelationshipComparator(Relationship.Comparator): 
    798            def any(self, expression): 
    799                "define the 'any' operation" 
    800                # ... 
    801 
    802 
    803        class MyCompositeComparator(Composite.Comparator): 
    804            def __gt__(self, other): 
    805                "redefine the 'greater than' operation" 
    806 
    807                return sql.and_( 
    808                    *[ 
    809                        a > b 
    810                        for a, b in zip( 
    811                            self.__clause_element__().clauses, 
    812                            other.__composite_values__(), 
    813                        ) 
    814                    ] 
    815                ) 
    816 
    817 
    818        # application of custom PropComparator subclasses 
    819 
    820        from sqlalchemy.orm import column_property, relationship, composite 
    821        from sqlalchemy import Column, String 
    822 
    823 
    824        class SomeMappedClass(Base): 
    825            some_column = column_property( 
    826                Column("some_column", String), 
    827                comparator_factory=MyColumnComparator, 
    828            ) 
    829 
    830            some_relationship = relationship( 
    831                SomeOtherClass, comparator_factory=MyRelationshipComparator 
    832            ) 
    833 
    834            some_composite = composite( 
    835                Column("a", String), 
    836                Column("b", String), 
    837                comparator_factory=MyCompositeComparator, 
    838            ) 
    839 
    840    Note that for column-level operator redefinition, it's usually 
    841    simpler to define the operators at the Core level, using the 
    842    :attr:`.TypeEngine.comparator_factory` attribute.  See 
    843    :ref:`types_operators` for more detail. 
    844 
    845    .. seealso:: 
    846 
    847        :class:`.ColumnProperty.Comparator` 
    848 
    849        :class:`.Relationship.Comparator` 
    850 
    851        :class:`.Composite.Comparator` 
    852 
    853        :class:`.ColumnOperators` 
    854 
    855        :ref:`types_operators` 
    856 
    857        :attr:`.TypeEngine.comparator_factory` 
    858 
    859    """ 
    860 
    861    __slots__ = "prop", "_parententity", "_adapt_to_entity" 
    862 
    863    __visit_name__ = "orm_prop_comparator" 
    864 
    865    _parententity: _InternalEntityType[Any] 
    866    _adapt_to_entity: Optional[AliasedInsp[Any]] 
    867    prop: RODescriptorReference[MapperProperty[_T_co]] 
    868 
    869    def __init__( 
    870        self, 
    871        prop: MapperProperty[_T], 
    872        parentmapper: _InternalEntityType[Any], 
    873        adapt_to_entity: Optional[AliasedInsp[Any]] = None, 
    874    ): 
    875        self.prop = prop 
    876        self._parententity = adapt_to_entity or parentmapper 
    877        self._adapt_to_entity = adapt_to_entity 
    878 
    879    @util.non_memoized_property 
    880    def property(self) -> MapperProperty[_T_co]: 
    881        """Return the :class:`.MapperProperty` associated with this 
    882        :class:`.PropComparator`. 
    883 
    884 
    885        Return values here will commonly be instances of 
    886        :class:`.ColumnProperty` or :class:`.Relationship`. 
    887 
    888 
    889        """ 
    890        return self.prop 
    891 
    892    def __clause_element__(self) -> roles.ColumnsClauseRole: 
    893        raise NotImplementedError("%r" % self) 
    894 
    895    def _bulk_update_tuples( 
    896        self, value: Any 
    897    ) -> Sequence[Tuple[_DMLColumnArgument, Any]]: 
    898        """Receive a SQL expression that represents a value in the SET 
    899        clause of an UPDATE statement. 
    900 
    901        Return a tuple that can be passed to a :class:`_expression.Update` 
    902        construct. 
    903 
    904        """ 
    905 
    906        return [(cast("_DMLColumnArgument", self.__clause_element__()), value)] 
    907 
    908    def _bulk_dml_setter(self, key: str) -> Optional[Callable[..., Any]]: 
    909        """return a callable that will process a bulk INSERT value""" 
    910 
    911        return None 
    912 
    913    def adapt_to_entity( 
    914        self, adapt_to_entity: AliasedInsp[Any] 
    915    ) -> PropComparator[_T_co]: 
    916        """Return a copy of this PropComparator which will use the given 
    917        :class:`.AliasedInsp` to produce corresponding expressions. 
    918        """ 
    919        return self.__class__(self.prop, self._parententity, adapt_to_entity) 
    920 
    921    @util.ro_non_memoized_property 
    922    def _parentmapper(self) -> Mapper[Any]: 
    923        """legacy; this is renamed to _parententity to be 
    924        compatible with QueryableAttribute.""" 
    925        return self._parententity.mapper 
    926 
    927    def _criterion_exists( 
    928        self, 
    929        criterion: Optional[_ColumnExpressionArgument[bool]] = None, 
    930        **kwargs: Any, 
    931    ) -> ColumnElement[Any]: 
    932        return self.prop.comparator._criterion_exists(criterion, **kwargs) 
    933 
    934    @util.ro_non_memoized_property 
    935    def adapter(self) -> Optional[_ORMAdapterProto]: 
    936        """Produce a callable that adapts column expressions 
    937        to suit an aliased version of this comparator. 
    938 
    939        """ 
    940        if self._adapt_to_entity is None: 
    941            return None 
    942        else: 
    943            return self._adapt_to_entity._orm_adapt_element 
    944 
    945    @util.ro_non_memoized_property 
    946    def info(self) -> _InfoType: 
    947        return self.prop.info 
    948 
    949    @staticmethod 
    950    def _any_op(a: Any, b: Any, **kwargs: Any) -> Any: 
    951        return a.any(b, **kwargs) 
    952 
    953    @staticmethod 
    954    def _has_op(left: Any, other: Any, **kwargs: Any) -> Any: 
    955        return left.has(other, **kwargs) 
    956 
    957    @staticmethod 
    958    def _of_type_op(a: Any, class_: Any) -> Any: 
    959        return a.of_type(class_) 
    960 
    961    any_op = cast(operators.OperatorType, _any_op) 
    962    has_op = cast(operators.OperatorType, _has_op) 
    963    of_type_op = cast(operators.OperatorType, _of_type_op) 
    964 
    965    if typing.TYPE_CHECKING: 
    966 
    967        def operate( 
    968            self, op: OperatorType, *other: Any, **kwargs: Any 
    969        ) -> ColumnElement[Any]: ... 
    970 
    971        def reverse_operate( 
    972            self, op: OperatorType, other: Any, **kwargs: Any 
    973        ) -> ColumnElement[Any]: ... 
    974 
    975    def of_type(self, class_: _EntityType[Any]) -> PropComparator[_T_co]: 
    976        r"""Redefine this object in terms of a polymorphic subclass, 
    977        :func:`_orm.with_polymorphic` construct, or :func:`_orm.aliased` 
    978        construct. 
    979 
    980        Returns a new PropComparator from which further criterion can be 
    981        evaluated. 
    982 
    983        e.g.:: 
    984 
    985            query.join(Company.employees.of_type(Engineer)).filter( 
    986                Engineer.name == "foo" 
    987            ) 
    988 
    989        :param \class_: a class or mapper indicating that criterion will be 
    990            against this specific subclass. 
    991 
    992        .. seealso:: 
    993 
    994            :ref:`orm_queryguide_joining_relationships_aliased` - in the 
    995            :ref:`queryguide_toplevel` 
    996 
    997            :ref:`inheritance_of_type` 
    998 
    999        """ 
    1000 
    1001        return self.operate(PropComparator.of_type_op, class_)  # type: ignore 
    1002 
    1003    def and_( 
    1004        self, *criteria: _ColumnExpressionArgument[bool] 
    1005    ) -> PropComparator[bool]: 
    1006        """Add additional criteria to the ON clause that's represented by this 
    1007        relationship attribute. 
    1008 
    1009        E.g.:: 
    1010 
    1011 
    1012            stmt = select(User).join( 
    1013                User.addresses.and_(Address.email_address != "foo") 
    1014            ) 
    1015 
    1016            stmt = select(User).options( 
    1017                joinedload(User.addresses.and_(Address.email_address != "foo")) 
    1018            ) 
    1019 
    1020        .. versionadded:: 1.4 
    1021 
    1022        .. seealso:: 
    1023 
    1024            :ref:`orm_queryguide_join_on_augmented` 
    1025 
    1026            :ref:`loader_option_criteria` 
    1027 
    1028            :func:`.with_loader_criteria` 
    1029 
    1030        """ 
    1031        return self.operate(operators.and_, *criteria)  # type: ignore 
    1032 
    1033    def any( 
    1034        self, 
    1035        criterion: Optional[_ColumnExpressionArgument[bool]] = None, 
    1036        **kwargs: Any, 
    1037    ) -> ColumnElement[bool]: 
    1038        r"""Return a SQL expression representing true if this element 
    1039        references a member which meets the given criterion. 
    1040 
    1041        The usual implementation of ``any()`` is 
    1042        :meth:`.Relationship.Comparator.any`. 
    1043 
    1044        :param criterion: an optional ClauseElement formulated against the 
    1045          member class' table or attributes. 
    1046 
    1047        :param \**kwargs: key/value pairs corresponding to member class 
    1048          attribute names which will be compared via equality to the 
    1049          corresponding values. 
    1050 
    1051        """ 
    1052 
    1053        return self.operate(PropComparator.any_op, criterion, **kwargs) 
    1054 
    1055    def has( 
    1056        self, 
    1057        criterion: Optional[_ColumnExpressionArgument[bool]] = None, 
    1058        **kwargs: Any, 
    1059    ) -> ColumnElement[bool]: 
    1060        r"""Return a SQL expression representing true if this element 
    1061        references a member which meets the given criterion. 
    1062 
    1063        The usual implementation of ``has()`` is 
    1064        :meth:`.Relationship.Comparator.has`. 
    1065 
    1066        :param criterion: an optional ClauseElement formulated against the 
    1067          member class' table or attributes. 
    1068 
    1069        :param \**kwargs: key/value pairs corresponding to member class 
    1070          attribute names which will be compared via equality to the 
    1071          corresponding values. 
    1072 
    1073        """ 
    1074 
    1075        return self.operate(PropComparator.has_op, criterion, **kwargs) 
    1076 
    1077 
    1078class StrategizedProperty(MapperProperty[_T]): 
    1079    """A MapperProperty which uses selectable strategies to affect 
    1080    loading behavior. 
    1081 
    1082    There is a single strategy selected by default.  Alternate 
    1083    strategies can be selected at Query time through the usage of 
    1084    ``StrategizedOption`` objects via the Query.options() method. 
    1085 
    1086    The mechanics of StrategizedProperty are used for every Query 
    1087    invocation for every mapped attribute participating in that Query, 
    1088    to determine first how the attribute will be rendered in SQL 
    1089    and secondly how the attribute will retrieve a value from a result 
    1090    row and apply it to a mapped object.  The routines here are very 
    1091    performance-critical. 
    1092 
    1093    """ 
    1094 
    1095    __slots__ = ( 
    1096        "_strategies", 
    1097        "strategy", 
    1098        "_wildcard_token", 
    1099        "_default_path_loader_key", 
    1100        "strategy_key", 
    1101    ) 
    1102    inherit_cache = True 
    1103    strategy_wildcard_key: ClassVar[str] 
    1104 
    1105    strategy_key: _StrategyKey 
    1106 
    1107    _strategies: Dict[_StrategyKey, LoaderStrategy] 
    1108 
    1109    def _memoized_attr__wildcard_token(self) -> Tuple[str]: 
    1110        return ( 
    1111            f"{self.strategy_wildcard_key}:{path_registry._WILDCARD_TOKEN}", 
    1112        ) 
    1113 
    1114    def _memoized_attr__default_path_loader_key( 
    1115        self, 
    1116    ) -> Tuple[str, Tuple[str]]: 
    1117        return ( 
    1118            "loader", 
    1119            (f"{self.strategy_wildcard_key}:{path_registry._DEFAULT_TOKEN}",), 
    1120        ) 
    1121 
    1122    def _get_context_loader( 
    1123        self, context: _ORMCompileState, path: _AbstractEntityRegistry 
    1124    ) -> Optional[_LoadElement]: 
    1125        load: Optional[_LoadElement] = None 
    1126 
    1127        search_path = path[self] 
    1128 
    1129        # search among: exact match, "attr.*", "default" strategy 
    1130        # if any. 
    1131        for path_key in ( 
    1132            search_path._loader_key, 
    1133            search_path._wildcard_path_loader_key, 
    1134            search_path._default_path_loader_key, 
    1135        ): 
    1136            if path_key in context.attributes: 
    1137                load = context.attributes[path_key] 
    1138                break 
    1139 
    1140                # note that if strategy_options.Load is placing non-actionable 
    1141                # objects in the context like defaultload(), we would 
    1142                # need to continue the loop here if we got such an 
    1143                # option as below. 
    1144                # if load.strategy or load.local_opts: 
    1145                #    break 
    1146 
    1147        return load 
    1148 
    1149    def _get_strategy(self, key: _StrategyKey) -> LoaderStrategy: 
    1150        try: 
    1151            return self._strategies[key] 
    1152        except KeyError: 
    1153            pass 
    1154 
    1155        # run outside to prevent transfer of exception context 
    1156        cls = self._strategy_lookup(self, *key) 
    1157        # this previously was setting self._strategies[cls], that's 
    1158        # a bad idea; should use strategy key at all times because every 
    1159        # strategy has multiple keys at this point 
    1160        self._strategies[key] = strategy = cls(self, key) 
    1161        return strategy 
    1162 
    1163    def setup( 
    1164        self, 
    1165        context: _ORMCompileState, 
    1166        query_entity: _MapperEntity, 
    1167        path: _AbstractEntityRegistry, 
    1168        adapter: Optional[ORMAdapter], 
    1169        **kwargs: Any, 
    1170    ) -> None: 
    1171        loader = self._get_context_loader(context, path) 
    1172        if loader and loader.strategy: 
    1173            strat = self._get_strategy(loader.strategy) 
    1174        else: 
    1175            strat = self.strategy 
    1176        strat.setup_query( 
    1177            context, query_entity, path, loader, adapter, **kwargs 
    1178        ) 
    1179 
    1180    def create_row_processor( 
    1181        self, 
    1182        context: _ORMCompileState, 
    1183        query_entity: _MapperEntity, 
    1184        path: _AbstractEntityRegistry, 
    1185        mapper: Mapper[Any], 
    1186        result: Result[Unpack[TupleAny]], 
    1187        adapter: Optional[ORMAdapter], 
    1188        populators: _PopulatorDict, 
    1189    ) -> None: 
    1190        loader = self._get_context_loader(context, path) 
    1191        if loader and loader.strategy: 
    1192            strat = self._get_strategy(loader.strategy) 
    1193        else: 
    1194            strat = self.strategy 
    1195        strat.create_row_processor( 
    1196            context, 
    1197            query_entity, 
    1198            path, 
    1199            loader, 
    1200            mapper, 
    1201            result, 
    1202            adapter, 
    1203            populators, 
    1204        ) 
    1205 
    1206    def do_init(self) -> None: 
    1207        self._strategies = {} 
    1208        self.strategy = self._get_strategy(self.strategy_key) 
    1209 
    1210    def post_instrument_class(self, mapper: Mapper[Any]) -> None: 
    1211        if not mapper.class_manager._attr_has_impl(self.key): 
    1212            self.strategy.init_class_attribute(mapper) 
    1213 
    1214    _all_strategies: collections.defaultdict[ 
    1215        Type[MapperProperty[Any]], Dict[_StrategyKey, Type[LoaderStrategy]] 
    1216    ] = collections.defaultdict(dict) 
    1217 
    1218    @classmethod 
    1219    def strategy_for(cls, **kw: Any) -> Callable[[_TLS], _TLS]: 
    1220        def decorate(dec_cls: _TLS) -> _TLS: 
    1221            # ensure each subclass of the strategy has its 
    1222            # own _strategy_keys collection 
    1223            if "_strategy_keys" not in dec_cls.__dict__: 
    1224                dec_cls._strategy_keys = [] 
    1225            key = tuple(sorted(kw.items())) 
    1226            cls._all_strategies[cls][key] = dec_cls 
    1227            dec_cls._strategy_keys.append(key) 
    1228            return dec_cls 
    1229 
    1230        return decorate 
    1231 
    1232    @classmethod 
    1233    def _strategy_lookup( 
    1234        cls, requesting_property: MapperProperty[Any], *key: Any 
    1235    ) -> Type[LoaderStrategy]: 
    1236        requesting_property.parent._with_polymorphic_mappers 
    1237 
    1238        for prop_cls in cls.__mro__: 
    1239            if prop_cls in cls._all_strategies: 
    1240                if TYPE_CHECKING: 
    1241                    assert issubclass(prop_cls, MapperProperty) 
    1242                strategies = cls._all_strategies[prop_cls] 
    1243                try: 
    1244                    return strategies[key] 
    1245                except KeyError: 
    1246                    pass 
    1247 
    1248        for property_type, strats in cls._all_strategies.items(): 
    1249            if key in strats: 
    1250                intended_property_type = property_type 
    1251                actual_strategy = strats[key] 
    1252                break 
    1253        else: 
    1254            intended_property_type = None 
    1255            actual_strategy = None 
    1256 
    1257        raise orm_exc.LoaderStrategyException( 
    1258            cls, 
    1259            requesting_property, 
    1260            intended_property_type, 
    1261            actual_strategy, 
    1262            key, 
    1263        ) 
    1264 
    1265 
    1266class ORMOption(ExecutableOption): 
    1267    """Base class for option objects that are passed to ORM queries. 
    1268 
    1269    These options may be consumed by :meth:`.Query.options`, 
    1270    :meth:`.Select.options`, or in a more general sense by any 
    1271    :meth:`.Executable.options` method.   They are interpreted at 
    1272    statement compile time or execution time in modern use.  The 
    1273    deprecated :class:`.MapperOption` is consumed at ORM query construction 
    1274    time. 
    1275 
    1276    .. versionadded:: 1.4 
    1277 
    1278    """ 
    1279 
    1280    __slots__ = () 
    1281 
    1282    _is_legacy_option = False 
    1283 
    1284    propagate_to_loaders = False 
    1285    """if True, indicate this option should be carried along 
    1286    to "secondary" SELECT statements that occur for relationship 
    1287    lazy loaders as well as attribute load / refresh operations. 
    1288 
    1289    """ 
    1290 
    1291    _is_core = False 
    1292 
    1293    _is_user_defined = False 
    1294 
    1295    _is_compile_state = False 
    1296 
    1297    _is_criteria_option = False 
    1298 
    1299    _is_strategy_option = False 
    1300 
    1301    def _adapt_cached_option_to_uncached_option( 
    1302        self, context: QueryContext, uncached_opt: ORMOption 
    1303    ) -> ORMOption: 
    1304        """adapt this option to the "uncached" version of itself in a 
    1305        loader strategy context. 
    1306 
    1307        given "self" which is an option from a cached query, as well as the 
    1308        corresponding option from the uncached version of the same query, 
    1309        return the option we should use in a new query, in the context of a 
    1310        loader strategy being asked to load related rows on behalf of that 
    1311        cached query, which is assumed to be building a new query based on 
    1312        entities passed to us from the cached query. 
    1313 
    1314        Currently this routine chooses between "self" and "uncached" without 
    1315        manufacturing anything new. If the option is itself a loader strategy 
    1316        option which has a path, that path needs to match to the entities being 
    1317        passed to us by the cached query, so the :class:`_orm.Load` subclass 
    1318        overrides this to return "self". For all other options, we return the 
    1319        uncached form which may have changing state, such as a 
    1320        with_loader_criteria() option which will very often have new state. 
    1321 
    1322        This routine could in the future involve 
    1323        generating a new option based on both inputs if use cases arise, 
    1324        such as if with_loader_criteria() needed to match up to 
    1325        ``AliasedClass`` instances given in the parent query. 
    1326 
    1327        However, longer term it might be better to restructure things such that 
    1328        ``AliasedClass`` entities are always matched up on their cache key, 
    1329        instead of identity, in things like paths and such, so that this whole 
    1330        issue of "the uncached option does not match the entities" goes away. 
    1331        However this would make ``PathRegistry`` more complicated and difficult 
    1332        to debug as well as potentially less performant in that it would be 
    1333        hashing enormous cache keys rather than a simple AliasedInsp. UNLESS, 
    1334        we could get cache keys overall to be reliably hashed into something 
    1335        like an md5 key. 
    1336 
    1337        .. versionadded:: 1.4.41 
    1338 
    1339        """ 
    1340        if uncached_opt is not None: 
    1341            return uncached_opt 
    1342        else: 
    1343            return self 
    1344 
    1345 
    1346class CompileStateOption(HasCacheKey, ORMOption): 
    1347    """base for :class:`.ORMOption` classes that affect the compilation of 
    1348    a SQL query and therefore need to be part of the cache key. 
    1349 
    1350    .. note::  :class:`.CompileStateOption` is generally non-public and 
    1351       should not be used as a base class for user-defined options; instead, 
    1352       use :class:`.UserDefinedOption`, which is easier to use as it does not 
    1353       interact with ORM compilation internals or caching. 
    1354 
    1355    :class:`.CompileStateOption` defines an internal attribute 
    1356    ``_is_compile_state=True`` which has the effect of the ORM compilation 
    1357    routines for SELECT and other statements will call upon these options when 
    1358    a SQL string is being compiled. As such, these classes implement 
    1359    :class:`.HasCacheKey` and need to provide robust ``_cache_key_traversal`` 
    1360    structures. 
    1361 
    1362    The :class:`.CompileStateOption` class is used to implement the ORM 
    1363    :class:`.LoaderOption` and :class:`.CriteriaOption` classes. 
    1364 
    1365    .. versionadded:: 1.4.28 
    1366 
    1367 
    1368    """ 
    1369 
    1370    __slots__ = () 
    1371 
    1372    _is_compile_state = True 
    1373 
    1374    def process_compile_state(self, compile_state: _ORMCompileState) -> None: 
    1375        """Apply a modification to a given :class:`.ORMCompileState`. 
    1376 
    1377        This method is part of the implementation of a particular 
    1378        :class:`.CompileStateOption` and is only invoked internally 
    1379        when an ORM query is compiled. 
    1380 
    1381        """ 
    1382 
    1383    def process_compile_state_replaced_entities( 
    1384        self, 
    1385        compile_state: _ORMCompileState, 
    1386        mapper_entities: Sequence[_MapperEntity], 
    1387    ) -> None: 
    1388        """Apply a modification to a given :class:`.ORMCompileState`, 
    1389        given entities that were replaced by with_only_columns() or 
    1390        with_entities(). 
    1391 
    1392        This method is part of the implementation of a particular 
    1393        :class:`.CompileStateOption` and is only invoked internally 
    1394        when an ORM query is compiled. 
    1395 
    1396        .. versionadded:: 1.4.19 
    1397 
    1398        """ 
    1399 
    1400 
    1401class LoaderOption(CompileStateOption): 
    1402    """Describe a loader modification to an ORM statement at compilation time. 
    1403 
    1404    .. versionadded:: 1.4 
    1405 
    1406    """ 
    1407 
    1408    __slots__ = () 
    1409 
    1410    def process_compile_state_replaced_entities( 
    1411        self, 
    1412        compile_state: _ORMCompileState, 
    1413        mapper_entities: Sequence[_MapperEntity], 
    1414    ) -> None: 
    1415        self.process_compile_state(compile_state) 
    1416 
    1417 
    1418class CriteriaOption(CompileStateOption): 
    1419    """Describe a WHERE criteria modification to an ORM statement at 
    1420    compilation time. 
    1421 
    1422    .. versionadded:: 1.4 
    1423 
    1424    """ 
    1425 
    1426    __slots__ = () 
    1427 
    1428    _is_criteria_option = True 
    1429 
    1430    def get_global_criteria(self, attributes: Dict[str, Any]) -> None: 
    1431        """update additional entity criteria options in the given 
    1432        attributes dictionary. 
    1433 
    1434        """ 
    1435 
    1436 
    1437class UserDefinedOption(ORMOption): 
    1438    """Base class for a user-defined option that can be consumed from the 
    1439    :meth:`.SessionEvents.do_orm_execute` event hook. 
    1440 
    1441    """ 
    1442 
    1443    __slots__ = ("payload",) 
    1444 
    1445    _is_legacy_option = False 
    1446 
    1447    _is_user_defined = True 
    1448 
    1449    propagate_to_loaders = False 
    1450    """if True, indicate this option should be carried along 
    1451    to "secondary" Query objects produced during lazy loads 
    1452    or refresh operations. 
    1453 
    1454    """ 
    1455 
    1456    def __init__(self, payload: Optional[Any] = None): 
    1457        self.payload = payload 
    1458 
    1459 
    1460@util.deprecated_cls( 
    1461    "1.4", 
    1462    "The :class:`.MapperOption class is deprecated and will be removed " 
    1463    "in a future release.   For " 
    1464    "modifications to queries on a per-execution basis, use the " 
    1465    ":class:`.UserDefinedOption` class to establish state within a " 
    1466    ":class:`.Query` or other Core statement, then use the " 
    1467    ":meth:`.SessionEvents.before_orm_execute` hook to consume them.", 
    1468    constructor=None, 
    1469) 
    1470class MapperOption(ORMOption): 
    1471    """Describe a modification to a Query""" 
    1472 
    1473    __slots__ = () 
    1474 
    1475    _is_legacy_option = True 
    1476 
    1477    propagate_to_loaders = False 
    1478    """if True, indicate this option should be carried along 
    1479    to "secondary" Query objects produced during lazy loads 
    1480    or refresh operations. 
    1481 
    1482    """ 
    1483 
    1484    def process_query(self, query: Query[Any]) -> None: 
    1485        """Apply a modification to the given :class:`_query.Query`.""" 
    1486 
    1487    def process_query_conditionally(self, query: Query[Any]) -> None: 
    1488        """same as process_query(), except that this option may not 
    1489        apply to the given query. 
    1490 
    1491        This is typically applied during a lazy load or scalar refresh 
    1492        operation to propagate options stated in the original Query to the 
    1493        new Query being used for the load.  It occurs for those options that 
    1494        specify propagate_to_loaders=True. 
    1495 
    1496        """ 
    1497 
    1498        self.process_query(query) 
    1499 
    1500 
    1501class LoaderStrategy: 
    1502    """Describe the loading behavior of a StrategizedProperty object. 
    1503 
    1504    The ``LoaderStrategy`` interacts with the querying process in three 
    1505    ways: 
    1506 
    1507    * it controls the configuration of the ``InstrumentedAttribute`` 
    1508      placed on a class to handle the behavior of the attribute.  this 
    1509      may involve setting up class-level callable functions to fire 
    1510      off a select operation when the attribute is first accessed 
    1511      (i.e. a lazy load) 
    1512 
    1513    * it processes the ``QueryContext`` at statement construction time, 
    1514      where it can modify the SQL statement that is being produced. 
    1515      For example, simple column attributes will add their represented 
    1516      column to the list of selected columns, a joined eager loader 
    1517      may establish join clauses to add to the statement. 
    1518 
    1519    * It produces "row processor" functions at result fetching time. 
    1520      These "row processor" functions populate a particular attribute 
    1521      on a particular mapped instance. 
    1522 
    1523    """ 
    1524 
    1525    __slots__ = ( 
    1526        "parent_property", 
    1527        "is_class_level", 
    1528        "parent", 
    1529        "key", 
    1530        "strategy_key", 
    1531        "strategy_opts", 
    1532    ) 
    1533 
    1534    _strategy_keys: ClassVar[List[_StrategyKey]] 
    1535 
    1536    def __init__( 
    1537        self, parent: MapperProperty[Any], strategy_key: _StrategyKey 
    1538    ): 
    1539        self.parent_property = parent 
    1540        self.is_class_level = False 
    1541        self.parent = self.parent_property.parent 
    1542        self.key = self.parent_property.key 
    1543        self.strategy_key = strategy_key 
    1544        self.strategy_opts = dict(strategy_key) 
    1545 
    1546    def init_class_attribute(self, mapper: Mapper[Any]) -> None: 
    1547        pass 
    1548 
    1549    def setup_query( 
    1550        self, 
    1551        compile_state: _ORMCompileState, 
    1552        query_entity: _MapperEntity, 
    1553        path: _AbstractEntityRegistry, 
    1554        loadopt: Optional[_LoadElement], 
    1555        adapter: Optional[ORMAdapter], 
    1556        **kwargs: Any, 
    1557    ) -> None: 
    1558        """Establish column and other state for a given QueryContext. 
    1559 
    1560        This method fulfills the contract specified by MapperProperty.setup(). 
    1561 
    1562        StrategizedProperty delegates its setup() method 
    1563        directly to this method. 
    1564 
    1565        """ 
    1566 
    1567    def create_row_processor( 
    1568        self, 
    1569        context: _ORMCompileState, 
    1570        query_entity: _MapperEntity, 
    1571        path: _AbstractEntityRegistry, 
    1572        loadopt: Optional[_LoadElement], 
    1573        mapper: Mapper[Any], 
    1574        result: Result[Unpack[TupleAny]], 
    1575        adapter: Optional[ORMAdapter], 
    1576        populators: _PopulatorDict, 
    1577    ) -> None: 
    1578        """Establish row processing functions for a given QueryContext. 
    1579 
    1580        This method fulfills the contract specified by 
    1581        MapperProperty.create_row_processor(). 
    1582 
    1583        StrategizedProperty delegates its create_row_processor() method 
    1584        directly to this method. 
    1585 
    1586        """ 
    1587 
    1588    def __str__(self) -> str: 
    1589        return str(self.parent_property)