Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
strategy_options.py2587 linesDownload Raw Back to orm
1# orm/strategy_options.py
2# Copyright (C) 2005-2026 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 Iterable
19from typing import Optional
20from typing import overload
21from typing import Sequence
22from typing import Tuple
23from typing import Type
24from typing import TypeVar
25from typing import Union
26
27from . import util as orm_util
28from ._typing import insp_is_aliased_class
29from ._typing import insp_is_attribute
30from ._typing import insp_is_mapper
31from ._typing import insp_is_mapper_property
32from .attributes import QueryableAttribute
33from .base import InspectionAttr
34from .interfaces import LoaderOption
35from .path_registry import _DEFAULT_TOKEN
36from .path_registry import _StrPathToken
37from .path_registry import _WILDCARD_TOKEN
38from .path_registry import AbstractEntityRegistry
39from .path_registry import path_is_property
40from .path_registry import PathRegistry
41from .path_registry import TokenRegistry
42from .util import _orm_full_deannotate
43from .util import AliasedInsp
44from .. import exc as sa_exc
45from .. import inspect
46from .. import util
47from ..sql import and_
48from ..sql import cache_key
49from ..sql import coercions
50from ..sql import roles
51from ..sql import traversals
52from ..sql import visitors
53from ..sql.base import _generative
54from ..util.typing import Final
55from ..util.typing import Literal
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    def noload(self, attr: _AttrType) -> Self:
480        """Indicate that the given relationship attribute should remain
481        unloaded.
482
483        The relationship attribute will return ``None`` when accessed without
484        producing any loading effect.
485
486        This function is part of the :class:`_orm.Load` interface and supports
487        both method-chained and standalone operation.
488
489        :func:`_orm.noload` applies to :func:`_orm.relationship` attributes
490        only.
491
492        .. legacy:: The :func:`_orm.noload` option is **legacy**.  As it
493           forces collections to be empty, which invariably leads to
494           non-intuitive and difficult to predict results.  There are no
495           legitimate uses for this option in modern SQLAlchemy.
496
497        .. seealso::
498
499            :ref:`loading_toplevel`
500
501        """
502
503        return self._set_relationship_strategy(attr, {"lazy": "noload"})
504
505    def raiseload(self, attr: _AttrType, sql_only: bool = False) -> Self:
506        """Indicate that the given attribute should raise an error if accessed.
507
508        A relationship attribute configured with :func:`_orm.raiseload` will
509        raise an :exc:`~sqlalchemy.exc.InvalidRequestError` upon access. The
510        typical way this is useful is when an application is attempting to
511        ensure that all relationship attributes that are accessed in a
512        particular context would have been already loaded via eager loading.
513        Instead of having to read through SQL logs to ensure lazy loads aren't
514        occurring, this strategy will cause them to raise immediately.
515
516        :func:`_orm.raiseload` applies to :func:`_orm.relationship` attributes
517        only. In order to apply raise-on-SQL behavior to a column-based
518        attribute, use the :paramref:`.orm.defer.raiseload` parameter on the
519        :func:`.defer` loader option.
520
521        :param sql_only: if True, raise only if the lazy load would emit SQL,
522         but not if it is only checking the identity map, or determining that
523         the related value should just be None due to missing keys. When False,
524         the strategy will raise for all varieties of relationship loading.
525
526        This function is part of the :class:`_orm.Load` interface and supports
527        both method-chained and standalone operation.
528
529        .. seealso::
530
531            :ref:`loading_toplevel`
532
533            :ref:`prevent_lazy_with_raiseload`
534
535            :ref:`orm_queryguide_deferred_raiseload`
536
537        """
538
539        return self._set_relationship_strategy(
540            attr, {"lazy": "raise_on_sql" if sql_only else "raise"}
541        )
542
543    def defaultload(self, attr: _AttrType) -> Self:
544        """Indicate an attribute should load using its predefined loader style.
545
546        The behavior of this loading option is to not change the current
547        loading style of the attribute, meaning that the previously configured
548        one is used or, if no previous style was selected, the default
549        loading will be used.
550
551        This method is used to link to other loader options further into
552        a chain of attributes without altering the loader style of the links
553        along the chain.  For example, to set joined eager loading for an
554        element of an element::
555
556            session.query(MyClass).options(
557                defaultload(MyClass.someattribute).joinedload(
558                    MyOtherClass.someotherattribute
559                )
560            )
561
562        :func:`.defaultload` is also useful for setting column-level options on
563        a related class, namely that of :func:`.defer` and :func:`.undefer`::
564
565            session.scalars(
566                select(MyClass).options(
567                    defaultload(MyClass.someattribute)
568                    .defer("some_column")
569                    .undefer("some_other_column")
570                )
571            )
572
573        .. seealso::
574
575            :ref:`orm_queryguide_relationship_sub_options`
576
577            :meth:`_orm.Load.options`
578
579        """
580        return self._set_relationship_strategy(attr, None)
581
582    def defer(self, key: _AttrType, raiseload: bool = False) -> Self:
583        r"""Indicate that the given column-oriented attribute should be
584        deferred, e.g. not loaded until accessed.
585
586        This function is part of the :class:`_orm.Load` interface and supports
587        both method-chained and standalone operation.
588
589        e.g.::
590
591            from sqlalchemy.orm import defer
592
593            session.query(MyClass).options(
594                defer(MyClass.attribute_one), defer(MyClass.attribute_two)
595            )
596
597        To specify a deferred load of an attribute on a related class,
598        the path can be specified one token at a time, specifying the loading
599        style for each link along the chain.  To leave the loading style
600        for a link unchanged, use :func:`_orm.defaultload`::
601
602            session.query(MyClass).options(
603                defaultload(MyClass.someattr).defer(RelatedClass.some_column)
604            )
605
606        Multiple deferral options related to a relationship can be bundled
607        at once using :meth:`_orm.Load.options`::
608
609
610            select(MyClass).options(
611                defaultload(MyClass.someattr).options(
612                    defer(RelatedClass.some_column),
613                    defer(RelatedClass.some_other_column),
614                    defer(RelatedClass.another_column),
615                )
616            )
617
618        :param key: Attribute to be deferred.
619
620        :param raiseload: raise :class:`.InvalidRequestError` rather than
621         lazy loading a value when the deferred attribute is accessed. Used
622         to prevent unwanted SQL from being emitted.
623
624        .. versionadded:: 1.4
625
626        .. seealso::
627
628            :ref:`orm_queryguide_column_deferral` - in the
629            :ref:`queryguide_toplevel`
630
631            :func:`_orm.load_only`
632
633            :func:`_orm.undefer`
634
635        """
636        strategy = {"deferred": True, "instrument": True}
637        if raiseload:
638            strategy["raiseload"] = True
639        return self._set_column_strategy(
640            _expand_column_strategy_attrs((key,)), strategy
641        )
642
643    def undefer(self, key: _AttrType) -> Self:
644        r"""Indicate that the given column-oriented attribute should be
645        undeferred, e.g. specified within the SELECT statement of the entity
646        as a whole.
647
648        The column being undeferred is typically set up on the mapping as a
649        :func:`.deferred` attribute.
650
651        This function is part of the :class:`_orm.Load` interface and supports
652        both method-chained and standalone operation.
653
654        Examples::
655
656            # undefer two columns
657            session.query(MyClass).options(
658                undefer(MyClass.col1), undefer(MyClass.col2)
659            )
660
661            # undefer all columns specific to a single class using Load + *
662            session.query(MyClass, MyOtherClass).options(Load(MyClass).undefer("*"))
663
664            # undefer a column on a related object
665            select(MyClass).options(defaultload(MyClass.items).undefer(MyClass.text))
666
667        :param key: Attribute to be undeferred.
668
669        .. seealso::
670
671            :ref:`orm_queryguide_column_deferral` - in the
672            :ref:`queryguide_toplevel`
673
674            :func:`_orm.defer`
675
676            :func:`_orm.undefer_group`
677
678        """  # noqa: E501
679        return self._set_column_strategy(
680            _expand_column_strategy_attrs((key,)),
681            {"deferred": False, "instrument": True},
682        )
683
684    def undefer_group(self, name: str) -> Self:
685        """Indicate that columns within the given deferred group name should be
686        undeferred.
687
688        The columns being undeferred are set up on the mapping as
689        :func:`.deferred` attributes and include a "group" name.
690
691        E.g::
692
693            session.query(MyClass).options(undefer_group("large_attrs"))
694
695        To undefer a group of attributes on a related entity, the path can be
696        spelled out using relationship loader options, such as
697        :func:`_orm.defaultload`::
698
699            select(MyClass).options(
700                defaultload("someattr").undefer_group("large_attrs")
701            )
702
703        .. seealso::
704
705            :ref:`orm_queryguide_column_deferral` - in the
706            :ref:`queryguide_toplevel`
707
708            :func:`_orm.defer`
709
710            :func:`_orm.undefer`
711
712        """
713        return self._set_column_strategy(
714            (_WILDCARD_TOKEN,), None, {f"undefer_group_{name}": True}
715        )
716
717    def with_expression(
718        self,
719        key: _AttrType,
720        expression: _ColumnExpressionArgument[Any],
721    ) -> Self:
722        r"""Apply an ad-hoc SQL expression to a "deferred expression"
723        attribute.
724
725        This option is used in conjunction with the
726        :func:`_orm.query_expression` mapper-level construct that indicates an
727        attribute which should be the target of an ad-hoc SQL expression.
728
729        E.g.::
730
731            stmt = select(SomeClass).options(
732                with_expression(SomeClass.x_y_expr, SomeClass.x + SomeClass.y)
733            )
734
735        .. versionadded:: 1.2
736
737        :param key: Attribute to be populated
738
739        :param expr: SQL expression to be applied to the attribute.
740
741        .. seealso::
742
743            :ref:`orm_queryguide_with_expression` - background and usage
744            examples
745
746        """
747
748        expression = _orm_full_deannotate(
749            coercions.expect(roles.LabeledColumnExprRole, expression)
750        )
751
752        return self._set_column_strategy(
753            (key,), {"query_expression": True}, extra_criteria=(expression,)
754        )
755
756    def selectin_polymorphic(self, classes: Iterable[Type[Any]]) -> Self:
757        """Indicate an eager load should take place for all attributes
758        specific to a subclass.
759
760        This uses an additional SELECT with IN against all matched primary
761        key values, and is the per-query analogue to the ``"selectin"``
762        setting on the :paramref:`.mapper.polymorphic_load` parameter.
763
764        .. versionadded:: 1.2
765
766        .. seealso::
767
768            :ref:`polymorphic_selectin`
769
770        """
771        self = self._set_class_strategy(
772            {"selectinload_polymorphic": True},
773            opts={
774                "entities": tuple(
775                    sorted((inspect(cls) for cls in classes), key=id)
776                )
777            },
778        )
779        return self
780
781    @overload
782    def _coerce_strat(self, strategy: _StrategySpec) -> _StrategyKey: ...
783
784    @overload
785    def _coerce_strat(self, strategy: Literal[None]) -> None: ...
786
787    def _coerce_strat(
788        self, strategy: Optional[_StrategySpec]
789    ) -> Optional[_StrategyKey]:
790        if strategy is not None:
791            strategy_key = tuple(sorted(strategy.items()))
792        else:
793            strategy_key = None
794        return strategy_key
795
796    @_generative
797    def _set_relationship_strategy(
798        self,
799        attr: _AttrType,
800        strategy: Optional[_StrategySpec],
801        propagate_to_loaders: bool = True,
802        opts: Optional[_OptsType] = None,
803        _reconcile_to_other: Optional[bool] = None,
804    ) -> Self:
805        strategy_key = self._coerce_strat(strategy)
806
807        self._clone_for_bind_strategy(
808            (attr,),
809            strategy_key,
810            _RELATIONSHIP_TOKEN,
811            opts=opts,
812            propagate_to_loaders=propagate_to_loaders,
813            reconcile_to_other=_reconcile_to_other,
814        )
815        return self
816
817    @_generative
818    def _set_column_strategy(
819        self,
820        attrs: Tuple[_AttrType, ...],
821        strategy: Optional[_StrategySpec],
822        opts: Optional[_OptsType] = None,
823        extra_criteria: Optional[Tuple[Any, ...]] = None,
824    ) -> Self:
825        strategy_key = self._coerce_strat(strategy)
826
827        self._clone_for_bind_strategy(
828            attrs,
829            strategy_key,
830            _COLUMN_TOKEN,
831            opts=opts,
832            attr_group=attrs,
833            extra_criteria=extra_criteria,
834        )
835        return self
836
837    @_generative
838    def _set_generic_strategy(
839        self,
840        attrs: Tuple[_AttrType, ...],
841        strategy: _StrategySpec,
842        _reconcile_to_other: Optional[bool] = None,
843    ) -> Self:
844        strategy_key = self._coerce_strat(strategy)
845        self._clone_for_bind_strategy(
846            attrs,
847            strategy_key,
848            None,
849            propagate_to_loaders=True,
850            reconcile_to_other=_reconcile_to_other,
851        )
852        return self
853
854    @_generative
855    def _set_class_strategy(
856        self, strategy: _StrategySpec, opts: _OptsType
857    ) -> Self:
858        strategy_key = self._coerce_strat(strategy)
859
860        self._clone_for_bind_strategy(None, strategy_key, None, opts=opts)
861        return self
862
863    def _apply_to_parent(self, parent: Load) -> None:
864        """apply this :class:`_orm._AbstractLoad` object as a sub-option o
865        a :class:`_orm.Load` object.
866
867        Implementation is provided by subclasses.
868
869        """
870        raise NotImplementedError()
871
872    def options(self, *opts: _AbstractLoad) -> Self:
873        r"""Apply a series of options as sub-options to this
874        :class:`_orm._AbstractLoad` object.
875
876        Implementation is provided by subclasses.
877
878        """
879        raise NotImplementedError()
880
881    def _clone_for_bind_strategy(
882        self,
883        attrs: Optional[Tuple[_AttrType, ...]],
884        strategy: Optional[_StrategyKey],
885        wildcard_key: Optional[_WildcardKeyType],
886        opts: Optional[_OptsType] = None,
887        attr_group: Optional[_AttrGroupType] = None,
888        propagate_to_loaders: bool = True,
889        reconcile_to_other: Optional[bool] = None,
890        extra_criteria: Optional[Tuple[Any, ...]] = None,
891    ) -> Self:
892        raise NotImplementedError()
893
894    def process_compile_state_replaced_entities(
895        self,
896        compile_state: ORMCompileState,
897        mapper_entities: Sequence[_MapperEntity],
898    ) -> None:
899        if not compile_state.compile_options._enable_eagerloads:
900            return
901
902        # process is being run here so that the options given are validated
903        # against what the lead entities were, as well as to accommodate
904        # for the entities having been replaced with equivalents
905        self._process(
906            compile_state,
907            mapper_entities,
908            not bool(compile_state.current_path),
909        )
910
911    def process_compile_state(self, compile_state: ORMCompileState) -> None:
912        if not compile_state.compile_options._enable_eagerloads:
913            return
914
915        self._process(
916            compile_state,
917            compile_state._lead_mapper_entities,
918            not bool(compile_state.current_path)
919            and not compile_state.compile_options._for_refresh_state,
920        )
921
922    def _process(
923        self,
924        compile_state: ORMCompileState,
925        mapper_entities: Sequence[_MapperEntity],
926        raiseerr: bool,
927    ) -> None:
928        """implemented by subclasses"""
929        raise NotImplementedError()
930
931    @classmethod
932    def _chop_path(
933        cls,
934        to_chop: _PathRepresentation,
935        path: PathRegistry,
936        debug: bool = False,
937    ) -> Optional[_PathRepresentation]:
938        i = -1
939
940        for i, (c_token, p_token) in enumerate(
941            zip(to_chop, path.natural_path)
942        ):
943            if isinstance(c_token, str):
944                if i == 0 and (
945                    c_token.endswith(f":{_DEFAULT_TOKEN}")
946                    or c_token.endswith(f":{_WILDCARD_TOKEN}")
947                ):
948                    return to_chop
949                elif (
950                    c_token != f"{_RELATIONSHIP_TOKEN}:{_WILDCARD_TOKEN}"
951                    and c_token != p_token.key  # type: ignore
952                ):
953                    return None
954
955            if c_token is p_token:
956                continue
957            elif (
958                isinstance(c_token, InspectionAttr)
959                and insp_is_mapper(c_token)
960                and insp_is_mapper(p_token)
961                and c_token.isa(p_token)
962            ):
963                continue
964
965            else:
966                return None
967        return to_chop[i + 1 :]
968
969
970class Load(_AbstractLoad):
971    """Represents loader options which modify the state of a
972    ORM-enabled :class:`_sql.Select` or a legacy :class:`_query.Query` in
973    order to affect how various mapped attributes are loaded.
974
975    The :class:`_orm.Load` object is in most cases used implicitly behind the
976    scenes when one makes use of a query option like :func:`_orm.joinedload`,
977    :func:`_orm.defer`, or similar.   It typically is not instantiated directly
978    except for in some very specific cases.
979
980    .. seealso::
981
982        :ref:`orm_queryguide_relationship_per_entity_wildcard` - illustrates an
983        example where direct use of :class:`_orm.Load` may be useful
984
985    """
986
987    __slots__ = (
988        "path",
989        "context",
990        "additional_source_entities",
991    )
992
993    _traverse_internals = [
994        ("path", visitors.ExtendedInternalTraversal.dp_has_cache_key),
995        (
996            "context",
997            visitors.InternalTraversal.dp_has_cache_key_list,
998        ),
999        ("propagate_to_loaders", visitors.InternalTraversal.dp_boolean),
1000        (
1001            "additional_source_entities",
1002            visitors.InternalTraversal.dp_has_cache_key_list,
1003        ),
1004    ]
1005    _cache_key_traversal = None
1006
1007    path: PathRegistry
1008    context: Tuple[_LoadElement, ...]
1009    additional_source_entities: Tuple[_InternalEntityType[Any], ...]
1010
1011    def __init__(self, entity: _EntityType[Any]):
1012        insp = cast("Union[Mapper[Any], AliasedInsp[Any]]", inspect(entity))
1013        insp._post_inspect
1014
1015        self.path = insp._path_registry
1016        self.context = ()
1017        self.propagate_to_loaders = False
1018        self.additional_source_entities = ()
1019
1020    def __str__(self) -> str:
1021        return f"Load({self.path[0]})"
1022
1023    @classmethod
1024    def _construct_for_existing_path(
1025        cls, path: AbstractEntityRegistry
1026    ) -> Load:
1027        load = cls.__new__(cls)
1028        load.path = path
1029        load.context = ()
1030        load.propagate_to_loaders = False
1031        load.additional_source_entities = ()
1032        return load
1033
1034    def _adapt_cached_option_to_uncached_option(
1035        self, context: QueryContext, uncached_opt: ORMOption
1036    ) -> ORMOption:
1037        if uncached_opt is self:
1038            return self
1039        return self._adjust_for_extra_criteria(context)
1040
1041    def _prepend_path(self, path: PathRegistry) -> Load:
1042        cloned = self._clone()
1043        cloned.context = tuple(
1044            element._prepend_path(path) for element in self.context
1045        )
1046        return cloned
1047
1048    def _adjust_for_extra_criteria(self, context: QueryContext) -> Load:
1049        """Apply the current bound parameters in a QueryContext to all
1050        occurrences "extra_criteria" stored within this ``Load`` object,
1051        returning a new instance of this ``Load`` object.
1052
1053        """
1054
1055        # avoid generating cache keys for the queries if we don't
1056        # actually have any extra_criteria options, which is the
1057        # common case
1058        for value in self.context:
1059            if value._extra_criteria:
1060                break
1061        else:
1062            return self
1063
1064        replacement_cache_key = context.user_passed_query._generate_cache_key()
1065
1066        if replacement_cache_key is None:
1067            return self
1068
1069        orig_query = context.compile_state.select_statement
1070        orig_cache_key = orig_query._generate_cache_key()
1071        assert orig_cache_key is not None
1072
1073        def process(
1074            opt: _LoadElement,
1075            replacement_cache_key: CacheKey,
1076            orig_cache_key: CacheKey,
1077        ) -> _LoadElement:
1078            cloned_opt = opt._clone()
1079
1080            cloned_opt._extra_criteria = tuple(
1081                replacement_cache_key._apply_params_to_element(
1082                    orig_cache_key, crit
1083                )
1084                for crit in cloned_opt._extra_criteria
1085            )
1086
1087            return cloned_opt
1088
1089        cloned = self._clone()
1090        cloned.context = tuple(
1091            (
1092                process(value, replacement_cache_key, orig_cache_key)
1093                if value._extra_criteria
1094                else value
1095            )
1096            for value in self.context
1097        )
1098        return cloned
1099
1100    def _reconcile_query_entities_with_us(self, mapper_entities, raiseerr):
1101        """called at process time to allow adjustment of the root
1102        entity inside of _LoadElement objects.
1103
1104        """
1105        path = self.path
1106
1107        for ent in mapper_entities:
1108            ezero = ent.entity_zero
1109            if ezero and orm_util._entity_corresponds_to(
1110                # technically this can be a token also, but this is
1111                # safe to pass to _entity_corresponds_to()
1112                ezero,
1113                cast("_InternalEntityType[Any]", path[0]),
1114            ):
1115                return ezero
1116
1117        return None
1118
1119    def _process(
1120        self,
1121        compile_state: ORMCompileState,
1122        mapper_entities: Sequence[_MapperEntity],
1123        raiseerr: bool,
1124    ) -> None:
1125        reconciled_lead_entity = self._reconcile_query_entities_with_us(
1126            mapper_entities, raiseerr
1127        )
1128
1129        # if the context has a current path, this is a lazy load
1130        has_current_path = bool(compile_state.compile_options._current_path)
1131
1132        for loader in self.context:
1133            # issue #11292
1134            # historically, propagate_to_loaders was only considered at
1135            # object loading time, whether or not to carry along options
1136            # onto an object's loaded state where it would be used by lazyload.
1137            # however, the defaultload() option needs to propagate in case
1138            # its sub-options propagate_to_loaders, but its sub-options
1139            # that dont propagate should not be applied for lazy loaders.
1140            # so we check again
1141            if has_current_path and not loader.propagate_to_loaders:
1142                continue
1143            loader.process_compile_state(
1144                self,
1145                compile_state,
1146                mapper_entities,
1147                reconciled_lead_entity,
1148                raiseerr,
1149            )
1150
1151    def _apply_to_parent(self, parent: Load) -> None:
1152        """apply this :class:`_orm.Load` object as a sub-option of another
1153        :class:`_orm.Load` object.
1154
1155        This method is used by the :meth:`_orm.Load.options` method.
1156
1157        """
1158        cloned = self._generate()
1159
1160        assert cloned.propagate_to_loaders == self.propagate_to_loaders
1161
1162        if not any(
1163            orm_util._entity_corresponds_to_use_path_impl(
1164                elem, cloned.path.odd_element(0)
1165            )
1166            for elem in (parent.path.odd_element(-1),)
1167            + parent.additional_source_entities
1168        ):
1169            if len(cloned.path) > 1:
1170                attrname = cloned.path[1]
1171                parent_entity = cloned.path[0]
1172            else:
1173                attrname = cloned.path[0]
1174                parent_entity = cloned.path[0]
1175            _raise_for_does_not_link(parent.path, attrname, parent_entity)
1176
1177        cloned.path = PathRegistry.coerce(parent.path[0:-1] + cloned.path[:])
1178
1179        if self.context:
1180            cloned.context = tuple(
1181                value._prepend_path_from(parent) for value in self.context
1182            )
1183
1184        if cloned.context:
1185            parent.context += cloned.context
1186            parent.additional_source_entities += (
1187                cloned.additional_source_entities
1188            )
1189
1190    @_generative
1191    def options(self, *opts: _AbstractLoad) -> Self:
1192        r"""Apply a series of options as sub-options to this
1193        :class:`_orm.Load`
1194        object.
1195
1196        E.g.::
1197
1198            query = session.query(Author)
1199            query = query.options(
1200                joinedload(Author.book).options(

Showing the first 1,200 of 2587 lines. Download the file for the rest.

codekingpro/portable-devtools · Team Ai