Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
strategies.py3483 linesDownload Raw Back to orm
1# orm/strategies.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: ignore-errors
8
9
10"""sqlalchemy.orm.interfaces.LoaderStrategy
11implementations, and related MapperOptions."""
12
13from __future__ import annotations
14
15import collections
16import itertools
17from typing import Any
18from typing import Dict
19from typing import Optional
20from typing import Tuple
21from typing import TYPE_CHECKING
22from typing import Union
23
24from . import attributes
25from . import exc as orm_exc
26from . import interfaces
27from . import loading
28from . import path_registry
29from . import properties
30from . import query
31from . import relationships
32from . import unitofwork
33from . import util as orm_util
34from .base import _DEFER_FOR_STATE
35from .base import _RAISE_FOR_STATE
36from .base import _SET_DEFERRED_EXPIRED
37from .base import ATTR_WAS_SET
38from .base import LoaderCallableStatus
39from .base import PASSIVE_OFF
40from .base import PassiveFlag
41from .context import _column_descriptions
42from .context import ORMCompileState
43from .context import ORMSelectCompileState
44from .context import QueryContext
45from .interfaces import LoaderStrategy
46from .interfaces import StrategizedProperty
47from .session import _state_session
48from .state import InstanceState
49from .strategy_options import Load
50from .util import _none_only_set
51from .util import AliasedClass
52from .. import event
53from .. import exc as sa_exc
54from .. import inspect
55from .. import log
56from .. import sql
57from .. import util
58from ..sql import util as sql_util
59from ..sql import visitors
60from ..sql.selectable import LABEL_STYLE_TABLENAME_PLUS_COL
61from ..sql.selectable import Select
62from ..util.typing import Literal
63
64if TYPE_CHECKING:
65    from .mapper import Mapper
66    from .relationships import RelationshipProperty
67    from ..sql.elements import ColumnElement
68
69
70def _register_attribute(
71    prop,
72    mapper,
73    useobject,
74    compare_function=None,
75    typecallable=None,
76    callable_=None,
77    proxy_property=None,
78    active_history=False,
79    impl_class=None,
80    **kw,
81):
82    listen_hooks = []
83
84    uselist = useobject and prop.uselist
85
86    if useobject and prop.single_parent:
87        listen_hooks.append(single_parent_validator)
88
89    if prop.key in prop.parent.validators:
90        fn, opts = prop.parent.validators[prop.key]
91        listen_hooks.append(
92            lambda desc, prop: orm_util._validator_events(
93                desc, prop.key, fn, **opts
94            )
95        )
96
97    if useobject:
98        listen_hooks.append(unitofwork.track_cascade_events)
99
100    # need to assemble backref listeners
101    # after the singleparentvalidator, mapper validator
102    if useobject:
103        backref = prop.back_populates
104        if backref and prop._effective_sync_backref:
105            listen_hooks.append(
106                lambda desc, prop: attributes.backref_listeners(
107                    desc, backref, uselist
108                )
109            )
110
111    # a single MapperProperty is shared down a class inheritance
112    # hierarchy, so we set up attribute instrumentation and backref event
113    # for each mapper down the hierarchy.
114
115    # typically, "mapper" is the same as prop.parent, due to the way
116    # the configure_mappers() process runs, however this is not strongly
117    # enforced, and in the case of a second configure_mappers() run the
118    # mapper here might not be prop.parent; also, a subclass mapper may
119    # be called here before a superclass mapper.  That is, can't depend
120    # on mappers not already being set up so we have to check each one.
121
122    for m in mapper.self_and_descendants:
123        if prop is m._props.get(
124            prop.key
125        ) and not m.class_manager._attr_has_impl(prop.key):
126            desc = attributes.register_attribute_impl(
127                m.class_,
128                prop.key,
129                parent_token=prop,
130                uselist=uselist,
131                compare_function=compare_function,
132                useobject=useobject,
133                trackparent=useobject
134                and (
135                    prop.single_parent
136                    or prop.direction is interfaces.ONETOMANY
137                ),
138                typecallable=typecallable,
139                callable_=callable_,
140                active_history=active_history,
141                impl_class=impl_class,
142                send_modified_events=not useobject or not prop.viewonly,
143                doc=prop.doc,
144                **kw,
145            )
146
147            for hook in listen_hooks:
148                hook(desc, prop)
149
150
151@properties.ColumnProperty.strategy_for(instrument=False, deferred=False)
152class UninstrumentedColumnLoader(LoaderStrategy):
153    """Represent a non-instrumented MapperProperty.
154
155    The polymorphic_on argument of mapper() often results in this,
156    if the argument is against the with_polymorphic selectable.
157
158    """
159
160    __slots__ = ("columns",)
161
162    def __init__(self, parent, strategy_key):
163        super().__init__(parent, strategy_key)
164        self.columns = self.parent_property.columns
165
166    def setup_query(
167        self,
168        compile_state,
169        query_entity,
170        path,
171        loadopt,
172        adapter,
173        column_collection=None,
174        **kwargs,
175    ):
176        for c in self.columns:
177            if adapter:
178                c = adapter.columns[c]
179            compile_state._append_dedupe_col_collection(c, column_collection)
180
181    def create_row_processor(
182        self,
183        context,
184        query_entity,
185        path,
186        loadopt,
187        mapper,
188        result,
189        adapter,
190        populators,
191    ):
192        pass
193
194
195@log.class_logger
196@properties.ColumnProperty.strategy_for(instrument=True, deferred=False)
197class ColumnLoader(LoaderStrategy):
198    """Provide loading behavior for a :class:`.ColumnProperty`."""
199
200    __slots__ = "columns", "is_composite"
201
202    def __init__(self, parent, strategy_key):
203        super().__init__(parent, strategy_key)
204        self.columns = self.parent_property.columns
205        self.is_composite = hasattr(self.parent_property, "composite_class")
206
207    def setup_query(
208        self,
209        compile_state,
210        query_entity,
211        path,
212        loadopt,
213        adapter,
214        column_collection,
215        memoized_populators,
216        check_for_adapt=False,
217        **kwargs,
218    ):
219        for c in self.columns:
220            if adapter:
221                if check_for_adapt:
222                    c = adapter.adapt_check_present(c)
223                    if c is None:
224                        return
225                else:
226                    c = adapter.columns[c]
227
228            compile_state._append_dedupe_col_collection(c, column_collection)
229
230        fetch = self.columns[0]
231        if adapter:
232            fetch = adapter.columns[fetch]
233            if fetch is None:
234                # None happens here only for dml bulk_persistence cases
235                # when context.DMLReturningColFilter is used
236                return
237
238        memoized_populators[self.parent_property] = fetch
239
240    def init_class_attribute(self, mapper):
241        self.is_class_level = True
242        coltype = self.columns[0].type
243        # TODO: check all columns ?  check for foreign key as well?
244        active_history = (
245            self.parent_property.active_history
246            or self.columns[0].primary_key
247            or (
248                mapper.version_id_col is not None
249                and mapper._columntoproperty.get(mapper.version_id_col, None)
250                is self.parent_property
251            )
252        )
253
254        _register_attribute(
255            self.parent_property,
256            mapper,
257            useobject=False,
258            compare_function=coltype.compare_values,
259            active_history=active_history,
260        )
261
262    def create_row_processor(
263        self,
264        context,
265        query_entity,
266        path,
267        loadopt,
268        mapper,
269        result,
270        adapter,
271        populators,
272    ):
273        # look through list of columns represented here
274        # to see which, if any, is present in the row.
275
276        for col in self.columns:
277            if adapter:
278                col = adapter.columns[col]
279            getter = result._getter(col, False)
280            if getter:
281                populators["quick"].append((self.key, getter))
282                break
283        else:
284            populators["expire"].append((self.key, True))
285
286
287@log.class_logger
288@properties.ColumnProperty.strategy_for(query_expression=True)
289class ExpressionColumnLoader(ColumnLoader):
290    def __init__(self, parent, strategy_key):
291        super().__init__(parent, strategy_key)
292
293        # compare to the "default" expression that is mapped in
294        # the column.   If it's sql.null, we don't need to render
295        # unless an expr is passed in the options.
296        null = sql.null().label(None)
297        self._have_default_expression = any(
298            not c.compare(null) for c in self.parent_property.columns
299        )
300
301    def setup_query(
302        self,
303        compile_state,
304        query_entity,
305        path,
306        loadopt,
307        adapter,
308        column_collection,
309        memoized_populators,
310        **kwargs,
311    ):
312        columns = None
313        if loadopt and loadopt._extra_criteria:
314            columns = loadopt._extra_criteria
315
316        elif self._have_default_expression:
317            columns = self.parent_property.columns
318
319        if columns is None:
320            return
321
322        for c in columns:
323            if adapter:
324                c = adapter.columns[c]
325            compile_state._append_dedupe_col_collection(c, column_collection)
326
327        fetch = columns[0]
328        if adapter:
329            fetch = adapter.columns[fetch]
330            if fetch is None:
331                # None is not expected to be the result of any
332                # adapter implementation here, however there may be theoretical
333                # usages of returning() with context.DMLReturningColFilter
334                return
335
336        memoized_populators[self.parent_property] = fetch
337
338    def create_row_processor(
339        self,
340        context,
341        query_entity,
342        path,
343        loadopt,
344        mapper,
345        result,
346        adapter,
347        populators,
348    ):
349        # look through list of columns represented here
350        # to see which, if any, is present in the row.
351        if loadopt and loadopt._extra_criteria:
352            columns = loadopt._extra_criteria
353
354            for col in columns:
355                if adapter:
356                    col = adapter.columns[col]
357                getter = result._getter(col, False)
358                if getter:
359                    populators["quick"].append((self.key, getter))
360                    break
361            else:
362                populators["expire"].append((self.key, True))
363
364    def init_class_attribute(self, mapper):
365        self.is_class_level = True
366
367        _register_attribute(
368            self.parent_property,
369            mapper,
370            useobject=False,
371            compare_function=self.columns[0].type.compare_values,
372            accepts_scalar_loader=False,
373        )
374
375
376@log.class_logger
377@properties.ColumnProperty.strategy_for(deferred=True, instrument=True)
378@properties.ColumnProperty.strategy_for(
379    deferred=True, instrument=True, raiseload=True
380)
381@properties.ColumnProperty.strategy_for(do_nothing=True)
382class DeferredColumnLoader(LoaderStrategy):
383    """Provide loading behavior for a deferred :class:`.ColumnProperty`."""
384
385    __slots__ = "columns", "group", "raiseload"
386
387    def __init__(self, parent, strategy_key):
388        super().__init__(parent, strategy_key)
389        if hasattr(self.parent_property, "composite_class"):
390            raise NotImplementedError(
391                "Deferred loading for composite types not implemented yet"
392            )
393        self.raiseload = self.strategy_opts.get("raiseload", False)
394        self.columns = self.parent_property.columns
395        self.group = self.parent_property.group
396
397    def create_row_processor(
398        self,
399        context,
400        query_entity,
401        path,
402        loadopt,
403        mapper,
404        result,
405        adapter,
406        populators,
407    ):
408        # for a DeferredColumnLoader, this method is only used during a
409        # "row processor only" query; see test_deferred.py ->
410        # tests with "rowproc_only" in their name.  As of the 1.0 series,
411        # loading._instance_processor doesn't use a "row processing" function
412        # to populate columns, instead it uses data in the "populators"
413        # dictionary.  Normally, the DeferredColumnLoader.setup_query()
414        # sets up that data in the "memoized_populators" dictionary
415        # and "create_row_processor()" here is never invoked.
416
417        if (
418            context.refresh_state
419            and context.query._compile_options._only_load_props
420            and self.key in context.query._compile_options._only_load_props
421        ):
422            self.parent_property._get_strategy(
423                (("deferred", False), ("instrument", True))
424            ).create_row_processor(
425                context,
426                query_entity,
427                path,
428                loadopt,
429                mapper,
430                result,
431                adapter,
432                populators,
433            )
434
435        elif not self.is_class_level:
436            if self.raiseload:
437                set_deferred_for_local_state = (
438                    self.parent_property._raise_column_loader
439                )
440            else:
441                set_deferred_for_local_state = (
442                    self.parent_property._deferred_column_loader
443                )
444            populators["new"].append((self.key, set_deferred_for_local_state))
445        else:
446            populators["expire"].append((self.key, False))
447
448    def init_class_attribute(self, mapper):
449        self.is_class_level = True
450
451        _register_attribute(
452            self.parent_property,
453            mapper,
454            useobject=False,
455            compare_function=self.columns[0].type.compare_values,
456            callable_=self._load_for_state,
457            load_on_unexpire=False,
458        )
459
460    def setup_query(
461        self,
462        compile_state,
463        query_entity,
464        path,
465        loadopt,
466        adapter,
467        column_collection,
468        memoized_populators,
469        only_load_props=None,
470        **kw,
471    ):
472        if (
473            (
474                compile_state.compile_options._render_for_subquery
475                and self.parent_property._renders_in_subqueries
476            )
477            or (
478                loadopt
479                and set(self.columns).intersection(
480                    self.parent._should_undefer_in_wildcard
481                )
482            )
483            or (
484                loadopt
485                and self.group
486                and loadopt.local_opts.get(
487                    "undefer_group_%s" % self.group, False
488                )
489            )
490            or (only_load_props and self.key in only_load_props)
491        ):
492            self.parent_property._get_strategy(
493                (("deferred", False), ("instrument", True))
494            ).setup_query(
495                compile_state,
496                query_entity,
497                path,
498                loadopt,
499                adapter,
500                column_collection,
501                memoized_populators,
502                **kw,
503            )
504        elif self.is_class_level:
505            memoized_populators[self.parent_property] = _SET_DEFERRED_EXPIRED
506        elif not self.raiseload:
507            memoized_populators[self.parent_property] = _DEFER_FOR_STATE
508        else:
509            memoized_populators[self.parent_property] = _RAISE_FOR_STATE
510
511    def _load_for_state(self, state, passive):
512        if not state.key:
513            return LoaderCallableStatus.ATTR_EMPTY
514
515        if not passive & PassiveFlag.SQL_OK:
516            return LoaderCallableStatus.PASSIVE_NO_RESULT
517
518        localparent = state.manager.mapper
519
520        if self.group:
521            toload = [
522                p.key
523                for p in localparent.iterate_properties
524                if isinstance(p, StrategizedProperty)
525                and isinstance(p.strategy, DeferredColumnLoader)
526                and p.group == self.group
527            ]
528        else:
529            toload = [self.key]
530
531        # narrow the keys down to just those which have no history
532        group = [k for k in toload if k in state.unmodified]
533
534        session = _state_session(state)
535        if session is None:
536            raise orm_exc.DetachedInstanceError(
537                "Parent instance %s is not bound to a Session; "
538                "deferred load operation of attribute '%s' cannot proceed"
539                % (orm_util.state_str(state), self.key)
540            )
541
542        if self.raiseload:
543            self._invoke_raise_load(state, passive, "raise")
544
545        loading.load_scalar_attributes(
546            state.mapper, state, set(group), PASSIVE_OFF
547        )
548
549        return LoaderCallableStatus.ATTR_WAS_SET
550
551    def _invoke_raise_load(self, state, passive, lazy):
552        raise sa_exc.InvalidRequestError(
553            "'%s' is not available due to raiseload=True" % (self,)
554        )
555
556
557class LoadDeferredColumns:
558    """serializable loader object used by DeferredColumnLoader"""
559
560    def __init__(self, key: str, raiseload: bool = False):
561        self.key = key
562        self.raiseload = raiseload
563
564    def __call__(self, state, passive=attributes.PASSIVE_OFF):
565        key = self.key
566
567        localparent = state.manager.mapper
568        prop = localparent._props[key]
569        if self.raiseload:
570            strategy_key = (
571                ("deferred", True),
572                ("instrument", True),
573                ("raiseload", True),
574            )
575        else:
576            strategy_key = (("deferred", True), ("instrument", True))
577        strategy = prop._get_strategy(strategy_key)
578        return strategy._load_for_state(state, passive)
579
580
581class AbstractRelationshipLoader(LoaderStrategy):
582    """LoaderStratgies which deal with related objects."""
583
584    __slots__ = "mapper", "target", "uselist", "entity"
585
586    def __init__(self, parent, strategy_key):
587        super().__init__(parent, strategy_key)
588        self.mapper = self.parent_property.mapper
589        self.entity = self.parent_property.entity
590        self.target = self.parent_property.target
591        self.uselist = self.parent_property.uselist
592
593    def _immediateload_create_row_processor(
594        self,
595        context,
596        query_entity,
597        path,
598        loadopt,
599        mapper,
600        result,
601        adapter,
602        populators,
603    ):
604        return self.parent_property._get_strategy(
605            (("lazy", "immediate"),)
606        ).create_row_processor(
607            context,
608            query_entity,
609            path,
610            loadopt,
611            mapper,
612            result,
613            adapter,
614            populators,
615        )
616
617
618@log.class_logger
619@relationships.RelationshipProperty.strategy_for(do_nothing=True)
620class DoNothingLoader(LoaderStrategy):
621    """Relationship loader that makes no change to the object's state.
622
623    Compared to NoLoader, this loader does not initialize the
624    collection/attribute to empty/none; the usual default LazyLoader will
625    take effect.
626
627    """
628
629
630@log.class_logger
631@relationships.RelationshipProperty.strategy_for(lazy="noload")
632@relationships.RelationshipProperty.strategy_for(lazy=None)
633class NoLoader(AbstractRelationshipLoader):
634    """Provide loading behavior for a :class:`.Relationship`
635    with "lazy=None".
636
637    """
638
639    __slots__ = ()
640
641    def init_class_attribute(self, mapper):
642        self.is_class_level = True
643
644        _register_attribute(
645            self.parent_property,
646            mapper,
647            useobject=True,
648            typecallable=self.parent_property.collection_class,
649        )
650
651    def create_row_processor(
652        self,
653        context,
654        query_entity,
655        path,
656        loadopt,
657        mapper,
658        result,
659        adapter,
660        populators,
661    ):
662        def invoke_no_load(state, dict_, row):
663            if self.uselist:
664                attributes.init_state_collection(state, dict_, self.key)
665            else:
666                dict_[self.key] = None
667
668        populators["new"].append((self.key, invoke_no_load))
669
670
671@log.class_logger
672@relationships.RelationshipProperty.strategy_for(lazy=True)
673@relationships.RelationshipProperty.strategy_for(lazy="select")
674@relationships.RelationshipProperty.strategy_for(lazy="raise")
675@relationships.RelationshipProperty.strategy_for(lazy="raise_on_sql")
676@relationships.RelationshipProperty.strategy_for(lazy="baked_select")
677class LazyLoader(
678    AbstractRelationshipLoader, util.MemoizedSlots, log.Identified
679):
680    """Provide loading behavior for a :class:`.Relationship`
681    with "lazy=True", that is loads when first accessed.
682
683    """
684
685    __slots__ = (
686        "_lazywhere",
687        "_rev_lazywhere",
688        "_lazyload_reverse_option",
689        "_order_by",
690        "use_get",
691        "is_aliased_class",
692        "_bind_to_col",
693        "_equated_columns",
694        "_rev_bind_to_col",
695        "_rev_equated_columns",
696        "_simple_lazy_clause",
697        "_raise_always",
698        "_raise_on_sql",
699    )
700
701    _lazywhere: ColumnElement[bool]
702    _bind_to_col: Dict[str, ColumnElement[Any]]
703    _rev_lazywhere: ColumnElement[bool]
704    _rev_bind_to_col: Dict[str, ColumnElement[Any]]
705
706    parent_property: RelationshipProperty[Any]
707
708    def __init__(
709        self, parent: RelationshipProperty[Any], strategy_key: Tuple[Any, ...]
710    ):
711        super().__init__(parent, strategy_key)
712        self._raise_always = self.strategy_opts["lazy"] == "raise"
713        self._raise_on_sql = self.strategy_opts["lazy"] == "raise_on_sql"
714
715        self.is_aliased_class = inspect(self.entity).is_aliased_class
716
717        join_condition = self.parent_property._join_condition
718        (
719            self._lazywhere,
720            self._bind_to_col,
721            self._equated_columns,
722        ) = join_condition.create_lazy_clause()
723
724        (
725            self._rev_lazywhere,
726            self._rev_bind_to_col,
727            self._rev_equated_columns,
728        ) = join_condition.create_lazy_clause(reverse_direction=True)
729
730        if self.parent_property.order_by:
731            self._order_by = [
732                sql_util._deep_annotate(elem, {"_orm_adapt": True})
733                for elem in util.to_list(self.parent_property.order_by)
734            ]
735        else:
736            self._order_by = None
737
738        self.logger.info("%s lazy loading clause %s", self, self._lazywhere)
739
740        # determine if our "lazywhere" clause is the same as the mapper's
741        # get() clause.  then we can just use mapper.get()
742        #
743        # TODO: the "not self.uselist" can be taken out entirely; a m2o
744        # load that populates for a list (very unusual, but is possible with
745        # the API) can still set for "None" and the attribute system will
746        # populate as an empty list.
747        self.use_get = (
748            not self.is_aliased_class
749            and not self.uselist
750            and self.entity._get_clause[0].compare(
751                self._lazywhere,
752                use_proxies=True,
753                compare_keys=False,
754                equivalents=self.mapper._equivalent_columns,
755            )
756        )
757
758        if self.use_get:
759            for col in list(self._equated_columns):
760                if col in self.mapper._equivalent_columns:
761                    for c in self.mapper._equivalent_columns[col]:
762                        self._equated_columns[c] = self._equated_columns[col]
763
764            self.logger.info(
765                "%s will use Session.get() to optimize instance loads", self
766            )
767
768    def init_class_attribute(self, mapper):
769        self.is_class_level = True
770
771        _legacy_inactive_history_style = (
772            self.parent_property._legacy_inactive_history_style
773        )
774
775        if self.parent_property.active_history:
776            active_history = True
777            _deferred_history = False
778
779        elif (
780            self.parent_property.direction is not interfaces.MANYTOONE
781            or not self.use_get
782        ):
783            if _legacy_inactive_history_style:
784                active_history = True
785                _deferred_history = False
786            else:
787                active_history = False
788                _deferred_history = True
789        else:
790            active_history = _deferred_history = False
791
792        _register_attribute(
793            self.parent_property,
794            mapper,
795            useobject=True,
796            callable_=self._load_for_state,
797            typecallable=self.parent_property.collection_class,
798            active_history=active_history,
799            _deferred_history=_deferred_history,
800        )
801
802    def _memoized_attr__simple_lazy_clause(self):
803        lazywhere = sql_util._deep_annotate(
804            self._lazywhere, {"_orm_adapt": True}
805        )
806
807        criterion, bind_to_col = (lazywhere, self._bind_to_col)
808
809        params = []
810
811        def visit_bindparam(bindparam):
812            bindparam.unique = False
813
814        visitors.traverse(criterion, {}, {"bindparam": visit_bindparam})
815
816        def visit_bindparam(bindparam):
817            if bindparam._identifying_key in bind_to_col:
818                params.append(
819                    (
820                        bindparam.key,
821                        bind_to_col[bindparam._identifying_key],
822                        None,
823                    )
824                )
825            elif bindparam.callable is None:
826                params.append((bindparam.key, None, bindparam.value))
827
828        criterion = visitors.cloned_traverse(
829            criterion, {}, {"bindparam": visit_bindparam}
830        )
831
832        return criterion, params
833
834    def _generate_lazy_clause(self, state, passive):
835        criterion, param_keys = self._simple_lazy_clause
836
837        if state is None:
838            return sql_util.adapt_criterion_to_null(
839                criterion, [key for key, ident, value in param_keys]
840            )
841
842        mapper = self.parent_property.parent
843
844        o = state.obj()  # strong ref
845        dict_ = attributes.instance_dict(o)
846
847        if passive & PassiveFlag.INIT_OK:
848            passive ^= PassiveFlag.INIT_OK
849
850        params = {}
851        for key, ident, value in param_keys:
852            if ident is not None:
853                if passive and passive & PassiveFlag.LOAD_AGAINST_COMMITTED:
854                    value = mapper._get_committed_state_attr_by_column(
855                        state, dict_, ident, passive
856                    )
857                else:
858                    value = mapper._get_state_attr_by_column(
859                        state, dict_, ident, passive
860                    )
861
862            params[key] = value
863
864        return criterion, params
865
866    def _invoke_raise_load(self, state, passive, lazy):
867        raise sa_exc.InvalidRequestError(
868            "'%s' is not available due to lazy='%s'" % (self, lazy)
869        )
870
871    def _load_for_state(
872        self,
873        state,
874        passive,
875        loadopt=None,
876        extra_criteria=(),
877        extra_options=(),
878        alternate_effective_path=None,
879        execution_options=util.EMPTY_DICT,
880    ):
881        if not state.key and (
882            (
883                not self.parent_property.load_on_pending
884                and not state._load_pending
885            )
886            or not state.session_id
887        ):
888            return LoaderCallableStatus.ATTR_EMPTY
889
890        pending = not state.key
891        primary_key_identity = None
892
893        use_get = self.use_get and (not loadopt or not loadopt._extra_criteria)
894
895        if (not passive & PassiveFlag.SQL_OK and not use_get) or (
896            not passive & attributes.NON_PERSISTENT_OK and pending
897        ):
898            return LoaderCallableStatus.PASSIVE_NO_RESULT
899
900        if (
901            # we were given lazy="raise"
902            self._raise_always
903            # the no_raise history-related flag was not passed
904            and not passive & PassiveFlag.NO_RAISE
905            and (
906                # if we are use_get and related_object_ok is disabled,
907                # which means we are at most looking in the identity map
908                # for history purposes or otherwise returning
909                # PASSIVE_NO_RESULT, don't raise.  This is also a
910                # history-related flag
911                not use_get
912                or passive & PassiveFlag.RELATED_OBJECT_OK
913            )
914        ):
915            self._invoke_raise_load(state, passive, "raise")
916
917        session = _state_session(state)
918        if not session:
919            if passive & PassiveFlag.NO_RAISE:
920                return LoaderCallableStatus.PASSIVE_NO_RESULT
921
922            raise orm_exc.DetachedInstanceError(
923                "Parent instance %s is not bound to a Session; "
924                "lazy load operation of attribute '%s' cannot proceed"
925                % (orm_util.state_str(state), self.key)
926            )
927
928        # if we have a simple primary key load, check the
929        # identity map without generating a Query at all
930        if use_get:
931            primary_key_identity = self._get_ident_for_use_get(
932                session, state, passive
933            )
934            if LoaderCallableStatus.PASSIVE_NO_RESULT in primary_key_identity:
935                return LoaderCallableStatus.PASSIVE_NO_RESULT
936            elif LoaderCallableStatus.NEVER_SET in primary_key_identity:
937                return LoaderCallableStatus.NEVER_SET
938
939            # test for None alone in primary_key_identity based on
940            # allow_partial_pks preference.   PASSIVE_NO_RESULT and NEVER_SET
941            # have already been tested above
942            if not self.mapper.allow_partial_pks:
943                if _none_only_set.intersection(primary_key_identity):
944                    return None
945            else:
946                if _none_only_set.issuperset(primary_key_identity):
947                    return None
948
949            if (
950                self.key in state.dict
951                and not passive & PassiveFlag.DEFERRED_HISTORY_LOAD
952            ):
953                return LoaderCallableStatus.ATTR_WAS_SET
954
955            # look for this identity in the identity map.  Delegate to the
956            # Query class in use, as it may have special rules for how it
957            # does this, including how it decides what the correct
958            # identity_token would be for this identity.
959
960            instance = session._identity_lookup(
961                self.entity,
962                primary_key_identity,
963                passive=passive,
964                lazy_loaded_from=state,
965            )
966
967            if instance is not None:
968                if instance is LoaderCallableStatus.PASSIVE_CLASS_MISMATCH:
969                    return None
970                else:
971                    return instance
972            elif (
973                not passive & PassiveFlag.SQL_OK
974                or not passive & PassiveFlag.RELATED_OBJECT_OK
975            ):
976                return LoaderCallableStatus.PASSIVE_NO_RESULT
977
978        return self._emit_lazyload(
979            session,
980            state,
981            primary_key_identity,
982            passive,
983            loadopt,
984            extra_criteria,
985            extra_options,
986            alternate_effective_path,
987            execution_options,
988        )
989
990    def _get_ident_for_use_get(self, session, state, passive):
991        instance_mapper = state.manager.mapper
992
993        if passive & PassiveFlag.LOAD_AGAINST_COMMITTED:
994            get_attr = instance_mapper._get_committed_state_attr_by_column
995        else:
996            get_attr = instance_mapper._get_state_attr_by_column
997
998        dict_ = state.dict
999
1000        return [
1001            get_attr(state, dict_, self._equated_columns[pk], passive=passive)
1002            for pk in self.mapper.primary_key
1003        ]
1004
1005    @util.preload_module("sqlalchemy.orm.strategy_options")
1006    def _emit_lazyload(
1007        self,
1008        session,
1009        state,
1010        primary_key_identity,
1011        passive,
1012        loadopt,
1013        extra_criteria,
1014        extra_options,
1015        alternate_effective_path,
1016        execution_options,
1017    ):
1018        strategy_options = util.preloaded.orm_strategy_options
1019
1020        clauseelement = self.entity.__clause_element__()
1021        stmt = Select._create_raw_select(
1022            _raw_columns=[clauseelement],
1023            _propagate_attrs=clauseelement._propagate_attrs,
1024            _label_style=LABEL_STYLE_TABLENAME_PLUS_COL,
1025            _compile_options=ORMCompileState.default_compile_options,
1026        )
1027        load_options = QueryContext.default_load_options
1028
1029        load_options += {
1030            "_invoke_all_eagers": False,
1031            "_lazy_loaded_from": state,
1032        }
1033
1034        if self.parent_property.secondary is not None:
1035            stmt = stmt.select_from(
1036                self.mapper, self.parent_property.secondary
1037            )
1038
1039        pending = not state.key
1040
1041        # don't autoflush on pending
1042        if pending or passive & attributes.NO_AUTOFLUSH:
1043            stmt._execution_options = util.immutabledict({"autoflush": False})
1044
1045        use_get = self.use_get
1046
1047        if state.load_options or (loadopt and loadopt._extra_criteria):
1048            if alternate_effective_path is None:
1049                effective_path = state.load_path[self.parent_property]
1050            else:
1051                effective_path = alternate_effective_path[self.parent_property]
1052
1053            opts = state.load_options
1054
1055            if loadopt and loadopt._extra_criteria:
1056                use_get = False
1057                opts += (
1058                    orm_util.LoaderCriteriaOption(self.entity, extra_criteria),
1059                )
1060
1061            stmt._with_options = opts
1062        elif alternate_effective_path is None:
1063            # this path is used if there are not already any options
1064            # in the query, but an event may want to add them
1065            effective_path = state.mapper._path_registry[self.parent_property]
1066        else:
1067            # added by immediateloader
1068            effective_path = alternate_effective_path[self.parent_property]
1069
1070        if extra_options:
1071            stmt._with_options += extra_options
1072
1073        stmt._compile_options += {"_current_path": effective_path}
1074
1075        if use_get:
1076            if self._raise_on_sql and not passive & PassiveFlag.NO_RAISE:
1077                self._invoke_raise_load(state, passive, "raise_on_sql")
1078
1079            return loading.load_on_pk_identity(
1080                session,
1081                stmt,
1082                primary_key_identity,
1083                load_options=load_options,
1084                execution_options=execution_options,
1085            )
1086
1087        if self._order_by:
1088            stmt._order_by_clauses = self._order_by
1089
1090        def _lazyload_reverse(compile_context):
1091            for rev in self.parent_property._reverse_property:
1092                # reverse props that are MANYTOONE are loading *this*
1093                # object from get(), so don't need to eager out to those.
1094                if (
1095                    rev.direction is interfaces.MANYTOONE
1096                    and rev._use_get
1097                    and not isinstance(rev.strategy, LazyLoader)
1098                ):
1099                    strategy_options.Load._construct_for_existing_path(
1100                        compile_context.compile_options._current_path[
1101                            rev.parent
1102                        ]
1103                    ).lazyload(rev).process_compile_state(compile_context)
1104
1105        stmt._with_context_options += (
1106            (_lazyload_reverse, self.parent_property),
1107        )
1108
1109        lazy_clause, params = self._generate_lazy_clause(state, passive)
1110
1111        if execution_options:
1112            execution_options = util.EMPTY_DICT.merge_with(
1113                execution_options,
1114                {
1115                    "_sa_orm_load_options": load_options,
1116                },
1117            )
1118        else:
1119            execution_options = {
1120                "_sa_orm_load_options": load_options,
1121            }
1122
1123        if (
1124            self.key in state.dict
1125            and not passive & PassiveFlag.DEFERRED_HISTORY_LOAD
1126        ):
1127            return LoaderCallableStatus.ATTR_WAS_SET
1128
1129        if pending:
1130            if util.has_intersection(orm_util._none_set, params.values()):
1131                return None
1132
1133        elif util.has_intersection(orm_util._never_set, params.values()):
1134            return None
1135
1136        if self._raise_on_sql and not passive & PassiveFlag.NO_RAISE:
1137            self._invoke_raise_load(state, passive, "raise_on_sql")
1138
1139        stmt._where_criteria = (lazy_clause,)
1140
1141        result = session.execute(
1142            stmt, params, execution_options=execution_options
1143        )
1144
1145        result = result.unique().scalars().all()
1146
1147        if self.uselist:
1148            return result
1149        else:
1150            l = len(result)
1151            if l:
1152                if l > 1:
1153                    util.warn(
1154                        "Multiple rows returned with "
1155                        "uselist=False for lazily-loaded attribute '%s' "
1156                        % self.parent_property
1157                    )
1158
1159                return result[0]
1160            else:
1161                return None
1162
1163    def create_row_processor(
1164        self,
1165        context,
1166        query_entity,
1167        path,
1168        loadopt,
1169        mapper,
1170        result,
1171        adapter,
1172        populators,
1173    ):
1174        key = self.key
1175
1176        if (
1177            context.load_options._is_user_refresh
1178            and context.query._compile_options._only_load_props
1179            and self.key in context.query._compile_options._only_load_props
1180        ):
1181            return self._immediateload_create_row_processor(
1182                context,
1183                query_entity,
1184                path,
1185                loadopt,
1186                mapper,
1187                result,
1188                adapter,
1189                populators,
1190            )
1191
1192        if not self.is_class_level or (loadopt and loadopt._extra_criteria):
1193            # we are not the primary manager for this attribute
1194            # on this class - set up a
1195            # per-instance lazyloader, which will override the
1196            # class-level behavior.
1197            # this currently only happens when using a
1198            # "lazyload" option on a "no load"
1199            # attribute - "eager" attributes always have a
1200            # class-level lazyloader installed.

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

codekingpro/portable-devtools · Team Ai