Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
session.py5291 linesDownload Raw Back to orm
1# orm/session.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
8"""Provides the Session class and related utilities."""
9
10from __future__ import annotations
11
12import contextlib
13from enum import Enum
14import itertools
15import sys
16import typing
17from typing import Any
18from typing import Callable
19from typing import cast
20from typing import Dict
21from typing import Generic
22from typing import Iterable
23from typing import Iterator
24from typing import List
25from typing import NoReturn
26from typing import Optional
27from typing import overload
28from typing import Sequence
29from typing import Set
30from typing import Tuple
31from typing import Type
32from typing import TYPE_CHECKING
33from typing import TypeVar
34from typing import Union
35import weakref
36
37from . import attributes
38from . import bulk_persistence
39from . import context
40from . import descriptor_props
41from . import exc
42from . import identity
43from . import loading
44from . import query
45from . import state as statelib
46from ._typing import _O
47from ._typing import insp_is_mapper
48from ._typing import is_composite_class
49from ._typing import is_orm_option
50from ._typing import is_user_defined_option
51from .base import _class_to_mapper
52from .base import _none_set
53from .base import _state_mapper
54from .base import instance_str
55from .base import LoaderCallableStatus
56from .base import object_mapper
57from .base import object_state
58from .base import PassiveFlag
59from .base import state_str
60from .context import FromStatement
61from .context import ORMCompileState
62from .identity import IdentityMap
63from .query import Query
64from .state import InstanceState
65from .state_changes import _StateChange
66from .state_changes import _StateChangeState
67from .state_changes import _StateChangeStates
68from .unitofwork import UOWTransaction
69from .. import engine
70from .. import exc as sa_exc
71from .. import sql
72from .. import util
73from ..engine import Connection
74from ..engine import Engine
75from ..engine.util import TransactionalContext
76from ..event import dispatcher
77from ..event import EventTarget
78from ..inspection import inspect
79from ..inspection import Inspectable
80from ..sql import coercions
81from ..sql import dml
82from ..sql import roles
83from ..sql import Select
84from ..sql import TableClause
85from ..sql import visitors
86from ..sql.base import _NoArg
87from ..sql.base import CompileState
88from ..sql.schema import Table
89from ..sql.selectable import ForUpdateArg
90from ..sql.selectable import LABEL_STYLE_TABLENAME_PLUS_COL
91from ..util import IdentitySet
92from ..util.typing import Literal
93from ..util.typing import Protocol
94
95if typing.TYPE_CHECKING:
96    from ._typing import _EntityType
97    from ._typing import _IdentityKeyType
98    from ._typing import _InstanceDict
99    from ._typing import OrmExecuteOptionsParameter
100    from .interfaces import ORMOption
101    from .interfaces import UserDefinedOption
102    from .mapper import Mapper
103    from .path_registry import PathRegistry
104    from .query import RowReturningQuery
105    from ..engine import CursorResult
106    from ..engine import Result
107    from ..engine import Row
108    from ..engine import RowMapping
109    from ..engine.base import Transaction
110    from ..engine.base import TwoPhaseTransaction
111    from ..engine.interfaces import _CoreAnyExecuteParams
112    from ..engine.interfaces import _CoreSingleExecuteParams
113    from ..engine.interfaces import _ExecuteOptions
114    from ..engine.interfaces import CoreExecuteOptionsParameter
115    from ..engine.result import ScalarResult
116    from ..event import _InstanceLevelDispatch
117    from ..sql._typing import _ColumnsClauseArgument
118    from ..sql._typing import _InfoType
119    from ..sql._typing import _T0
120    from ..sql._typing import _T1
121    from ..sql._typing import _T2
122    from ..sql._typing import _T3
123    from ..sql._typing import _T4
124    from ..sql._typing import _T5
125    from ..sql._typing import _T6
126    from ..sql._typing import _T7
127    from ..sql._typing import _TypedColumnClauseArgument as _TCCA
128    from ..sql.base import Executable
129    from ..sql.base import ExecutableOption
130    from ..sql.dml import UpdateBase
131    from ..sql.elements import ClauseElement
132    from ..sql.roles import TypedColumnsClauseRole
133    from ..sql.selectable import ForUpdateParameter
134    from ..sql.selectable import TypedReturnsRows
135
136_T = TypeVar("_T", bound=Any)
137
138__all__ = [
139    "Session",
140    "SessionTransaction",
141    "sessionmaker",
142    "ORMExecuteState",
143    "close_all_sessions",
144    "make_transient",
145    "make_transient_to_detached",
146    "object_session",
147]
148
149_sessions: weakref.WeakValueDictionary[int, Session] = (
150    weakref.WeakValueDictionary()
151)
152"""Weak-referencing dictionary of :class:`.Session` objects.
153"""
154
155statelib._sessions = _sessions
156
157_PKIdentityArgument = Union[Any, Tuple[Any, ...]]
158
159_BindArguments = Dict[str, Any]
160
161_EntityBindKey = Union[Type[_O], "Mapper[_O]"]
162_SessionBindKey = Union[Type[Any], "Mapper[Any]", "TableClause", str]
163_SessionBind = Union["Engine", "Connection"]
164
165JoinTransactionMode = Literal[
166    "conditional_savepoint",
167    "rollback_only",
168    "control_fully",
169    "create_savepoint",
170]
171
172
173class _ConnectionCallableProto(Protocol):
174    """a callable that returns a :class:`.Connection` given an instance.
175
176    This callable, when present on a :class:`.Session`, is called only from the
177    ORM's persistence mechanism (i.e. the unit of work flush process) to allow
178    for connection-per-instance schemes (i.e. horizontal sharding) to be used
179    as persistence time.
180
181    This callable is not present on a plain :class:`.Session`, however
182    is established when using the horizontal sharding extension.
183
184    """
185
186    def __call__(
187        self,
188        mapper: Optional[Mapper[Any]] = None,
189        instance: Optional[object] = None,
190        **kw: Any,
191    ) -> Connection: ...
192
193
194def _state_session(state: InstanceState[Any]) -> Optional[Session]:
195    """Given an :class:`.InstanceState`, return the :class:`.Session`
196    associated, if any.
197    """
198    return state.session
199
200
201class _SessionClassMethods:
202    """Class-level methods for :class:`.Session`, :class:`.sessionmaker`."""
203
204    @classmethod
205    @util.deprecated(
206        "1.3",
207        "The :meth:`.Session.close_all` method is deprecated and will be "
208        "removed in a future release.  Please refer to "
209        ":func:`.session.close_all_sessions`.",
210    )
211    def close_all(cls) -> None:
212        """Close *all* sessions in memory."""
213
214        close_all_sessions()
215
216    @classmethod
217    @util.preload_module("sqlalchemy.orm.util")
218    def identity_key(
219        cls,
220        class_: Optional[Type[Any]] = None,
221        ident: Union[Any, Tuple[Any, ...]] = None,
222        *,
223        instance: Optional[Any] = None,
224        row: Optional[Union[Row[Any], RowMapping]] = None,
225        identity_token: Optional[Any] = None,
226    ) -> _IdentityKeyType[Any]:
227        """Return an identity key.
228
229        This is an alias of :func:`.util.identity_key`.
230
231        """
232        return util.preloaded.orm_util.identity_key(
233            class_,
234            ident,
235            instance=instance,
236            row=row,
237            identity_token=identity_token,
238        )
239
240    @classmethod
241    def object_session(cls, instance: object) -> Optional[Session]:
242        """Return the :class:`.Session` to which an object belongs.
243
244        This is an alias of :func:`.object_session`.
245
246        """
247
248        return object_session(instance)
249
250
251class SessionTransactionState(_StateChangeState):
252    ACTIVE = 1
253    PREPARED = 2
254    COMMITTED = 3
255    DEACTIVE = 4
256    CLOSED = 5
257    PROVISIONING_CONNECTION = 6
258
259
260# backwards compatibility
261ACTIVE, PREPARED, COMMITTED, DEACTIVE, CLOSED, PROVISIONING_CONNECTION = tuple(
262    SessionTransactionState
263)
264
265
266class ORMExecuteState(util.MemoizedSlots):
267    """Represents a call to the :meth:`_orm.Session.execute` method, as passed
268    to the :meth:`.SessionEvents.do_orm_execute` event hook.
269
270    .. versionadded:: 1.4
271
272    .. seealso::
273
274        :ref:`session_execute_events` - top level documentation on how
275        to use :meth:`_orm.SessionEvents.do_orm_execute`
276
277    """
278
279    __slots__ = (
280        "session",
281        "statement",
282        "parameters",
283        "execution_options",
284        "local_execution_options",
285        "bind_arguments",
286        "identity_token",
287        "_compile_state_cls",
288        "_starting_event_idx",
289        "_events_todo",
290        "_update_execution_options",
291    )
292
293    session: Session
294    """The :class:`_orm.Session` in use."""
295
296    statement: Executable
297    """The SQL statement being invoked.
298
299    For an ORM selection as would
300    be retrieved from :class:`_orm.Query`, this is an instance of
301    :class:`_sql.select` that was generated from the ORM query.
302    """
303
304    parameters: Optional[_CoreAnyExecuteParams]
305    """Dictionary of parameters that was passed to
306    :meth:`_orm.Session.execute`."""
307
308    execution_options: _ExecuteOptions
309    """The complete dictionary of current execution options.
310
311    This is a merge of the statement level options with the
312    locally passed execution options.
313
314    .. seealso::
315
316        :attr:`_orm.ORMExecuteState.local_execution_options`
317
318        :meth:`_sql.Executable.execution_options`
319
320        :ref:`orm_queryguide_execution_options`
321
322    """
323
324    local_execution_options: _ExecuteOptions
325    """Dictionary view of the execution options passed to the
326    :meth:`.Session.execute` method.
327
328    This does not include options that may be associated with the statement
329    being invoked.
330
331    .. seealso::
332
333        :attr:`_orm.ORMExecuteState.execution_options`
334
335    """
336
337    bind_arguments: _BindArguments
338    """The dictionary passed as the
339    :paramref:`_orm.Session.execute.bind_arguments` dictionary.
340
341    This dictionary may be used by extensions to :class:`_orm.Session` to pass
342    arguments that will assist in determining amongst a set of database
343    connections which one should be used to invoke this statement.
344
345    """
346
347    _compile_state_cls: Optional[Type[ORMCompileState]]
348    _starting_event_idx: int
349    _events_todo: List[Any]
350    _update_execution_options: Optional[_ExecuteOptions]
351
352    def __init__(
353        self,
354        session: Session,
355        statement: Executable,
356        parameters: Optional[_CoreAnyExecuteParams],
357        execution_options: _ExecuteOptions,
358        bind_arguments: _BindArguments,
359        compile_state_cls: Optional[Type[ORMCompileState]],
360        events_todo: List[_InstanceLevelDispatch[Session]],
361    ):
362        """Construct a new :class:`_orm.ORMExecuteState`.
363
364        this object is constructed internally.
365
366        """
367        self.session = session
368        self.statement = statement
369        self.parameters = parameters
370        self.local_execution_options = execution_options
371        self.execution_options = statement._execution_options.union(
372            execution_options
373        )
374        self.bind_arguments = bind_arguments
375        self._compile_state_cls = compile_state_cls
376        self._events_todo = list(events_todo)
377
378    def _remaining_events(self) -> List[_InstanceLevelDispatch[Session]]:
379        return self._events_todo[self._starting_event_idx + 1 :]
380
381    def invoke_statement(
382        self,
383        statement: Optional[Executable] = None,
384        params: Optional[_CoreAnyExecuteParams] = None,
385        execution_options: Optional[OrmExecuteOptionsParameter] = None,
386        bind_arguments: Optional[_BindArguments] = None,
387    ) -> Result[Any]:
388        """Execute the statement represented by this
389        :class:`.ORMExecuteState`, without re-invoking events that have
390        already proceeded.
391
392        This method essentially performs a re-entrant execution of the current
393        statement for which the :meth:`.SessionEvents.do_orm_execute` event is
394        being currently invoked.    The use case for this is for event handlers
395        that want to override how the ultimate
396        :class:`_engine.Result` object is returned, such as for schemes that
397        retrieve results from an offline cache or which concatenate results
398        from multiple executions.
399
400        When the :class:`_engine.Result` object is returned by the actual
401        handler function within :meth:`_orm.SessionEvents.do_orm_execute` and
402        is propagated to the calling
403        :meth:`_orm.Session.execute` method, the remainder of the
404        :meth:`_orm.Session.execute` method is preempted and the
405        :class:`_engine.Result` object is returned to the caller of
406        :meth:`_orm.Session.execute` immediately.
407
408        :param statement: optional statement to be invoked, in place of the
409         statement currently represented by :attr:`.ORMExecuteState.statement`.
410
411        :param params: optional dictionary of parameters or list of parameters
412         which will be merged into the existing
413         :attr:`.ORMExecuteState.parameters` of this :class:`.ORMExecuteState`.
414
415         .. versionchanged:: 2.0 a list of parameter dictionaries is accepted
416            for executemany executions.
417
418        :param execution_options: optional dictionary of execution options
419         will be merged into the existing
420         :attr:`.ORMExecuteState.execution_options` of this
421         :class:`.ORMExecuteState`.
422
423        :param bind_arguments: optional dictionary of bind_arguments
424         which will be merged amongst the current
425         :attr:`.ORMExecuteState.bind_arguments`
426         of this :class:`.ORMExecuteState`.
427
428        :return: a :class:`_engine.Result` object with ORM-level results.
429
430        .. seealso::
431
432            :ref:`do_orm_execute_re_executing` - background and examples on the
433            appropriate usage of :meth:`_orm.ORMExecuteState.invoke_statement`.
434
435
436        """
437
438        if statement is None:
439            statement = self.statement
440
441        _bind_arguments = dict(self.bind_arguments)
442        if bind_arguments:
443            _bind_arguments.update(bind_arguments)
444        _bind_arguments["_sa_skip_events"] = True
445
446        _params: Optional[_CoreAnyExecuteParams]
447        if params:
448            if self.is_executemany:
449                _params = []
450                exec_many_parameters = cast(
451                    "List[Dict[str, Any]]", self.parameters
452                )
453                for _existing_params, _new_params in itertools.zip_longest(
454                    exec_many_parameters,
455                    cast("List[Dict[str, Any]]", params),
456                ):
457                    if _existing_params is None or _new_params is None:
458                        raise sa_exc.InvalidRequestError(
459                            f"Can't apply executemany parameters to "
460                            f"statement; number of parameter sets passed to "
461                            f"Session.execute() ({len(exec_many_parameters)}) "
462                            f"does not match number of parameter sets given "
463                            f"to ORMExecuteState.invoke_statement() "
464                            f"({len(params)})"
465                        )
466                    _existing_params = dict(_existing_params)
467                    _existing_params.update(_new_params)
468                    _params.append(_existing_params)
469            else:
470                _params = dict(cast("Dict[str, Any]", self.parameters))
471                _params.update(cast("Dict[str, Any]", params))
472        else:
473            _params = self.parameters
474
475        _execution_options = self.local_execution_options
476        if execution_options:
477            _execution_options = _execution_options.union(execution_options)
478
479        return self.session._execute_internal(
480            statement,
481            _params,
482            execution_options=_execution_options,
483            bind_arguments=_bind_arguments,
484            _parent_execute_state=self,
485        )
486
487    @property
488    def bind_mapper(self) -> Optional[Mapper[Any]]:
489        """Return the :class:`_orm.Mapper` that is the primary "bind" mapper.
490
491        For an :class:`_orm.ORMExecuteState` object invoking an ORM
492        statement, that is, the :attr:`_orm.ORMExecuteState.is_orm_statement`
493        attribute is ``True``, this attribute will return the
494        :class:`_orm.Mapper` that is considered to be the "primary" mapper
495        of the statement.   The term "bind mapper" refers to the fact that
496        a :class:`_orm.Session` object may be "bound" to multiple
497        :class:`_engine.Engine` objects keyed to mapped classes, and the
498        "bind mapper" determines which of those :class:`_engine.Engine` objects
499        would be selected.
500
501        For a statement that is invoked against a single mapped class,
502        :attr:`_orm.ORMExecuteState.bind_mapper` is intended to be a reliable
503        way of getting this mapper.
504
505        .. versionadded:: 1.4.0b2
506
507        .. seealso::
508
509            :attr:`_orm.ORMExecuteState.all_mappers`
510
511
512        """
513        mp: Optional[Mapper[Any]] = self.bind_arguments.get("mapper", None)
514        return mp
515
516    @property
517    def all_mappers(self) -> Sequence[Mapper[Any]]:
518        """Return a sequence of all :class:`_orm.Mapper` objects that are
519        involved at the top level of this statement.
520
521        By "top level" we mean those :class:`_orm.Mapper` objects that would
522        be represented in the result set rows for a :func:`_sql.select`
523        query, or for a :func:`_dml.update` or :func:`_dml.delete` query,
524        the mapper that is the main subject of the UPDATE or DELETE.
525
526        .. versionadded:: 1.4.0b2
527
528        .. seealso::
529
530            :attr:`_orm.ORMExecuteState.bind_mapper`
531
532
533
534        """
535        if not self.is_orm_statement:
536            return []
537        elif isinstance(self.statement, (Select, FromStatement)):
538            result = []
539            seen = set()
540            for d in self.statement.column_descriptions:
541                ent = d["entity"]
542                if ent:
543                    insp = inspect(ent, raiseerr=False)
544                    if insp and insp.mapper and insp.mapper not in seen:
545                        seen.add(insp.mapper)
546                        result.append(insp.mapper)
547            return result
548        elif self.statement.is_dml and self.bind_mapper:
549            return [self.bind_mapper]
550        else:
551            return []
552
553    @property
554    def is_orm_statement(self) -> bool:
555        """return True if the operation is an ORM statement.
556
557        This indicates that the select(), insert(), update(), or delete()
558        being invoked contains ORM entities as subjects.   For a statement
559        that does not have ORM entities and instead refers only to
560        :class:`.Table` metadata, it is invoked as a Core SQL statement
561        and no ORM-level automation takes place.
562
563        """
564        return self._compile_state_cls is not None
565
566    @property
567    def is_executemany(self) -> bool:
568        """return True if the parameters are a multi-element list of
569        dictionaries with more than one dictionary.
570
571        .. versionadded:: 2.0
572
573        """
574        return isinstance(self.parameters, list)
575
576    @property
577    def is_select(self) -> bool:
578        """return True if this is a SELECT operation.
579
580        .. versionchanged:: 2.0.30 - the attribute is also True for a
581           :meth:`_sql.Select.from_statement` construct that is itself against
582           a :class:`_sql.Select` construct, such as
583           ``select(Entity).from_statement(select(..))``
584
585        """
586        return self.statement.is_select
587
588    @property
589    def is_from_statement(self) -> bool:
590        """return True if this operation is a
591        :meth:`_sql.Select.from_statement` operation.
592
593        This is independent from :attr:`_orm.ORMExecuteState.is_select`, as a
594        ``select().from_statement()`` construct can be used with
595        INSERT/UPDATE/DELETE RETURNING types of statements as well.
596        :attr:`_orm.ORMExecuteState.is_select` will only be set if the
597        :meth:`_sql.Select.from_statement` is itself against a
598        :class:`_sql.Select` construct.
599
600        .. versionadded:: 2.0.30
601
602        """
603        return self.statement.is_from_statement
604
605    @property
606    def is_insert(self) -> bool:
607        """return True if this is an INSERT operation.
608
609        .. versionchanged:: 2.0.30 - the attribute is also True for a
610           :meth:`_sql.Select.from_statement` construct that is itself against
611           a :class:`_sql.Insert` construct, such as
612           ``select(Entity).from_statement(insert(..))``
613
614        """
615        return self.statement.is_dml and self.statement.is_insert
616
617    @property
618    def is_update(self) -> bool:
619        """return True if this is an UPDATE operation.
620
621        .. versionchanged:: 2.0.30 - the attribute is also True for a
622           :meth:`_sql.Select.from_statement` construct that is itself against
623           a :class:`_sql.Update` construct, such as
624           ``select(Entity).from_statement(update(..))``
625
626        """
627        return self.statement.is_dml and self.statement.is_update
628
629    @property
630    def is_delete(self) -> bool:
631        """return True if this is a DELETE operation.
632
633        .. versionchanged:: 2.0.30 - the attribute is also True for a
634           :meth:`_sql.Select.from_statement` construct that is itself against
635           a :class:`_sql.Delete` construct, such as
636           ``select(Entity).from_statement(delete(..))``
637
638        """
639        return self.statement.is_dml and self.statement.is_delete
640
641    @property
642    def _is_crud(self) -> bool:
643        return isinstance(self.statement, (dml.Update, dml.Delete))
644
645    def update_execution_options(self, **opts: Any) -> None:
646        """Update the local execution options with new values."""
647        self.local_execution_options = self.local_execution_options.union(opts)
648
649    def _orm_compile_options(
650        self,
651    ) -> Optional[
652        Union[
653            context.ORMCompileState.default_compile_options,
654            Type[context.ORMCompileState.default_compile_options],
655        ]
656    ]:
657        if not self.is_select:
658            return None
659        try:
660            opts = self.statement._compile_options
661        except AttributeError:
662            return None
663
664        if opts is not None and opts.isinstance(
665            context.ORMCompileState.default_compile_options
666        ):
667            return opts  # type: ignore
668        else:
669            return None
670
671    @property
672    def lazy_loaded_from(self) -> Optional[InstanceState[Any]]:
673        """An :class:`.InstanceState` that is using this statement execution
674        for a lazy load operation.
675
676        The primary rationale for this attribute is to support the horizontal
677        sharding extension, where it is available within specific query
678        execution time hooks created by this extension.   To that end, the
679        attribute is only intended to be meaningful at **query execution
680        time**, and importantly not any time prior to that, including query
681        compilation time.
682
683        """
684        return self.load_options._lazy_loaded_from
685
686    @property
687    def loader_strategy_path(self) -> Optional[PathRegistry]:
688        """Return the :class:`.PathRegistry` for the current load path.
689
690        This object represents the "path" in a query along relationships
691        when a particular object or collection is being loaded.
692
693        """
694        opts = self._orm_compile_options()
695        if opts is not None:
696            return opts._current_path
697        else:
698            return None
699
700    @property
701    def is_column_load(self) -> bool:
702        """Return True if the operation is refreshing column-oriented
703        attributes on an existing ORM object.
704
705        This occurs during operations such as :meth:`_orm.Session.refresh`,
706        as well as when an attribute deferred by :func:`_orm.defer` is
707        being loaded, or an attribute that was expired either directly
708        by :meth:`_orm.Session.expire` or via a commit operation is being
709        loaded.
710
711        Handlers will very likely not want to add any options to queries
712        when such an operation is occurring as the query should be a straight
713        primary key fetch which should not have any additional WHERE criteria,
714        and loader options travelling with the instance
715        will have already been added to the query.
716
717        .. versionadded:: 1.4.0b2
718
719        .. seealso::
720
721            :attr:`_orm.ORMExecuteState.is_relationship_load`
722
723        """
724        opts = self._orm_compile_options()
725        return opts is not None and opts._for_refresh_state
726
727    @property
728    def is_relationship_load(self) -> bool:
729        """Return True if this load is loading objects on behalf of a
730        relationship.
731
732        This means, the loader in effect is either a LazyLoader,
733        SelectInLoader, SubqueryLoader, or similar, and the entire
734        SELECT statement being emitted is on behalf of a relationship
735        load.
736
737        Handlers will very likely not want to add any options to queries
738        when such an operation is occurring, as loader options are already
739        capable of being propagated to relationship loaders and should
740        be already present.
741
742        .. seealso::
743
744            :attr:`_orm.ORMExecuteState.is_column_load`
745
746        """
747        opts = self._orm_compile_options()
748        if opts is None:
749            return False
750        path = self.loader_strategy_path
751        return path is not None and not path.is_root
752
753    @property
754    def load_options(
755        self,
756    ) -> Union[
757        context.QueryContext.default_load_options,
758        Type[context.QueryContext.default_load_options],
759    ]:
760        """Return the load_options that will be used for this execution."""
761
762        if not self.is_select:
763            raise sa_exc.InvalidRequestError(
764                "This ORM execution is not against a SELECT statement "
765                "so there are no load options."
766            )
767
768        lo: Union[
769            context.QueryContext.default_load_options,
770            Type[context.QueryContext.default_load_options],
771        ] = self.execution_options.get(
772            "_sa_orm_load_options", context.QueryContext.default_load_options
773        )
774        return lo
775
776    @property
777    def update_delete_options(
778        self,
779    ) -> Union[
780        bulk_persistence.BulkUDCompileState.default_update_options,
781        Type[bulk_persistence.BulkUDCompileState.default_update_options],
782    ]:
783        """Return the update_delete_options that will be used for this
784        execution."""
785
786        if not self._is_crud:
787            raise sa_exc.InvalidRequestError(
788                "This ORM execution is not against an UPDATE or DELETE "
789                "statement so there are no update options."
790            )
791        uo: Union[
792            bulk_persistence.BulkUDCompileState.default_update_options,
793            Type[bulk_persistence.BulkUDCompileState.default_update_options],
794        ] = self.execution_options.get(
795            "_sa_orm_update_options",
796            bulk_persistence.BulkUDCompileState.default_update_options,
797        )
798        return uo
799
800    @property
801    def _non_compile_orm_options(self) -> Sequence[ORMOption]:
802        return [
803            opt
804            for opt in self.statement._with_options
805            if is_orm_option(opt) and not opt._is_compile_state
806        ]
807
808    @property
809    def user_defined_options(self) -> Sequence[UserDefinedOption]:
810        """The sequence of :class:`.UserDefinedOptions` that have been
811        associated with the statement being invoked.
812
813        """
814        return [
815            opt
816            for opt in self.statement._with_options
817            if is_user_defined_option(opt)
818        ]
819
820
821class SessionTransactionOrigin(Enum):
822    """indicates the origin of a :class:`.SessionTransaction`.
823
824    This enumeration is present on the
825    :attr:`.SessionTransaction.origin` attribute of any
826    :class:`.SessionTransaction` object.
827
828    .. versionadded:: 2.0
829
830    """
831
832    AUTOBEGIN = 0
833    """transaction were started by autobegin"""
834
835    BEGIN = 1
836    """transaction were started by calling :meth:`_orm.Session.begin`"""
837
838    BEGIN_NESTED = 2
839    """tranaction were started by :meth:`_orm.Session.begin_nested`"""
840
841    SUBTRANSACTION = 3
842    """transaction is an internal "subtransaction" """
843
844
845class SessionTransaction(_StateChange, TransactionalContext):
846    """A :class:`.Session`-level transaction.
847
848    :class:`.SessionTransaction` is produced from the
849    :meth:`_orm.Session.begin`
850    and :meth:`_orm.Session.begin_nested` methods.   It's largely an internal
851    object that in modern use provides a context manager for session
852    transactions.
853
854    Documentation on interacting with :class:`_orm.SessionTransaction` is
855    at: :ref:`unitofwork_transaction`.
856
857
858    .. versionchanged:: 1.4  The scoping and API methods to work with the
859       :class:`_orm.SessionTransaction` object directly have been simplified.
860
861    .. seealso::
862
863        :ref:`unitofwork_transaction`
864
865        :meth:`.Session.begin`
866
867        :meth:`.Session.begin_nested`
868
869        :meth:`.Session.rollback`
870
871        :meth:`.Session.commit`
872
873        :meth:`.Session.in_transaction`
874
875        :meth:`.Session.in_nested_transaction`
876
877        :meth:`.Session.get_transaction`
878
879        :meth:`.Session.get_nested_transaction`
880
881
882    """
883
884    _rollback_exception: Optional[BaseException] = None
885
886    _connections: Dict[
887        Union[Engine, Connection], Tuple[Connection, Transaction, bool, bool]
888    ]
889    session: Session
890    _parent: Optional[SessionTransaction]
891
892    _state: SessionTransactionState
893
894    _new: weakref.WeakKeyDictionary[InstanceState[Any], object]
895    _deleted: weakref.WeakKeyDictionary[InstanceState[Any], object]
896    _dirty: weakref.WeakKeyDictionary[InstanceState[Any], object]
897    _key_switches: weakref.WeakKeyDictionary[
898        InstanceState[Any], Tuple[Any, Any]
899    ]
900
901    origin: SessionTransactionOrigin
902    """Origin of this :class:`_orm.SessionTransaction`.
903
904    Refers to a :class:`.SessionTransactionOrigin` instance which is an
905    enumeration indicating the source event that led to constructing
906    this :class:`_orm.SessionTransaction`.
907
908    .. versionadded:: 2.0
909
910    """
911
912    nested: bool = False
913    """Indicates if this is a nested, or SAVEPOINT, transaction.
914
915    When :attr:`.SessionTransaction.nested` is True, it is expected
916    that :attr:`.SessionTransaction.parent` will be present as well,
917    linking to the enclosing :class:`.SessionTransaction`.
918
919    .. seealso::
920
921        :attr:`.SessionTransaction.origin`
922
923    """
924
925    def __init__(
926        self,
927        session: Session,
928        origin: SessionTransactionOrigin,
929        parent: Optional[SessionTransaction] = None,
930    ):
931        TransactionalContext._trans_ctx_check(session)
932
933        self.session = session
934        self._connections = {}
935        self._parent = parent
936        self.nested = nested = origin is SessionTransactionOrigin.BEGIN_NESTED
937        self.origin = origin
938
939        if session._close_state is _SessionCloseState.CLOSED:
940            raise sa_exc.InvalidRequestError(
941                "This Session has been permanently closed and is unable "
942                "to handle any more transaction requests."
943            )
944
945        if nested:
946            if not parent:
947                raise sa_exc.InvalidRequestError(
948                    "Can't start a SAVEPOINT transaction when no existing "
949                    "transaction is in progress"
950                )
951
952            self._previous_nested_transaction = session._nested_transaction
953        elif origin is SessionTransactionOrigin.SUBTRANSACTION:
954            assert parent is not None
955        else:
956            assert parent is None
957
958        self._state = SessionTransactionState.ACTIVE
959
960        self._take_snapshot()
961
962        # make sure transaction is assigned before we call the
963        # dispatch
964        self.session._transaction = self
965
966        self.session.dispatch.after_transaction_create(self.session, self)
967
968    def _raise_for_prerequisite_state(
969        self, operation_name: str, state: _StateChangeState
970    ) -> NoReturn:
971        if state is SessionTransactionState.DEACTIVE:
972            if self._rollback_exception:
973                raise sa_exc.PendingRollbackError(
974                    "This Session's transaction has been rolled back "
975                    "due to a previous exception during flush."
976                    " To begin a new transaction with this Session, "
977                    "first issue Session.rollback()."
978                    f" Original exception was: {self._rollback_exception}",
979                    code="7s2a",
980                )
981            else:
982                raise sa_exc.InvalidRequestError(
983                    "This session is in 'inactive' state, due to the "
984                    "SQL transaction being rolled back; no further SQL "
985                    "can be emitted within this transaction."
986                )
987        elif state is SessionTransactionState.CLOSED:
988            raise sa_exc.ResourceClosedError("This transaction is closed")
989        elif state is SessionTransactionState.PROVISIONING_CONNECTION:
990            raise sa_exc.InvalidRequestError(
991                "This session is provisioning a new connection; concurrent "
992                "operations are not permitted",
993                code="isce",
994            )
995        else:
996            raise sa_exc.InvalidRequestError(
997                f"This session is in '{state.name.lower()}' state; no "
998                "further SQL can be emitted within this transaction."
999            )
1000
1001    @property
1002    def parent(self) -> Optional[SessionTransaction]:
1003        """The parent :class:`.SessionTransaction` of this
1004        :class:`.SessionTransaction`.
1005
1006        If this attribute is ``None``, indicates this
1007        :class:`.SessionTransaction` is at the top of the stack, and
1008        corresponds to a real "COMMIT"/"ROLLBACK"
1009        block.  If non-``None``, then this is either a "subtransaction"
1010        (an internal marker object used by the flush process) or a
1011        "nested" / SAVEPOINT transaction.  If the
1012        :attr:`.SessionTransaction.nested` attribute is ``True``, then
1013        this is a SAVEPOINT, and if ``False``, indicates this a subtransaction.
1014
1015        """
1016        return self._parent
1017
1018    @property
1019    def is_active(self) -> bool:
1020        return (
1021            self.session is not None
1022            and self._state is SessionTransactionState.ACTIVE
1023        )
1024
1025    @property
1026    def _is_transaction_boundary(self) -> bool:
1027        return self.nested or not self._parent
1028
1029    @_StateChange.declare_states(
1030        (SessionTransactionState.ACTIVE,), _StateChangeStates.NO_CHANGE
1031    )
1032    def connection(
1033        self,
1034        bindkey: Optional[Mapper[Any]],
1035        execution_options: Optional[_ExecuteOptions] = None,
1036        **kwargs: Any,
1037    ) -> Connection:
1038        bind = self.session.get_bind(bindkey, **kwargs)
1039        return self._connection_for_bind(bind, execution_options)
1040
1041    @_StateChange.declare_states(
1042        (SessionTransactionState.ACTIVE,), _StateChangeStates.NO_CHANGE
1043    )
1044    def _begin(self, nested: bool = False) -> SessionTransaction:
1045        return SessionTransaction(
1046            self.session,
1047            (
1048                SessionTransactionOrigin.BEGIN_NESTED
1049                if nested
1050                else SessionTransactionOrigin.SUBTRANSACTION
1051            ),
1052            self,
1053        )
1054
1055    def _iterate_self_and_parents(
1056        self, upto: Optional[SessionTransaction] = None
1057    ) -> Iterable[SessionTransaction]:
1058        current = self
1059        result: Tuple[SessionTransaction, ...] = ()
1060        while current:
1061            result += (current,)
1062            if current._parent is upto:
1063                break
1064            elif current._parent is None:
1065                raise sa_exc.InvalidRequestError(
1066                    "Transaction %s is not on the active transaction list"
1067                    % (upto)
1068                )
1069            else:
1070                current = current._parent
1071
1072        return result
1073
1074    def _take_snapshot(self) -> None:
1075        if not self._is_transaction_boundary:
1076            parent = self._parent
1077            assert parent is not None
1078            self._new = parent._new
1079            self._deleted = parent._deleted
1080            self._dirty = parent._dirty
1081            self._key_switches = parent._key_switches
1082            return
1083
1084        is_begin = self.origin in (
1085            SessionTransactionOrigin.BEGIN,
1086            SessionTransactionOrigin.AUTOBEGIN,
1087        )
1088        if not is_begin and not self.session._flushing:
1089            self.session.flush()
1090
1091        self._new = weakref.WeakKeyDictionary()
1092        self._deleted = weakref.WeakKeyDictionary()
1093        self._dirty = weakref.WeakKeyDictionary()
1094        self._key_switches = weakref.WeakKeyDictionary()
1095
1096    def _restore_snapshot(self, dirty_only: bool = False) -> None:
1097        """Restore the restoration state taken before a transaction began.
1098
1099        Corresponds to a rollback.
1100
1101        """
1102        assert self._is_transaction_boundary
1103
1104        to_expunge = set(self._new).union(self.session._new)
1105        self.session._expunge_states(to_expunge, to_transient=True)
1106
1107        for s, (oldkey, newkey) in self._key_switches.items():
1108            # we probably can do this conditionally based on
1109            # if we expunged or not, but safe_discard does that anyway
1110            self.session.identity_map.safe_discard(s)
1111
1112            # restore the old key
1113            s.key = oldkey
1114
1115            # now restore the object, but only if we didn't expunge
1116            if s not in to_expunge:
1117                self.session.identity_map.replace(s)
1118
1119        for s in set(self._deleted).union(self.session._deleted):
1120            self.session._update_impl(s, revert_deletion=True)
1121
1122        assert not self.session._deleted
1123
1124        for s in self.session.identity_map.all_states():
1125            if not dirty_only or s.modified or s in self._dirty:
1126                s._expire(s.dict, self.session.identity_map._modified)
1127
1128    def _remove_snapshot(self) -> None:
1129        """Remove the restoration state taken before a transaction began.
1130
1131        Corresponds to a commit.
1132
1133        """
1134        assert self._is_transaction_boundary
1135
1136        if not self.nested and self.session.expire_on_commit:
1137            for s in self.session.identity_map.all_states():
1138                s._expire(s.dict, self.session.identity_map._modified)
1139
1140            statelib.InstanceState._detach_states(
1141                list(self._deleted), self.session
1142            )
1143            self._deleted.clear()
1144        elif self.nested:
1145            parent = self._parent
1146            assert parent is not None
1147            parent._new.update(self._new)
1148            parent._dirty.update(self._dirty)
1149            parent._deleted.update(self._deleted)
1150            parent._key_switches.update(self._key_switches)
1151
1152    @_StateChange.declare_states(
1153        (SessionTransactionState.ACTIVE,), _StateChangeStates.NO_CHANGE
1154    )
1155    def _connection_for_bind(
1156        self,
1157        bind: _SessionBind,
1158        execution_options: Optional[CoreExecuteOptionsParameter],
1159    ) -> Connection:
1160        if bind in self._connections:
1161            if execution_options:
1162                util.warn(
1163                    "Connection is already established for the "
1164                    "given bind; execution_options ignored"
1165                )
1166            return self._connections[bind][0]
1167
1168        self._state = SessionTransactionState.PROVISIONING_CONNECTION
1169
1170        local_connect = False
1171        should_commit = True
1172
1173        try:
1174            if self._parent:
1175                conn = self._parent._connection_for_bind(
1176                    bind, execution_options
1177                )
1178                if not self.nested:
1179                    return conn
1180            else:
1181                if isinstance(bind, engine.Connection):
1182                    conn = bind
1183                    if conn.engine in self._connections:
1184                        raise sa_exc.InvalidRequestError(
1185                            "Session already has a Connection associated "
1186                            "for the given Connection's Engine"
1187                        )
1188                else:
1189                    conn = bind.connect()
1190                    local_connect = True
1191
1192            try:
1193                if execution_options:
1194                    conn = conn.execution_options(**execution_options)
1195
1196                transaction: Transaction
1197                if self.session.twophase and self._parent is None:
1198                    # TODO: shouldn't we only be here if not
1199                    # conn.in_transaction() ?
1200                    # if twophase is set and conn.in_transaction(), validate

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

codekingpro/portable-devtools · Team Ai