1# orm/strategy_options.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# mypy: allow-untyped-defs, allow-untyped-calls 
    8 
    9""" """ 
    10 
    11from __future__ import annotations 
    12 
    13import typing 
    14from typing import Any 
    15from typing import Callable 
    16from typing import cast 
    17from typing import Dict 
    18from typing import Final 
    19from typing import Iterable 
    20from typing import Literal 
    21from typing import Optional 
    22from typing import overload 
    23from typing import Sequence 
    24from typing import Tuple 
    25from typing import Type 
    26from typing import TypeVar 
    27from typing import Union 
    28 
    29from . import util as orm_util 
    30from ._typing import insp_is_aliased_class 
    31from ._typing import insp_is_attribute 
    32from ._typing import insp_is_mapper 
    33from ._typing import insp_is_mapper_property 
    34from .attributes import QueryableAttribute 
    35from .base import InspectionAttr 
    36from .interfaces import LoaderOption 
    37from .path_registry import _AbstractEntityRegistry 
    38from .path_registry import _DEFAULT_TOKEN 
    39from .path_registry import _StrPathToken 
    40from .path_registry import _TokenRegistry 
    41from .path_registry import _WILDCARD_TOKEN 
    42from .path_registry import path_is_property 
    43from .path_registry import PathRegistry 
    44from .util import _orm_full_deannotate 
    45from .util import AliasedInsp 
    46from .. import exc as sa_exc 
    47from .. import inspect 
    48from .. import util 
    49from ..sql import and_ 
    50from ..sql import cache_key 
    51from ..sql import coercions 
    52from ..sql import roles 
    53from ..sql import traversals 
    54from ..sql import visitors 
    55from ..sql.base import _generative 
    56from ..util.typing import Self 
    57 
    58_RELATIONSHIP_TOKEN: Final[Literal["relationship"]] = "relationship" 
    59_COLUMN_TOKEN: Final[Literal["column"]] = "column" 
    60 
    61_FN = TypeVar("_FN", bound="Callable[..., Any]") 
    62 
    63if typing.TYPE_CHECKING: 
    64    from ._typing import _EntityType 
    65    from ._typing import _InternalEntityType 
    66    from .context import _MapperEntity 
    67    from .context import _ORMCompileState 
    68    from .context import QueryContext 
    69    from .interfaces import _StrategyKey 
    70    from .interfaces import MapperProperty 
    71    from .interfaces import ORMOption 
    72    from .mapper import Mapper 
    73    from .path_registry import _PathRepresentation 
    74    from ..sql._typing import _ColumnExpressionArgument 
    75    from ..sql._typing import _FromClauseArgument 
    76    from ..sql.cache_key import _CacheKeyTraversalType 
    77    from ..sql.cache_key import CacheKey 
    78 
    79 
    80_AttrType = Union[Literal["*"], "QueryableAttribute[Any]"] 
    81 
    82_WildcardKeyType = Literal["relationship", "column"] 
    83_StrategySpec = Dict[str, Any] 
    84_OptsType = Dict[str, Any] 
    85_AttrGroupType = Tuple[_AttrType, ...] 
    86 
    87 
    88class _AbstractLoad(traversals.GenerativeOnTraversal, LoaderOption): 
    89    __slots__ = ("propagate_to_loaders",) 
    90 
    91    _is_strategy_option = True 
    92    propagate_to_loaders: bool 
    93 
    94    def contains_eager( 
    95        self, 
    96        attr: _AttrType, 
    97        alias: Optional[_FromClauseArgument] = None, 
    98        _is_chain: bool = False, 
    99        _propagate_to_loaders: bool = False, 
    100    ) -> Self: 
    101        r"""Indicate that the given attribute should be eagerly loaded from 
    102        columns stated manually in the query. 
    103 
    104        This function is part of the :class:`_orm.Load` interface and supports 
    105        both method-chained and standalone operation. 
    106 
    107        The option is used in conjunction with an explicit join that loads 
    108        the desired rows, i.e.:: 
    109 
    110            sess.query(Order).join(Order.user).options(contains_eager(Order.user)) 
    111 
    112        The above query would join from the ``Order`` entity to its related 
    113        ``User`` entity, and the returned ``Order`` objects would have the 
    114        ``Order.user`` attribute pre-populated. 
    115 
    116        It may also be used for customizing the entries in an eagerly loaded 
    117        collection; queries will normally want to use the 
    118        :ref:`orm_queryguide_populate_existing` execution option assuming the 
    119        primary collection of parent objects may already have been loaded:: 
    120 
    121            sess.query(User).join(User.addresses).filter( 
    122                Address.email_address.like("%@aol.com") 
    123            ).options(contains_eager(User.addresses)).populate_existing() 
    124 
    125        See the section :ref:`contains_eager` for complete usage details. 
    126 
    127        .. seealso:: 
    128 
    129            :ref:`loading_toplevel` 
    130 
    131            :ref:`contains_eager` 
    132 
    133        """ 
    134        if alias is not None: 
    135            if not isinstance(alias, str): 
    136                coerced_alias = coercions.expect(roles.FromClauseRole, alias) 
    137            else: 
    138                util.warn_deprecated( 
    139                    "Passing a string name for the 'alias' argument to " 
    140                    "'contains_eager()` is deprecated, and will not work in a " 
    141                    "future release.  Please use a sqlalchemy.alias() or " 
    142                    "sqlalchemy.orm.aliased() construct.", 
    143                    version="1.4", 
    144                ) 
    145                coerced_alias = alias 
    146 
    147        elif getattr(attr, "_of_type", None): 
    148            assert isinstance(attr, QueryableAttribute) 
    149            ot: Optional[_InternalEntityType[Any]] = inspect(attr._of_type) 
    150            assert ot is not None 
    151            coerced_alias = ot.selectable 
    152        else: 
    153            coerced_alias = None 
    154 
    155        cloned = self._set_relationship_strategy( 
    156            attr, 
    157            {"lazy": "joined"}, 
    158            propagate_to_loaders=_propagate_to_loaders, 
    159            opts={"eager_from_alias": coerced_alias}, 
    160            _reconcile_to_other=True if _is_chain else None, 
    161        ) 
    162        return cloned 
    163 
    164    def load_only(self, *attrs: _AttrType, raiseload: bool = False) -> Self: 
    165        r"""Indicate that for a particular entity, only the given list 
    166        of column-based attribute names should be loaded; all others will be 
    167        deferred. 
    168 
    169        This function is part of the :class:`_orm.Load` interface and supports 
    170        both method-chained and standalone operation. 
    171 
    172        Example - given a class ``User``, load only the ``name`` and 
    173        ``fullname`` attributes:: 
    174 
    175            session.query(User).options(load_only(User.name, User.fullname)) 
    176 
    177        Example - given a relationship ``User.addresses -> Address``, specify 
    178        subquery loading for the ``User.addresses`` collection, but on each 
    179        ``Address`` object load only the ``email_address`` attribute:: 
    180 
    181            session.query(User).options( 
    182                subqueryload(User.addresses).load_only(Address.email_address) 
    183            ) 
    184 
    185        For a statement that has multiple entities, 
    186        the lead entity can be 
    187        specifically referred to using the :class:`_orm.Load` constructor:: 
    188 
    189            stmt = ( 
    190                select(User, Address) 
    191                .join(User.addresses) 
    192                .options( 
    193                    Load(User).load_only(User.name, User.fullname), 
    194                    Load(Address).load_only(Address.email_address), 
    195                ) 
    196            ) 
    197 
    198        When used together with the 
    199        :ref:`populate_existing <orm_queryguide_populate_existing>` 
    200        execution option only the attributes listed will be refreshed. 
    201 
    202        :param \*attrs: Attributes to be loaded, all others will be deferred. 
    203 
    204        :param raiseload: raise :class:`.InvalidRequestError` rather than 
    205         lazy loading a value when a deferred attribute is accessed. Used 
    206         to prevent unwanted SQL from being emitted. 
    207 
    208         .. versionadded:: 2.0 
    209 
    210        .. seealso:: 
    211 
    212            :ref:`orm_queryguide_column_deferral` - in the 
    213            :ref:`queryguide_toplevel` 
    214 
    215        :param \*attrs: Attributes to be loaded, all others will be deferred. 
    216 
    217        :param raiseload: raise :class:`.InvalidRequestError` rather than 
    218         lazy loading a value when a deferred attribute is accessed. Used 
    219         to prevent unwanted SQL from being emitted. 
    220 
    221         .. versionadded:: 2.0 
    222 
    223        """ 
    224        cloned = self._set_column_strategy( 
    225            _expand_column_strategy_attrs(attrs), 
    226            {"deferred": False, "instrument": True}, 
    227        ) 
    228 
    229        wildcard_strategy = {"deferred": True, "instrument": True} 
    230        if raiseload: 
    231            wildcard_strategy["raiseload"] = True 
    232 
    233        cloned = cloned._set_column_strategy( 
    234            ("*",), 
    235            wildcard_strategy, 
    236        ) 
    237        return cloned 
    238 
    239    def joinedload( 
    240        self, 
    241        attr: _AttrType, 
    242        innerjoin: Optional[bool] = None, 
    243    ) -> Self: 
    244        """Indicate that the given attribute should be loaded using joined 
    245        eager loading. 
    246 
    247        This function is part of the :class:`_orm.Load` interface and supports 
    248        both method-chained and standalone operation. 
    249 
    250        examples:: 
    251 
    252            # joined-load the "orders" collection on "User" 
    253            select(User).options(joinedload(User.orders)) 
    254 
    255            # joined-load Order.items and then Item.keywords 
    256            select(Order).options(joinedload(Order.items).joinedload(Item.keywords)) 
    257 
    258            # lazily load Order.items, but when Items are loaded, 
    259            # joined-load the keywords collection 
    260            select(Order).options(lazyload(Order.items).joinedload(Item.keywords)) 
    261 
    262        :param innerjoin: if ``True``, indicates that the joined eager load 
    263         should use an inner join instead of the default of left outer join:: 
    264 
    265            select(Order).options(joinedload(Order.user, innerjoin=True)) 
    266 
    267        In order to chain multiple eager joins together where some may be 
    268        OUTER and others INNER, right-nested joins are used to link them:: 
    269 
    270            select(A).options( 
    271                joinedload(A.bs, innerjoin=False).joinedload(B.cs, innerjoin=True) 
    272            ) 
    273 
    274        The above query, linking A.bs via "outer" join and B.cs via "inner" 
    275        join would render the joins as "a LEFT OUTER JOIN (b JOIN c)". When 
    276        using older versions of SQLite (< 3.7.16), this form of JOIN is 
    277        translated to use full subqueries as this syntax is otherwise not 
    278        directly supported. 
    279 
    280        The ``innerjoin`` flag can also be stated with the term ``"unnested"``. 
    281        This indicates that an INNER JOIN should be used, *unless* the join 
    282        is linked to a LEFT OUTER JOIN to the left, in which case it 
    283        will render as LEFT OUTER JOIN.  For example, supposing ``A.bs`` 
    284        is an outerjoin:: 
    285 
    286            select(A).options(joinedload(A.bs).joinedload(B.cs, innerjoin="unnested")) 
    287 
    288        The above join will render as "a LEFT OUTER JOIN b LEFT OUTER JOIN c", 
    289        rather than as "a LEFT OUTER JOIN (b JOIN c)". 
    290 
    291        .. note:: The "unnested" flag does **not** affect the JOIN rendered 
    292            from a many-to-many association table, e.g. a table configured as 
    293            :paramref:`_orm.relationship.secondary`, to the target table; for 
    294            correctness of results, these joins are always INNER and are 
    295            therefore right-nested if linked to an OUTER join. 
    296 
    297        .. note:: 
    298 
    299            The joins produced by :func:`_orm.joinedload` are **anonymously 
    300            aliased**. The criteria by which the join proceeds cannot be 
    301            modified, nor can the ORM-enabled :class:`_sql.Select` or legacy 
    302            :class:`_query.Query` refer to these joins in any way, including 
    303            ordering. See :ref:`zen_of_eager_loading` for further detail. 
    304 
    305            To produce a specific SQL JOIN which is explicitly available, use 
    306            :meth:`_sql.Select.join` and :meth:`_query.Query.join`. To combine 
    307            explicit JOINs with eager loading of collections, use 
    308            :func:`_orm.contains_eager`; see :ref:`contains_eager`. 
    309 
    310        .. seealso:: 
    311 
    312            :ref:`loading_toplevel` 
    313 
    314            :ref:`joined_eager_loading` 
    315 
    316        """  # noqa: E501 
    317        loader = self._set_relationship_strategy( 
    318            attr, 
    319            {"lazy": "joined"}, 
    320            opts=( 
    321                {"innerjoin": innerjoin} 
    322                if innerjoin is not None 
    323                else util.EMPTY_DICT 
    324            ), 
    325        ) 
    326        return loader 
    327 
    328    def subqueryload(self, attr: _AttrType) -> Self: 
    329        """Indicate that the given attribute should be loaded using 
    330        subquery eager loading. 
    331 
    332        This function is part of the :class:`_orm.Load` interface and supports 
    333        both method-chained and standalone operation. 
    334 
    335        examples:: 
    336 
    337            # subquery-load the "orders" collection on "User" 
    338            select(User).options(subqueryload(User.orders)) 
    339 
    340            # subquery-load Order.items and then Item.keywords 
    341            select(Order).options( 
    342                subqueryload(Order.items).subqueryload(Item.keywords) 
    343            ) 
    344 
    345            # lazily load Order.items, but when Items are loaded, 
    346            # subquery-load the keywords collection 
    347            select(Order).options(lazyload(Order.items).subqueryload(Item.keywords)) 
    348 
    349        .. seealso:: 
    350 
    351            :ref:`loading_toplevel` 
    352 
    353            :ref:`subquery_eager_loading` 
    354 
    355        """ 
    356        return self._set_relationship_strategy(attr, {"lazy": "subquery"}) 
    357 
    358    def selectinload( 
    359        self, 
    360        attr: _AttrType, 
    361        recursion_depth: Optional[int] = None, 
    362    ) -> Self: 
    363        """Indicate that the given attribute should be loaded using 
    364        SELECT IN eager loading. 
    365 
    366        This function is part of the :class:`_orm.Load` interface and supports 
    367        both method-chained and standalone operation. 
    368 
    369        examples:: 
    370 
    371            # selectin-load the "orders" collection on "User" 
    372            select(User).options(selectinload(User.orders)) 
    373 
    374            # selectin-load Order.items and then Item.keywords 
    375            select(Order).options( 
    376                selectinload(Order.items).selectinload(Item.keywords) 
    377            ) 
    378 
    379            # lazily load Order.items, but when Items are loaded, 
    380            # selectin-load the keywords collection 
    381            select(Order).options(lazyload(Order.items).selectinload(Item.keywords)) 
    382 
    383        :param recursion_depth: optional int; when set to a positive integer 
    384         in conjunction with a self-referential relationship, 
    385         indicates "selectin" loading will continue that many levels deep 
    386         automatically until no items are found. 
    387 
    388         .. note:: The :paramref:`_orm.selectinload.recursion_depth` option 
    389            currently supports only self-referential relationships.  There 
    390            is not yet an option to automatically traverse recursive structures 
    391            with more than one relationship involved. 
    392 
    393            Additionally, the :paramref:`_orm.selectinload.recursion_depth` 
    394            parameter is new and experimental and should be treated as "alpha" 
    395            status for the 2.0 series. 
    396 
    397         .. versionadded:: 2.0 added 
    398            :paramref:`_orm.selectinload.recursion_depth` 
    399 
    400 
    401        .. seealso:: 
    402 
    403            :ref:`loading_toplevel` 
    404 
    405            :ref:`selectin_eager_loading` 
    406 
    407        """ 
    408        return self._set_relationship_strategy( 
    409            attr, 
    410            {"lazy": "selectin"}, 
    411            opts={"recursion_depth": recursion_depth}, 
    412        ) 
    413 
    414    def lazyload(self, attr: _AttrType) -> Self: 
    415        """Indicate that the given attribute should be loaded using "lazy" 
    416        loading. 
    417 
    418        This function is part of the :class:`_orm.Load` interface and supports 
    419        both method-chained and standalone operation. 
    420 
    421        .. seealso:: 
    422 
    423            :ref:`loading_toplevel` 
    424 
    425            :ref:`lazy_loading` 
    426 
    427        """ 
    428        return self._set_relationship_strategy(attr, {"lazy": "select"}) 
    429 
    430    def immediateload( 
    431        self, 
    432        attr: _AttrType, 
    433        recursion_depth: Optional[int] = None, 
    434    ) -> Self: 
    435        """Indicate that the given attribute should be loaded using 
    436        an immediate load with a per-attribute SELECT statement. 
    437 
    438        The load is achieved using the "lazyloader" strategy and does not 
    439        fire off any additional eager loaders. 
    440 
    441        The :func:`.immediateload` option is superseded in general 
    442        by the :func:`.selectinload` option, which performs the same task 
    443        more efficiently by emitting a SELECT for all loaded objects. 
    444 
    445        This function is part of the :class:`_orm.Load` interface and supports 
    446        both method-chained and standalone operation. 
    447 
    448        :param recursion_depth: optional int; when set to a positive integer 
    449         in conjunction with a self-referential relationship, 
    450         indicates "selectin" loading will continue that many levels deep 
    451         automatically until no items are found. 
    452 
    453         .. note:: The :paramref:`_orm.immediateload.recursion_depth` option 
    454            currently supports only self-referential relationships.  There 
    455            is not yet an option to automatically traverse recursive structures 
    456            with more than one relationship involved. 
    457 
    458         .. warning:: This parameter is new and experimental and should be 
    459            treated as "alpha" status 
    460 
    461         .. versionadded:: 2.0 added 
    462            :paramref:`_orm.immediateload.recursion_depth` 
    463 
    464 
    465        .. seealso:: 
    466 
    467            :ref:`loading_toplevel` 
    468 
    469            :ref:`selectin_eager_loading` 
    470 
    471        """ 
    472        loader = self._set_relationship_strategy( 
    473            attr, 
    474            {"lazy": "immediate"}, 
    475            opts={"recursion_depth": recursion_depth}, 
    476        ) 
    477        return loader 
    478 
    479    @util.deprecated( 
    480        "2.1", 
    481        "The :func:`_orm.noload` option is deprecated and will be removed " 
    482        "in a future release.  This option " 
    483        "produces incorrect results by returning ``None`` for related " 
    484        "items.", 
    485    ) 
    486    def noload(self, attr: _AttrType) -> Self: 
    487        """Indicate that the given relationship attribute should remain 
    488        unloaded. 
    489 
    490        The relationship attribute will return ``None`` when accessed without 
    491        producing any loading effect. 
    492 
    493        :func:`_orm.noload` applies to :func:`_orm.relationship` attributes 
    494        only. 
    495 
    496        .. seealso:: 
    497 
    498            :ref:`loading_toplevel` 
    499 
    500        """ 
    501 
    502        return self._set_relationship_strategy(attr, {"lazy": "noload"}) 
    503 
    504    def raiseload(self, attr: _AttrType, sql_only: bool = False) -> Self: 
    505        """Indicate that the given attribute should raise an error if accessed. 
    506 
    507        A relationship attribute configured with :func:`_orm.raiseload` will 
    508        raise an :exc:`~sqlalchemy.exc.InvalidRequestError` upon access. The 
    509        typical way this is useful is when an application is attempting to 
    510        ensure that all relationship attributes that are accessed in a 
    511        particular context would have been already loaded via eager loading. 
    512        Instead of having to read through SQL logs to ensure lazy loads aren't 
    513        occurring, this strategy will cause them to raise immediately. 
    514 
    515        :func:`_orm.raiseload` applies to :func:`_orm.relationship` attributes 
    516        only. In order to apply raise-on-SQL behavior to a column-based 
    517        attribute, use the :paramref:`.orm.defer.raiseload` parameter on the 
    518        :func:`.defer` loader option. 
    519 
    520        :param sql_only: if True, raise only if the lazy load would emit SQL, 
    521         but not if it is only checking the identity map, or determining that 
    522         the related value should just be None due to missing keys. When False, 
    523         the strategy will raise for all varieties of relationship loading. 
    524 
    525        This function is part of the :class:`_orm.Load` interface and supports 
    526        both method-chained and standalone operation. 
    527 
    528        .. seealso:: 
    529 
    530            :ref:`loading_toplevel` 
    531 
    532            :ref:`prevent_lazy_with_raiseload` 
    533 
    534            :ref:`orm_queryguide_deferred_raiseload` 
    535 
    536        """ 
    537 
    538        return self._set_relationship_strategy( 
    539            attr, {"lazy": "raise_on_sql" if sql_only else "raise"} 
    540        ) 
    541 
    542    def defaultload(self, attr: _AttrType) -> Self: 
    543        """Indicate an attribute should load using its predefined loader style. 
    544 
    545        The behavior of this loading option is to not change the current 
    546        loading style of the attribute, meaning that the previously configured 
    547        one is used or, if no previous style was selected, the default 
    548        loading will be used. 
    549 
    550        This method is used to link to other loader options further into 
    551        a chain of attributes without altering the loader style of the links 
    552        along the chain.  For example, to set joined eager loading for an 
    553        element of an element:: 
    554 
    555            session.query(MyClass).options( 
    556                defaultload(MyClass.someattribute).joinedload( 
    557                    MyOtherClass.someotherattribute 
    558                ) 
    559            ) 
    560 
    561        :func:`.defaultload` is also useful for setting column-level options on 
    562        a related class, namely that of :func:`.defer` and :func:`.undefer`:: 
    563 
    564            session.scalars( 
    565                select(MyClass).options( 
    566                    defaultload(MyClass.someattribute) 
    567                    .defer("some_column") 
    568                    .undefer("some_other_column") 
    569                ) 
    570            ) 
    571 
    572        .. seealso:: 
    573 
    574            :ref:`orm_queryguide_relationship_sub_options` 
    575 
    576            :meth:`_orm.Load.options` 
    577 
    578        """ 
    579        return self._set_relationship_strategy(attr, None) 
    580 
    581    def defer(self, key: _AttrType, raiseload: bool = False) -> Self: 
    582        r"""Indicate that the given column-oriented attribute should be 
    583        deferred, e.g. not loaded until accessed. 
    584 
    585        This function is part of the :class:`_orm.Load` interface and supports 
    586        both method-chained and standalone operation. 
    587 
    588        e.g.:: 
    589 
    590            from sqlalchemy.orm import defer 
    591 
    592            session.query(MyClass).options( 
    593                defer(MyClass.attribute_one), defer(MyClass.attribute_two) 
    594            ) 
    595 
    596        To specify a deferred load of an attribute on a related class, 
    597        the path can be specified one token at a time, specifying the loading 
    598        style for each link along the chain.  To leave the loading style 
    599        for a link unchanged, use :func:`_orm.defaultload`:: 
    600 
    601            session.query(MyClass).options( 
    602                defaultload(MyClass.someattr).defer(RelatedClass.some_column) 
    603            ) 
    604 
    605        Multiple deferral options related to a relationship can be bundled 
    606        at once using :meth:`_orm.Load.options`:: 
    607 
    608 
    609            select(MyClass).options( 
    610                defaultload(MyClass.someattr).options( 
    611                    defer(RelatedClass.some_column), 
    612                    defer(RelatedClass.some_other_column), 
    613                    defer(RelatedClass.another_column), 
    614                ) 
    615            ) 
    616 
    617        :param key: Attribute to be deferred. 
    618 
    619        :param raiseload: raise :class:`.InvalidRequestError` rather than 
    620         lazy loading a value when the deferred attribute is accessed. Used 
    621         to prevent unwanted SQL from being emitted. 
    622 
    623        .. versionadded:: 1.4 
    624 
    625        .. seealso:: 
    626 
    627            :ref:`orm_queryguide_column_deferral` - in the 
    628            :ref:`queryguide_toplevel` 
    629 
    630            :func:`_orm.load_only` 
    631 
    632            :func:`_orm.undefer` 
    633 
    634        """ 
    635        strategy = {"deferred": True, "instrument": True} 
    636        if raiseload: 
    637            strategy["raiseload"] = True 
    638        return self._set_column_strategy( 
    639            _expand_column_strategy_attrs((key,)), strategy 
    640        ) 
    641 
    642    def undefer(self, key: _AttrType) -> Self: 
    643        r"""Indicate that the given column-oriented attribute should be 
    644        undeferred, e.g. specified within the SELECT statement of the entity 
    645        as a whole. 
    646 
    647        The column being undeferred is typically set up on the mapping as a 
    648        :func:`.deferred` attribute. 
    649 
    650        This function is part of the :class:`_orm.Load` interface and supports 
    651        both method-chained and standalone operation. 
    652 
    653        Examples:: 
    654 
    655            # undefer two columns 
    656            session.query(MyClass).options( 
    657                undefer(MyClass.col1), undefer(MyClass.col2) 
    658            ) 
    659 
    660            # undefer all columns specific to a single class using Load + * 
    661            session.query(MyClass, MyOtherClass).options(Load(MyClass).undefer("*")) 
    662 
    663            # undefer a column on a related object 
    664            select(MyClass).options(defaultload(MyClass.items).undefer(MyClass.text)) 
    665 
    666        :param key: Attribute to be undeferred. 
    667 
    668        .. seealso:: 
    669 
    670            :ref:`orm_queryguide_column_deferral` - in the 
    671            :ref:`queryguide_toplevel` 
    672 
    673            :func:`_orm.defer` 
    674 
    675            :func:`_orm.undefer_group` 
    676 
    677        """  # noqa: E501 
    678        return self._set_column_strategy( 
    679            _expand_column_strategy_attrs((key,)), 
    680            {"deferred": False, "instrument": True}, 
    681        ) 
    682 
    683    def undefer_group(self, name: str) -> Self: 
    684        """Indicate that columns within the given deferred group name should be 
    685        undeferred. 
    686 
    687        The columns being undeferred are set up on the mapping as 
    688        :func:`.deferred` attributes and include a "group" name. 
    689 
    690        E.g:: 
    691 
    692            session.query(MyClass).options(undefer_group("large_attrs")) 
    693 
    694        To undefer a group of attributes on a related entity, the path can be 
    695        spelled out using relationship loader options, such as 
    696        :func:`_orm.defaultload`:: 
    697 
    698            select(MyClass).options( 
    699                defaultload("someattr").undefer_group("large_attrs") 
    700            ) 
    701 
    702        .. seealso:: 
    703 
    704            :ref:`orm_queryguide_column_deferral` - in the 
    705            :ref:`queryguide_toplevel` 
    706 
    707            :func:`_orm.defer` 
    708 
    709            :func:`_orm.undefer` 
    710 
    711        """ 
    712        return self._set_column_strategy( 
    713            (_WILDCARD_TOKEN,), None, {f"undefer_group_{name}": True} 
    714        ) 
    715 
    716    def with_expression( 
    717        self, 
    718        key: _AttrType, 
    719        expression: _ColumnExpressionArgument[Any], 
    720    ) -> Self: 
    721        r"""Apply an ad-hoc SQL expression to a "deferred expression" 
    722        attribute. 
    723 
    724        This option is used in conjunction with the 
    725        :func:`_orm.query_expression` mapper-level construct that indicates an 
    726        attribute which should be the target of an ad-hoc SQL expression. 
    727 
    728        E.g.:: 
    729 
    730            stmt = select(SomeClass).options( 
    731                with_expression(SomeClass.x_y_expr, SomeClass.x + SomeClass.y) 
    732            ) 
    733 
    734        :param key: Attribute to be populated 
    735 
    736        :param expr: SQL expression to be applied to the attribute. 
    737 
    738        .. seealso:: 
    739 
    740            :ref:`orm_queryguide_with_expression` - background and usage 
    741            examples 
    742 
    743        """ 
    744 
    745        expression = _orm_full_deannotate( 
    746            coercions.expect(roles.LabeledColumnExprRole, expression) 
    747        ) 
    748 
    749        return self._set_column_strategy( 
    750            (key,), {"query_expression": True}, extra_criteria=(expression,) 
    751        ) 
    752 
    753    def selectin_polymorphic(self, classes: Iterable[Type[Any]]) -> Self: 
    754        """Indicate an eager load should take place for all attributes 
    755        specific to a subclass. 
    756 
    757        This uses an additional SELECT with IN against all matched primary 
    758        key values, and is the per-query analogue to the ``"selectin"`` 
    759        setting on the :paramref:`.mapper.polymorphic_load` parameter. 
    760 
    761        .. seealso:: 
    762 
    763            :ref:`polymorphic_selectin` 
    764 
    765        """ 
    766        self = self._set_class_strategy( 
    767            {"selectinload_polymorphic": True}, 
    768            opts={ 
    769                "entities": tuple( 
    770                    sorted((inspect(cls) for cls in classes), key=id) 
    771                ) 
    772            }, 
    773        ) 
    774        return self 
    775 
    776    @overload 
    777    def _coerce_strat(self, strategy: _StrategySpec) -> _StrategyKey: ... 
    778 
    779    @overload 
    780    def _coerce_strat(self, strategy: Literal[None]) -> None: ... 
    781 
    782    def _coerce_strat( 
    783        self, strategy: Optional[_StrategySpec] 
    784    ) -> Optional[_StrategyKey]: 
    785        if strategy is not None: 
    786            strategy_key = tuple(sorted(strategy.items())) 
    787        else: 
    788            strategy_key = None 
    789        return strategy_key 
    790 
    791    @_generative 
    792    def _set_relationship_strategy( 
    793        self, 
    794        attr: _AttrType, 
    795        strategy: Optional[_StrategySpec], 
    796        propagate_to_loaders: bool = True, 
    797        opts: Optional[_OptsType] = None, 
    798        _reconcile_to_other: Optional[bool] = None, 
    799    ) -> Self: 
    800        strategy_key = self._coerce_strat(strategy) 
    801 
    802        self._clone_for_bind_strategy( 
    803            (attr,), 
    804            strategy_key, 
    805            _RELATIONSHIP_TOKEN, 
    806            opts=opts, 
    807            propagate_to_loaders=propagate_to_loaders, 
    808            reconcile_to_other=_reconcile_to_other, 
    809        ) 
    810        return self 
    811 
    812    @_generative 
    813    def _set_column_strategy( 
    814        self, 
    815        attrs: Tuple[_AttrType, ...], 
    816        strategy: Optional[_StrategySpec], 
    817        opts: Optional[_OptsType] = None, 
    818        extra_criteria: Optional[Tuple[Any, ...]] = None, 
    819    ) -> Self: 
    820        strategy_key = self._coerce_strat(strategy) 
    821 
    822        self._clone_for_bind_strategy( 
    823            attrs, 
    824            strategy_key, 
    825            _COLUMN_TOKEN, 
    826            opts=opts, 
    827            attr_group=attrs, 
    828            extra_criteria=extra_criteria, 
    829        ) 
    830        return self 
    831 
    832    @_generative 
    833    def _set_generic_strategy( 
    834        self, 
    835        attrs: Tuple[_AttrType, ...], 
    836        strategy: _StrategySpec, 
    837        _reconcile_to_other: Optional[bool] = None, 
    838    ) -> Self: 
    839        strategy_key = self._coerce_strat(strategy) 
    840        self._clone_for_bind_strategy( 
    841            attrs, 
    842            strategy_key, 
    843            None, 
    844            propagate_to_loaders=True, 
    845            reconcile_to_other=_reconcile_to_other, 
    846        ) 
    847        return self 
    848 
    849    @_generative 
    850    def _set_class_strategy( 
    851        self, strategy: _StrategySpec, opts: _OptsType 
    852    ) -> Self: 
    853        strategy_key = self._coerce_strat(strategy) 
    854 
    855        self._clone_for_bind_strategy(None, strategy_key, None, opts=opts) 
    856        return self 
    857 
    858    def _apply_to_parent(self, parent: Load) -> None: 
    859        """apply this :class:`_orm._AbstractLoad` object as a sub-option o 
    860        a :class:`_orm.Load` object. 
    861 
    862        Implementation is provided by subclasses. 
    863 
    864        """ 
    865        raise NotImplementedError() 
    866 
    867    def options(self, *opts: _AbstractLoad) -> Self: 
    868        r"""Apply a series of options as sub-options to this 
    869        :class:`_orm._AbstractLoad` object. 
    870 
    871        Implementation is provided by subclasses. 
    872 
    873        """ 
    874        raise NotImplementedError() 
    875 
    876    def _clone_for_bind_strategy( 
    877        self, 
    878        attrs: Optional[Tuple[_AttrType, ...]], 
    879        strategy: Optional[_StrategyKey], 
    880        wildcard_key: Optional[_WildcardKeyType], 
    881        opts: Optional[_OptsType] = None, 
    882        attr_group: Optional[_AttrGroupType] = None, 
    883        propagate_to_loaders: bool = True, 
    884        reconcile_to_other: Optional[bool] = None, 
    885        extra_criteria: Optional[Tuple[Any, ...]] = None, 
    886    ) -> Self: 
    887        raise NotImplementedError() 
    888 
    889    def process_compile_state_replaced_entities( 
    890        self, 
    891        compile_state: _ORMCompileState, 
    892        mapper_entities: Sequence[_MapperEntity], 
    893    ) -> None: 
    894        if not compile_state.compile_options._enable_eagerloads: 
    895            return 
    896 
    897        # process is being run here so that the options given are validated 
    898        # against what the lead entities were, as well as to accommodate 
    899        # for the entities having been replaced with equivalents 
    900        self._process( 
    901            compile_state, 
    902            mapper_entities, 
    903            not bool(compile_state.current_path), 
    904        ) 
    905 
    906    def process_compile_state(self, compile_state: _ORMCompileState) -> None: 
    907        if not compile_state.compile_options._enable_eagerloads: 
    908            return 
    909 
    910        self._process( 
    911            compile_state, 
    912            compile_state._lead_mapper_entities, 
    913            not bool(compile_state.current_path) 
    914            and not compile_state.compile_options._for_refresh_state, 
    915        ) 
    916 
    917    def _process( 
    918        self, 
    919        compile_state: _ORMCompileState, 
    920        mapper_entities: Sequence[_MapperEntity], 
    921        raiseerr: bool, 
    922    ) -> None: 
    923        """implemented by subclasses""" 
    924        raise NotImplementedError() 
    925 
    926    @classmethod 
    927    def _chop_path( 
    928        cls, 
    929        to_chop: _PathRepresentation, 
    930        path: PathRegistry, 
    931        debug: bool = False, 
    932    ) -> Optional[_PathRepresentation]: 
    933        i = -1 
    934 
    935        for i, (c_token, p_token) in enumerate( 
    936            zip(to_chop, path.natural_path) 
    937        ): 
    938            if isinstance(c_token, str): 
    939                if i == 0 and ( 
    940                    c_token.endswith(f":{_DEFAULT_TOKEN}") 
    941                    or c_token.endswith(f":{_WILDCARD_TOKEN}") 
    942                ): 
    943                    return to_chop 
    944                elif ( 
    945                    c_token != f"{_RELATIONSHIP_TOKEN}:{_WILDCARD_TOKEN}" 
    946                    and c_token != p_token.key  # type: ignore 
    947                ): 
    948                    return None 
    949 
    950            if c_token is p_token: 
    951                continue 
    952            elif ( 
    953                isinstance(c_token, InspectionAttr) 
    954                and insp_is_mapper(c_token) 
    955                and insp_is_mapper(p_token) 
    956                and c_token.isa(p_token) 
    957            ): 
    958                continue 
    959 
    960            else: 
    961                return None 
    962        return to_chop[i + 1 :] 
    963 
    964 
    965class Load(_AbstractLoad): 
    966    """Represents loader options which modify the state of a 
    967    ORM-enabled :class:`_sql.Select` or a legacy :class:`_query.Query` in 
    968    order to affect how various mapped attributes are loaded. 
    969 
    970    The :class:`_orm.Load` object is in most cases used implicitly behind the 
    971    scenes when one makes use of a query option like :func:`_orm.joinedload`, 
    972    :func:`_orm.defer`, or similar.   It typically is not instantiated directly 
    973    except for in some very specific cases. 
    974 
    975    .. seealso:: 
    976 
    977        :ref:`orm_queryguide_relationship_per_entity_wildcard` - illustrates an 
    978        example where direct use of :class:`_orm.Load` may be useful 
    979 
    980    """ 
    981 
    982    __slots__ = ( 
    983        "path", 
    984        "context", 
    985        "additional_source_entities", 
    986    ) 
    987 
    988    _traverse_internals = [ 
    989        ("path", visitors.ExtendedInternalTraversal.dp_has_cache_key), 
    990        ( 
    991            "context", 
    992            visitors.InternalTraversal.dp_has_cache_key_list, 
    993        ), 
    994        ("propagate_to_loaders", visitors.InternalTraversal.dp_boolean), 
    995        ( 
    996            "additional_source_entities", 
    997            visitors.InternalTraversal.dp_has_cache_key_list, 
    998        ), 
    999    ] 
    1000    _cache_key_traversal = None 
    1001 
    1002    path: PathRegistry 
    1003    context: Tuple[_LoadElement, ...] 
    1004    additional_source_entities: Tuple[_InternalEntityType[Any], ...] 
    1005 
    1006    def __init__(self, entity: _EntityType[Any]): 
    1007        insp = cast("Union[Mapper[Any], AliasedInsp[Any]]", inspect(entity)) 
    1008        insp._post_inspect 
    1009 
    1010        self.path = insp._path_registry 
    1011        self.context = () 
    1012        self.propagate_to_loaders = False 
    1013        self.additional_source_entities = () 
    1014 
    1015    def __str__(self) -> str: 
    1016        return f"Load({self.path[0]})" 
    1017 
    1018    @classmethod 
    1019    def _construct_for_existing_path( 
    1020        cls, path: _AbstractEntityRegistry 
    1021    ) -> Load: 
    1022        load = cls.__new__(cls) 
    1023        load.path = path 
    1024        load.context = () 
    1025        load.propagate_to_loaders = False 
    1026        load.additional_source_entities = () 
    1027        return load 
    1028 
    1029    def _adapt_cached_option_to_uncached_option( 
    1030        self, context: QueryContext, uncached_opt: ORMOption 
    1031    ) -> ORMOption: 
    1032        if uncached_opt is self: 
    1033            return self 
    1034        return self._adjust_for_extra_criteria(context) 
    1035 
    1036    def _prepend_path(self, path: PathRegistry) -> Load: 
    1037        cloned = self._clone() 
    1038        cloned.context = tuple( 
    1039            element._prepend_path(path) for element in self.context 
    1040        ) 
    1041        return cloned 
    1042 
    1043    def _adjust_for_extra_criteria(self, context: QueryContext) -> Load: 
    1044        """Apply the current bound parameters in a QueryContext to all 
    1045        occurrences "extra_criteria" stored within this ``Load`` object, 
    1046        returning a new instance of this ``Load`` object. 
    1047 
    1048        """ 
    1049 
    1050        # avoid generating cache keys for the queries if we don't 
    1051        # actually have any extra_criteria options, which is the 
    1052        # common case 
    1053        for value in self.context: 
    1054            if value._extra_criteria: 
    1055                break 
    1056        else: 
    1057            return self 
    1058 
    1059        replacement_cache_key = context.user_passed_query._generate_cache_key() 
    1060 
    1061        if replacement_cache_key is None: 
    1062            return self 
    1063 
    1064        orig_query = context.compile_state.select_statement 
    1065        orig_cache_key = orig_query._generate_cache_key() 
    1066        assert orig_cache_key is not None 
    1067 
    1068        def process( 
    1069            opt: _LoadElement, 
    1070            replacement_cache_key: CacheKey, 
    1071            orig_cache_key: CacheKey, 
    1072        ) -> _LoadElement: 
    1073            cloned_opt = opt._clone() 
    1074 
    1075            cloned_opt._extra_criteria = tuple( 
    1076                replacement_cache_key._apply_params_to_element( 
    1077                    orig_cache_key, crit 
    1078                ) 
    1079                for crit in cloned_opt._extra_criteria 
    1080            ) 
    1081 
    1082            return cloned_opt 
    1083 
    1084        cloned = self._clone() 
    1085        cloned.context = tuple( 
    1086            ( 
    1087                process(value, replacement_cache_key, orig_cache_key) 
    1088                if value._extra_criteria 
    1089                else value 
    1090            ) 
    1091            for value in self.context 
    1092        ) 
    1093        return cloned 
    1094 
    1095    def _reconcile_query_entities_with_us(self, mapper_entities, raiseerr): 
    1096        """called at process time to allow adjustment of the root 
    1097        entity inside of _LoadElement objects. 
    1098 
    1099        """ 
    1100        path = self.path 
    1101 
    1102        for ent in mapper_entities: 
    1103            ezero = ent.entity_zero 
    1104            if ezero and orm_util._entity_corresponds_to( 
    1105                # technically this can be a token also, but this is 
    1106                # safe to pass to _entity_corresponds_to() 
    1107                ezero, 
    1108                cast("_InternalEntityType[Any]", path[0]), 
    1109            ): 
    1110                return ezero 
    1111 
    1112        return None 
    1113 
    1114    def _process( 
    1115        self, 
    1116        compile_state: _ORMCompileState, 
    1117        mapper_entities: Sequence[_MapperEntity], 
    1118        raiseerr: bool, 
    1119    ) -> None: 
    1120        reconciled_lead_entity = self._reconcile_query_entities_with_us( 
    1121            mapper_entities, raiseerr 
    1122        ) 
    1123 
    1124        # if the context has a current path, this is a lazy load 
    1125        has_current_path = bool(compile_state.compile_options._current_path) 
    1126 
    1127        for loader in self.context: 
    1128            # issue #11292 
    1129            # historically, propagate_to_loaders was only considered at 
    1130            # object loading time, whether or not to carry along options 
    1131            # onto an object's loaded state where it would be used by lazyload. 
    1132            # however, the defaultload() option needs to propagate in case 
    1133            # its sub-options propagate_to_loaders, but its sub-options 
    1134            # that dont propagate should not be applied for lazy loaders. 
    1135            # so we check again 
    1136            if has_current_path and not loader.propagate_to_loaders: 
    1137                continue 
    1138            loader.process_compile_state( 
    1139                self, 
    1140                compile_state, 
    1141                mapper_entities, 
    1142                reconciled_lead_entity, 
    1143                raiseerr, 
    1144            ) 
    1145 
    1146    def _apply_to_parent(self, parent: Load) -> None: 
    1147        """apply this :class:`_orm.Load` object as a sub-option of another 
    1148        :class:`_orm.Load` object. 
    1149 
    1150        This method is used by the :meth:`_orm.Load.options` method. 
    1151 
    1152        """ 
    1153        cloned = self._generate() 
    1154 
    1155        assert cloned.propagate_to_loaders == self.propagate_to_loaders 
    1156 
    1157        if not any( 
    1158            orm_util._entity_corresponds_to_use_path_impl( 
    1159                elem, cloned.path.odd_element(0) 
    1160            ) 
    1161            for elem in (parent.path.odd_element(-1),) 
    1162            + parent.additional_source_entities 
    1163        ): 
    1164            if len(cloned.path) > 1: 
    1165                attrname = cloned.path[1] 
    1166                parent_entity = cloned.path[0] 
    1167            else: 
    1168                attrname = cloned.path[0] 
    1169                parent_entity = cloned.path[0] 
    1170            _raise_for_does_not_link(parent.path, attrname, parent_entity) 
    1171 
    1172        cloned.path = PathRegistry.coerce(parent.path[0:-1] + cloned.path[:]) 
    1173 
    1174        if self.context: 
    1175            cloned.context = tuple( 
    1176                value._prepend_path_from(parent) for value in self.context 
    1177            ) 
    1178 
    1179        if cloned.context: 
    1180            parent.context += cloned.context 
    1181            parent.additional_source_entities += ( 
    1182                cloned.additional_source_entities 
    1183            ) 
    1184 
    1185    @_generative 
    1186    def options(self, *opts: _AbstractLoad) -> Self: 
    1187        r"""Apply a series of options as sub-options to this 
    1188        :class:`_orm.Load` 
    1189        object. 
    1190 
    1191        E.g.:: 
    1192 
    1193            query = session.query(Author) 
    1194            query = query.options( 
    1195                joinedload(Author.book).options( 
    1196                    load_only(Book.summary, Book.excerpt), 
    1197                    joinedload(Book.citations).options(joinedload(Citation.author)), 
    1198                ) 
    1199            ) 
    1200 
    1201        :param \*opts: A series of loader option objects (ultimately 
    1202         :class:`_orm.Load` objects) which should be applied to the path 
    1203         specified by this :class:`_orm.Load` object. 
    1204 
    1205        .. seealso:: 
    1206 
    1207            :func:`.defaultload` 
    1208 
    1209            :ref:`orm_queryguide_relationship_sub_options` 
    1210 
    1211        """ 
    1212        for opt in opts: 
    1213            try: 
    1214                opt._apply_to_parent(self) 
    1215            except AttributeError as ae: 
    1216                if not isinstance(opt, _AbstractLoad): 
    1217                    raise sa_exc.ArgumentError( 
    1218                        f"Loader option {opt} is not compatible with the " 
    1219                        "Load.options() method." 
    1220                    ) from ae 
    1221                else: 
    1222                    raise 
    1223        return self 
    1224 
    1225    def _clone_for_bind_strategy( 
    1226        self, 
    1227        attrs: Optional[Tuple[_AttrType, ...]], 
    1228        strategy: Optional[_StrategyKey], 
    1229        wildcard_key: Optional[_WildcardKeyType], 
    1230        opts: Optional[_OptsType] = None, 
    1231        attr_group: Optional[_AttrGroupType] = None, 
    1232        propagate_to_loaders: bool = True, 
    1233        reconcile_to_other: Optional[bool] = None, 
    1234        extra_criteria: Optional[Tuple[Any, ...]] = None, 
    1235    ) -> Self: 
    1236        # for individual strategy that needs to propagate, set the whole 
    1237        # Load container to also propagate, so that it shows up in 
    1238        # InstanceState.load_options 
    1239        if propagate_to_loaders: 
    1240            self.propagate_to_loaders = True 
    1241 
    1242        if self.path.is_token: 
    1243            raise sa_exc.ArgumentError( 
    1244                "Wildcard token cannot be followed by another entity" 
    1245            ) 
    1246 
    1247        elif path_is_property(self.path): 
    1248            # re-use the lookup which will raise a nicely formatted 
    1249            # LoaderStrategyException 
    1250            if strategy: 
    1251                self.path.prop._strategy_lookup(self.path.prop, strategy[0]) 
    1252            else: 
    1253                raise sa_exc.ArgumentError( 
    1254                    f"Mapped attribute '{self.path.prop}' does not " 
    1255                    "refer to a mapped entity" 
    1256                ) 
    1257 
    1258        if attrs is None: 
    1259            load_element = _ClassStrategyLoad.create( 
    1260                self.path, 
    1261                None, 
    1262                strategy, 
    1263                wildcard_key, 
    1264                opts, 
    1265                propagate_to_loaders, 
    1266                attr_group=attr_group, 
    1267                reconcile_to_other=reconcile_to_other, 
    1268                extra_criteria=extra_criteria, 
    1269            ) 
    1270            if load_element: 
    1271                self.context += (load_element,) 
    1272                assert opts is not None 
    1273                self.additional_source_entities += cast( 
    1274                    "Tuple[_InternalEntityType[Any]]", opts["entities"] 
    1275                ) 
    1276 
    1277        else: 
    1278            for attr in attrs: 
    1279                if isinstance(attr, str): 
    1280                    load_element = _TokenStrategyLoad.create( 
    1281                        self.path, 
    1282                        attr, 
    1283                        strategy, 
    1284                        wildcard_key, 
    1285                        opts, 
    1286                        propagate_to_loaders, 
    1287                        attr_group=attr_group, 
    1288                        reconcile_to_other=reconcile_to_other, 
    1289                        extra_criteria=extra_criteria, 
    1290                    ) 
    1291                else: 
    1292                    load_element = _AttributeStrategyLoad.create( 
    1293                        self.path, 
    1294                        attr, 
    1295                        strategy, 
    1296                        wildcard_key, 
    1297                        opts, 
    1298                        propagate_to_loaders, 
    1299                        attr_group=attr_group, 
    1300                        reconcile_to_other=reconcile_to_other, 
    1301                        extra_criteria=extra_criteria, 
    1302                    ) 
    1303 
    1304                if load_element: 
    1305                    # for relationship options, update self.path on this Load 
    1306                    # object with the latest path. 
    1307                    if wildcard_key is _RELATIONSHIP_TOKEN: 
    1308                        self.path = load_element.path 
    1309                    self.context += (load_element,) 
    1310 
    1311                    # this seems to be effective for selectinloader, 
    1312                    # giving the extra match to one more level deep. 
    1313                    # but does not work for immediateloader, which still 
    1314                    # must add additional options at load time 
    1315                    if load_element.local_opts.get("recursion_depth", False): 
    1316                        r1 = load_element._recurse() 
    1317                        self.context += (r1,) 
    1318 
    1319        return self 
    1320 
    1321    def __getstate__(self): 
    1322        d = self._shallow_to_dict() 
    1323        d["path"] = self.path.serialize() 
    1324        return d 
    1325 
    1326    def __setstate__(self, state): 
    1327        state["path"] = PathRegistry.deserialize(state["path"]) 
    1328        self._shallow_from_dict(state) 
    1329 
    1330 
    1331class _WildcardLoad(_AbstractLoad): 
    1332    """represent a standalone '*' load operation""" 
    1333 
    1334    __slots__ = ("strategy", "path", "local_opts") 
    1335 
    1336    _traverse_internals = [ 
    1337        ("strategy", visitors.ExtendedInternalTraversal.dp_plain_obj), 
    1338        ("path", visitors.ExtendedInternalTraversal.dp_plain_obj), 
    1339        ( 
    1340            "local_opts", 
    1341            visitors.ExtendedInternalTraversal.dp_string_multi_dict, 
    1342        ), 
    1343    ] 
    1344    cache_key_traversal: _CacheKeyTraversalType = None 
    1345 
    1346    strategy: Optional[Tuple[Any, ...]] 
    1347    local_opts: _OptsType 
    1348    path: Union[Tuple[()], Tuple[str]] 
    1349    propagate_to_loaders = False 
    1350 
    1351    def __init__(self) -> None: 
    1352        self.path = () 
    1353        self.strategy = None 
    1354        self.local_opts = util.EMPTY_DICT 
    1355 
    1356    def _clone_for_bind_strategy( 
    1357        self, 
    1358        attrs, 
    1359        strategy, 
    1360        wildcard_key, 
    1361        opts=None, 
    1362        attr_group=None, 
    1363        propagate_to_loaders=True, 
    1364        reconcile_to_other=None, 
    1365        extra_criteria=None, 
    1366    ): 
    1367        assert attrs is not None 
    1368        attr = attrs[0] 
    1369        assert ( 
    1370            wildcard_key 
    1371            and isinstance(attr, str) 
    1372            and attr in (_WILDCARD_TOKEN, _DEFAULT_TOKEN) 
    1373        ) 
    1374 
    1375        attr = f"{wildcard_key}:{attr}" 
    1376 
    1377        self.strategy = strategy 
    1378        self.path = (attr,) 
    1379        if opts: 
    1380            self.local_opts = util.immutabledict(opts) 
    1381 
    1382        assert extra_criteria is None 
    1383 
    1384    def options(self, *opts: _AbstractLoad) -> Self: 
    1385        raise NotImplementedError("Star option does not support sub-options") 
    1386 
    1387    def _apply_to_parent(self, parent: Load) -> None: 
    1388        """apply this :class:`_orm._WildcardLoad` object as a sub-option of 
    1389        a :class:`_orm.Load` object. 
    1390 
    1391        This method is used by the :meth:`_orm.Load.options` method.   Note 
    1392        that :class:`_orm.WildcardLoad` itself can't have sub-options, but 
    1393        it may be used as the sub-option of a :class:`_orm.Load` object. 
    1394 
    1395        """ 
    1396        assert self.path 
    1397        attr = self.path[0] 
    1398        if attr.endswith(_DEFAULT_TOKEN): 
    1399            attr = f"{attr.split(':')[0]}:{_WILDCARD_TOKEN}" 
    1400 
    1401        effective_path = cast(_AbstractEntityRegistry, parent.path).token(attr) 
    1402 
    1403        assert effective_path.is_token 
    1404 
    1405        loader = _TokenStrategyLoad.create( 
    1406            effective_path, 
    1407            None, 
    1408            self.strategy, 
    1409            None, 
    1410            self.local_opts, 
    1411            self.propagate_to_loaders, 
    1412        ) 
    1413 
    1414        parent.context += (loader,) 
    1415 
    1416    def _process(self, compile_state, mapper_entities, raiseerr): 
    1417        is_refresh = compile_state.compile_options._for_refresh_state 
    1418 
    1419        if is_refresh and not self.propagate_to_loaders: 
    1420            return 
    1421 
    1422        entities = [ent.entity_zero for ent in mapper_entities] 
    1423        current_path = compile_state.current_path 
    1424 
    1425        start_path: _PathRepresentation = self.path 
    1426 
    1427        if current_path: 
    1428            # TODO: no cases in test suite where we actually get 
    1429            # None back here 
    1430            new_path = self._chop_path(start_path, current_path) 
    1431            if new_path is None: 
    1432                return 
    1433 
    1434            # chop_path does not actually "chop" a wildcard token path, 
    1435            # just returns it 
    1436            assert new_path == start_path 
    1437 
    1438        # start_path is a single-token tuple 
    1439        assert start_path and len(start_path) == 1 
    1440 
    1441        token = start_path[0] 
    1442        assert isinstance(token, str) 
    1443        entity = self._find_entity_basestring(entities, token, raiseerr) 
    1444 
    1445        if not entity: 
    1446            return 
    1447 
    1448        path_element = entity 
    1449 
    1450        # transfer our entity-less state into a Load() object 
    1451        # with a real entity path.  Start with the lead entity 
    1452        # we just located, then go through the rest of our path 
    1453        # tokens and populate into the Load(). 
    1454 
    1455        assert isinstance(token, str) 
    1456        loader = _TokenStrategyLoad.create( 
    1457            path_element._path_registry, 
    1458            token, 
    1459            self.strategy, 
    1460            None, 
    1461            self.local_opts, 
    1462            self.propagate_to_loaders, 
    1463            raiseerr=raiseerr, 
    1464        ) 
    1465        if not loader: 
    1466            return 
    1467 
    1468        assert loader.path.is_token 
    1469 
    1470        # don't pass a reconciled lead entity here 
    1471        loader.process_compile_state( 
    1472            self, compile_state, mapper_entities, None, raiseerr 
    1473        ) 
    1474 
    1475        return loader 
    1476 
    1477    def _find_entity_basestring( 
    1478        self, 
    1479        entities: Iterable[_InternalEntityType[Any]], 
    1480        token: str, 
    1481        raiseerr: bool, 
    1482    ) -> Optional[_InternalEntityType[Any]]: 
    1483        if token.endswith(f":{_WILDCARD_TOKEN}"): 
    1484            if len(list(entities)) != 1: 
    1485                if raiseerr: 
    1486                    raise sa_exc.ArgumentError( 
    1487                        "Can't apply wildcard ('*') or load_only() " 
    1488                        f"loader option to multiple entities " 
    1489                        f"{', '.join(str(ent) for ent in entities)}. Specify " 
    1490                        "loader options for each entity individually, such as " 
    1491                        f"""{ 
    1492                            ", ".join( 
    1493                                f"Load({ent}).some_option('*')" 
    1494                                for ent in entities 
    1495                            ) 
    1496                        }.""" 
    1497                    ) 
    1498        elif token.endswith(_DEFAULT_TOKEN): 
    1499            raiseerr = False 
    1500 
    1501        for ent in entities: 
    1502            # return only the first _MapperEntity when searching 
    1503            # based on string prop name.   Ideally object 
    1504            # attributes are used to specify more exactly. 
    1505            return ent 
    1506        else: 
    1507            if raiseerr: 
    1508                raise sa_exc.ArgumentError( 
    1509                    "Query has only expression-based entities - " 
    1510                    f'can\'t find property named "{token}".' 
    1511                ) 
    1512            else: 
    1513                return None 
    1514 
    1515    def __getstate__(self) -> Dict[str, Any]: 
    1516        d = self._shallow_to_dict() 
    1517        return d 
    1518 
    1519    def __setstate__(self, state: Dict[str, Any]) -> None: 
    1520        self._shallow_from_dict(state) 
    1521 
    1522 
    1523class _LoadElement( 
    1524    cache_key.HasCacheKey, traversals.HasShallowCopy, visitors.Traversible 
    1525): 
    1526    """represents strategy information to select for a LoaderStrategy 
    1527    and pass options to it. 
    1528 
    1529    :class:`._LoadElement` objects provide the inner datastructure 
    1530    stored by a :class:`_orm.Load` object and are also the object passed 
    1531    to methods like :meth:`.LoaderStrategy.setup_query`. 
    1532 
    1533    .. versionadded:: 2.0 
    1534 
    1535    """ 
    1536 
    1537    __slots__ = ( 
    1538        "path", 
    1539        "strategy", 
    1540        "propagate_to_loaders", 
    1541        "local_opts", 
    1542        "_extra_criteria", 
    1543        "_reconcile_to_other", 
    1544    ) 
    1545    __visit_name__ = "load_element" 
    1546 
    1547    _traverse_internals = [ 
    1548        ("path", visitors.ExtendedInternalTraversal.dp_has_cache_key), 
    1549        ("strategy", visitors.ExtendedInternalTraversal.dp_plain_obj), 
    1550        ( 
    1551            "local_opts", 
    1552            visitors.ExtendedInternalTraversal.dp_string_multi_dict, 
    1553        ), 
    1554        ("_extra_criteria", visitors.InternalTraversal.dp_clauseelement_list), 
    1555        ("propagate_to_loaders", visitors.InternalTraversal.dp_plain_obj), 
    1556        ("_reconcile_to_other", visitors.InternalTraversal.dp_plain_obj), 
    1557    ] 
    1558    _cache_key_traversal = None 
    1559 
    1560    _extra_criteria: Tuple[Any, ...] 
    1561 
    1562    _reconcile_to_other: Optional[bool] 
    1563    strategy: Optional[_StrategyKey] 
    1564    path: PathRegistry 
    1565    propagate_to_loaders: bool 
    1566 
    1567    local_opts: util.immutabledict[str, Any] 
    1568 
    1569    is_token_strategy: bool 
    1570    is_class_strategy: bool 
    1571 
    1572    def __hash__(self) -> int: 
    1573        return id(self) 
    1574 
    1575    def __eq__(self, other): 
    1576        return traversals.compare(self, other) 
    1577 
    1578    @property 
    1579    def is_opts_only(self) -> bool: 
    1580        return bool(self.local_opts and self.strategy is None) 
    1581 
    1582    def _clone(self, **kw: Any) -> _LoadElement: 
    1583        cls = self.__class__ 
    1584        s = cls.__new__(cls) 
    1585 
    1586        self._shallow_copy_to(s) 
    1587        return s 
    1588 
    1589    def _update_opts(self, **kw: Any) -> _LoadElement: 
    1590        new = self._clone() 
    1591        new.local_opts = new.local_opts.union(kw) 
    1592        return new 
    1593 
    1594    def __getstate__(self) -> Dict[str, Any]: 
    1595        d = self._shallow_to_dict() 
    1596        d["path"] = self.path.serialize() 
    1597        return d 
    1598 
    1599    def __setstate__(self, state: Dict[str, Any]) -> None: 
    1600        state["path"] = PathRegistry.deserialize(state["path"]) 
    1601        self._shallow_from_dict(state) 
    1602 
    1603    def _raise_for_no_match(self, parent_loader, mapper_entities): 
    1604        path = parent_loader.path 
    1605 
    1606        found_entities = False 
    1607        for ent in mapper_entities: 
    1608            ezero = ent.entity_zero 
    1609            if ezero: 
    1610                found_entities = True 
    1611                break 
    1612 
    1613        if not found_entities: 
    1614            raise sa_exc.ArgumentError( 
    1615                "Query has only expression-based entities; " 
    1616                f"attribute loader options for {path[0]} can't " 
    1617                "be applied here." 
    1618            ) 
    1619        else: 
    1620            raise sa_exc.ArgumentError( 
    1621                f"Mapped class {path[0]} does not apply to any of the " 
    1622                f"root entities in this query, e.g. " 
    1623                f"""{ 
    1624                    ", ".join( 
    1625                        str(x.entity_zero) 
    1626                        for x in mapper_entities if x.entity_zero 
    1627                    )}. Please """ 
    1628                "specify the full path " 
    1629                "from one of the root entities to the target " 
    1630                "attribute. " 
    1631            ) 
    1632 
    1633    def _adjust_effective_path_for_current_path( 
    1634        self, effective_path: PathRegistry, current_path: PathRegistry 
    1635    ) -> Optional[PathRegistry]: 
    1636        """receives the 'current_path' entry from an :class:`.ORMCompileState` 
    1637        instance, which is set during lazy loads and secondary loader strategy 
    1638        loads, and adjusts the given path to be relative to the 
    1639        current_path. 
    1640 
    1641        E.g. given a loader path and current path: 
    1642 
    1643        .. sourcecode:: text 
    1644 
    1645            lp: User -> orders -> Order -> items -> Item -> keywords -> Keyword 
    1646 
    1647            cp: User -> orders -> Order -> items 
    1648 
    1649        The adjusted path would be: 
    1650 
    1651        .. sourcecode:: text 
    1652 
    1653            Item -> keywords -> Keyword 
    1654 
    1655 
    1656        """ 
    1657        chopped_start_path = Load._chop_path( 
    1658            effective_path.natural_path, current_path 
    1659        ) 
    1660        if not chopped_start_path: 
    1661            return None 
    1662 
    1663        tokens_removed_from_start_path = len(effective_path) - len( 
    1664            chopped_start_path 
    1665        ) 
    1666 
    1667        loader_lead_path_element = self.path[tokens_removed_from_start_path] 
    1668 
    1669        effective_path = PathRegistry.coerce( 
    1670            (loader_lead_path_element,) + chopped_start_path[1:] 
    1671        ) 
    1672 
    1673        return effective_path 
    1674 
    1675    def _init_path( 
    1676        self, path, attr, wildcard_key, attr_group, raiseerr, extra_criteria 
    1677    ): 
    1678        """Apply ORM attributes and/or wildcard to an existing path, producing 
    1679        a new path. 
    1680 
    1681        This method is used within the :meth:`.create` method to initialize 
    1682        a :class:`._LoadElement` object. 
    1683 
    1684        """ 
    1685        raise NotImplementedError() 
    1686 
    1687    def _prepare_for_compile_state( 
    1688        self, 
    1689        parent_loader, 
    1690        compile_state, 
    1691        mapper_entities, 
    1692        reconciled_lead_entity, 
    1693        raiseerr, 
    1694    ): 
    1695        """implemented by subclasses.""" 
    1696        raise NotImplementedError() 
    1697 
    1698    def process_compile_state( 
    1699        self, 
    1700        parent_loader, 
    1701        compile_state, 
    1702        mapper_entities, 
    1703        reconciled_lead_entity, 
    1704        raiseerr, 
    1705    ): 
    1706        """populate ORMCompileState.attributes with loader state for this 
    1707        _LoadElement. 
    1708 
    1709        """ 
    1710        keys = self._prepare_for_compile_state( 
    1711            parent_loader, 
    1712            compile_state, 
    1713            mapper_entities, 
    1714            reconciled_lead_entity, 
    1715            raiseerr, 
    1716        ) 
    1717        for key in keys: 
    1718            if key in compile_state.attributes: 
    1719                compile_state.attributes[key] = _LoadElement._reconcile( 
    1720                    self, compile_state.attributes[key] 
    1721                ) 
    1722            else: 
    1723                compile_state.attributes[key] = self 
    1724 
    1725    @classmethod 
    1726    def create( 
    1727        cls, 
    1728        path: PathRegistry, 
    1729        attr: Union[_AttrType, _StrPathToken, None], 
    1730        strategy: Optional[_StrategyKey], 
    1731        wildcard_key: Optional[_WildcardKeyType], 
    1732        local_opts: Optional[_OptsType], 
    1733        propagate_to_loaders: bool, 
    1734        raiseerr: bool = True, 
    1735        attr_group: Optional[_AttrGroupType] = None, 
    1736        reconcile_to_other: Optional[bool] = None, 
    1737        extra_criteria: Optional[Tuple[Any, ...]] = None, 
    1738    ) -> _LoadElement: 
    1739        """Create a new :class:`._LoadElement` object.""" 
    1740 
    1741        opt = cls.__new__(cls) 
    1742        opt.path = path 
    1743        opt.strategy = strategy 
    1744        opt.propagate_to_loaders = propagate_to_loaders 
    1745        opt.local_opts = ( 
    1746            util.immutabledict(local_opts) if local_opts else util.EMPTY_DICT 
    1747        ) 
    1748        opt._extra_criteria = () 
    1749 
    1750        if reconcile_to_other is not None: 
    1751            opt._reconcile_to_other = reconcile_to_other 
    1752        elif strategy is None and not local_opts: 
    1753            opt._reconcile_to_other = True 
    1754        else: 
    1755            opt._reconcile_to_other = None 
    1756 
    1757        path = opt._init_path( 
    1758            path, attr, wildcard_key, attr_group, raiseerr, extra_criteria 
    1759        ) 
    1760 
    1761        if not path: 
    1762            return None  # type: ignore 
    1763 
    1764        assert opt.is_token_strategy == path.is_token 
    1765 
    1766        opt.path = path 
    1767        return opt 
    1768 
    1769    def __init__(self) -> None: 
    1770        raise NotImplementedError() 
    1771 
    1772    def _recurse(self) -> _LoadElement: 
    1773        cloned = self._clone() 
    1774        cloned.path = PathRegistry.coerce(self.path[:] + self.path[-2:]) 
    1775 
    1776        return cloned 
    1777 
    1778    def _prepend_path_from(self, parent: Load) -> _LoadElement: 
    1779        """adjust the path of this :class:`._LoadElement` to be 
    1780        a subpath of that of the given parent :class:`_orm.Load` object's 
    1781        path. 
    1782 
    1783        This is used by the :meth:`_orm.Load._apply_to_parent` method, 
    1784        which is in turn part of the :meth:`_orm.Load.options` method. 
    1785 
    1786        """ 
    1787 
    1788        if not any( 
    1789            orm_util._entity_corresponds_to_use_path_impl( 
    1790                elem, 
    1791                self.path.odd_element(0), 
    1792            ) 
    1793            for elem in (parent.path.odd_element(-1),) 
    1794            + parent.additional_source_entities 
    1795        ): 
    1796            raise sa_exc.ArgumentError( 
    1797                f'Attribute "{self.path[1]}" does not link ' 
    1798                f'from element "{parent.path[-1]}".' 
    1799            ) 
    1800 
    1801        return self._prepend_path(parent.path) 
    1802 
    1803    def _prepend_path(self, path: PathRegistry) -> _LoadElement: 
    1804        cloned = self._clone() 
    1805 
    1806        assert cloned.strategy == self.strategy 
    1807        assert cloned.local_opts == self.local_opts 
    1808        assert cloned.is_class_strategy == self.is_class_strategy 
    1809 
    1810        cloned.path = PathRegistry.coerce(path[0:-1] + cloned.path[:]) 
    1811 
    1812        return cloned 
    1813 
    1814    @staticmethod 
    1815    def _reconcile( 
    1816        replacement: _LoadElement, existing: _LoadElement 
    1817    ) -> _LoadElement: 
    1818        """define behavior for when two Load objects are to be put into 
    1819        the context.attributes under the same key. 
    1820 
    1821        :param replacement: ``_LoadElement`` that seeks to replace the 
    1822         existing one 
    1823 
    1824        :param existing: ``_LoadElement`` that is already present. 
    1825 
    1826        """ 
    1827        # mapper inheritance loading requires fine-grained "block other 
    1828        # options" / "allow these options to be overridden" behaviors 
    1829        # see test_poly_loading.py 
    1830 
    1831        if replacement._reconcile_to_other: 
    1832            return existing 
    1833        elif replacement._reconcile_to_other is False: 
    1834            return replacement 
    1835        elif existing._reconcile_to_other: 
    1836            return replacement 
    1837        elif existing._reconcile_to_other is False: 
    1838            return existing 
    1839 
    1840        if existing is replacement: 
    1841            return replacement 
    1842        elif ( 
    1843            existing.strategy == replacement.strategy 
    1844            and existing.local_opts == replacement.local_opts 
    1845        ): 
    1846            return replacement 
    1847        elif replacement.is_opts_only: 
    1848            existing = existing._clone() 
    1849            existing.local_opts = existing.local_opts.union( 
    1850                replacement.local_opts 
    1851            ) 
    1852            existing._extra_criteria += replacement._extra_criteria 
    1853            return existing 
    1854        elif existing.is_opts_only: 
    1855            replacement = replacement._clone() 
    1856            replacement.local_opts = replacement.local_opts.union( 
    1857                existing.local_opts 
    1858            ) 
    1859            replacement._extra_criteria += existing._extra_criteria 
    1860            return replacement 
    1861        elif replacement.path.is_token: 
    1862            # use 'last one wins' logic for wildcard options.  this is also 
    1863            # kind of inconsistent vs. options that are specific paths which 
    1864            # will raise as below 
    1865            return replacement 
    1866 
    1867        raise sa_exc.InvalidRequestError( 
    1868            f"Loader strategies for {replacement.path} conflict" 
    1869        ) 
    1870 
    1871 
    1872class _AttributeStrategyLoad(_LoadElement): 
    1873    """Loader strategies against specific relationship or column paths. 
    1874 
    1875    e.g.:: 
    1876 
    1877        joinedload(User.addresses) 
    1878        defer(Order.name) 
    1879        selectinload(User.orders).lazyload(Order.items) 
    1880 
    1881    """ 
    1882 
    1883    __slots__ = ("_of_type", "_path_with_polymorphic_path") 
    1884 
    1885    __visit_name__ = "attribute_strategy_load_element" 
    1886 
    1887    _traverse_internals = _LoadElement._traverse_internals + [ 
    1888        ("_of_type", visitors.ExtendedInternalTraversal.dp_multi), 
    1889        ( 
    1890            "_path_with_polymorphic_path", 
    1891            visitors.ExtendedInternalTraversal.dp_has_cache_key, 
    1892        ), 
    1893    ] 
    1894 
    1895    _of_type: Union[Mapper[Any], AliasedInsp[Any], None] 
    1896    _path_with_polymorphic_path: Optional[PathRegistry] 
    1897 
    1898    is_class_strategy = False 
    1899    is_token_strategy = False 
    1900 
    1901    def _init_path( 
    1902        self, path, attr, wildcard_key, attr_group, raiseerr, extra_criteria 
    1903    ): 
    1904        assert attr is not None 
    1905        self._of_type = None 
    1906        self._path_with_polymorphic_path = None 
    1907        insp, _, prop = _parse_attr_argument(attr) 
    1908 
    1909        if insp.is_property: 
    1910            # direct property can be sent from internal strategy logic 
    1911            # that sets up specific loaders, such as 
    1912            # emit_lazyload->_lazyload_reverse 
    1913            # prop = found_property = attr 
    1914            prop = attr 
    1915            path = path[prop] 
    1916 
    1917            if path.has_entity: 
    1918                path = path.entity_path 
    1919            return path 
    1920 
    1921        elif not insp.is_attribute: 
    1922            # should not reach here; 
    1923            assert False 
    1924 
    1925        # here we assume we have user-passed InstrumentedAttribute 
    1926        if not orm_util._entity_corresponds_to_use_path_impl( 
    1927            path[-1], attr.parent 
    1928        ): 
    1929            if raiseerr: 
    1930                if attr_group and attr is not attr_group[0]: 
    1931                    raise sa_exc.ArgumentError( 
    1932                        "Can't apply wildcard ('*') or load_only() " 
    1933                        "loader option to multiple entities in the " 
    1934                        "same option. Use separate options per entity." 
    1935                    ) 
    1936                else: 
    1937                    _raise_for_does_not_link(path, str(attr), attr.parent) 
    1938            else: 
    1939                return None 
    1940 
    1941        # note the essential logic of this attribute was very different in 
    1942        # 1.4, where there were caching failures in e.g. 
    1943        # test_relationship_criteria.py::RelationshipCriteriaTest:: 
    1944        # test_selectinload_nested_criteria[True] if an existing 
    1945        # "_extra_criteria" on a Load object were replaced with that coming 
    1946        # from an attribute.   This appears to have been an artifact of how 
    1947        # _UnboundLoad / Load interacted together, which was opaque and 
    1948        # poorly defined. 
    1949        if extra_criteria: 
    1950            assert not attr._extra_criteria 
    1951            self._extra_criteria = extra_criteria 
    1952        else: 
    1953            self._extra_criteria = attr._extra_criteria 
    1954 
    1955        if getattr(attr, "_of_type", None): 
    1956            ac = attr._of_type 
    1957            ext_info = inspect(ac) 
    1958            self._of_type = ext_info 
    1959 
    1960            self._path_with_polymorphic_path = path.entity_path[prop] 
    1961 
    1962            path = path[prop][ext_info] 
    1963 
    1964        else: 
    1965            path = path[prop] 
    1966 
    1967        if path.has_entity: 
    1968            path = path.entity_path 
    1969 
    1970        return path 
    1971 
    1972    def _generate_extra_criteria(self, context): 
    1973        """Apply the current bound parameters in a QueryContext to the 
    1974        immediate "extra_criteria" stored with this Load object. 
    1975 
    1976        Load objects are typically pulled from the cached version of 
    1977        the statement from a QueryContext.  The statement currently being 
    1978        executed will have new values (and keys) for bound parameters in the 
    1979        extra criteria which need to be applied by loader strategies when 
    1980        they handle this criteria for a result set. 
    1981 
    1982        """ 
    1983 
    1984        assert ( 
    1985            self._extra_criteria 
    1986        ), "this should only be called if _extra_criteria is present" 
    1987 
    1988        orig_query = context.compile_state.select_statement 
    1989        current_query = context.query 
    1990 
    1991        # NOTE: while it seems like we should not do the "apply" operation 
    1992        # here if orig_query is current_query, skipping it in the "optimized" 
    1993        # case causes the query to be different from a cache key perspective, 
    1994        # because we are creating a copy of the criteria which is no longer 
    1995        # the same identity of the _extra_criteria in the loader option 
    1996        # itself.  cache key logic produces a different key for 
    1997        # (A, copy_of_A) vs. (A, A), because in the latter case it shortens 
    1998        # the second part of the key to just indicate on identity. 
    1999 
    2000        # if orig_query is current_query: 
    2001        # not cached yet.   just do the and_() 
    2002        #    return and_(*self._extra_criteria) 
    2003 
    2004        k1 = orig_query._generate_cache_key() 
    2005        k2 = current_query._generate_cache_key() 
    2006 
    2007        return k2._apply_params_to_element(k1, and_(*self._extra_criteria)) 
    2008 
    2009    def _set_of_type_info(self, context, current_path): 
    2010        assert self._path_with_polymorphic_path 
    2011 
    2012        pwpi = self._of_type 
    2013        assert pwpi 
    2014        if not pwpi.is_aliased_class: 
    2015            pwpi = inspect( 
    2016                orm_util.AliasedInsp._with_polymorphic_factory( 
    2017                    pwpi.mapper.base_mapper, 
    2018                    (pwpi.mapper,), 
    2019                    aliased=True, 
    2020                    _use_mapper_path=True, 
    2021                ) 
    2022            ) 
    2023        start_path = self._path_with_polymorphic_path 
    2024        if current_path: 
    2025            new_path = self._adjust_effective_path_for_current_path( 
    2026                start_path, current_path 
    2027            ) 
    2028            if new_path is None: 
    2029                return 
    2030            start_path = new_path 
    2031 
    2032        key = ("path_with_polymorphic", start_path.natural_path) 
    2033        if key in context: 
    2034            existing_aliased_insp = context[key] 
    2035            this_aliased_insp = pwpi 
    2036            new_aliased_insp = existing_aliased_insp._merge_with( 
    2037                this_aliased_insp 
    2038            ) 
    2039            context[key] = new_aliased_insp 
    2040        else: 
    2041            context[key] = pwpi 
    2042 
    2043    def _prepare_for_compile_state( 
    2044        self, 
    2045        parent_loader, 
    2046        compile_state, 
    2047        mapper_entities, 
    2048        reconciled_lead_entity, 
    2049        raiseerr, 
    2050    ): 
    2051        # _AttributeStrategyLoad 
    2052 
    2053        current_path = compile_state.current_path 
    2054        is_refresh = compile_state.compile_options._for_refresh_state 
    2055        assert not self.path.is_token 
    2056 
    2057        if is_refresh and not self.propagate_to_loaders: 
    2058            return [] 
    2059 
    2060        if self._of_type: 
    2061            # apply additional with_polymorphic alias that may have been 
    2062            # generated.  this has to happen even if this is a defaultload 
    2063            self._set_of_type_info(compile_state.attributes, current_path) 
    2064 
    2065        # omit setting loader attributes for a "defaultload" type of option 
    2066        if not self.strategy and not self.local_opts: 
    2067            return [] 
    2068 
    2069        if raiseerr and not reconciled_lead_entity: 
    2070            self._raise_for_no_match(parent_loader, mapper_entities) 
    2071 
    2072        if self.path.has_entity: 
    2073            effective_path = self.path.parent 
    2074        else: 
    2075            effective_path = self.path 
    2076 
    2077        if current_path: 
    2078            assert effective_path is not None 
    2079            effective_path = self._adjust_effective_path_for_current_path( 
    2080                effective_path, current_path 
    2081            ) 
    2082            if effective_path is None: 
    2083                return [] 
    2084 
    2085        return [("loader", cast(PathRegistry, effective_path).natural_path)] 
    2086 
    2087    def __getstate__(self): 
    2088        d = super().__getstate__() 
    2089 
    2090        # can't pickle this.  See 
    2091        # test_pickled.py -> test_lazyload_extra_criteria_not_supported 
    2092        # where we should be emitting a warning for the usual case where this 
    2093        # would be non-None 
    2094        d["_extra_criteria"] = () 
    2095 
    2096        if self._path_with_polymorphic_path: 
    2097            d["_path_with_polymorphic_path"] = ( 
    2098                self._path_with_polymorphic_path.serialize() 
    2099            ) 
    2100 
    2101        if self._of_type: 
    2102            if self._of_type.is_aliased_class: 
    2103                d["_of_type"] = None 
    2104            elif self._of_type.is_mapper: 
    2105                d["_of_type"] = self._of_type.class_ 
    2106            else: 
    2107                assert False, "unexpected object for _of_type" 
    2108 
    2109        return d 
    2110 
    2111    def __setstate__(self, state): 
    2112        super().__setstate__(state) 
    2113 
    2114        if state.get("_path_with_polymorphic_path", None): 
    2115            self._path_with_polymorphic_path = PathRegistry.deserialize( 
    2116                state["_path_with_polymorphic_path"] 
    2117            ) 
    2118        else: 
    2119            self._path_with_polymorphic_path = None 
    2120 
    2121        if state.get("_of_type", None): 
    2122            self._of_type = inspect(state["_of_type"]) 
    2123        else: 
    2124            self._of_type = None 
    2125 
    2126 
    2127class _TokenStrategyLoad(_LoadElement): 
    2128    """Loader strategies against wildcard attributes 
    2129 
    2130    e.g.:: 
    2131 
    2132        raiseload("*") 
    2133        Load(User).lazyload("*") 
    2134        defer("*") 
    2135        load_only(User.name, User.email)  # will create a defer('*') 
    2136        joinedload(User.addresses).raiseload("*") 
    2137 
    2138    """ 
    2139 
    2140    __visit_name__ = "token_strategy_load_element" 
    2141 
    2142    inherit_cache = True 
    2143    is_class_strategy = False 
    2144    is_token_strategy = True 
    2145 
    2146    def _init_path( 
    2147        self, path, attr, wildcard_key, attr_group, raiseerr, extra_criteria 
    2148    ): 
    2149        # assert isinstance(attr, str) or attr is None 
    2150        if attr is not None: 
    2151            default_token = attr.endswith(_DEFAULT_TOKEN) 
    2152            if attr.endswith(_WILDCARD_TOKEN) or default_token: 
    2153                if wildcard_key: 
    2154                    attr = f"{wildcard_key}:{attr}" 
    2155 
    2156                path = path.token(attr) 
    2157                return path 
    2158            else: 
    2159                raise sa_exc.ArgumentError( 
    2160                    "Strings are not accepted for attribute names in loader " 
    2161                    "options; please use class-bound attributes directly." 
    2162                ) 
    2163        return path 
    2164 
    2165    def _prepare_for_compile_state( 
    2166        self, 
    2167        parent_loader, 
    2168        compile_state, 
    2169        mapper_entities, 
    2170        reconciled_lead_entity, 
    2171        raiseerr, 
    2172    ): 
    2173        # _TokenStrategyLoad 
    2174 
    2175        current_path = compile_state.current_path 
    2176        is_refresh = compile_state.compile_options._for_refresh_state 
    2177 
    2178        assert self.path.is_token 
    2179 
    2180        if is_refresh and not self.propagate_to_loaders: 
    2181            return [] 
    2182 
    2183        # omit setting attributes for a "defaultload" type of option 
    2184        if not self.strategy and not self.local_opts: 
    2185            return [] 
    2186 
    2187        effective_path = self.path 
    2188        if reconciled_lead_entity: 
    2189            effective_path = PathRegistry.coerce( 
    2190                (reconciled_lead_entity,) + effective_path.path[1:] 
    2191            ) 
    2192 
    2193        if current_path: 
    2194            new_effective_path = self._adjust_effective_path_for_current_path( 
    2195                effective_path, current_path 
    2196            ) 
    2197            if new_effective_path is None: 
    2198                return [] 
    2199            effective_path = new_effective_path 
    2200 
    2201        # for a wildcard token, expand out the path we set 
    2202        # to encompass everything from the query entity on 
    2203        # forward.  not clear if this is necessary when current_path 
    2204        # is set. 
    2205 
    2206        return [ 
    2207            ("loader", natural_path) 
    2208            for natural_path in ( 
    2209                cast( 
    2210                    _TokenRegistry, effective_path 
    2211                )._generate_natural_for_superclasses() 
    2212            ) 
    2213        ] 
    2214 
    2215 
    2216class _ClassStrategyLoad(_LoadElement): 
    2217    """Loader strategies that deals with a class as a target, not 
    2218    an attribute path 
    2219 
    2220    e.g.:: 
    2221 
    2222        q = s.query(Person).options( 
    2223            selectin_polymorphic(Person, [Engineer, Manager]) 
    2224        ) 
    2225 
    2226    """ 
    2227 
    2228    inherit_cache = True 
    2229    is_class_strategy = True 
    2230    is_token_strategy = False 
    2231 
    2232    __visit_name__ = "class_strategy_load_element" 
    2233 
    2234    def _init_path( 
    2235        self, path, attr, wildcard_key, attr_group, raiseerr, extra_criteria 
    2236    ): 
    2237        return path 
    2238 
    2239    def _prepare_for_compile_state( 
    2240        self, 
    2241        parent_loader, 
    2242        compile_state, 
    2243        mapper_entities, 
    2244        reconciled_lead_entity, 
    2245        raiseerr, 
    2246    ): 
    2247        # _ClassStrategyLoad 
    2248 
    2249        current_path = compile_state.current_path 
    2250        is_refresh = compile_state.compile_options._for_refresh_state 
    2251 
    2252        if is_refresh and not self.propagate_to_loaders: 
    2253            return [] 
    2254 
    2255        # omit setting attributes for a "defaultload" type of option 
    2256        if not self.strategy and not self.local_opts: 
    2257            return [] 
    2258 
    2259        effective_path = self.path 
    2260 
    2261        if current_path: 
    2262            new_effective_path = self._adjust_effective_path_for_current_path( 
    2263                effective_path, current_path 
    2264            ) 
    2265            if new_effective_path is None: 
    2266                return [] 
    2267            effective_path = new_effective_path 
    2268 
    2269        return [("loader", effective_path.natural_path)] 
    2270 
    2271 
    2272def _generate_from_keys( 
    2273    meth: Callable[..., _AbstractLoad], 
    2274    keys: Tuple[_AttrType, ...], 
    2275    chained: bool, 
    2276    kw: Any, 
    2277) -> _AbstractLoad: 
    2278    lead_element: Optional[_AbstractLoad] = None 
    2279 
    2280    attr: Any 
    2281    for is_default, _keys in (True, keys[0:-1]), (False, keys[-1:]): 
    2282        for attr in _keys: 
    2283            if isinstance(attr, str): 
    2284                if attr.startswith("." + _WILDCARD_TOKEN): 
    2285                    util.warn_deprecated( 
    2286                        "The undocumented `.{WILDCARD}` format is " 
    2287                        "deprecated " 
    2288                        "and will be removed in a future version as " 
    2289                        "it is " 
    2290                        "believed to be unused. " 
    2291                        "If you have been using this functionality, " 
    2292                        "please " 
    2293                        "comment on Issue #4390 on the SQLAlchemy project " 
    2294                        "tracker.", 
    2295                        version="1.4", 
    2296                    ) 
    2297                    attr = attr[1:] 
    2298 
    2299                if attr == _WILDCARD_TOKEN: 
    2300                    if is_default: 
    2301                        raise sa_exc.ArgumentError( 
    2302                            "Wildcard token cannot be followed by " 
    2303                            "another entity", 
    2304                        ) 
    2305 
    2306                    if lead_element is None: 
    2307                        lead_element = _WildcardLoad() 
    2308 
    2309                    lead_element = meth(lead_element, _DEFAULT_TOKEN, **kw) 
    2310 
    2311                else: 
    2312                    raise sa_exc.ArgumentError( 
    2313                        "Strings are not accepted for attribute names in " 
    2314                        "loader options; please use class-bound " 
    2315                        "attributes directly.", 
    2316                    ) 
    2317            else: 
    2318                if lead_element is None: 
    2319                    _, lead_entity, _ = _parse_attr_argument(attr) 
    2320                    lead_element = Load(lead_entity) 
    2321 
    2322                if is_default: 
    2323                    if not chained: 
    2324                        lead_element = lead_element.defaultload(attr) 
    2325                    else: 
    2326                        lead_element = meth( 
    2327                            lead_element, attr, _is_chain=True, **kw 
    2328                        ) 
    2329                else: 
    2330                    lead_element = meth(lead_element, attr, **kw) 
    2331 
    2332    assert lead_element 
    2333    return lead_element 
    2334 
    2335 
    2336def _parse_attr_argument( 
    2337    attr: _AttrType, 
    2338) -> Tuple[InspectionAttr, _InternalEntityType[Any], MapperProperty[Any]]: 
    2339    """parse an attribute or wildcard argument to produce an 
    2340    :class:`._AbstractLoad` instance. 
    2341 
    2342    This is used by the standalone loader strategy functions like 
    2343    ``joinedload()``, ``defer()``, etc. to produce :class:`_orm.Load` or 
    2344    :class:`._WildcardLoad` objects. 
    2345 
    2346    """ 
    2347    try: 
    2348        # TODO: need to figure out this None thing being returned by 
    2349        # inspect(), it should not have None as an option in most cases 
    2350        # if at all 
    2351        insp: InspectionAttr = inspect(attr)  # type: ignore 
    2352    except sa_exc.NoInspectionAvailable as err: 
    2353        raise sa_exc.ArgumentError( 
    2354            "expected ORM mapped attribute for loader strategy argument" 
    2355        ) from err 
    2356 
    2357    lead_entity: _InternalEntityType[Any] 
    2358 
    2359    if insp_is_mapper_property(insp): 
    2360        lead_entity = insp.parent 
    2361        prop = insp 
    2362    elif insp_is_attribute(insp): 
    2363        lead_entity = insp.parent 
    2364        prop = insp.prop 
    2365    else: 
    2366        raise sa_exc.ArgumentError( 
    2367            "expected ORM mapped attribute for loader strategy argument" 
    2368        ) 
    2369 
    2370    return insp, lead_entity, prop 
    2371 
    2372 
    2373def loader_unbound_fn(fn: _FN) -> _FN: 
    2374    """decorator that applies docstrings between standalone loader functions 
    2375    and the loader methods on :class:`._AbstractLoad`. 
    2376 
    2377    """ 
    2378    bound_fn = getattr(_AbstractLoad, fn.__name__) 
    2379    fn_doc = bound_fn.__doc__ 
    2380    bound_fn.__doc__ = f"""Produce a new :class:`_orm.Load` object with the 
    2381:func:`_orm.{fn.__name__}` option applied. 
    2382 
    2383See :func:`_orm.{fn.__name__}` for usage examples. 
    2384 
    2385""" 
    2386 
    2387    fn.__doc__ = fn_doc 
    2388    return fn 
    2389 
    2390 
    2391def _expand_column_strategy_attrs( 
    2392    attrs: Tuple[_AttrType, ...], 
    2393) -> Tuple[_AttrType, ...]: 
    2394    return cast( 
    2395        "Tuple[_AttrType, ...]", 
    2396        tuple( 
    2397            a 
    2398            for attr in attrs 
    2399            for a in ( 
    2400                cast("QueryableAttribute[Any]", attr)._column_strategy_attrs() 
    2401                if hasattr(attr, "_column_strategy_attrs") 
    2402                else (attr,) 
    2403            ) 
    2404        ), 
    2405    ) 
    2406 
    2407 
    2408# standalone functions follow.  docstrings are filled in 
    2409# by the ``@loader_unbound_fn`` decorator. 
    2410 
    2411 
    2412@loader_unbound_fn 
    2413def contains_eager(*keys: _AttrType, **kw: Any) -> _AbstractLoad: 
    2414    return _generate_from_keys(Load.contains_eager, keys, True, kw) 
    2415 
    2416 
    2417@loader_unbound_fn 
    2418def load_only(*attrs: _AttrType, raiseload: bool = False) -> _AbstractLoad: 
    2419    # TODO: attrs against different classes.  we likely have to 
    2420    # add some extra state to Load of some kind 
    2421    attrs = _expand_column_strategy_attrs(attrs) 
    2422    _, lead_element, _ = _parse_attr_argument(attrs[0]) 
    2423    return Load(lead_element).load_only(*attrs, raiseload=raiseload) 
    2424 
    2425 
    2426@loader_unbound_fn 
    2427def joinedload(*keys: _AttrType, **kw: Any) -> _AbstractLoad: 
    2428    return _generate_from_keys(Load.joinedload, keys, False, kw) 
    2429 
    2430 
    2431@loader_unbound_fn 
    2432def subqueryload(*keys: _AttrType) -> _AbstractLoad: 
    2433    return _generate_from_keys(Load.subqueryload, keys, False, {}) 
    2434 
    2435 
    2436@loader_unbound_fn 
    2437def selectinload( 
    2438    *keys: _AttrType, recursion_depth: Optional[int] = None 
    2439) -> _AbstractLoad: 
    2440    return _generate_from_keys( 
    2441        Load.selectinload, keys, False, {"recursion_depth": recursion_depth} 
    2442    ) 
    2443 
    2444 
    2445@loader_unbound_fn 
    2446def lazyload(*keys: _AttrType) -> _AbstractLoad: 
    2447    return _generate_from_keys(Load.lazyload, keys, False, {}) 
    2448 
    2449 
    2450@loader_unbound_fn 
    2451def immediateload( 
    2452    *keys: _AttrType, recursion_depth: Optional[int] = None 
    2453) -> _AbstractLoad: 
    2454    return _generate_from_keys( 
    2455        Load.immediateload, keys, False, {"recursion_depth": recursion_depth} 
    2456    ) 
    2457 
    2458 
    2459@loader_unbound_fn 
    2460def noload(*keys: _AttrType) -> _AbstractLoad: 
    2461    return _generate_from_keys(Load.noload, keys, False, {}) 
    2462 
    2463 
    2464@loader_unbound_fn 
    2465def raiseload(*keys: _AttrType, **kw: Any) -> _AbstractLoad: 
    2466    return _generate_from_keys(Load.raiseload, keys, False, kw) 
    2467 
    2468 
    2469@loader_unbound_fn 
    2470def defaultload(*keys: _AttrType) -> _AbstractLoad: 
    2471    return _generate_from_keys(Load.defaultload, keys, False, {}) 
    2472 
    2473 
    2474@loader_unbound_fn 
    2475def defer(key: _AttrType, *, raiseload: bool = False) -> _AbstractLoad: 
    2476    if raiseload: 
    2477        kw = {"raiseload": raiseload} 
    2478    else: 
    2479        kw = {} 
    2480 
    2481    return _generate_from_keys(Load.defer, (key,), False, kw) 
    2482 
    2483 
    2484@loader_unbound_fn 
    2485def undefer(key: _AttrType) -> _AbstractLoad: 
    2486    return _generate_from_keys(Load.undefer, (key,), False, {}) 
    2487 
    2488 
    2489@loader_unbound_fn 
    2490def undefer_group(name: str) -> _AbstractLoad: 
    2491    element = _WildcardLoad() 
    2492    return element.undefer_group(name) 
    2493 
    2494 
    2495@loader_unbound_fn 
    2496def with_expression( 
    2497    key: _AttrType, expression: _ColumnExpressionArgument[Any] 
    2498) -> _AbstractLoad: 
    2499    return _generate_from_keys( 
    2500        Load.with_expression, (key,), False, {"expression": expression} 
    2501    ) 
    2502 
    2503 
    2504@loader_unbound_fn 
    2505def selectin_polymorphic( 
    2506    base_cls: _EntityType[Any], classes: Iterable[Type[Any]] 
    2507) -> _AbstractLoad: 
    2508    ul = Load(base_cls) 
    2509    return ul.selectin_polymorphic(classes) 
    2510 
    2511 
    2512def _raise_for_does_not_link(path, attrname, parent_entity): 
    2513    if len(path) > 1: 
    2514        path_is_of_type = path[-1].entity is not path[-2].mapper.class_ 
    2515        if insp_is_aliased_class(parent_entity): 
    2516            parent_entity_str = str(parent_entity) 
    2517        else: 
    2518            parent_entity_str = parent_entity.class_.__name__ 
    2519 
    2520        raise sa_exc.ArgumentError( 
    2521            f'ORM mapped entity or attribute "{attrname}" does not ' 
    2522            f'link from relationship "{path[-2]}%s".%s' 
    2523            % ( 
    2524                f".of_type({path[-1]})" if path_is_of_type else "", 
    2525                ( 
    2526                    "  Did you mean to use " 
    2527                    f'"{path[-2]}' 
    2528                    f'.of_type({parent_entity_str})" or "loadopt.options(' 
    2529                    f"selectin_polymorphic({path[-2].mapper.class_.__name__}, " 
    2530                    f'[{parent_entity_str}]), ...)" ?' 
    2531                    if not path_is_of_type 
    2532                    and not path[-1].is_aliased_class 
    2533                    and orm_util._entity_corresponds_to( 
    2534                        path.entity, inspect(parent_entity).mapper 
    2535                    ) 
    2536                    else "" 
    2537                ), 
    2538            ) 
    2539        ) 
    2540    else: 
    2541        raise sa_exc.ArgumentError( 
    2542            f'ORM mapped attribute "{attrname}" does not ' 
    2543            f'link mapped class "{path[-1]}"' 
    2544        )