Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
strategy_options.py2570 linesDownload Raw Back to orm
1# orm/strategy_options.py
2# Copyright (C) 2005-2024 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
11"""
12
13from __future__ import annotations
14
15import typing
16from typing import Any
17from typing import Callable
18from typing import cast
19from typing import Dict
20from typing import Iterable
21from typing import Optional
22from typing import overload
23from typing import Sequence
24from typing import Tuple
25from typing import Type
26from typing import TypeVar
27from typing import Union
28
29from . import util as orm_util
30from ._typing import insp_is_aliased_class
31from ._typing import insp_is_attribute
32from ._typing import insp_is_mapper
33from ._typing import insp_is_mapper_property
34from .attributes import QueryableAttribute
35from .base import InspectionAttr
36from .interfaces import LoaderOption
37from .path_registry import _DEFAULT_TOKEN
38from .path_registry import _StrPathToken
39from .path_registry import _WILDCARD_TOKEN
40from .path_registry import AbstractEntityRegistry
41from .path_registry import path_is_property
42from .path_registry import PathRegistry
43from .path_registry import TokenRegistry
44from .util import _orm_full_deannotate
45from .util import AliasedInsp
46from .. import exc as sa_exc
47from .. import inspect
48from .. import util
49from ..sql import and_
50from ..sql import cache_key
51from ..sql import coercions
52from ..sql import roles
53from ..sql import traversals
54from ..sql import visitors
55from ..sql.base import _generative
56from ..util.typing import Final
57from ..util.typing import Literal
58from ..util.typing import Self
59
60_RELATIONSHIP_TOKEN: Final[Literal["relationship"]] = "relationship"
61_COLUMN_TOKEN: Final[Literal["column"]] = "column"
62
63_FN = TypeVar("_FN", bound="Callable[..., Any]")
64
65if typing.TYPE_CHECKING:
66    from ._typing import _EntityType
67    from ._typing import _InternalEntityType
68    from .context import _MapperEntity
69    from .context import ORMCompileState
70    from .context import QueryContext
71    from .interfaces import _StrategyKey
72    from .interfaces import MapperProperty
73    from .interfaces import ORMOption
74    from .mapper import Mapper
75    from .path_registry import _PathRepresentation
76    from ..sql._typing import _ColumnExpressionArgument
77    from ..sql._typing import _FromClauseArgument
78    from ..sql.cache_key import _CacheKeyTraversalType
79    from ..sql.cache_key import CacheKey
80
81
82_AttrType = Union[Literal["*"], "QueryableAttribute[Any]"]
83
84_WildcardKeyType = Literal["relationship", "column"]
85_StrategySpec = Dict[str, Any]
86_OptsType = Dict[str, Any]
87_AttrGroupType = Tuple[_AttrType, ...]
88
89
90class _AbstractLoad(traversals.GenerativeOnTraversal, LoaderOption):
91    __slots__ = ("propagate_to_loaders",)
92
93    _is_strategy_option = True
94    propagate_to_loaders: bool
95
96    def contains_eager(
97        self,
98        attr: _AttrType,
99        alias: Optional[_FromClauseArgument] = None,
100        _is_chain: bool = False,
101        _propagate_to_loaders: bool = False,
102    ) -> Self:
103        r"""Indicate that the given attribute should be eagerly loaded from
104        columns stated manually in the query.
105
106        This function is part of the :class:`_orm.Load` interface and supports
107        both method-chained and standalone operation.
108
109        The option is used in conjunction with an explicit join that loads
110        the desired rows, i.e.::
111
112            sess.query(Order).join(Order.user).options(
113                contains_eager(Order.user)
114            )
115
116        The above query would join from the ``Order`` entity to its related
117        ``User`` entity, and the returned ``Order`` objects would have the
118        ``Order.user`` attribute pre-populated.
119
120        It may also be used for customizing the entries in an eagerly loaded
121        collection; queries will normally want to use the
122        :ref:`orm_queryguide_populate_existing` execution option assuming the
123        primary collection of parent objects may already have been loaded::
124
125            sess.query(User).join(User.addresses).filter(
126                Address.email_address.like("%@aol.com")
127            ).options(contains_eager(User.addresses)).populate_existing()
128
129        See the section :ref:`contains_eager` for complete usage details.
130
131        .. seealso::
132
133            :ref:`loading_toplevel`
134
135            :ref:`contains_eager`
136
137        """
138        if alias is not None:
139            if not isinstance(alias, str):
140                coerced_alias = coercions.expect(roles.FromClauseRole, alias)
141            else:
142                util.warn_deprecated(
143                    "Passing a string name for the 'alias' argument to "
144                    "'contains_eager()` is deprecated, and will not work in a "
145                    "future release.  Please use a sqlalchemy.alias() or "
146                    "sqlalchemy.orm.aliased() construct.",
147                    version="1.4",
148                )
149                coerced_alias = alias
150
151        elif getattr(attr, "_of_type", None):
152            assert isinstance(attr, QueryableAttribute)
153            ot: Optional[_InternalEntityType[Any]] = inspect(attr._of_type)
154            assert ot is not None
155            coerced_alias = ot.selectable
156        else:
157            coerced_alias = None
158
159        cloned = self._set_relationship_strategy(
160            attr,
161            {"lazy": "joined"},
162            propagate_to_loaders=_propagate_to_loaders,
163            opts={"eager_from_alias": coerced_alias},
164            _reconcile_to_other=True if _is_chain else None,
165        )
166        return cloned
167
168    def load_only(self, *attrs: _AttrType, raiseload: bool = False) -> Self:
169        r"""Indicate that for a particular entity, only the given list
170        of column-based attribute names should be loaded; all others will be
171        deferred.
172
173        This function is part of the :class:`_orm.Load` interface and supports
174        both method-chained and standalone operation.
175
176        Example - given a class ``User``, load only the ``name`` and
177        ``fullname`` attributes::
178
179            session.query(User).options(load_only(User.name, User.fullname))
180
181        Example - given a relationship ``User.addresses -> Address``, specify
182        subquery loading for the ``User.addresses`` collection, but on each
183        ``Address`` object load only the ``email_address`` attribute::
184
185            session.query(User).options(
186                subqueryload(User.addresses).load_only(Address.email_address)
187            )
188
189        For a statement that has multiple entities,
190        the lead entity can be
191        specifically referred to using the :class:`_orm.Load` constructor::
192
193            stmt = (
194                select(User, Address)
195                .join(User.addresses)
196                .options(
197                    Load(User).load_only(User.name, User.fullname),
198                    Load(Address).load_only(Address.email_address),
199                )
200            )
201
202        When used together with the
203        :ref:`populate_existing <orm_queryguide_populate_existing>`
204        execution option only the attributes listed will be refreshed.
205
206        :param \*attrs: Attributes to be loaded, all others will be deferred.
207
208        :param raiseload: raise :class:`.InvalidRequestError` rather than
209         lazy loading a value when a deferred attribute is accessed. Used
210         to prevent unwanted SQL from being emitted.
211
212         .. versionadded:: 2.0
213
214        .. seealso::
215
216            :ref:`orm_queryguide_column_deferral` - in the
217            :ref:`queryguide_toplevel`
218
219        :param \*attrs: Attributes to be loaded, all others will be deferred.
220
221        :param raiseload: raise :class:`.InvalidRequestError` rather than
222         lazy loading a value when a deferred attribute is accessed. Used
223         to prevent unwanted SQL from being emitted.
224
225         .. versionadded:: 2.0
226
227        """
228        cloned = self._set_column_strategy(
229            attrs,
230            {"deferred": False, "instrument": True},
231        )
232
233        wildcard_strategy = {"deferred": True, "instrument": True}
234        if raiseload:
235            wildcard_strategy["raiseload"] = True
236
237        cloned = cloned._set_column_strategy(
238            ("*",),
239            wildcard_strategy,
240        )
241        return cloned
242
243    def joinedload(
244        self,
245        attr: _AttrType,
246        innerjoin: Optional[bool] = None,
247    ) -> Self:
248        """Indicate that the given attribute should be loaded using joined
249        eager loading.
250
251        This function is part of the :class:`_orm.Load` interface and supports
252        both method-chained and standalone operation.
253
254        examples::
255
256            # joined-load the "orders" collection on "User"
257            select(User).options(joinedload(User.orders))
258
259            # joined-load Order.items and then Item.keywords
260            select(Order).options(
261                joinedload(Order.items).joinedload(Item.keywords)
262            )
263
264            # lazily load Order.items, but when Items are loaded,
265            # joined-load the keywords collection
266            select(Order).options(
267                lazyload(Order.items).joinedload(Item.keywords)
268            )
269
270        :param innerjoin: if ``True``, indicates that the joined eager load
271         should use an inner join instead of the default of left outer join::
272
273            select(Order).options(joinedload(Order.user, innerjoin=True))
274
275        In order to chain multiple eager joins together where some may be
276        OUTER and others INNER, right-nested joins are used to link them::
277
278            select(A).options(
279                joinedload(A.bs, innerjoin=False).joinedload(
280                    B.cs, innerjoin=True
281                )
282            )
283
284        The above query, linking A.bs via "outer" join and B.cs via "inner"
285        join would render the joins as "a LEFT OUTER JOIN (b JOIN c)". When
286        using older versions of SQLite (< 3.7.16), this form of JOIN is
287        translated to use full subqueries as this syntax is otherwise not
288        directly supported.
289
290        The ``innerjoin`` flag can also be stated with the term ``"unnested"``.
291        This indicates that an INNER JOIN should be used, *unless* the join
292        is linked to a LEFT OUTER JOIN to the left, in which case it
293        will render as LEFT OUTER JOIN.  For example, supposing ``A.bs``
294        is an outerjoin::
295
296            select(A).options(
297                joinedload(A.bs).joinedload(B.cs, innerjoin="unnested")
298            )
299
300
301        The above join will render as "a LEFT OUTER JOIN b LEFT OUTER JOIN c",
302        rather than as "a LEFT OUTER JOIN (b JOIN c)".
303
304        .. note:: The "unnested" flag does **not** affect the JOIN rendered
305            from a many-to-many association table, e.g. a table configured as
306            :paramref:`_orm.relationship.secondary`, to the target table; for
307            correctness of results, these joins are always INNER and are
308            therefore right-nested if linked to an OUTER join.
309
310        .. note::
311
312            The joins produced by :func:`_orm.joinedload` are **anonymously
313            aliased**. The criteria by which the join proceeds cannot be
314            modified, nor can the ORM-enabled :class:`_sql.Select` or legacy
315            :class:`_query.Query` refer to these joins in any way, including
316            ordering. See :ref:`zen_of_eager_loading` for further detail.
317
318            To produce a specific SQL JOIN which is explicitly available, use
319            :meth:`_sql.Select.join` and :meth:`_query.Query.join`. To combine
320            explicit JOINs with eager loading of collections, use
321            :func:`_orm.contains_eager`; see :ref:`contains_eager`.
322
323        .. seealso::
324
325            :ref:`loading_toplevel`
326
327            :ref:`joined_eager_loading`
328
329        """
330        loader = self._set_relationship_strategy(
331            attr,
332            {"lazy": "joined"},
333            opts=(
334                {"innerjoin": innerjoin}
335                if innerjoin is not None
336                else util.EMPTY_DICT
337            ),
338        )
339        return loader
340
341    def subqueryload(self, attr: _AttrType) -> Self:
342        """Indicate that the given attribute should be loaded using
343        subquery eager loading.
344
345        This function is part of the :class:`_orm.Load` interface and supports
346        both method-chained and standalone operation.
347
348        examples::
349
350            # subquery-load the "orders" collection on "User"
351            select(User).options(subqueryload(User.orders))
352
353            # subquery-load Order.items and then Item.keywords
354            select(Order).options(
355                subqueryload(Order.items).subqueryload(Item.keywords)
356            )
357
358            # lazily load Order.items, but when Items are loaded,
359            # subquery-load the keywords collection
360            select(Order).options(
361                lazyload(Order.items).subqueryload(Item.keywords)
362            )
363
364
365        .. seealso::
366
367            :ref:`loading_toplevel`
368
369            :ref:`subquery_eager_loading`
370
371        """
372        return self._set_relationship_strategy(attr, {"lazy": "subquery"})
373
374    def selectinload(
375        self,
376        attr: _AttrType,
377        recursion_depth: Optional[int] = None,
378    ) -> Self:
379        """Indicate that the given attribute should be loaded using
380        SELECT IN eager loading.
381
382        This function is part of the :class:`_orm.Load` interface and supports
383        both method-chained and standalone operation.
384
385        examples::
386
387            # selectin-load the "orders" collection on "User"
388            select(User).options(selectinload(User.orders))
389
390            # selectin-load Order.items and then Item.keywords
391            select(Order).options(
392                selectinload(Order.items).selectinload(Item.keywords)
393            )
394
395            # lazily load Order.items, but when Items are loaded,
396            # selectin-load the keywords collection
397            select(Order).options(
398                lazyload(Order.items).selectinload(Item.keywords)
399            )
400
401        :param recursion_depth: optional int; when set to a positive integer
402         in conjunction with a self-referential relationship,
403         indicates "selectin" loading will continue that many levels deep
404         automatically until no items are found.
405
406         .. note:: The :paramref:`_orm.selectinload.recursion_depth` option
407            currently supports only self-referential relationships.  There
408            is not yet an option to automatically traverse recursive structures
409            with more than one relationship involved.
410
411            Additionally, the :paramref:`_orm.selectinload.recursion_depth`
412            parameter is new and experimental and should be treated as "alpha"
413            status for the 2.0 series.
414
415         .. versionadded:: 2.0 added
416            :paramref:`_orm.selectinload.recursion_depth`
417
418
419        .. seealso::
420
421            :ref:`loading_toplevel`
422
423            :ref:`selectin_eager_loading`
424
425        """
426        return self._set_relationship_strategy(
427            attr,
428            {"lazy": "selectin"},
429            opts={"recursion_depth": recursion_depth},
430        )
431
432    def lazyload(self, attr: _AttrType) -> Self:
433        """Indicate that the given attribute should be loaded using "lazy"
434        loading.
435
436        This function is part of the :class:`_orm.Load` interface and supports
437        both method-chained and standalone operation.
438
439        .. seealso::
440
441            :ref:`loading_toplevel`
442
443            :ref:`lazy_loading`
444
445        """
446        return self._set_relationship_strategy(attr, {"lazy": "select"})
447
448    def immediateload(
449        self,
450        attr: _AttrType,
451        recursion_depth: Optional[int] = None,
452    ) -> Self:
453        """Indicate that the given attribute should be loaded using
454        an immediate load with a per-attribute SELECT statement.
455
456        The load is achieved using the "lazyloader" strategy and does not
457        fire off any additional eager loaders.
458
459        The :func:`.immediateload` option is superseded in general
460        by the :func:`.selectinload` option, which performs the same task
461        more efficiently by emitting a SELECT for all loaded objects.
462
463        This function is part of the :class:`_orm.Load` interface and supports
464        both method-chained and standalone operation.
465
466        :param recursion_depth: optional int; when set to a positive integer
467         in conjunction with a self-referential relationship,
468         indicates "selectin" loading will continue that many levels deep
469         automatically until no items are found.
470
471         .. note:: The :paramref:`_orm.immediateload.recursion_depth` option
472            currently supports only self-referential relationships.  There
473            is not yet an option to automatically traverse recursive structures
474            with more than one relationship involved.
475
476         .. warning:: This parameter is new and experimental and should be
477            treated as "alpha" status
478
479         .. versionadded:: 2.0 added
480            :paramref:`_orm.immediateload.recursion_depth`
481
482
483        .. seealso::
484
485            :ref:`loading_toplevel`
486
487            :ref:`selectin_eager_loading`
488
489        """
490        loader = self._set_relationship_strategy(
491            attr,
492            {"lazy": "immediate"},
493            opts={"recursion_depth": recursion_depth},
494        )
495        return loader
496
497    def noload(self, attr: _AttrType) -> Self:
498        """Indicate that the given relationship attribute should remain
499        unloaded.
500
501        The relationship attribute will return ``None`` when accessed without
502        producing any loading effect.
503
504        This function is part of the :class:`_orm.Load` interface and supports
505        both method-chained and standalone operation.
506
507        :func:`_orm.noload` applies to :func:`_orm.relationship` attributes
508        only.
509
510        .. note:: Setting this loading strategy as the default strategy
511            for a relationship using the :paramref:`.orm.relationship.lazy`
512            parameter may cause issues with flushes, such if a delete operation
513            needs to load related objects and instead ``None`` was returned.
514
515        .. seealso::
516
517            :ref:`loading_toplevel`
518
519        """
520
521        return self._set_relationship_strategy(attr, {"lazy": "noload"})
522
523    def raiseload(self, attr: _AttrType, sql_only: bool = False) -> Self:
524        """Indicate that the given attribute should raise an error if accessed.
525
526        A relationship attribute configured with :func:`_orm.raiseload` will
527        raise an :exc:`~sqlalchemy.exc.InvalidRequestError` upon access. The
528        typical way this is useful is when an application is attempting to
529        ensure that all relationship attributes that are accessed in a
530        particular context would have been already loaded via eager loading.
531        Instead of having to read through SQL logs to ensure lazy loads aren't
532        occurring, this strategy will cause them to raise immediately.
533
534        :func:`_orm.raiseload` applies to :func:`_orm.relationship` attributes
535        only. In order to apply raise-on-SQL behavior to a column-based
536        attribute, use the :paramref:`.orm.defer.raiseload` parameter on the
537        :func:`.defer` loader option.
538
539        :param sql_only: if True, raise only if the lazy load would emit SQL,
540         but not if it is only checking the identity map, or determining that
541         the related value should just be None due to missing keys. When False,
542         the strategy will raise for all varieties of relationship loading.
543
544        This function is part of the :class:`_orm.Load` interface and supports
545        both method-chained and standalone operation.
546
547        .. seealso::
548
549            :ref:`loading_toplevel`
550
551            :ref:`prevent_lazy_with_raiseload`
552
553            :ref:`orm_queryguide_deferred_raiseload`
554
555        """
556
557        return self._set_relationship_strategy(
558            attr, {"lazy": "raise_on_sql" if sql_only else "raise"}
559        )
560
561    def defaultload(self, attr: _AttrType) -> Self:
562        """Indicate an attribute should load using its predefined loader style.
563
564        The behavior of this loading option is to not change the current
565        loading style of the attribute, meaning that the previously configured
566        one is used or, if no previous style was selected, the default
567        loading will be used.
568
569        This method is used to link to other loader options further into
570        a chain of attributes without altering the loader style of the links
571        along the chain.  For example, to set joined eager loading for an
572        element of an element::
573
574            session.query(MyClass).options(
575                defaultload(MyClass.someattribute).joinedload(
576                    MyOtherClass.someotherattribute
577                )
578            )
579
580        :func:`.defaultload` is also useful for setting column-level options on
581        a related class, namely that of :func:`.defer` and :func:`.undefer`::
582
583            session.scalars(
584                select(MyClass).options(
585                    defaultload(MyClass.someattribute)
586                    .defer("some_column")
587                    .undefer("some_other_column")
588                )
589            )
590
591        .. seealso::
592
593            :ref:`orm_queryguide_relationship_sub_options`
594
595            :meth:`_orm.Load.options`
596
597        """
598        return self._set_relationship_strategy(attr, None)
599
600    def defer(self, key: _AttrType, raiseload: bool = False) -> Self:
601        r"""Indicate that the given column-oriented attribute should be
602        deferred, e.g. not loaded until accessed.
603
604        This function is part of the :class:`_orm.Load` interface and supports
605        both method-chained and standalone operation.
606
607        e.g.::
608
609            from sqlalchemy.orm import defer
610
611            session.query(MyClass).options(
612                defer(MyClass.attribute_one),
613                defer(MyClass.attribute_two)
614            )
615
616        To specify a deferred load of an attribute on a related class,
617        the path can be specified one token at a time, specifying the loading
618        style for each link along the chain.  To leave the loading style
619        for a link unchanged, use :func:`_orm.defaultload`::
620
621            session.query(MyClass).options(
622                defaultload(MyClass.someattr).defer(RelatedClass.some_column)
623            )
624
625        Multiple deferral options related to a relationship can be bundled
626        at once using :meth:`_orm.Load.options`::
627
628
629            select(MyClass).options(
630                defaultload(MyClass.someattr).options(
631                    defer(RelatedClass.some_column),
632                    defer(RelatedClass.some_other_column),
633                    defer(RelatedClass.another_column)
634                )
635            )
636
637        :param key: Attribute to be deferred.
638
639        :param raiseload: raise :class:`.InvalidRequestError` rather than
640         lazy loading a value when the deferred attribute is accessed. Used
641         to prevent unwanted SQL from being emitted.
642
643        .. versionadded:: 1.4
644
645        .. seealso::
646
647            :ref:`orm_queryguide_column_deferral` - in the
648            :ref:`queryguide_toplevel`
649
650            :func:`_orm.load_only`
651
652            :func:`_orm.undefer`
653
654        """
655        strategy = {"deferred": True, "instrument": True}
656        if raiseload:
657            strategy["raiseload"] = True
658        return self._set_column_strategy((key,), strategy)
659
660    def undefer(self, key: _AttrType) -> Self:
661        r"""Indicate that the given column-oriented attribute should be
662        undeferred, e.g. specified within the SELECT statement of the entity
663        as a whole.
664
665        The column being undeferred is typically set up on the mapping as a
666        :func:`.deferred` attribute.
667
668        This function is part of the :class:`_orm.Load` interface and supports
669        both method-chained and standalone operation.
670
671        Examples::
672
673            # undefer two columns
674            session.query(MyClass).options(
675                undefer(MyClass.col1), undefer(MyClass.col2)
676            )
677
678            # undefer all columns specific to a single class using Load + *
679            session.query(MyClass, MyOtherClass).options(
680                Load(MyClass).undefer("*")
681            )
682
683            # undefer a column on a related object
684            select(MyClass).options(
685                defaultload(MyClass.items).undefer(MyClass.text)
686            )
687
688        :param key: Attribute to be undeferred.
689
690        .. seealso::
691
692            :ref:`orm_queryguide_column_deferral` - in the
693            :ref:`queryguide_toplevel`
694
695            :func:`_orm.defer`
696
697            :func:`_orm.undefer_group`
698
699        """
700        return self._set_column_strategy(
701            (key,), {"deferred": False, "instrument": True}
702        )
703
704    def undefer_group(self, name: str) -> Self:
705        """Indicate that columns within the given deferred group name should be
706        undeferred.
707
708        The columns being undeferred are set up on the mapping as
709        :func:`.deferred` attributes and include a "group" name.
710
711        E.g::
712
713            session.query(MyClass).options(undefer_group("large_attrs"))
714
715        To undefer a group of attributes on a related entity, the path can be
716        spelled out using relationship loader options, such as
717        :func:`_orm.defaultload`::
718
719            select(MyClass).options(
720                defaultload("someattr").undefer_group("large_attrs")
721            )
722
723        .. seealso::
724
725            :ref:`orm_queryguide_column_deferral` - in the
726            :ref:`queryguide_toplevel`
727
728            :func:`_orm.defer`
729
730            :func:`_orm.undefer`
731
732        """
733        return self._set_column_strategy(
734            (_WILDCARD_TOKEN,), None, {f"undefer_group_{name}": True}
735        )
736
737    def with_expression(
738        self,
739        key: _AttrType,
740        expression: _ColumnExpressionArgument[Any],
741    ) -> Self:
742        r"""Apply an ad-hoc SQL expression to a "deferred expression"
743        attribute.
744
745        This option is used in conjunction with the
746        :func:`_orm.query_expression` mapper-level construct that indicates an
747        attribute which should be the target of an ad-hoc SQL expression.
748
749        E.g.::
750
751            stmt = select(SomeClass).options(
752                with_expression(SomeClass.x_y_expr, SomeClass.x + SomeClass.y)
753            )
754
755        .. versionadded:: 1.2
756
757        :param key: Attribute to be populated
758
759        :param expr: SQL expression to be applied to the attribute.
760
761        .. seealso::
762
763            :ref:`orm_queryguide_with_expression` - background and usage
764            examples
765
766        """
767
768        expression = _orm_full_deannotate(
769            coercions.expect(roles.LabeledColumnExprRole, expression)
770        )
771
772        return self._set_column_strategy(
773            (key,), {"query_expression": True}, extra_criteria=(expression,)
774        )
775
776    def selectin_polymorphic(self, classes: Iterable[Type[Any]]) -> Self:
777        """Indicate an eager load should take place for all attributes
778        specific to a subclass.
779
780        This uses an additional SELECT with IN against all matched primary
781        key values, and is the per-query analogue to the ``"selectin"``
782        setting on the :paramref:`.mapper.polymorphic_load` parameter.
783
784        .. versionadded:: 1.2
785
786        .. seealso::
787
788            :ref:`polymorphic_selectin`
789
790        """
791        self = self._set_class_strategy(
792            {"selectinload_polymorphic": True},
793            opts={
794                "entities": tuple(
795                    sorted((inspect(cls) for cls in classes), key=id)
796                )
797            },
798        )
799        return self
800
801    @overload
802    def _coerce_strat(self, strategy: _StrategySpec) -> _StrategyKey: ...
803
804    @overload
805    def _coerce_strat(self, strategy: Literal[None]) -> None: ...
806
807    def _coerce_strat(
808        self, strategy: Optional[_StrategySpec]
809    ) -> Optional[_StrategyKey]:
810        if strategy is not None:
811            strategy_key = tuple(sorted(strategy.items()))
812        else:
813            strategy_key = None
814        return strategy_key
815
816    @_generative
817    def _set_relationship_strategy(
818        self,
819        attr: _AttrType,
820        strategy: Optional[_StrategySpec],
821        propagate_to_loaders: bool = True,
822        opts: Optional[_OptsType] = None,
823        _reconcile_to_other: Optional[bool] = None,
824    ) -> Self:
825        strategy_key = self._coerce_strat(strategy)
826
827        self._clone_for_bind_strategy(
828            (attr,),
829            strategy_key,
830            _RELATIONSHIP_TOKEN,
831            opts=opts,
832            propagate_to_loaders=propagate_to_loaders,
833            reconcile_to_other=_reconcile_to_other,
834        )
835        return self
836
837    @_generative
838    def _set_column_strategy(
839        self,
840        attrs: Tuple[_AttrType, ...],
841        strategy: Optional[_StrategySpec],
842        opts: Optional[_OptsType] = None,
843        extra_criteria: Optional[Tuple[Any, ...]] = None,
844    ) -> Self:
845        strategy_key = self._coerce_strat(strategy)
846
847        self._clone_for_bind_strategy(
848            attrs,
849            strategy_key,
850            _COLUMN_TOKEN,
851            opts=opts,
852            attr_group=attrs,
853            extra_criteria=extra_criteria,
854        )
855        return self
856
857    @_generative
858    def _set_generic_strategy(
859        self,
860        attrs: Tuple[_AttrType, ...],
861        strategy: _StrategySpec,
862        _reconcile_to_other: Optional[bool] = None,
863    ) -> Self:
864        strategy_key = self._coerce_strat(strategy)
865        self._clone_for_bind_strategy(
866            attrs,
867            strategy_key,
868            None,
869            propagate_to_loaders=True,
870            reconcile_to_other=_reconcile_to_other,
871        )
872        return self
873
874    @_generative
875    def _set_class_strategy(
876        self, strategy: _StrategySpec, opts: _OptsType
877    ) -> Self:
878        strategy_key = self._coerce_strat(strategy)
879
880        self._clone_for_bind_strategy(None, strategy_key, None, opts=opts)
881        return self
882
883    def _apply_to_parent(self, parent: Load) -> None:
884        """apply this :class:`_orm._AbstractLoad` object as a sub-option o
885        a :class:`_orm.Load` object.
886
887        Implementation is provided by subclasses.
888
889        """
890        raise NotImplementedError()
891
892    def options(self, *opts: _AbstractLoad) -> Self:
893        r"""Apply a series of options as sub-options to this
894        :class:`_orm._AbstractLoad` object.
895
896        Implementation is provided by subclasses.
897
898        """
899        raise NotImplementedError()
900
901    def _clone_for_bind_strategy(
902        self,
903        attrs: Optional[Tuple[_AttrType, ...]],
904        strategy: Optional[_StrategyKey],
905        wildcard_key: Optional[_WildcardKeyType],
906        opts: Optional[_OptsType] = None,
907        attr_group: Optional[_AttrGroupType] = None,
908        propagate_to_loaders: bool = True,
909        reconcile_to_other: Optional[bool] = None,
910        extra_criteria: Optional[Tuple[Any, ...]] = None,
911    ) -> Self:
912        raise NotImplementedError()
913
914    def process_compile_state_replaced_entities(
915        self,
916        compile_state: ORMCompileState,
917        mapper_entities: Sequence[_MapperEntity],
918    ) -> None:
919        if not compile_state.compile_options._enable_eagerloads:
920            return
921
922        # process is being run here so that the options given are validated
923        # against what the lead entities were, as well as to accommodate
924        # for the entities having been replaced with equivalents
925        self._process(
926            compile_state,
927            mapper_entities,
928            not bool(compile_state.current_path),
929        )
930
931    def process_compile_state(self, compile_state: ORMCompileState) -> None:
932        if not compile_state.compile_options._enable_eagerloads:
933            return
934
935        self._process(
936            compile_state,
937            compile_state._lead_mapper_entities,
938            not bool(compile_state.current_path)
939            and not compile_state.compile_options._for_refresh_state,
940        )
941
942    def _process(
943        self,
944        compile_state: ORMCompileState,
945        mapper_entities: Sequence[_MapperEntity],
946        raiseerr: bool,
947    ) -> None:
948        """implemented by subclasses"""
949        raise NotImplementedError()
950
951    @classmethod
952    def _chop_path(
953        cls,
954        to_chop: _PathRepresentation,
955        path: PathRegistry,
956        debug: bool = False,
957    ) -> Optional[_PathRepresentation]:
958        i = -1
959
960        for i, (c_token, p_token) in enumerate(
961            zip(to_chop, path.natural_path)
962        ):
963            if isinstance(c_token, str):
964                if i == 0 and (
965                    c_token.endswith(f":{_DEFAULT_TOKEN}")
966                    or c_token.endswith(f":{_WILDCARD_TOKEN}")
967                ):
968                    return to_chop
969                elif (
970                    c_token != f"{_RELATIONSHIP_TOKEN}:{_WILDCARD_TOKEN}"
971                    and c_token != p_token.key  # type: ignore
972                ):
973                    return None
974
975            if c_token is p_token:
976                continue
977            elif (
978                isinstance(c_token, InspectionAttr)
979                and insp_is_mapper(c_token)
980                and insp_is_mapper(p_token)
981                and c_token.isa(p_token)
982            ):
983                continue
984
985            else:
986                return None
987        return to_chop[i + 1 :]
988
989
990class Load(_AbstractLoad):
991    """Represents loader options which modify the state of a
992    ORM-enabled :class:`_sql.Select` or a legacy :class:`_query.Query` in
993    order to affect how various mapped attributes are loaded.
994
995    The :class:`_orm.Load` object is in most cases used implicitly behind the
996    scenes when one makes use of a query option like :func:`_orm.joinedload`,
997    :func:`_orm.defer`, or similar.   It typically is not instantiated directly
998    except for in some very specific cases.
999
1000    .. seealso::
1001
1002        :ref:`orm_queryguide_relationship_per_entity_wildcard` - illustrates an
1003        example where direct use of :class:`_orm.Load` may be useful
1004
1005    """
1006
1007    __slots__ = (
1008        "path",
1009        "context",
1010        "additional_source_entities",
1011    )
1012
1013    _traverse_internals = [
1014        ("path", visitors.ExtendedInternalTraversal.dp_has_cache_key),
1015        (
1016            "context",
1017            visitors.InternalTraversal.dp_has_cache_key_list,
1018        ),
1019        ("propagate_to_loaders", visitors.InternalTraversal.dp_boolean),
1020        (
1021            "additional_source_entities",
1022            visitors.InternalTraversal.dp_has_cache_key_list,
1023        ),
1024    ]
1025    _cache_key_traversal = None
1026
1027    path: PathRegistry
1028    context: Tuple[_LoadElement, ...]
1029    additional_source_entities: Tuple[_InternalEntityType[Any], ...]
1030
1031    def __init__(self, entity: _EntityType[Any]):
1032        insp = cast("Union[Mapper[Any], AliasedInsp[Any]]", inspect(entity))
1033        insp._post_inspect
1034
1035        self.path = insp._path_registry
1036        self.context = ()
1037        self.propagate_to_loaders = False
1038        self.additional_source_entities = ()
1039
1040    def __str__(self) -> str:
1041        return f"Load({self.path[0]})"
1042
1043    @classmethod
1044    def _construct_for_existing_path(
1045        cls, path: AbstractEntityRegistry
1046    ) -> Load:
1047        load = cls.__new__(cls)
1048        load.path = path
1049        load.context = ()
1050        load.propagate_to_loaders = False
1051        load.additional_source_entities = ()
1052        return load
1053
1054    def _adapt_cached_option_to_uncached_option(
1055        self, context: QueryContext, uncached_opt: ORMOption
1056    ) -> ORMOption:
1057        if uncached_opt is self:
1058            return self
1059        return self._adjust_for_extra_criteria(context)
1060
1061    def _prepend_path(self, path: PathRegistry) -> Load:
1062        cloned = self._clone()
1063        cloned.context = tuple(
1064            element._prepend_path(path) for element in self.context
1065        )
1066        return cloned
1067
1068    def _adjust_for_extra_criteria(self, context: QueryContext) -> Load:
1069        """Apply the current bound parameters in a QueryContext to all
1070        occurrences "extra_criteria" stored within this ``Load`` object,
1071        returning a new instance of this ``Load`` object.
1072
1073        """
1074
1075        # avoid generating cache keys for the queries if we don't
1076        # actually have any extra_criteria options, which is the
1077        # common case
1078        for value in self.context:
1079            if value._extra_criteria:
1080                break
1081        else:
1082            return self
1083
1084        replacement_cache_key = context.query._generate_cache_key()
1085
1086        if replacement_cache_key is None:
1087            return self
1088
1089        orig_query = context.compile_state.select_statement
1090        orig_cache_key = orig_query._generate_cache_key()
1091        assert orig_cache_key is not None
1092
1093        def process(
1094            opt: _LoadElement,
1095            replacement_cache_key: CacheKey,
1096            orig_cache_key: CacheKey,
1097        ) -> _LoadElement:
1098            cloned_opt = opt._clone()
1099
1100            cloned_opt._extra_criteria = tuple(
1101                replacement_cache_key._apply_params_to_element(
1102                    orig_cache_key, crit
1103                )
1104                for crit in cloned_opt._extra_criteria
1105            )
1106
1107            return cloned_opt
1108
1109        cloned = self._clone()
1110        cloned.context = tuple(
1111            (
1112                process(value, replacement_cache_key, orig_cache_key)
1113                if value._extra_criteria
1114                else value
1115            )
1116            for value in self.context
1117        )
1118        return cloned
1119
1120    def _reconcile_query_entities_with_us(self, mapper_entities, raiseerr):
1121        """called at process time to allow adjustment of the root
1122        entity inside of _LoadElement objects.
1123
1124        """
1125        path = self.path
1126
1127        ezero = None
1128        for ent in mapper_entities:
1129            ezero = ent.entity_zero
1130            if ezero and orm_util._entity_corresponds_to(
1131                # technically this can be a token also, but this is
1132                # safe to pass to _entity_corresponds_to()
1133                ezero,
1134                cast("_InternalEntityType[Any]", path[0]),
1135            ):
1136                return ezero
1137
1138        return None
1139
1140    def _process(
1141        self,
1142        compile_state: ORMCompileState,
1143        mapper_entities: Sequence[_MapperEntity],
1144        raiseerr: bool,
1145    ) -> None:
1146        reconciled_lead_entity = self._reconcile_query_entities_with_us(
1147            mapper_entities, raiseerr
1148        )
1149
1150        # if the context has a current path, this is a lazy load
1151        has_current_path = bool(compile_state.compile_options._current_path)
1152
1153        for loader in self.context:
1154            # issue #11292
1155            # historically, propagate_to_loaders was only considered at
1156            # object loading time, whether or not to carry along options
1157            # onto an object's loaded state where it would be used by lazyload.
1158            # however, the defaultload() option needs to propagate in case
1159            # its sub-options propagate_to_loaders, but its sub-options
1160            # that dont propagate should not be applied for lazy loaders.
1161            # so we check again
1162            if has_current_path and not loader.propagate_to_loaders:
1163                continue
1164            loader.process_compile_state(
1165                self,
1166                compile_state,
1167                mapper_entities,
1168                reconciled_lead_entity,
1169                raiseerr,
1170            )
1171
1172    def _apply_to_parent(self, parent: Load) -> None:
1173        """apply this :class:`_orm.Load` object as a sub-option of another
1174        :class:`_orm.Load` object.
1175
1176        This method is used by the :meth:`_orm.Load.options` method.
1177
1178        """
1179        cloned = self._generate()
1180
1181        assert cloned.propagate_to_loaders == self.propagate_to_loaders
1182
1183        if not any(
1184            orm_util._entity_corresponds_to_use_path_impl(
1185                elem, cloned.path.odd_element(0)
1186            )
1187            for elem in (parent.path.odd_element(-1),)
1188            + parent.additional_source_entities
1189        ):
1190            if len(cloned.path) > 1:
1191                attrname = cloned.path[1]
1192                parent_entity = cloned.path[0]
1193            else:
1194                attrname = cloned.path[0]
1195                parent_entity = cloned.path[0]
1196            _raise_for_does_not_link(parent.path, attrname, parent_entity)
1197
1198        cloned.path = PathRegistry.coerce(parent.path[0:-1] + cloned.path[:])
1199
1200        if self.context:

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

codekingpro/portable-devtools · Team Ai