| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568 |
- # orm/strategy_options.py
- # Copyright (C) 2005-2025 the SQLAlchemy authors and contributors
- # <see AUTHORS file>
- #
- # This module is part of SQLAlchemy and is released under
- # the MIT License: https://www.opensource.org/licenses/mit-license.php
- # mypy: allow-untyped-defs, allow-untyped-calls
- """ """
- from __future__ import annotations
- import typing
- from typing import Any
- from typing import Callable
- from typing import cast
- from typing import Dict
- from typing import Iterable
- from typing import Optional
- from typing import overload
- from typing import Sequence
- from typing import Tuple
- from typing import Type
- from typing import TypeVar
- from typing import Union
- from . import util as orm_util
- from ._typing import insp_is_aliased_class
- from ._typing import insp_is_attribute
- from ._typing import insp_is_mapper
- from ._typing import insp_is_mapper_property
- from .attributes import QueryableAttribute
- from .base import InspectionAttr
- from .interfaces import LoaderOption
- from .path_registry import _DEFAULT_TOKEN
- from .path_registry import _StrPathToken
- from .path_registry import _WILDCARD_TOKEN
- from .path_registry import AbstractEntityRegistry
- from .path_registry import path_is_property
- from .path_registry import PathRegistry
- from .path_registry import TokenRegistry
- from .util import _orm_full_deannotate
- from .util import AliasedInsp
- from .. import exc as sa_exc
- from .. import inspect
- from .. import util
- from ..sql import and_
- from ..sql import cache_key
- from ..sql import coercions
- from ..sql import roles
- from ..sql import traversals
- from ..sql import visitors
- from ..sql.base import _generative
- from ..util.typing import Final
- from ..util.typing import Literal
- from ..util.typing import Self
- _RELATIONSHIP_TOKEN: Final[Literal["relationship"]] = "relationship"
- _COLUMN_TOKEN: Final[Literal["column"]] = "column"
- _FN = TypeVar("_FN", bound="Callable[..., Any]")
- if typing.TYPE_CHECKING:
- from ._typing import _EntityType
- from ._typing import _InternalEntityType
- from .context import _MapperEntity
- from .context import ORMCompileState
- from .context import QueryContext
- from .interfaces import _StrategyKey
- from .interfaces import MapperProperty
- from .interfaces import ORMOption
- from .mapper import Mapper
- from .path_registry import _PathRepresentation
- from ..sql._typing import _ColumnExpressionArgument
- from ..sql._typing import _FromClauseArgument
- from ..sql.cache_key import _CacheKeyTraversalType
- from ..sql.cache_key import CacheKey
- _AttrType = Union[Literal["*"], "QueryableAttribute[Any]"]
- _WildcardKeyType = Literal["relationship", "column"]
- _StrategySpec = Dict[str, Any]
- _OptsType = Dict[str, Any]
- _AttrGroupType = Tuple[_AttrType, ...]
- class _AbstractLoad(traversals.GenerativeOnTraversal, LoaderOption):
- __slots__ = ("propagate_to_loaders",)
- _is_strategy_option = True
- propagate_to_loaders: bool
- def contains_eager(
- self,
- attr: _AttrType,
- alias: Optional[_FromClauseArgument] = None,
- _is_chain: bool = False,
- _propagate_to_loaders: bool = False,
- ) -> Self:
- r"""Indicate that the given attribute should be eagerly loaded from
- columns stated manually in the query.
- This function is part of the :class:`_orm.Load` interface and supports
- both method-chained and standalone operation.
- The option is used in conjunction with an explicit join that loads
- the desired rows, i.e.::
- sess.query(Order).join(Order.user).options(contains_eager(Order.user))
- The above query would join from the ``Order`` entity to its related
- ``User`` entity, and the returned ``Order`` objects would have the
- ``Order.user`` attribute pre-populated.
- It may also be used for customizing the entries in an eagerly loaded
- collection; queries will normally want to use the
- :ref:`orm_queryguide_populate_existing` execution option assuming the
- primary collection of parent objects may already have been loaded::
- sess.query(User).join(User.addresses).filter(
- Address.email_address.like("%@aol.com")
- ).options(contains_eager(User.addresses)).populate_existing()
- See the section :ref:`contains_eager` for complete usage details.
- .. seealso::
- :ref:`loading_toplevel`
- :ref:`contains_eager`
- """
- if alias is not None:
- if not isinstance(alias, str):
- coerced_alias = coercions.expect(roles.FromClauseRole, alias)
- else:
- util.warn_deprecated(
- "Passing a string name for the 'alias' argument to "
- "'contains_eager()` is deprecated, and will not work in a "
- "future release. Please use a sqlalchemy.alias() or "
- "sqlalchemy.orm.aliased() construct.",
- version="1.4",
- )
- coerced_alias = alias
- elif getattr(attr, "_of_type", None):
- assert isinstance(attr, QueryableAttribute)
- ot: Optional[_InternalEntityType[Any]] = inspect(attr._of_type)
- assert ot is not None
- coerced_alias = ot.selectable
- else:
- coerced_alias = None
- cloned = self._set_relationship_strategy(
- attr,
- {"lazy": "joined"},
- propagate_to_loaders=_propagate_to_loaders,
- opts={"eager_from_alias": coerced_alias},
- _reconcile_to_other=True if _is_chain else None,
- )
- return cloned
- def load_only(self, *attrs: _AttrType, raiseload: bool = False) -> Self:
- r"""Indicate that for a particular entity, only the given list
- of column-based attribute names should be loaded; all others will be
- deferred.
- This function is part of the :class:`_orm.Load` interface and supports
- both method-chained and standalone operation.
- Example - given a class ``User``, load only the ``name`` and
- ``fullname`` attributes::
- session.query(User).options(load_only(User.name, User.fullname))
- Example - given a relationship ``User.addresses -> Address``, specify
- subquery loading for the ``User.addresses`` collection, but on each
- ``Address`` object load only the ``email_address`` attribute::
- session.query(User).options(
- subqueryload(User.addresses).load_only(Address.email_address)
- )
- For a statement that has multiple entities,
- the lead entity can be
- specifically referred to using the :class:`_orm.Load` constructor::
- stmt = (
- select(User, Address)
- .join(User.addresses)
- .options(
- Load(User).load_only(User.name, User.fullname),
- Load(Address).load_only(Address.email_address),
- )
- )
- When used together with the
- :ref:`populate_existing <orm_queryguide_populate_existing>`
- execution option only the attributes listed will be refreshed.
- :param \*attrs: Attributes to be loaded, all others will be deferred.
- :param raiseload: raise :class:`.InvalidRequestError` rather than
- lazy loading a value when a deferred attribute is accessed. Used
- to prevent unwanted SQL from being emitted.
- .. versionadded:: 2.0
- .. seealso::
- :ref:`orm_queryguide_column_deferral` - in the
- :ref:`queryguide_toplevel`
- :param \*attrs: Attributes to be loaded, all others will be deferred.
- :param raiseload: raise :class:`.InvalidRequestError` rather than
- lazy loading a value when a deferred attribute is accessed. Used
- to prevent unwanted SQL from being emitted.
- .. versionadded:: 2.0
- """
- cloned = self._set_column_strategy(
- _expand_column_strategy_attrs(attrs),
- {"deferred": False, "instrument": True},
- )
- wildcard_strategy = {"deferred": True, "instrument": True}
- if raiseload:
- wildcard_strategy["raiseload"] = True
- cloned = cloned._set_column_strategy(
- ("*",),
- wildcard_strategy,
- )
- return cloned
- def joinedload(
- self,
- attr: _AttrType,
- innerjoin: Optional[bool] = None,
- ) -> Self:
- """Indicate that the given attribute should be loaded using joined
- eager loading.
- This function is part of the :class:`_orm.Load` interface and supports
- both method-chained and standalone operation.
- examples::
- # joined-load the "orders" collection on "User"
- select(User).options(joinedload(User.orders))
- # joined-load Order.items and then Item.keywords
- select(Order).options(joinedload(Order.items).joinedload(Item.keywords))
- # lazily load Order.items, but when Items are loaded,
- # joined-load the keywords collection
- select(Order).options(lazyload(Order.items).joinedload(Item.keywords))
- :param innerjoin: if ``True``, indicates that the joined eager load
- should use an inner join instead of the default of left outer join::
- select(Order).options(joinedload(Order.user, innerjoin=True))
- In order to chain multiple eager joins together where some may be
- OUTER and others INNER, right-nested joins are used to link them::
- select(A).options(
- joinedload(A.bs, innerjoin=False).joinedload(B.cs, innerjoin=True)
- )
- The above query, linking A.bs via "outer" join and B.cs via "inner"
- join would render the joins as "a LEFT OUTER JOIN (b JOIN c)". When
- using older versions of SQLite (< 3.7.16), this form of JOIN is
- translated to use full subqueries as this syntax is otherwise not
- directly supported.
- The ``innerjoin`` flag can also be stated with the term ``"unnested"``.
- This indicates that an INNER JOIN should be used, *unless* the join
- is linked to a LEFT OUTER JOIN to the left, in which case it
- will render as LEFT OUTER JOIN. For example, supposing ``A.bs``
- is an outerjoin::
- select(A).options(joinedload(A.bs).joinedload(B.cs, innerjoin="unnested"))
- The above join will render as "a LEFT OUTER JOIN b LEFT OUTER JOIN c",
- rather than as "a LEFT OUTER JOIN (b JOIN c)".
- .. note:: The "unnested" flag does **not** affect the JOIN rendered
- from a many-to-many association table, e.g. a table configured as
- :paramref:`_orm.relationship.secondary`, to the target table; for
- correctness of results, these joins are always INNER and are
- therefore right-nested if linked to an OUTER join.
- .. note::
- The joins produced by :func:`_orm.joinedload` are **anonymously
- aliased**. The criteria by which the join proceeds cannot be
- modified, nor can the ORM-enabled :class:`_sql.Select` or legacy
- :class:`_query.Query` refer to these joins in any way, including
- ordering. See :ref:`zen_of_eager_loading` for further detail.
- To produce a specific SQL JOIN which is explicitly available, use
- :meth:`_sql.Select.join` and :meth:`_query.Query.join`. To combine
- explicit JOINs with eager loading of collections, use
- :func:`_orm.contains_eager`; see :ref:`contains_eager`.
- .. seealso::
- :ref:`loading_toplevel`
- :ref:`joined_eager_loading`
- """ # noqa: E501
- loader = self._set_relationship_strategy(
- attr,
- {"lazy": "joined"},
- opts=(
- {"innerjoin": innerjoin}
- if innerjoin is not None
- else util.EMPTY_DICT
- ),
- )
- return loader
- def subqueryload(self, attr: _AttrType) -> Self:
- """Indicate that the given attribute should be loaded using
- subquery eager loading.
- This function is part of the :class:`_orm.Load` interface and supports
- both method-chained and standalone operation.
- examples::
- # subquery-load the "orders" collection on "User"
- select(User).options(subqueryload(User.orders))
- # subquery-load Order.items and then Item.keywords
- select(Order).options(
- subqueryload(Order.items).subqueryload(Item.keywords)
- )
- # lazily load Order.items, but when Items are loaded,
- # subquery-load the keywords collection
- select(Order).options(lazyload(Order.items).subqueryload(Item.keywords))
- .. seealso::
- :ref:`loading_toplevel`
- :ref:`subquery_eager_loading`
- """
- return self._set_relationship_strategy(attr, {"lazy": "subquery"})
- def selectinload(
- self,
- attr: _AttrType,
- recursion_depth: Optional[int] = None,
- ) -> Self:
- """Indicate that the given attribute should be loaded using
- SELECT IN eager loading.
- This function is part of the :class:`_orm.Load` interface and supports
- both method-chained and standalone operation.
- examples::
- # selectin-load the "orders" collection on "User"
- select(User).options(selectinload(User.orders))
- # selectin-load Order.items and then Item.keywords
- select(Order).options(
- selectinload(Order.items).selectinload(Item.keywords)
- )
- # lazily load Order.items, but when Items are loaded,
- # selectin-load the keywords collection
- select(Order).options(lazyload(Order.items).selectinload(Item.keywords))
- :param recursion_depth: optional int; when set to a positive integer
- in conjunction with a self-referential relationship,
- indicates "selectin" loading will continue that many levels deep
- automatically until no items are found.
- .. note:: The :paramref:`_orm.selectinload.recursion_depth` option
- currently supports only self-referential relationships. There
- is not yet an option to automatically traverse recursive structures
- with more than one relationship involved.
- Additionally, the :paramref:`_orm.selectinload.recursion_depth`
- parameter is new and experimental and should be treated as "alpha"
- status for the 2.0 series.
- .. versionadded:: 2.0 added
- :paramref:`_orm.selectinload.recursion_depth`
- .. seealso::
- :ref:`loading_toplevel`
- :ref:`selectin_eager_loading`
- """
- return self._set_relationship_strategy(
- attr,
- {"lazy": "selectin"},
- opts={"recursion_depth": recursion_depth},
- )
- def lazyload(self, attr: _AttrType) -> Self:
- """Indicate that the given attribute should be loaded using "lazy"
- loading.
- This function is part of the :class:`_orm.Load` interface and supports
- both method-chained and standalone operation.
- .. seealso::
- :ref:`loading_toplevel`
- :ref:`lazy_loading`
- """
- return self._set_relationship_strategy(attr, {"lazy": "select"})
- def immediateload(
- self,
- attr: _AttrType,
- recursion_depth: Optional[int] = None,
- ) -> Self:
- """Indicate that the given attribute should be loaded using
- an immediate load with a per-attribute SELECT statement.
- The load is achieved using the "lazyloader" strategy and does not
- fire off any additional eager loaders.
- The :func:`.immediateload` option is superseded in general
- by the :func:`.selectinload` option, which performs the same task
- more efficiently by emitting a SELECT for all loaded objects.
- This function is part of the :class:`_orm.Load` interface and supports
- both method-chained and standalone operation.
- :param recursion_depth: optional int; when set to a positive integer
- in conjunction with a self-referential relationship,
- indicates "selectin" loading will continue that many levels deep
- automatically until no items are found.
- .. note:: The :paramref:`_orm.immediateload.recursion_depth` option
- currently supports only self-referential relationships. There
- is not yet an option to automatically traverse recursive structures
- with more than one relationship involved.
- .. warning:: This parameter is new and experimental and should be
- treated as "alpha" status
- .. versionadded:: 2.0 added
- :paramref:`_orm.immediateload.recursion_depth`
- .. seealso::
- :ref:`loading_toplevel`
- :ref:`selectin_eager_loading`
- """
- loader = self._set_relationship_strategy(
- attr,
- {"lazy": "immediate"},
- opts={"recursion_depth": recursion_depth},
- )
- return loader
- def noload(self, attr: _AttrType) -> Self:
- """Indicate that the given relationship attribute should remain
- unloaded.
- The relationship attribute will return ``None`` when accessed without
- producing any loading effect.
- This function is part of the :class:`_orm.Load` interface and supports
- both method-chained and standalone operation.
- :func:`_orm.noload` applies to :func:`_orm.relationship` attributes
- only.
- .. legacy:: The :func:`_orm.noload` option is **legacy**. As it
- forces collections to be empty, which invariably leads to
- non-intuitive and difficult to predict results. There are no
- legitimate uses for this option in modern SQLAlchemy.
- .. seealso::
- :ref:`loading_toplevel`
- """
- return self._set_relationship_strategy(attr, {"lazy": "noload"})
- def raiseload(self, attr: _AttrType, sql_only: bool = False) -> Self:
- """Indicate that the given attribute should raise an error if accessed.
- A relationship attribute configured with :func:`_orm.raiseload` will
- raise an :exc:`~sqlalchemy.exc.InvalidRequestError` upon access. The
- typical way this is useful is when an application is attempting to
- ensure that all relationship attributes that are accessed in a
- particular context would have been already loaded via eager loading.
- Instead of having to read through SQL logs to ensure lazy loads aren't
- occurring, this strategy will cause them to raise immediately.
- :func:`_orm.raiseload` applies to :func:`_orm.relationship` attributes
- only. In order to apply raise-on-SQL behavior to a column-based
- attribute, use the :paramref:`.orm.defer.raiseload` parameter on the
- :func:`.defer` loader option.
- :param sql_only: if True, raise only if the lazy load would emit SQL,
- but not if it is only checking the identity map, or determining that
- the related value should just be None due to missing keys. When False,
- the strategy will raise for all varieties of relationship loading.
- This function is part of the :class:`_orm.Load` interface and supports
- both method-chained and standalone operation.
- .. seealso::
- :ref:`loading_toplevel`
- :ref:`prevent_lazy_with_raiseload`
- :ref:`orm_queryguide_deferred_raiseload`
- """
- return self._set_relationship_strategy(
- attr, {"lazy": "raise_on_sql" if sql_only else "raise"}
- )
- def defaultload(self, attr: _AttrType) -> Self:
- """Indicate an attribute should load using its predefined loader style.
- The behavior of this loading option is to not change the current
- loading style of the attribute, meaning that the previously configured
- one is used or, if no previous style was selected, the default
- loading will be used.
- This method is used to link to other loader options further into
- a chain of attributes without altering the loader style of the links
- along the chain. For example, to set joined eager loading for an
- element of an element::
- session.query(MyClass).options(
- defaultload(MyClass.someattribute).joinedload(
- MyOtherClass.someotherattribute
- )
- )
- :func:`.defaultload` is also useful for setting column-level options on
- a related class, namely that of :func:`.defer` and :func:`.undefer`::
- session.scalars(
- select(MyClass).options(
- defaultload(MyClass.someattribute)
- .defer("some_column")
- .undefer("some_other_column")
- )
- )
- .. seealso::
- :ref:`orm_queryguide_relationship_sub_options`
- :meth:`_orm.Load.options`
- """
- return self._set_relationship_strategy(attr, None)
- def defer(self, key: _AttrType, raiseload: bool = False) -> Self:
- r"""Indicate that the given column-oriented attribute should be
- deferred, e.g. not loaded until accessed.
- This function is part of the :class:`_orm.Load` interface and supports
- both method-chained and standalone operation.
- e.g.::
- from sqlalchemy.orm import defer
- session.query(MyClass).options(
- defer(MyClass.attribute_one), defer(MyClass.attribute_two)
- )
- To specify a deferred load of an attribute on a related class,
- the path can be specified one token at a time, specifying the loading
- style for each link along the chain. To leave the loading style
- for a link unchanged, use :func:`_orm.defaultload`::
- session.query(MyClass).options(
- defaultload(MyClass.someattr).defer(RelatedClass.some_column)
- )
- Multiple deferral options related to a relationship can be bundled
- at once using :meth:`_orm.Load.options`::
- select(MyClass).options(
- defaultload(MyClass.someattr).options(
- defer(RelatedClass.some_column),
- defer(RelatedClass.some_other_column),
- defer(RelatedClass.another_column),
- )
- )
- :param key: Attribute to be deferred.
- :param raiseload: raise :class:`.InvalidRequestError` rather than
- lazy loading a value when the deferred attribute is accessed. Used
- to prevent unwanted SQL from being emitted.
- .. versionadded:: 1.4
- .. seealso::
- :ref:`orm_queryguide_column_deferral` - in the
- :ref:`queryguide_toplevel`
- :func:`_orm.load_only`
- :func:`_orm.undefer`
- """
- strategy = {"deferred": True, "instrument": True}
- if raiseload:
- strategy["raiseload"] = True
- return self._set_column_strategy(
- _expand_column_strategy_attrs((key,)), strategy
- )
- def undefer(self, key: _AttrType) -> Self:
- r"""Indicate that the given column-oriented attribute should be
- undeferred, e.g. specified within the SELECT statement of the entity
- as a whole.
- The column being undeferred is typically set up on the mapping as a
- :func:`.deferred` attribute.
- This function is part of the :class:`_orm.Load` interface and supports
- both method-chained and standalone operation.
- Examples::
- # undefer two columns
- session.query(MyClass).options(
- undefer(MyClass.col1), undefer(MyClass.col2)
- )
- # undefer all columns specific to a single class using Load + *
- session.query(MyClass, MyOtherClass).options(Load(MyClass).undefer("*"))
- # undefer a column on a related object
- select(MyClass).options(defaultload(MyClass.items).undefer(MyClass.text))
- :param key: Attribute to be undeferred.
- .. seealso::
- :ref:`orm_queryguide_column_deferral` - in the
- :ref:`queryguide_toplevel`
- :func:`_orm.defer`
- :func:`_orm.undefer_group`
- """ # noqa: E501
- return self._set_column_strategy(
- _expand_column_strategy_attrs((key,)),
- {"deferred": False, "instrument": True},
- )
- def undefer_group(self, name: str) -> Self:
- """Indicate that columns within the given deferred group name should be
- undeferred.
- The columns being undeferred are set up on the mapping as
- :func:`.deferred` attributes and include a "group" name.
- E.g::
- session.query(MyClass).options(undefer_group("large_attrs"))
- To undefer a group of attributes on a related entity, the path can be
- spelled out using relationship loader options, such as
- :func:`_orm.defaultload`::
- select(MyClass).options(
- defaultload("someattr").undefer_group("large_attrs")
- )
- .. seealso::
- :ref:`orm_queryguide_column_deferral` - in the
- :ref:`queryguide_toplevel`
- :func:`_orm.defer`
- :func:`_orm.undefer`
- """
- return self._set_column_strategy(
- (_WILDCARD_TOKEN,), None, {f"undefer_group_{name}": True}
- )
- def with_expression(
- self,
- key: _AttrType,
- expression: _ColumnExpressionArgument[Any],
- ) -> Self:
- r"""Apply an ad-hoc SQL expression to a "deferred expression"
- attribute.
- This option is used in conjunction with the
- :func:`_orm.query_expression` mapper-level construct that indicates an
- attribute which should be the target of an ad-hoc SQL expression.
- E.g.::
- stmt = select(SomeClass).options(
- with_expression(SomeClass.x_y_expr, SomeClass.x + SomeClass.y)
- )
- .. versionadded:: 1.2
- :param key: Attribute to be populated
- :param expr: SQL expression to be applied to the attribute.
- .. seealso::
- :ref:`orm_queryguide_with_expression` - background and usage
- examples
- """
- expression = _orm_full_deannotate(
- coercions.expect(roles.LabeledColumnExprRole, expression)
- )
- return self._set_column_strategy(
- (key,), {"query_expression": True}, extra_criteria=(expression,)
- )
- def selectin_polymorphic(self, classes: Iterable[Type[Any]]) -> Self:
- """Indicate an eager load should take place for all attributes
- specific to a subclass.
- This uses an additional SELECT with IN against all matched primary
- key values, and is the per-query analogue to the ``"selectin"``
- setting on the :paramref:`.mapper.polymorphic_load` parameter.
- .. versionadded:: 1.2
- .. seealso::
- :ref:`polymorphic_selectin`
- """
- self = self._set_class_strategy(
- {"selectinload_polymorphic": True},
- opts={
- "entities": tuple(
- sorted((inspect(cls) for cls in classes), key=id)
- )
- },
- )
- return self
- @overload
- def _coerce_strat(self, strategy: _StrategySpec) -> _StrategyKey: ...
- @overload
- def _coerce_strat(self, strategy: Literal[None]) -> None: ...
- def _coerce_strat(
- self, strategy: Optional[_StrategySpec]
- ) -> Optional[_StrategyKey]:
- if strategy is not None:
- strategy_key = tuple(sorted(strategy.items()))
- else:
- strategy_key = None
- return strategy_key
- @_generative
- def _set_relationship_strategy(
- self,
- attr: _AttrType,
- strategy: Optional[_StrategySpec],
- propagate_to_loaders: bool = True,
- opts: Optional[_OptsType] = None,
- _reconcile_to_other: Optional[bool] = None,
- ) -> Self:
- strategy_key = self._coerce_strat(strategy)
- self._clone_for_bind_strategy(
- (attr,),
- strategy_key,
- _RELATIONSHIP_TOKEN,
- opts=opts,
- propagate_to_loaders=propagate_to_loaders,
- reconcile_to_other=_reconcile_to_other,
- )
- return self
- @_generative
- def _set_column_strategy(
- self,
- attrs: Tuple[_AttrType, ...],
- strategy: Optional[_StrategySpec],
- opts: Optional[_OptsType] = None,
- extra_criteria: Optional[Tuple[Any, ...]] = None,
- ) -> Self:
- strategy_key = self._coerce_strat(strategy)
- self._clone_for_bind_strategy(
- attrs,
- strategy_key,
- _COLUMN_TOKEN,
- opts=opts,
- attr_group=attrs,
- extra_criteria=extra_criteria,
- )
- return self
- @_generative
- def _set_generic_strategy(
- self,
- attrs: Tuple[_AttrType, ...],
- strategy: _StrategySpec,
- _reconcile_to_other: Optional[bool] = None,
- ) -> Self:
- strategy_key = self._coerce_strat(strategy)
- self._clone_for_bind_strategy(
- attrs,
- strategy_key,
- None,
- propagate_to_loaders=True,
- reconcile_to_other=_reconcile_to_other,
- )
- return self
- @_generative
- def _set_class_strategy(
- self, strategy: _StrategySpec, opts: _OptsType
- ) -> Self:
- strategy_key = self._coerce_strat(strategy)
- self._clone_for_bind_strategy(None, strategy_key, None, opts=opts)
- return self
- def _apply_to_parent(self, parent: Load) -> None:
- """apply this :class:`_orm._AbstractLoad` object as a sub-option o
- a :class:`_orm.Load` object.
- Implementation is provided by subclasses.
- """
- raise NotImplementedError()
- def options(self, *opts: _AbstractLoad) -> Self:
- r"""Apply a series of options as sub-options to this
- :class:`_orm._AbstractLoad` object.
- Implementation is provided by subclasses.
- """
- raise NotImplementedError()
- def _clone_for_bind_strategy(
- self,
- attrs: Optional[Tuple[_AttrType, ...]],
- strategy: Optional[_StrategyKey],
- wildcard_key: Optional[_WildcardKeyType],
- opts: Optional[_OptsType] = None,
- attr_group: Optional[_AttrGroupType] = None,
- propagate_to_loaders: bool = True,
- reconcile_to_other: Optional[bool] = None,
- extra_criteria: Optional[Tuple[Any, ...]] = None,
- ) -> Self:
- raise NotImplementedError()
- def process_compile_state_replaced_entities(
- self,
- compile_state: ORMCompileState,
- mapper_entities: Sequence[_MapperEntity],
- ) -> None:
- if not compile_state.compile_options._enable_eagerloads:
- return
- # process is being run here so that the options given are validated
- # against what the lead entities were, as well as to accommodate
- # for the entities having been replaced with equivalents
- self._process(
- compile_state,
- mapper_entities,
- not bool(compile_state.current_path),
- )
- def process_compile_state(self, compile_state: ORMCompileState) -> None:
- if not compile_state.compile_options._enable_eagerloads:
- return
- self._process(
- compile_state,
- compile_state._lead_mapper_entities,
- not bool(compile_state.current_path)
- and not compile_state.compile_options._for_refresh_state,
- )
- def _process(
- self,
- compile_state: ORMCompileState,
- mapper_entities: Sequence[_MapperEntity],
- raiseerr: bool,
- ) -> None:
- """implemented by subclasses"""
- raise NotImplementedError()
- @classmethod
- def _chop_path(
- cls,
- to_chop: _PathRepresentation,
- path: PathRegistry,
- debug: bool = False,
- ) -> Optional[_PathRepresentation]:
- i = -1
- for i, (c_token, p_token) in enumerate(
- zip(to_chop, path.natural_path)
- ):
- if isinstance(c_token, str):
- if i == 0 and (
- c_token.endswith(f":{_DEFAULT_TOKEN}")
- or c_token.endswith(f":{_WILDCARD_TOKEN}")
- ):
- return to_chop
- elif (
- c_token != f"{_RELATIONSHIP_TOKEN}:{_WILDCARD_TOKEN}"
- and c_token != p_token.key # type: ignore
- ):
- return None
- if c_token is p_token:
- continue
- elif (
- isinstance(c_token, InspectionAttr)
- and insp_is_mapper(c_token)
- and insp_is_mapper(p_token)
- and c_token.isa(p_token)
- ):
- continue
- else:
- return None
- return to_chop[i + 1 :]
- class Load(_AbstractLoad):
- """Represents loader options which modify the state of a
- ORM-enabled :class:`_sql.Select` or a legacy :class:`_query.Query` in
- order to affect how various mapped attributes are loaded.
- The :class:`_orm.Load` object is in most cases used implicitly behind the
- scenes when one makes use of a query option like :func:`_orm.joinedload`,
- :func:`_orm.defer`, or similar. It typically is not instantiated directly
- except for in some very specific cases.
- .. seealso::
- :ref:`orm_queryguide_relationship_per_entity_wildcard` - illustrates an
- example where direct use of :class:`_orm.Load` may be useful
- """
- __slots__ = (
- "path",
- "context",
- "additional_source_entities",
- )
- _traverse_internals = [
- ("path", visitors.ExtendedInternalTraversal.dp_has_cache_key),
- (
- "context",
- visitors.InternalTraversal.dp_has_cache_key_list,
- ),
- ("propagate_to_loaders", visitors.InternalTraversal.dp_boolean),
- (
- "additional_source_entities",
- visitors.InternalTraversal.dp_has_cache_key_list,
- ),
- ]
- _cache_key_traversal = None
- path: PathRegistry
- context: Tuple[_LoadElement, ...]
- additional_source_entities: Tuple[_InternalEntityType[Any], ...]
- def __init__(self, entity: _EntityType[Any]):
- insp = cast("Union[Mapper[Any], AliasedInsp[Any]]", inspect(entity))
- insp._post_inspect
- self.path = insp._path_registry
- self.context = ()
- self.propagate_to_loaders = False
- self.additional_source_entities = ()
- def __str__(self) -> str:
- return f"Load({self.path[0]})"
- @classmethod
- def _construct_for_existing_path(
- cls, path: AbstractEntityRegistry
- ) -> Load:
- load = cls.__new__(cls)
- load.path = path
- load.context = ()
- load.propagate_to_loaders = False
- load.additional_source_entities = ()
- return load
- def _adapt_cached_option_to_uncached_option(
- self, context: QueryContext, uncached_opt: ORMOption
- ) -> ORMOption:
- if uncached_opt is self:
- return self
- return self._adjust_for_extra_criteria(context)
- def _prepend_path(self, path: PathRegistry) -> Load:
- cloned = self._clone()
- cloned.context = tuple(
- element._prepend_path(path) for element in self.context
- )
- return cloned
- def _adjust_for_extra_criteria(self, context: QueryContext) -> Load:
- """Apply the current bound parameters in a QueryContext to all
- occurrences "extra_criteria" stored within this ``Load`` object,
- returning a new instance of this ``Load`` object.
- """
- # avoid generating cache keys for the queries if we don't
- # actually have any extra_criteria options, which is the
- # common case
- for value in self.context:
- if value._extra_criteria:
- break
- else:
- return self
- replacement_cache_key = context.user_passed_query._generate_cache_key()
- if replacement_cache_key is None:
- return self
- orig_query = context.compile_state.select_statement
- orig_cache_key = orig_query._generate_cache_key()
- assert orig_cache_key is not None
- def process(
- opt: _LoadElement,
- replacement_cache_key: CacheKey,
- orig_cache_key: CacheKey,
- ) -> _LoadElement:
- cloned_opt = opt._clone()
- cloned_opt._extra_criteria = tuple(
- replacement_cache_key._apply_params_to_element(
- orig_cache_key, crit
- )
- for crit in cloned_opt._extra_criteria
- )
- return cloned_opt
- cloned = self._clone()
- cloned.context = tuple(
- (
- process(value, replacement_cache_key, orig_cache_key)
- if value._extra_criteria
- else value
- )
- for value in self.context
- )
- return cloned
- def _reconcile_query_entities_with_us(self, mapper_entities, raiseerr):
- """called at process time to allow adjustment of the root
- entity inside of _LoadElement objects.
- """
- path = self.path
- for ent in mapper_entities:
- ezero = ent.entity_zero
- if ezero and orm_util._entity_corresponds_to(
- # technically this can be a token also, but this is
- # safe to pass to _entity_corresponds_to()
- ezero,
- cast("_InternalEntityType[Any]", path[0]),
- ):
- return ezero
- return None
- def _process(
- self,
- compile_state: ORMCompileState,
- mapper_entities: Sequence[_MapperEntity],
- raiseerr: bool,
- ) -> None:
- reconciled_lead_entity = self._reconcile_query_entities_with_us(
- mapper_entities, raiseerr
- )
- # if the context has a current path, this is a lazy load
- has_current_path = bool(compile_state.compile_options._current_path)
- for loader in self.context:
- # issue #11292
- # historically, propagate_to_loaders was only considered at
- # object loading time, whether or not to carry along options
- # onto an object's loaded state where it would be used by lazyload.
- # however, the defaultload() option needs to propagate in case
- # its sub-options propagate_to_loaders, but its sub-options
- # that dont propagate should not be applied for lazy loaders.
- # so we check again
- if has_current_path and not loader.propagate_to_loaders:
- continue
- loader.process_compile_state(
- self,
- compile_state,
- mapper_entities,
- reconciled_lead_entity,
- raiseerr,
- )
- def _apply_to_parent(self, parent: Load) -> None:
- """apply this :class:`_orm.Load` object as a sub-option of another
- :class:`_orm.Load` object.
- This method is used by the :meth:`_orm.Load.options` method.
- """
- cloned = self._generate()
- assert cloned.propagate_to_loaders == self.propagate_to_loaders
- if not any(
- orm_util._entity_corresponds_to_use_path_impl(
- elem, cloned.path.odd_element(0)
- )
- for elem in (parent.path.odd_element(-1),)
- + parent.additional_source_entities
- ):
- if len(cloned.path) > 1:
- attrname = cloned.path[1]
- parent_entity = cloned.path[0]
- else:
- attrname = cloned.path[0]
- parent_entity = cloned.path[0]
- _raise_for_does_not_link(parent.path, attrname, parent_entity)
- cloned.path = PathRegistry.coerce(parent.path[0:-1] + cloned.path[:])
- if self.context:
- cloned.context = tuple(
- value._prepend_path_from(parent) for value in self.context
- )
- if cloned.context:
- parent.context += cloned.context
- parent.additional_source_entities += (
- cloned.additional_source_entities
- )
- @_generative
- def options(self, *opts: _AbstractLoad) -> Self:
- r"""Apply a series of options as sub-options to this
- :class:`_orm.Load`
- object.
- E.g.::
- query = session.query(Author)
- query = query.options(
- joinedload(Author.book).options(
- load_only(Book.summary, Book.excerpt),
- joinedload(Book.citations).options(joinedload(Citation.author)),
- )
- )
- :param \*opts: A series of loader option objects (ultimately
- :class:`_orm.Load` objects) which should be applied to the path
- specified by this :class:`_orm.Load` object.
- .. versionadded:: 1.3.6
- .. seealso::
- :func:`.defaultload`
- :ref:`orm_queryguide_relationship_sub_options`
- """
- for opt in opts:
- try:
- opt._apply_to_parent(self)
- except AttributeError as ae:
- if not isinstance(opt, _AbstractLoad):
- raise sa_exc.ArgumentError(
- f"Loader option {opt} is not compatible with the "
- "Load.options() method."
- ) from ae
- else:
- raise
- return self
- def _clone_for_bind_strategy(
- self,
- attrs: Optional[Tuple[_AttrType, ...]],
- strategy: Optional[_StrategyKey],
- wildcard_key: Optional[_WildcardKeyType],
- opts: Optional[_OptsType] = None,
- attr_group: Optional[_AttrGroupType] = None,
- propagate_to_loaders: bool = True,
- reconcile_to_other: Optional[bool] = None,
- extra_criteria: Optional[Tuple[Any, ...]] = None,
- ) -> Self:
- # for individual strategy that needs to propagate, set the whole
- # Load container to also propagate, so that it shows up in
- # InstanceState.load_options
- if propagate_to_loaders:
- self.propagate_to_loaders = True
- if self.path.is_token:
- raise sa_exc.ArgumentError(
- "Wildcard token cannot be followed by another entity"
- )
- elif path_is_property(self.path):
- # re-use the lookup which will raise a nicely formatted
- # LoaderStrategyException
- if strategy:
- self.path.prop._strategy_lookup(self.path.prop, strategy[0])
- else:
- raise sa_exc.ArgumentError(
- f"Mapped attribute '{self.path.prop}' does not "
- "refer to a mapped entity"
- )
- if attrs is None:
- load_element = _ClassStrategyLoad.create(
- self.path,
- None,
- strategy,
- wildcard_key,
- opts,
- propagate_to_loaders,
- attr_group=attr_group,
- reconcile_to_other=reconcile_to_other,
- extra_criteria=extra_criteria,
- )
- if load_element:
- self.context += (load_element,)
- assert opts is not None
- self.additional_source_entities += cast(
- "Tuple[_InternalEntityType[Any]]", opts["entities"]
- )
- else:
- for attr in attrs:
- if isinstance(attr, str):
- load_element = _TokenStrategyLoad.create(
- self.path,
- attr,
- strategy,
- wildcard_key,
- opts,
- propagate_to_loaders,
- attr_group=attr_group,
- reconcile_to_other=reconcile_to_other,
- extra_criteria=extra_criteria,
- )
- else:
- load_element = _AttributeStrategyLoad.create(
- self.path,
- attr,
- strategy,
- wildcard_key,
- opts,
- propagate_to_loaders,
- attr_group=attr_group,
- reconcile_to_other=reconcile_to_other,
- extra_criteria=extra_criteria,
- )
- if load_element:
- # for relationship options, update self.path on this Load
- # object with the latest path.
- if wildcard_key is _RELATIONSHIP_TOKEN:
- self.path = load_element.path
- self.context += (load_element,)
- # this seems to be effective for selectinloader,
- # giving the extra match to one more level deep.
- # but does not work for immediateloader, which still
- # must add additional options at load time
- if load_element.local_opts.get("recursion_depth", False):
- r1 = load_element._recurse()
- self.context += (r1,)
- return self
- def __getstate__(self):
- d = self._shallow_to_dict()
- d["path"] = self.path.serialize()
- return d
- def __setstate__(self, state):
- state["path"] = PathRegistry.deserialize(state["path"])
- self._shallow_from_dict(state)
- class _WildcardLoad(_AbstractLoad):
- """represent a standalone '*' load operation"""
- __slots__ = ("strategy", "path", "local_opts")
- _traverse_internals = [
- ("strategy", visitors.ExtendedInternalTraversal.dp_plain_obj),
- ("path", visitors.ExtendedInternalTraversal.dp_plain_obj),
- (
- "local_opts",
- visitors.ExtendedInternalTraversal.dp_string_multi_dict,
- ),
- ]
- cache_key_traversal: _CacheKeyTraversalType = None
- strategy: Optional[Tuple[Any, ...]]
- local_opts: _OptsType
- path: Union[Tuple[()], Tuple[str]]
- propagate_to_loaders = False
- def __init__(self) -> None:
- self.path = ()
- self.strategy = None
- self.local_opts = util.EMPTY_DICT
- def _clone_for_bind_strategy(
- self,
- attrs,
- strategy,
- wildcard_key,
- opts=None,
- attr_group=None,
- propagate_to_loaders=True,
- reconcile_to_other=None,
- extra_criteria=None,
- ):
- assert attrs is not None
- attr = attrs[0]
- assert (
- wildcard_key
- and isinstance(attr, str)
- and attr in (_WILDCARD_TOKEN, _DEFAULT_TOKEN)
- )
- attr = f"{wildcard_key}:{attr}"
- self.strategy = strategy
- self.path = (attr,)
- if opts:
- self.local_opts = util.immutabledict(opts)
- assert extra_criteria is None
- def options(self, *opts: _AbstractLoad) -> Self:
- raise NotImplementedError("Star option does not support sub-options")
- def _apply_to_parent(self, parent: Load) -> None:
- """apply this :class:`_orm._WildcardLoad` object as a sub-option of
- a :class:`_orm.Load` object.
- This method is used by the :meth:`_orm.Load.options` method. Note
- that :class:`_orm.WildcardLoad` itself can't have sub-options, but
- it may be used as the sub-option of a :class:`_orm.Load` object.
- """
- assert self.path
- attr = self.path[0]
- if attr.endswith(_DEFAULT_TOKEN):
- attr = f"{attr.split(':')[0]}:{_WILDCARD_TOKEN}"
- effective_path = cast(AbstractEntityRegistry, parent.path).token(attr)
- assert effective_path.is_token
- loader = _TokenStrategyLoad.create(
- effective_path,
- None,
- self.strategy,
- None,
- self.local_opts,
- self.propagate_to_loaders,
- )
- parent.context += (loader,)
- def _process(self, compile_state, mapper_entities, raiseerr):
- is_refresh = compile_state.compile_options._for_refresh_state
- if is_refresh and not self.propagate_to_loaders:
- return
- entities = [ent.entity_zero for ent in mapper_entities]
- current_path = compile_state.current_path
- start_path: _PathRepresentation = self.path
- if current_path:
- # TODO: no cases in test suite where we actually get
- # None back here
- new_path = self._chop_path(start_path, current_path)
- if new_path is None:
- return
- # chop_path does not actually "chop" a wildcard token path,
- # just returns it
- assert new_path == start_path
- # start_path is a single-token tuple
- assert start_path and len(start_path) == 1
- token = start_path[0]
- assert isinstance(token, str)
- entity = self._find_entity_basestring(entities, token, raiseerr)
- if not entity:
- return
- path_element = entity
- # transfer our entity-less state into a Load() object
- # with a real entity path. Start with the lead entity
- # we just located, then go through the rest of our path
- # tokens and populate into the Load().
- assert isinstance(token, str)
- loader = _TokenStrategyLoad.create(
- path_element._path_registry,
- token,
- self.strategy,
- None,
- self.local_opts,
- self.propagate_to_loaders,
- raiseerr=raiseerr,
- )
- if not loader:
- return
- assert loader.path.is_token
- # don't pass a reconciled lead entity here
- loader.process_compile_state(
- self, compile_state, mapper_entities, None, raiseerr
- )
- return loader
- def _find_entity_basestring(
- self,
- entities: Iterable[_InternalEntityType[Any]],
- token: str,
- raiseerr: bool,
- ) -> Optional[_InternalEntityType[Any]]:
- if token.endswith(f":{_WILDCARD_TOKEN}"):
- if len(list(entities)) != 1:
- if raiseerr:
- raise sa_exc.ArgumentError(
- "Can't apply wildcard ('*') or load_only() "
- f"loader option to multiple entities "
- f"{', '.join(str(ent) for ent in entities)}. Specify "
- "loader options for each entity individually, such as "
- f"""{
- ", ".join(
- f"Load({ent}).some_option('*')"
- for ent in entities
- )
- }."""
- )
- elif token.endswith(_DEFAULT_TOKEN):
- raiseerr = False
- for ent in entities:
- # return only the first _MapperEntity when searching
- # based on string prop name. Ideally object
- # attributes are used to specify more exactly.
- return ent
- else:
- if raiseerr:
- raise sa_exc.ArgumentError(
- "Query has only expression-based entities - "
- f'can\'t find property named "{token}".'
- )
- else:
- return None
- def __getstate__(self) -> Dict[str, Any]:
- d = self._shallow_to_dict()
- return d
- def __setstate__(self, state: Dict[str, Any]) -> None:
- self._shallow_from_dict(state)
- class _LoadElement(
- cache_key.HasCacheKey, traversals.HasShallowCopy, visitors.Traversible
- ):
- """represents strategy information to select for a LoaderStrategy
- and pass options to it.
- :class:`._LoadElement` objects provide the inner datastructure
- stored by a :class:`_orm.Load` object and are also the object passed
- to methods like :meth:`.LoaderStrategy.setup_query`.
- .. versionadded:: 2.0
- """
- __slots__ = (
- "path",
- "strategy",
- "propagate_to_loaders",
- "local_opts",
- "_extra_criteria",
- "_reconcile_to_other",
- )
- __visit_name__ = "load_element"
- _traverse_internals = [
- ("path", visitors.ExtendedInternalTraversal.dp_has_cache_key),
- ("strategy", visitors.ExtendedInternalTraversal.dp_plain_obj),
- (
- "local_opts",
- visitors.ExtendedInternalTraversal.dp_string_multi_dict,
- ),
- ("_extra_criteria", visitors.InternalTraversal.dp_clauseelement_list),
- ("propagate_to_loaders", visitors.InternalTraversal.dp_plain_obj),
- ("_reconcile_to_other", visitors.InternalTraversal.dp_plain_obj),
- ]
- _cache_key_traversal = None
- _extra_criteria: Tuple[Any, ...]
- _reconcile_to_other: Optional[bool]
- strategy: Optional[_StrategyKey]
- path: PathRegistry
- propagate_to_loaders: bool
- local_opts: util.immutabledict[str, Any]
- is_token_strategy: bool
- is_class_strategy: bool
- def __hash__(self) -> int:
- return id(self)
- def __eq__(self, other):
- return traversals.compare(self, other)
- @property
- def is_opts_only(self) -> bool:
- return bool(self.local_opts and self.strategy is None)
- def _clone(self, **kw: Any) -> _LoadElement:
- cls = self.__class__
- s = cls.__new__(cls)
- self._shallow_copy_to(s)
- return s
- def _update_opts(self, **kw: Any) -> _LoadElement:
- new = self._clone()
- new.local_opts = new.local_opts.union(kw)
- return new
- def __getstate__(self) -> Dict[str, Any]:
- d = self._shallow_to_dict()
- d["path"] = self.path.serialize()
- return d
- def __setstate__(self, state: Dict[str, Any]) -> None:
- state["path"] = PathRegistry.deserialize(state["path"])
- self._shallow_from_dict(state)
- def _raise_for_no_match(self, parent_loader, mapper_entities):
- path = parent_loader.path
- found_entities = False
- for ent in mapper_entities:
- ezero = ent.entity_zero
- if ezero:
- found_entities = True
- break
- if not found_entities:
- raise sa_exc.ArgumentError(
- "Query has only expression-based entities; "
- f"attribute loader options for {path[0]} can't "
- "be applied here."
- )
- else:
- raise sa_exc.ArgumentError(
- f"Mapped class {path[0]} does not apply to any of the "
- f"root entities in this query, e.g. "
- f"""{
- ", ".join(
- str(x.entity_zero)
- for x in mapper_entities if x.entity_zero
- )}. Please """
- "specify the full path "
- "from one of the root entities to the target "
- "attribute. "
- )
- def _adjust_effective_path_for_current_path(
- self, effective_path: PathRegistry, current_path: PathRegistry
- ) -> Optional[PathRegistry]:
- """receives the 'current_path' entry from an :class:`.ORMCompileState`
- instance, which is set during lazy loads and secondary loader strategy
- loads, and adjusts the given path to be relative to the
- current_path.
- E.g. given a loader path and current path:
- .. sourcecode:: text
- lp: User -> orders -> Order -> items -> Item -> keywords -> Keyword
- cp: User -> orders -> Order -> items
- The adjusted path would be:
- .. sourcecode:: text
- Item -> keywords -> Keyword
- """
- chopped_start_path = Load._chop_path(
- effective_path.natural_path, current_path
- )
- if not chopped_start_path:
- return None
- tokens_removed_from_start_path = len(effective_path) - len(
- chopped_start_path
- )
- loader_lead_path_element = self.path[tokens_removed_from_start_path]
- effective_path = PathRegistry.coerce(
- (loader_lead_path_element,) + chopped_start_path[1:]
- )
- return effective_path
- def _init_path(
- self, path, attr, wildcard_key, attr_group, raiseerr, extra_criteria
- ):
- """Apply ORM attributes and/or wildcard to an existing path, producing
- a new path.
- This method is used within the :meth:`.create` method to initialize
- a :class:`._LoadElement` object.
- """
- raise NotImplementedError()
- def _prepare_for_compile_state(
- self,
- parent_loader,
- compile_state,
- mapper_entities,
- reconciled_lead_entity,
- raiseerr,
- ):
- """implemented by subclasses."""
- raise NotImplementedError()
- def process_compile_state(
- self,
- parent_loader,
- compile_state,
- mapper_entities,
- reconciled_lead_entity,
- raiseerr,
- ):
- """populate ORMCompileState.attributes with loader state for this
- _LoadElement.
- """
- keys = self._prepare_for_compile_state(
- parent_loader,
- compile_state,
- mapper_entities,
- reconciled_lead_entity,
- raiseerr,
- )
- for key in keys:
- if key in compile_state.attributes:
- compile_state.attributes[key] = _LoadElement._reconcile(
- self, compile_state.attributes[key]
- )
- else:
- compile_state.attributes[key] = self
- @classmethod
- def create(
- cls,
- path: PathRegistry,
- attr: Union[_AttrType, _StrPathToken, None],
- strategy: Optional[_StrategyKey],
- wildcard_key: Optional[_WildcardKeyType],
- local_opts: Optional[_OptsType],
- propagate_to_loaders: bool,
- raiseerr: bool = True,
- attr_group: Optional[_AttrGroupType] = None,
- reconcile_to_other: Optional[bool] = None,
- extra_criteria: Optional[Tuple[Any, ...]] = None,
- ) -> _LoadElement:
- """Create a new :class:`._LoadElement` object."""
- opt = cls.__new__(cls)
- opt.path = path
- opt.strategy = strategy
- opt.propagate_to_loaders = propagate_to_loaders
- opt.local_opts = (
- util.immutabledict(local_opts) if local_opts else util.EMPTY_DICT
- )
- opt._extra_criteria = ()
- if reconcile_to_other is not None:
- opt._reconcile_to_other = reconcile_to_other
- elif strategy is None and not local_opts:
- opt._reconcile_to_other = True
- else:
- opt._reconcile_to_other = None
- path = opt._init_path(
- path, attr, wildcard_key, attr_group, raiseerr, extra_criteria
- )
- if not path:
- return None # type: ignore
- assert opt.is_token_strategy == path.is_token
- opt.path = path
- return opt
- def __init__(self) -> None:
- raise NotImplementedError()
- def _recurse(self) -> _LoadElement:
- cloned = self._clone()
- cloned.path = PathRegistry.coerce(self.path[:] + self.path[-2:])
- return cloned
- def _prepend_path_from(self, parent: Load) -> _LoadElement:
- """adjust the path of this :class:`._LoadElement` to be
- a subpath of that of the given parent :class:`_orm.Load` object's
- path.
- This is used by the :meth:`_orm.Load._apply_to_parent` method,
- which is in turn part of the :meth:`_orm.Load.options` method.
- """
- if not any(
- orm_util._entity_corresponds_to_use_path_impl(
- elem,
- self.path.odd_element(0),
- )
- for elem in (parent.path.odd_element(-1),)
- + parent.additional_source_entities
- ):
- raise sa_exc.ArgumentError(
- f'Attribute "{self.path[1]}" does not link '
- f'from element "{parent.path[-1]}".'
- )
- return self._prepend_path(parent.path)
- def _prepend_path(self, path: PathRegistry) -> _LoadElement:
- cloned = self._clone()
- assert cloned.strategy == self.strategy
- assert cloned.local_opts == self.local_opts
- assert cloned.is_class_strategy == self.is_class_strategy
- cloned.path = PathRegistry.coerce(path[0:-1] + cloned.path[:])
- return cloned
- @staticmethod
- def _reconcile(
- replacement: _LoadElement, existing: _LoadElement
- ) -> _LoadElement:
- """define behavior for when two Load objects are to be put into
- the context.attributes under the same key.
- :param replacement: ``_LoadElement`` that seeks to replace the
- existing one
- :param existing: ``_LoadElement`` that is already present.
- """
- # mapper inheritance loading requires fine-grained "block other
- # options" / "allow these options to be overridden" behaviors
- # see test_poly_loading.py
- if replacement._reconcile_to_other:
- return existing
- elif replacement._reconcile_to_other is False:
- return replacement
- elif existing._reconcile_to_other:
- return replacement
- elif existing._reconcile_to_other is False:
- return existing
- if existing is replacement:
- return replacement
- elif (
- existing.strategy == replacement.strategy
- and existing.local_opts == replacement.local_opts
- ):
- return replacement
- elif replacement.is_opts_only:
- existing = existing._clone()
- existing.local_opts = existing.local_opts.union(
- replacement.local_opts
- )
- existing._extra_criteria += replacement._extra_criteria
- return existing
- elif existing.is_opts_only:
- replacement = replacement._clone()
- replacement.local_opts = replacement.local_opts.union(
- existing.local_opts
- )
- replacement._extra_criteria += existing._extra_criteria
- return replacement
- elif replacement.path.is_token:
- # use 'last one wins' logic for wildcard options. this is also
- # kind of inconsistent vs. options that are specific paths which
- # will raise as below
- return replacement
- raise sa_exc.InvalidRequestError(
- f"Loader strategies for {replacement.path} conflict"
- )
- class _AttributeStrategyLoad(_LoadElement):
- """Loader strategies against specific relationship or column paths.
- e.g.::
- joinedload(User.addresses)
- defer(Order.name)
- selectinload(User.orders).lazyload(Order.items)
- """
- __slots__ = ("_of_type", "_path_with_polymorphic_path")
- __visit_name__ = "attribute_strategy_load_element"
- _traverse_internals = _LoadElement._traverse_internals + [
- ("_of_type", visitors.ExtendedInternalTraversal.dp_multi),
- (
- "_path_with_polymorphic_path",
- visitors.ExtendedInternalTraversal.dp_has_cache_key,
- ),
- ]
- _of_type: Union[Mapper[Any], AliasedInsp[Any], None]
- _path_with_polymorphic_path: Optional[PathRegistry]
- is_class_strategy = False
- is_token_strategy = False
- def _init_path(
- self, path, attr, wildcard_key, attr_group, raiseerr, extra_criteria
- ):
- assert attr is not None
- self._of_type = None
- self._path_with_polymorphic_path = None
- insp, _, prop = _parse_attr_argument(attr)
- if insp.is_property:
- # direct property can be sent from internal strategy logic
- # that sets up specific loaders, such as
- # emit_lazyload->_lazyload_reverse
- # prop = found_property = attr
- prop = attr
- path = path[prop]
- if path.has_entity:
- path = path.entity_path
- return path
- elif not insp.is_attribute:
- # should not reach here;
- assert False
- # here we assume we have user-passed InstrumentedAttribute
- if not orm_util._entity_corresponds_to_use_path_impl(
- path[-1], attr.parent
- ):
- if raiseerr:
- if attr_group and attr is not attr_group[0]:
- raise sa_exc.ArgumentError(
- "Can't apply wildcard ('*') or load_only() "
- "loader option to multiple entities in the "
- "same option. Use separate options per entity."
- )
- else:
- _raise_for_does_not_link(path, str(attr), attr.parent)
- else:
- return None
- # note the essential logic of this attribute was very different in
- # 1.4, where there were caching failures in e.g.
- # test_relationship_criteria.py::RelationshipCriteriaTest::
- # test_selectinload_nested_criteria[True] if an existing
- # "_extra_criteria" on a Load object were replaced with that coming
- # from an attribute. This appears to have been an artifact of how
- # _UnboundLoad / Load interacted together, which was opaque and
- # poorly defined.
- if extra_criteria:
- assert not attr._extra_criteria
- self._extra_criteria = extra_criteria
- else:
- self._extra_criteria = attr._extra_criteria
- if getattr(attr, "_of_type", None):
- ac = attr._of_type
- ext_info = inspect(ac)
- self._of_type = ext_info
- self._path_with_polymorphic_path = path.entity_path[prop]
- path = path[prop][ext_info]
- else:
- path = path[prop]
- if path.has_entity:
- path = path.entity_path
- return path
- def _generate_extra_criteria(self, context):
- """Apply the current bound parameters in a QueryContext to the
- immediate "extra_criteria" stored with this Load object.
- Load objects are typically pulled from the cached version of
- the statement from a QueryContext. The statement currently being
- executed will have new values (and keys) for bound parameters in the
- extra criteria which need to be applied by loader strategies when
- they handle this criteria for a result set.
- """
- assert (
- self._extra_criteria
- ), "this should only be called if _extra_criteria is present"
- orig_query = context.compile_state.select_statement
- current_query = context.query
- # NOTE: while it seems like we should not do the "apply" operation
- # here if orig_query is current_query, skipping it in the "optimized"
- # case causes the query to be different from a cache key perspective,
- # because we are creating a copy of the criteria which is no longer
- # the same identity of the _extra_criteria in the loader option
- # itself. cache key logic produces a different key for
- # (A, copy_of_A) vs. (A, A), because in the latter case it shortens
- # the second part of the key to just indicate on identity.
- # if orig_query is current_query:
- # not cached yet. just do the and_()
- # return and_(*self._extra_criteria)
- k1 = orig_query._generate_cache_key()
- k2 = current_query._generate_cache_key()
- return k2._apply_params_to_element(k1, and_(*self._extra_criteria))
- def _set_of_type_info(self, context, current_path):
- assert self._path_with_polymorphic_path
- pwpi = self._of_type
- assert pwpi
- if not pwpi.is_aliased_class:
- pwpi = inspect(
- orm_util.AliasedInsp._with_polymorphic_factory(
- pwpi.mapper.base_mapper,
- (pwpi.mapper,),
- aliased=True,
- _use_mapper_path=True,
- )
- )
- start_path = self._path_with_polymorphic_path
- if current_path:
- new_path = self._adjust_effective_path_for_current_path(
- start_path, current_path
- )
- if new_path is None:
- return
- start_path = new_path
- key = ("path_with_polymorphic", start_path.natural_path)
- if key in context:
- existing_aliased_insp = context[key]
- this_aliased_insp = pwpi
- new_aliased_insp = existing_aliased_insp._merge_with(
- this_aliased_insp
- )
- context[key] = new_aliased_insp
- else:
- context[key] = pwpi
- def _prepare_for_compile_state(
- self,
- parent_loader,
- compile_state,
- mapper_entities,
- reconciled_lead_entity,
- raiseerr,
- ):
- # _AttributeStrategyLoad
- current_path = compile_state.current_path
- is_refresh = compile_state.compile_options._for_refresh_state
- assert not self.path.is_token
- if is_refresh and not self.propagate_to_loaders:
- return []
- if self._of_type:
- # apply additional with_polymorphic alias that may have been
- # generated. this has to happen even if this is a defaultload
- self._set_of_type_info(compile_state.attributes, current_path)
- # omit setting loader attributes for a "defaultload" type of option
- if not self.strategy and not self.local_opts:
- return []
- if raiseerr and not reconciled_lead_entity:
- self._raise_for_no_match(parent_loader, mapper_entities)
- if self.path.has_entity:
- effective_path = self.path.parent
- else:
- effective_path = self.path
- if current_path:
- assert effective_path is not None
- effective_path = self._adjust_effective_path_for_current_path(
- effective_path, current_path
- )
- if effective_path is None:
- return []
- return [("loader", cast(PathRegistry, effective_path).natural_path)]
- def __getstate__(self):
- d = super().__getstate__()
- # can't pickle this. See
- # test_pickled.py -> test_lazyload_extra_criteria_not_supported
- # where we should be emitting a warning for the usual case where this
- # would be non-None
- d["_extra_criteria"] = ()
- if self._path_with_polymorphic_path:
- d["_path_with_polymorphic_path"] = (
- self._path_with_polymorphic_path.serialize()
- )
- if self._of_type:
- if self._of_type.is_aliased_class:
- d["_of_type"] = None
- elif self._of_type.is_mapper:
- d["_of_type"] = self._of_type.class_
- else:
- assert False, "unexpected object for _of_type"
- return d
- def __setstate__(self, state):
- super().__setstate__(state)
- if state.get("_path_with_polymorphic_path", None):
- self._path_with_polymorphic_path = PathRegistry.deserialize(
- state["_path_with_polymorphic_path"]
- )
- else:
- self._path_with_polymorphic_path = None
- if state.get("_of_type", None):
- self._of_type = inspect(state["_of_type"])
- else:
- self._of_type = None
- class _TokenStrategyLoad(_LoadElement):
- """Loader strategies against wildcard attributes
- e.g.::
- raiseload("*")
- Load(User).lazyload("*")
- defer("*")
- load_only(User.name, User.email) # will create a defer('*')
- joinedload(User.addresses).raiseload("*")
- """
- __visit_name__ = "token_strategy_load_element"
- inherit_cache = True
- is_class_strategy = False
- is_token_strategy = True
- def _init_path(
- self, path, attr, wildcard_key, attr_group, raiseerr, extra_criteria
- ):
- # assert isinstance(attr, str) or attr is None
- if attr is not None:
- default_token = attr.endswith(_DEFAULT_TOKEN)
- if attr.endswith(_WILDCARD_TOKEN) or default_token:
- if wildcard_key:
- attr = f"{wildcard_key}:{attr}"
- path = path.token(attr)
- return path
- else:
- raise sa_exc.ArgumentError(
- "Strings are not accepted for attribute names in loader "
- "options; please use class-bound attributes directly."
- )
- return path
- def _prepare_for_compile_state(
- self,
- parent_loader,
- compile_state,
- mapper_entities,
- reconciled_lead_entity,
- raiseerr,
- ):
- # _TokenStrategyLoad
- current_path = compile_state.current_path
- is_refresh = compile_state.compile_options._for_refresh_state
- assert self.path.is_token
- if is_refresh and not self.propagate_to_loaders:
- return []
- # omit setting attributes for a "defaultload" type of option
- if not self.strategy and not self.local_opts:
- return []
- effective_path = self.path
- if reconciled_lead_entity:
- effective_path = PathRegistry.coerce(
- (reconciled_lead_entity,) + effective_path.path[1:]
- )
- if current_path:
- new_effective_path = self._adjust_effective_path_for_current_path(
- effective_path, current_path
- )
- if new_effective_path is None:
- return []
- effective_path = new_effective_path
- # for a wildcard token, expand out the path we set
- # to encompass everything from the query entity on
- # forward. not clear if this is necessary when current_path
- # is set.
- return [
- ("loader", natural_path)
- for natural_path in (
- cast(
- TokenRegistry, effective_path
- )._generate_natural_for_superclasses()
- )
- ]
- class _ClassStrategyLoad(_LoadElement):
- """Loader strategies that deals with a class as a target, not
- an attribute path
- e.g.::
- q = s.query(Person).options(
- selectin_polymorphic(Person, [Engineer, Manager])
- )
- """
- inherit_cache = True
- is_class_strategy = True
- is_token_strategy = False
- __visit_name__ = "class_strategy_load_element"
- def _init_path(
- self, path, attr, wildcard_key, attr_group, raiseerr, extra_criteria
- ):
- return path
- def _prepare_for_compile_state(
- self,
- parent_loader,
- compile_state,
- mapper_entities,
- reconciled_lead_entity,
- raiseerr,
- ):
- # _ClassStrategyLoad
- current_path = compile_state.current_path
- is_refresh = compile_state.compile_options._for_refresh_state
- if is_refresh and not self.propagate_to_loaders:
- return []
- # omit setting attributes for a "defaultload" type of option
- if not self.strategy and not self.local_opts:
- return []
- effective_path = self.path
- if current_path:
- new_effective_path = self._adjust_effective_path_for_current_path(
- effective_path, current_path
- )
- if new_effective_path is None:
- return []
- effective_path = new_effective_path
- return [("loader", effective_path.natural_path)]
- def _generate_from_keys(
- meth: Callable[..., _AbstractLoad],
- keys: Tuple[_AttrType, ...],
- chained: bool,
- kw: Any,
- ) -> _AbstractLoad:
- lead_element: Optional[_AbstractLoad] = None
- attr: Any
- for is_default, _keys in (True, keys[0:-1]), (False, keys[-1:]):
- for attr in _keys:
- if isinstance(attr, str):
- if attr.startswith("." + _WILDCARD_TOKEN):
- util.warn_deprecated(
- "The undocumented `.{WILDCARD}` format is "
- "deprecated "
- "and will be removed in a future version as "
- "it is "
- "believed to be unused. "
- "If you have been using this functionality, "
- "please "
- "comment on Issue #4390 on the SQLAlchemy project "
- "tracker.",
- version="1.4",
- )
- attr = attr[1:]
- if attr == _WILDCARD_TOKEN:
- if is_default:
- raise sa_exc.ArgumentError(
- "Wildcard token cannot be followed by "
- "another entity",
- )
- if lead_element is None:
- lead_element = _WildcardLoad()
- lead_element = meth(lead_element, _DEFAULT_TOKEN, **kw)
- else:
- raise sa_exc.ArgumentError(
- "Strings are not accepted for attribute names in "
- "loader options; please use class-bound "
- "attributes directly.",
- )
- else:
- if lead_element is None:
- _, lead_entity, _ = _parse_attr_argument(attr)
- lead_element = Load(lead_entity)
- if is_default:
- if not chained:
- lead_element = lead_element.defaultload(attr)
- else:
- lead_element = meth(
- lead_element, attr, _is_chain=True, **kw
- )
- else:
- lead_element = meth(lead_element, attr, **kw)
- assert lead_element
- return lead_element
- def _parse_attr_argument(
- attr: _AttrType,
- ) -> Tuple[InspectionAttr, _InternalEntityType[Any], MapperProperty[Any]]:
- """parse an attribute or wildcard argument to produce an
- :class:`._AbstractLoad` instance.
- This is used by the standalone loader strategy functions like
- ``joinedload()``, ``defer()``, etc. to produce :class:`_orm.Load` or
- :class:`._WildcardLoad` objects.
- """
- try:
- # TODO: need to figure out this None thing being returned by
- # inspect(), it should not have None as an option in most cases
- # if at all
- insp: InspectionAttr = inspect(attr) # type: ignore
- except sa_exc.NoInspectionAvailable as err:
- raise sa_exc.ArgumentError(
- "expected ORM mapped attribute for loader strategy argument"
- ) from err
- lead_entity: _InternalEntityType[Any]
- if insp_is_mapper_property(insp):
- lead_entity = insp.parent
- prop = insp
- elif insp_is_attribute(insp):
- lead_entity = insp.parent
- prop = insp.prop
- else:
- raise sa_exc.ArgumentError(
- "expected ORM mapped attribute for loader strategy argument"
- )
- return insp, lead_entity, prop
- def loader_unbound_fn(fn: _FN) -> _FN:
- """decorator that applies docstrings between standalone loader functions
- and the loader methods on :class:`._AbstractLoad`.
- """
- bound_fn = getattr(_AbstractLoad, fn.__name__)
- fn_doc = bound_fn.__doc__
- bound_fn.__doc__ = f"""Produce a new :class:`_orm.Load` object with the
- :func:`_orm.{fn.__name__}` option applied.
- See :func:`_orm.{fn.__name__}` for usage examples.
- """
- fn.__doc__ = fn_doc
- return fn
- def _expand_column_strategy_attrs(
- attrs: Tuple[_AttrType, ...],
- ) -> Tuple[_AttrType, ...]:
- return cast(
- "Tuple[_AttrType, ...]",
- tuple(
- a
- for attr in attrs
- for a in (
- cast("QueryableAttribute[Any]", attr)._column_strategy_attrs()
- if hasattr(attr, "_column_strategy_attrs")
- else (attr,)
- )
- ),
- )
- # standalone functions follow. docstrings are filled in
- # by the ``@loader_unbound_fn`` decorator.
- @loader_unbound_fn
- def contains_eager(*keys: _AttrType, **kw: Any) -> _AbstractLoad:
- return _generate_from_keys(Load.contains_eager, keys, True, kw)
- @loader_unbound_fn
- def load_only(*attrs: _AttrType, raiseload: bool = False) -> _AbstractLoad:
- # TODO: attrs against different classes. we likely have to
- # add some extra state to Load of some kind
- attrs = _expand_column_strategy_attrs(attrs)
- _, lead_element, _ = _parse_attr_argument(attrs[0])
- return Load(lead_element).load_only(*attrs, raiseload=raiseload)
- @loader_unbound_fn
- def joinedload(*keys: _AttrType, **kw: Any) -> _AbstractLoad:
- return _generate_from_keys(Load.joinedload, keys, False, kw)
- @loader_unbound_fn
- def subqueryload(*keys: _AttrType) -> _AbstractLoad:
- return _generate_from_keys(Load.subqueryload, keys, False, {})
- @loader_unbound_fn
- def selectinload(
- *keys: _AttrType, recursion_depth: Optional[int] = None
- ) -> _AbstractLoad:
- return _generate_from_keys(
- Load.selectinload, keys, False, {"recursion_depth": recursion_depth}
- )
- @loader_unbound_fn
- def lazyload(*keys: _AttrType) -> _AbstractLoad:
- return _generate_from_keys(Load.lazyload, keys, False, {})
- @loader_unbound_fn
- def immediateload(
- *keys: _AttrType, recursion_depth: Optional[int] = None
- ) -> _AbstractLoad:
- return _generate_from_keys(
- Load.immediateload, keys, False, {"recursion_depth": recursion_depth}
- )
- @loader_unbound_fn
- def noload(*keys: _AttrType) -> _AbstractLoad:
- return _generate_from_keys(Load.noload, keys, False, {})
- @loader_unbound_fn
- def raiseload(*keys: _AttrType, **kw: Any) -> _AbstractLoad:
- return _generate_from_keys(Load.raiseload, keys, False, kw)
- @loader_unbound_fn
- def defaultload(*keys: _AttrType) -> _AbstractLoad:
- return _generate_from_keys(Load.defaultload, keys, False, {})
- @loader_unbound_fn
- def defer(
- key: _AttrType, *addl_attrs: _AttrType, raiseload: bool = False
- ) -> _AbstractLoad:
- if addl_attrs:
- util.warn_deprecated(
- "The *addl_attrs on orm.defer is deprecated. Please use "
- "method chaining in conjunction with defaultload() to "
- "indicate a path.",
- version="1.3",
- )
- if raiseload:
- kw = {"raiseload": raiseload}
- else:
- kw = {}
- return _generate_from_keys(Load.defer, (key,) + addl_attrs, False, kw)
- @loader_unbound_fn
- def undefer(key: _AttrType, *addl_attrs: _AttrType) -> _AbstractLoad:
- if addl_attrs:
- util.warn_deprecated(
- "The *addl_attrs on orm.undefer is deprecated. Please use "
- "method chaining in conjunction with defaultload() to "
- "indicate a path.",
- version="1.3",
- )
- return _generate_from_keys(Load.undefer, (key,) + addl_attrs, False, {})
- @loader_unbound_fn
- def undefer_group(name: str) -> _AbstractLoad:
- element = _WildcardLoad()
- return element.undefer_group(name)
- @loader_unbound_fn
- def with_expression(
- key: _AttrType, expression: _ColumnExpressionArgument[Any]
- ) -> _AbstractLoad:
- return _generate_from_keys(
- Load.with_expression, (key,), False, {"expression": expression}
- )
- @loader_unbound_fn
- def selectin_polymorphic(
- base_cls: _EntityType[Any], classes: Iterable[Type[Any]]
- ) -> _AbstractLoad:
- ul = Load(base_cls)
- return ul.selectin_polymorphic(classes)
- def _raise_for_does_not_link(path, attrname, parent_entity):
- if len(path) > 1:
- path_is_of_type = path[-1].entity is not path[-2].mapper.class_
- if insp_is_aliased_class(parent_entity):
- parent_entity_str = str(parent_entity)
- else:
- parent_entity_str = parent_entity.class_.__name__
- raise sa_exc.ArgumentError(
- f'ORM mapped entity or attribute "{attrname}" does not '
- f'link from relationship "{path[-2]}%s".%s'
- % (
- f".of_type({path[-1]})" if path_is_of_type else "",
- (
- " Did you mean to use "
- f'"{path[-2]}'
- f'.of_type({parent_entity_str})" or "loadopt.options('
- f"selectin_polymorphic({path[-2].mapper.class_.__name__}, "
- f'[{parent_entity_str}]), ...)" ?'
- if not path_is_of_type
- and not path[-1].is_aliased_class
- and orm_util._entity_corresponds_to(
- path.entity, inspect(parent_entity).mapper
- )
- else ""
- ),
- )
- )
- else:
- raise sa_exc.ArgumentError(
- f'ORM mapped attribute "{attrname}" does not '
- f'link mapped class "{path[-1]}"'
- )
|