1# orm/session.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
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 Literal
26from typing import NoReturn
27from typing import Optional
28from typing import overload
29from typing import Protocol
30from typing import Sequence
31from typing import Set
32from typing import Tuple
33from typing import Type
34from typing import TYPE_CHECKING
35from typing import TypeVar
36from typing import Union
37import weakref
38
39from . import attributes
40from . import bulk_persistence
41from . import context
42from . import descriptor_props
43from . import exc
44from . import identity
45from . import loading
46from . import query
47from . import state as statelib
48from ._typing import _O
49from ._typing import insp_is_mapper
50from ._typing import is_composite_class
51from ._typing import is_orm_option
52from ._typing import is_user_defined_option
53from .base import _class_to_mapper
54from .base import _none_set
55from .base import _state_mapper
56from .base import instance_str
57from .base import LoaderCallableStatus
58from .base import object_mapper
59from .base import object_state
60from .base import PassiveFlag
61from .base import state_str
62from .context import _ORMCompileState
63from .context import FromStatement
64from .identity import IdentityMap
65from .query import Query
66from .state import InstanceState
67from .state_changes import _StateChange
68from .state_changes import _StateChangeState
69from .state_changes import _StateChangeStates
70from .unitofwork import UOWTransaction
71from .. import engine
72from .. import exc as sa_exc
73from .. import sql
74from .. import util
75from ..engine import Connection
76from ..engine import Engine
77from ..engine.util import TransactionalContext
78from ..event import dispatcher
79from ..event import EventTarget
80from ..inspection import inspect
81from ..inspection import Inspectable
82from ..sql import coercions
83from ..sql import dml
84from ..sql import roles
85from ..sql import Select
86from ..sql import TableClause
87from ..sql import visitors
88from ..sql.base import _NoArg
89from ..sql.base import CompileState
90from ..sql.schema import Table
91from ..sql.selectable import ForUpdateArg
92from ..util import deprecated_params
93from ..util import IdentitySet
94from ..util.typing import Never
95from ..util.typing import TupleAny
96from ..util.typing import TypeVarTuple
97from ..util.typing import Unpack
98
99if typing.TYPE_CHECKING:
100 from ._typing import _EntityType
101 from ._typing import _IdentityKeyType
102 from ._typing import _InstanceDict
103 from ._typing import OrmExecuteOptionsParameter
104 from .interfaces import ORMOption
105 from .interfaces import UserDefinedOption
106 from .mapper import Mapper
107 from .path_registry import PathRegistry
108 from .query import RowReturningQuery
109 from ..engine import Result
110 from ..engine import Row
111 from ..engine import RowMapping
112 from ..engine.base import Transaction
113 from ..engine.base import TwoPhaseTransaction
114 from ..engine.interfaces import _CoreAnyExecuteParams
115 from ..engine.interfaces import _CoreSingleExecuteParams
116 from ..engine.interfaces import _ExecuteOptions
117 from ..engine.interfaces import CoreExecuteOptionsParameter
118 from ..engine.result import ScalarResult
119 from ..event import _InstanceLevelDispatch
120 from ..sql._typing import _ColumnsClauseArgument
121 from ..sql._typing import _InfoType
122 from ..sql._typing import _T0
123 from ..sql._typing import _T1
124 from ..sql._typing import _T2
125 from ..sql._typing import _T3
126 from ..sql._typing import _T4
127 from ..sql._typing import _T5
128 from ..sql._typing import _T6
129 from ..sql._typing import _T7
130 from ..sql._typing import _TypedColumnClauseArgument as _TCCA
131 from ..sql.base import Executable
132 from ..sql.base import ExecutableOption
133 from ..sql.elements import ClauseElement
134 from ..sql.roles import TypedColumnsClauseRole
135 from ..sql.selectable import ForUpdateParameter
136 from ..sql.selectable import TypedReturnsRows
137
138_T = TypeVar("_T", bound=Any)
139_Ts = TypeVarTuple("_Ts")
140
141__all__ = [
142 "Session",
143 "SessionTransaction",
144 "sessionmaker",
145 "ORMExecuteState",
146 "close_all_sessions",
147 "make_transient",
148 "make_transient_to_detached",
149 "object_session",
150]
151
152_sessions: weakref.WeakValueDictionary[int, Session] = (
153 weakref.WeakValueDictionary()
154)
155"""Weak-referencing dictionary of :class:`.Session` objects.
156"""
157
158statelib._sessions = _sessions
159
160_PKIdentityArgument = Union[Any, Tuple[Any, ...]]
161
162_BindArguments = Dict[str, Any]
163
164_EntityBindKey = Union[Type[_O], "Mapper[_O]"]
165_SessionBindKey = Union[Type[Any], "Mapper[Any]", "TableClause", str]
166_SessionBind = Union["Engine", "Connection"]
167
168JoinTransactionMode = Literal[
169 "conditional_savepoint",
170 "rollback_only",
171 "control_fully",
172 "create_savepoint",
173]
174
175
176class _ConnectionCallableProto(Protocol):
177 """a callable that returns a :class:`.Connection` given an instance.
178
179 This callable, when present on a :class:`.Session`, is called only from the
180 ORM's persistence mechanism (i.e. the unit of work flush process) to allow
181 for connection-per-instance schemes (i.e. horizontal sharding) to be used
182 as persistence time.
183
184 This callable is not present on a plain :class:`.Session`, however
185 is established when using the horizontal sharding extension.
186
187 """
188
189 def __call__(
190 self,
191 mapper: Optional[Mapper[Any]] = None,
192 instance: Optional[object] = None,
193 **kw: Any,
194 ) -> Connection: ...
195
196
197def _state_session(state: InstanceState[Any]) -> Optional[Session]:
198 """Given an :class:`.InstanceState`, return the :class:`.Session`
199 associated, if any.
200 """
201 return state.session
202
203
204class _SessionClassMethods:
205 """Class-level methods for :class:`.Session`, :class:`.sessionmaker`."""
206
207 @classmethod
208 @util.preload_module("sqlalchemy.orm.util")
209 def identity_key(
210 cls,
211 class_: Optional[Type[Any]] = None,
212 ident: Union[Any, Tuple[Any, ...]] = None,
213 *,
214 instance: Optional[Any] = None,
215 row: Optional[Union[Row[Unpack[TupleAny]], RowMapping]] = None,
216 identity_token: Optional[Any] = None,
217 ) -> _IdentityKeyType[Any]:
218 """Return an identity key.
219
220 This is an alias of :func:`.util.identity_key`.
221
222 """
223 return util.preloaded.orm_util.identity_key(
224 class_,
225 ident,
226 instance=instance,
227 row=row,
228 identity_token=identity_token,
229 )
230
231 @classmethod
232 def object_session(cls, instance: object) -> Optional[Session]:
233 """Return the :class:`.Session` to which an object belongs.
234
235 This is an alias of :func:`.object_session`.
236
237 """
238
239 return object_session(instance)
240
241
242class SessionTransactionState(_StateChangeState):
243 ACTIVE = 1
244 PREPARED = 2
245 COMMITTED = 3
246 DEACTIVE = 4
247 CLOSED = 5
248 PROVISIONING_CONNECTION = 6
249
250
251# backwards compatibility
252ACTIVE, PREPARED, COMMITTED, DEACTIVE, CLOSED, PROVISIONING_CONNECTION = tuple(
253 SessionTransactionState
254)
255
256
257class ORMExecuteState(util.MemoizedSlots):
258 """Represents a call to the :meth:`_orm.Session.execute` method, as passed
259 to the :meth:`.SessionEvents.do_orm_execute` event hook.
260
261 .. versionadded:: 1.4
262
263 .. seealso::
264
265 :ref:`session_execute_events` - top level documentation on how
266 to use :meth:`_orm.SessionEvents.do_orm_execute`
267
268 """
269
270 __slots__ = (
271 "session",
272 "statement",
273 "parameters",
274 "execution_options",
275 "local_execution_options",
276 "bind_arguments",
277 "identity_token",
278 "_compile_state_cls",
279 "_starting_event_idx",
280 "_events_todo",
281 "_update_execution_options",
282 )
283
284 session: Session
285 """The :class:`_orm.Session` in use."""
286
287 statement: Executable
288 """The SQL statement being invoked.
289
290 For an ORM selection as would
291 be retrieved from :class:`_orm.Query`, this is an instance of
292 :class:`_sql.select` that was generated from the ORM query.
293 """
294
295 parameters: Optional[_CoreAnyExecuteParams]
296 """Optional mapping or list of mappings of parameters that was passed to
297 :meth:`_orm.Session.execute`.
298
299 May be mutated or re-assigned in place, which will take effect as the
300 effective parameters passed to the method.
301
302 .. versionchanged:: 2.1 :attr:`.ORMExecuteState.parameters` may now be
303 mutated or replaced.
304
305 """
306
307 execution_options: _ExecuteOptions
308 """The complete dictionary of current execution options.
309
310 This is a merge of the statement level options with the
311 locally passed execution options.
312
313 .. seealso::
314
315 :attr:`_orm.ORMExecuteState.local_execution_options`
316
317 :meth:`_sql.Executable.execution_options`
318
319 :ref:`orm_queryguide_execution_options`
320
321 """
322
323 local_execution_options: _ExecuteOptions
324 """Dictionary view of the execution options passed to the
325 :meth:`.Session.execute` method.
326
327 This does not include options that may be associated with the statement
328 being invoked.
329
330 .. seealso::
331
332 :attr:`_orm.ORMExecuteState.execution_options`
333
334 """
335
336 bind_arguments: _BindArguments
337 """The dictionary passed as the
338 :paramref:`_orm.Session.execute.bind_arguments` dictionary.
339
340 This dictionary may be used by extensions to :class:`_orm.Session` to pass
341 arguments that will assist in determining amongst a set of database
342 connections which one should be used to invoke this statement.
343
344 """
345
346 _compile_state_cls: Optional[Type[_ORMCompileState]]
347 _starting_event_idx: int
348 _events_todo: List[Any]
349 _update_execution_options: Optional[_ExecuteOptions]
350
351 def __init__(
352 self,
353 session: Session,
354 statement: Executable,
355 parameters: Optional[_CoreAnyExecuteParams],
356 execution_options: _ExecuteOptions,
357 bind_arguments: _BindArguments,
358 compile_state_cls: Optional[Type[_ORMCompileState]],
359 events_todo: List[_InstanceLevelDispatch[Session]],
360 ):
361 """Construct a new :class:`_orm.ORMExecuteState`.
362
363 this object is constructed internally.
364
365 """
366 self.session = session
367 self.statement = statement
368 self.parameters = parameters
369 self.local_execution_options = execution_options
370 self.execution_options = statement._execution_options.union(
371 execution_options
372 )
373 self.bind_arguments = bind_arguments
374 self._compile_state_cls = compile_state_cls
375 self._events_todo = list(events_todo)
376
377 def _remaining_events(self) -> List[_InstanceLevelDispatch[Session]]:
378 return self._events_todo[self._starting_event_idx + 1 :]
379
380 def invoke_statement(
381 self,
382 statement: Optional[Executable] = None,
383 params: Optional[_CoreAnyExecuteParams] = None,
384 execution_options: Optional[OrmExecuteOptionsParameter] = None,
385 bind_arguments: Optional[_BindArguments] = None,
386 ) -> Result[Unpack[TupleAny]]:
387 """Execute the statement represented by this
388 :class:`.ORMExecuteState`, without re-invoking events that have
389 already proceeded.
390
391 This method essentially performs a re-entrant execution of the current
392 statement for which the :meth:`.SessionEvents.do_orm_execute` event is
393 being currently invoked. The use case for this is for event handlers
394 that want to override how the ultimate
395 :class:`_engine.Result` object is returned, such as for schemes that
396 retrieve results from an offline cache or which concatenate results
397 from multiple executions.
398
399 When the :class:`_engine.Result` object is returned by the actual
400 handler function within :meth:`_orm.SessionEvents.do_orm_execute` and
401 is propagated to the calling
402 :meth:`_orm.Session.execute` method, the remainder of the
403 :meth:`_orm.Session.execute` method is preempted and the
404 :class:`_engine.Result` object is returned to the caller of
405 :meth:`_orm.Session.execute` immediately.
406
407 :param statement: optional statement to be invoked, in place of the
408 statement currently represented by :attr:`.ORMExecuteState.statement`.
409
410 :param params: optional dictionary of parameters or list of parameters
411 which will be merged into the existing
412 :attr:`.ORMExecuteState.parameters` of this :class:`.ORMExecuteState`.
413
414 .. versionchanged:: 2.0 a list of parameter dictionaries is accepted
415 for executemany executions.
416
417 :param execution_options: optional dictionary of execution options
418 will be merged into the existing
419 :attr:`.ORMExecuteState.execution_options` of this
420 :class:`.ORMExecuteState`.
421
422 :param bind_arguments: optional dictionary of bind_arguments
423 which will be merged amongst the current
424 :attr:`.ORMExecuteState.bind_arguments`
425 of this :class:`.ORMExecuteState`.
426
427 :return: a :class:`_engine.Result` object with ORM-level results.
428
429 .. seealso::
430
431 :ref:`do_orm_execute_re_executing` - background and examples on the
432 appropriate usage of :meth:`_orm.ORMExecuteState.invoke_statement`.
433
434
435 """
436
437 if statement is None:
438 statement = self.statement
439
440 _bind_arguments = dict(self.bind_arguments)
441 if bind_arguments:
442 _bind_arguments.update(bind_arguments)
443 _bind_arguments["_sa_skip_events"] = True
444
445 _params: Optional[_CoreAnyExecuteParams]
446 if params:
447 if self.is_executemany:
448 _params = []
449 exec_many_parameters = cast(
450 "List[Dict[str, Any]]", self.parameters
451 )
452 for _existing_params, _new_params in itertools.zip_longest(
453 exec_many_parameters,
454 cast("List[Dict[str, Any]]", params),
455 ):
456 if _existing_params is None or _new_params is None:
457 raise sa_exc.InvalidRequestError(
458 f"Can't apply executemany parameters to "
459 f"statement; number of parameter sets passed to "
460 f"Session.execute() ({len(exec_many_parameters)}) "
461 f"does not match number of parameter sets given "
462 f"to ORMExecuteState.invoke_statement() "
463 f"({len(params)})"
464 )
465 _existing_params = dict(_existing_params)
466 _existing_params.update(_new_params)
467 _params.append(_existing_params)
468 else:
469 _params = dict(cast("Dict[str, Any]", self.parameters))
470 _params.update(cast("Dict[str, Any]", params))
471 else:
472 _params = self.parameters
473
474 _execution_options = self.local_execution_options
475 if execution_options:
476 _execution_options = _execution_options.union(execution_options)
477
478 return self.session._execute_internal(
479 statement,
480 _params,
481 execution_options=_execution_options,
482 bind_arguments=_bind_arguments,
483 _parent_execute_state=self,
484 )
485
486 @property
487 def bind_mapper(self) -> Optional[Mapper[Any]]:
488 """Return the :class:`_orm.Mapper` that is the primary "bind" mapper.
489
490 For an :class:`_orm.ORMExecuteState` object invoking an ORM
491 statement, that is, the :attr:`_orm.ORMExecuteState.is_orm_statement`
492 attribute is ``True``, this attribute will return the
493 :class:`_orm.Mapper` that is considered to be the "primary" mapper
494 of the statement. The term "bind mapper" refers to the fact that
495 a :class:`_orm.Session` object may be "bound" to multiple
496 :class:`_engine.Engine` objects keyed to mapped classes, and the
497 "bind mapper" determines which of those :class:`_engine.Engine` objects
498 would be selected.
499
500 For a statement that is invoked against a single mapped class,
501 :attr:`_orm.ORMExecuteState.bind_mapper` is intended to be a reliable
502 way of getting this mapper.
503
504 .. versionadded:: 1.4.0b2
505
506 .. seealso::
507
508 :attr:`_orm.ORMExecuteState.all_mappers`
509
510
511 """
512 mp: Optional[Mapper[Any]] = self.bind_arguments.get("mapper", None)
513 return mp
514
515 @property
516 def all_mappers(self) -> Sequence[Mapper[Any]]:
517 """Return a sequence of all :class:`_orm.Mapper` objects that are
518 involved at the top level of this statement.
519
520 By "top level" we mean those :class:`_orm.Mapper` objects that would
521 be represented in the result set rows for a :func:`_sql.select`
522 query, or for a :func:`_dml.update` or :func:`_dml.delete` query,
523 the mapper that is the main subject of the UPDATE or DELETE.
524
525 .. versionadded:: 1.4.0b2
526
527 .. seealso::
528
529 :attr:`_orm.ORMExecuteState.bind_mapper`
530
531
532
533 """
534 if not self.is_orm_statement:
535 return []
536 elif isinstance(self.statement, (Select, FromStatement)):
537 result = []
538 seen = set()
539 for d in self.statement.column_descriptions:
540 ent = d["entity"]
541 if ent:
542 insp = inspect(ent, raiseerr=False)
543 if insp and insp.mapper and insp.mapper not in seen:
544 seen.add(insp.mapper)
545 result.append(insp.mapper)
546 return result
547 elif self.statement.is_dml and self.bind_mapper:
548 return [self.bind_mapper]
549 else:
550 return []
551
552 @property
553 def is_orm_statement(self) -> bool:
554 """return True if the operation is an ORM statement.
555
556 This indicates that the select(), insert(), update(), or delete()
557 being invoked contains ORM entities as subjects. For a statement
558 that does not have ORM entities and instead refers only to
559 :class:`.Table` metadata, it is invoked as a Core SQL statement
560 and no ORM-level automation takes place.
561
562 """
563 return self._compile_state_cls is not None
564
565 @property
566 def is_executemany(self) -> bool:
567 """return True if the parameters are a multi-element list of
568 dictionaries with more than one dictionary.
569
570 .. versionadded:: 2.0
571
572 """
573 return isinstance(self.parameters, list)
574
575 @property
576 def is_select(self) -> bool:
577 """return True if this is a SELECT operation.
578
579 .. versionchanged:: 2.0.30 - the attribute is also True for a
580 :meth:`_sql.Select.from_statement` construct that is itself against
581 a :class:`_sql.Select` construct, such as
582 ``select(Entity).from_statement(select(..))``
583
584 """
585 return self.statement.is_select
586
587 @property
588 def is_from_statement(self) -> bool:
589 """return True if this operation is a
590 :meth:`_sql.Select.from_statement` operation.
591
592 This is independent from :attr:`_orm.ORMExecuteState.is_select`, as a
593 ``select().from_statement()`` construct can be used with
594 INSERT/UPDATE/DELETE RETURNING types of statements as well.
595 :attr:`_orm.ORMExecuteState.is_select` will only be set if the
596 :meth:`_sql.Select.from_statement` is itself against a
597 :class:`_sql.Select` construct.
598
599 .. versionadded:: 2.0.30
600
601 """
602 return self.statement.is_from_statement
603
604 @property
605 def is_insert(self) -> bool:
606 """return True if this is an INSERT operation.
607
608 .. versionchanged:: 2.0.30 - the attribute is also True for a
609 :meth:`_sql.Select.from_statement` construct that is itself against
610 a :class:`_sql.Insert` construct, such as
611 ``select(Entity).from_statement(insert(..))``
612
613 """
614 return self.statement.is_dml and self.statement.is_insert
615
616 @property
617 def is_update(self) -> bool:
618 """return True if this is an UPDATE operation.
619
620 .. versionchanged:: 2.0.30 - the attribute is also True for a
621 :meth:`_sql.Select.from_statement` construct that is itself against
622 a :class:`_sql.Update` construct, such as
623 ``select(Entity).from_statement(update(..))``
624
625 """
626 return self.statement.is_dml and self.statement.is_update
627
628 @property
629 def is_delete(self) -> bool:
630 """return True if this is a DELETE operation.
631
632 .. versionchanged:: 2.0.30 - the attribute is also True for a
633 :meth:`_sql.Select.from_statement` construct that is itself against
634 a :class:`_sql.Delete` construct, such as
635 ``select(Entity).from_statement(delete(..))``
636
637 """
638 return self.statement.is_dml and self.statement.is_delete
639
640 @property
641 def _is_crud(self) -> bool:
642 return isinstance(self.statement, (dml.Update, dml.Delete))
643
644 def update_execution_options(self, **opts: Any) -> None:
645 """Update the local execution options with new values."""
646 self.local_execution_options = self.local_execution_options.union(opts)
647
648 def _orm_compile_options(
649 self,
650 ) -> Optional[
651 Union[
652 context._ORMCompileState.default_compile_options,
653 Type[context._ORMCompileState.default_compile_options],
654 ]
655 ]:
656 if not self.is_select:
657 return None
658 try:
659 opts = self.statement._compile_options
660 except AttributeError:
661 return None
662
663 if opts is not None and opts.isinstance(
664 context._ORMCompileState.default_compile_options
665 ):
666 return opts # type: ignore
667 else:
668 return None
669
670 @property
671 def lazy_loaded_from(self) -> Optional[InstanceState[Any]]:
672 """An :class:`.InstanceState` that is using this statement execution
673 for a lazy load operation.
674
675 The primary rationale for this attribute is to support the horizontal
676 sharding extension, where it is available within specific query
677 execution time hooks created by this extension. To that end, the
678 attribute is only intended to be meaningful at **query execution
679 time**, and importantly not any time prior to that, including query
680 compilation time.
681
682 """
683 return self.load_options._lazy_loaded_from
684
685 @property
686 def loader_strategy_path(self) -> Optional[PathRegistry]:
687 """Return the :class:`.PathRegistry` for the current load path.
688
689 This object represents the "path" in a query along relationships
690 when a particular object or collection is being loaded.
691
692 """
693 opts = self._orm_compile_options()
694 if opts is not None:
695 return opts._current_path
696 else:
697 return None
698
699 @property
700 def is_column_load(self) -> bool:
701 """Return True if the operation is refreshing column-oriented
702 attributes on an existing ORM object.
703
704 This occurs during operations such as :meth:`_orm.Session.refresh`,
705 as well as when an attribute deferred by :func:`_orm.defer` is
706 being loaded, or an attribute that was expired either directly
707 by :meth:`_orm.Session.expire` or via a commit operation is being
708 loaded.
709
710 Handlers will very likely not want to add any options to queries
711 when such an operation is occurring as the query should be a straight
712 primary key fetch which should not have any additional WHERE criteria,
713 and loader options travelling with the instance
714 will have already been added to the query.
715
716 .. versionadded:: 1.4.0b2
717
718 .. seealso::
719
720 :attr:`_orm.ORMExecuteState.is_relationship_load`
721
722 """
723 opts = self._orm_compile_options()
724 return opts is not None and opts._for_refresh_state
725
726 @property
727 def is_relationship_load(self) -> bool:
728 """Return True if this load is loading objects on behalf of a
729 relationship.
730
731 This means, the loader in effect is either a LazyLoader,
732 SelectInLoader, SubqueryLoader, or similar, and the entire
733 SELECT statement being emitted is on behalf of a relationship
734 load.
735
736 Handlers will very likely not want to add any options to queries
737 when such an operation is occurring, as loader options are already
738 capable of being propagated to relationship loaders and should
739 be already present.
740
741 .. seealso::
742
743 :attr:`_orm.ORMExecuteState.is_column_load`
744
745 """
746 opts = self._orm_compile_options()
747 if opts is None:
748 return False
749 path = self.loader_strategy_path
750 return path is not None and not path.is_root
751
752 @property
753 def load_options(
754 self,
755 ) -> Union[
756 context.QueryContext.default_load_options,
757 Type[context.QueryContext.default_load_options],
758 ]:
759 """Return the load_options that will be used for this execution."""
760
761 if not self.is_select:
762 raise sa_exc.InvalidRequestError(
763 "This ORM execution is not against a SELECT statement "
764 "so there are no load options."
765 )
766
767 lo: Union[
768 context.QueryContext.default_load_options,
769 Type[context.QueryContext.default_load_options],
770 ] = self.execution_options.get(
771 "_sa_orm_load_options", context.QueryContext.default_load_options
772 )
773 return lo
774
775 @property
776 def update_delete_options(
777 self,
778 ) -> Union[
779 bulk_persistence._BulkUDCompileState.default_update_options,
780 Type[bulk_persistence._BulkUDCompileState.default_update_options],
781 ]:
782 """Return the update_delete_options that will be used for this
783 execution."""
784
785 if not self._is_crud:
786 raise sa_exc.InvalidRequestError(
787 "This ORM execution is not against an UPDATE or DELETE "
788 "statement so there are no update options."
789 )
790 uo: Union[
791 bulk_persistence._BulkUDCompileState.default_update_options,
792 Type[bulk_persistence._BulkUDCompileState.default_update_options],
793 ] = self.execution_options.get(
794 "_sa_orm_update_options",
795 bulk_persistence._BulkUDCompileState.default_update_options,
796 )
797 return uo
798
799 @property
800 def _non_compile_orm_options(self) -> Sequence[ORMOption]:
801 return [
802 opt
803 for opt in self.statement._with_options
804 if is_orm_option(opt) and not opt._is_compile_state
805 ]
806
807 @property
808 def user_defined_options(self) -> Sequence[UserDefinedOption]:
809 """The sequence of :class:`.UserDefinedOptions` that have been
810 associated with the statement being invoked.
811
812 """
813 return [
814 opt
815 for opt in self.statement._with_options
816 if is_user_defined_option(opt)
817 ]
818
819
820class SessionTransactionOrigin(Enum):
821 """indicates the origin of a :class:`.SessionTransaction`.
822
823 This enumeration is present on the
824 :attr:`.SessionTransaction.origin` attribute of any
825 :class:`.SessionTransaction` object.
826
827 .. versionadded:: 2.0
828
829 """
830
831 AUTOBEGIN = 0
832 """transaction were started by autobegin"""
833
834 BEGIN = 1
835 """transaction were started by calling :meth:`_orm.Session.begin`"""
836
837 BEGIN_NESTED = 2
838 """transaction were started by :meth:`_orm.Session.begin_nested`"""
839
840 SUBTRANSACTION = 3
841 """transaction is an internal "subtransaction" """
842
843
844class SessionTransaction(_StateChange, TransactionalContext):
845 """A :class:`.Session`-level transaction.
846
847 :class:`.SessionTransaction` is produced from the
848 :meth:`_orm.Session.begin`
849 and :meth:`_orm.Session.begin_nested` methods. It's largely an internal
850 object that in modern use provides a context manager for session
851 transactions.
852
853 Documentation on interacting with :class:`_orm.SessionTransaction` is
854 at: :ref:`unitofwork_transaction`.
855
856
857 .. versionchanged:: 1.4 The scoping and API methods to work with the
858 :class:`_orm.SessionTransaction` object directly have been simplified.
859
860 .. seealso::
861
862 :ref:`unitofwork_transaction`
863
864 :meth:`.Session.begin`
865
866 :meth:`.Session.begin_nested`
867
868 :meth:`.Session.rollback`
869
870 :meth:`.Session.commit`
871
872 :meth:`.Session.in_transaction`
873
874 :meth:`.Session.in_nested_transaction`
875
876 :meth:`.Session.get_transaction`
877
878 :meth:`.Session.get_nested_transaction`
879
880
881 """
882
883 _rollback_exception: Optional[BaseException] = None
884
885 _connections: Dict[
886 Union[Engine, Connection], Tuple[Connection, Transaction, bool, bool]
887 ]
888 session: Session
889 _parent: Optional[SessionTransaction]
890
891 _state: SessionTransactionState
892
893 _new: weakref.WeakKeyDictionary[InstanceState[Any], object]
894 _deleted: weakref.WeakKeyDictionary[InstanceState[Any], object]
895 _dirty: weakref.WeakKeyDictionary[InstanceState[Any], object]
896 _key_switches: weakref.WeakKeyDictionary[
897 InstanceState[Any], Tuple[Any, Any]
898 ]
899
900 origin: SessionTransactionOrigin
901 """Origin of this :class:`_orm.SessionTransaction`.
902
903 Refers to a :class:`.SessionTransactionOrigin` instance which is an
904 enumeration indicating the source event that led to constructing
905 this :class:`_orm.SessionTransaction`.
906
907 .. versionadded:: 2.0
908
909 """
910
911 nested: bool = False
912 """Indicates if this is a nested, or SAVEPOINT, transaction.
913
914 When :attr:`.SessionTransaction.nested` is True, it is expected
915 that :attr:`.SessionTransaction.parent` will be present as well,
916 linking to the enclosing :class:`.SessionTransaction`.
917
918 .. seealso::
919
920 :attr:`.SessionTransaction.origin`
921
922 """
923
924 def __init__(
925 self,
926 session: Session,
927 origin: SessionTransactionOrigin,
928 parent: Optional[SessionTransaction] = None,
929 ):
930 TransactionalContext._trans_ctx_check(session)
931
932 self.session = session
933 self._connections = {}
934 self._parent = parent
935 self.nested = nested = origin is SessionTransactionOrigin.BEGIN_NESTED
936 self.origin = origin
937
938 if session._close_state is _SessionCloseState.CLOSED:
939 raise sa_exc.InvalidRequestError(
940 "This Session has been permanently closed and is unable "
941 "to handle any more transaction requests."
942 )
943
944 if nested:
945 if not parent:
946 raise sa_exc.InvalidRequestError(
947 "Can't start a SAVEPOINT transaction when no existing "
948 "transaction is in progress"
949 )
950
951 self._previous_nested_transaction = session._nested_transaction
952 elif origin is SessionTransactionOrigin.SUBTRANSACTION:
953 assert parent is not None
954 else:
955 assert parent is None
956
957 self._state = SessionTransactionState.ACTIVE
958
959 self._take_snapshot()
960
961 # make sure transaction is assigned before we call the
962 # dispatch
963 self.session._transaction = self
964
965 self.session.dispatch.after_transaction_create(self.session, self)
966
967 def _raise_for_prerequisite_state(
968 self, operation_name: str, state: _StateChangeState
969 ) -> NoReturn:
970 if state is SessionTransactionState.DEACTIVE:
971 if self._rollback_exception:
972 raise sa_exc.PendingRollbackError(
973 "This Session's transaction has been rolled back "
974 "due to a previous exception during flush."
975 " To begin a new transaction with this Session, "
976 "first issue Session.rollback()."
977 f" Original exception was: {self._rollback_exception}",
978 code="7s2a",
979 )
980 else:
981 raise sa_exc.InvalidRequestError(
982 "This session is in 'inactive' state, due to the "
983 "SQL transaction being rolled back; no further SQL "
984 "can be emitted within this transaction."
985 )
986 elif state is SessionTransactionState.CLOSED:
987 raise sa_exc.ResourceClosedError("This transaction is closed")
988 elif state is SessionTransactionState.PROVISIONING_CONNECTION:
989 raise sa_exc.InvalidRequestError(
990 "This session is provisioning a new connection; concurrent "
991 "operations are not permitted",
992 code="isce",
993 )
994 else:
995 raise sa_exc.InvalidRequestError(
996 f"This session is in '{state.name.lower()}' state; no "
997 "further SQL can be emitted within this transaction."
998 )
999
1000 @property
1001 def parent(self) -> Optional[SessionTransaction]:
1002 """The parent :class:`.SessionTransaction` of this
1003 :class:`.SessionTransaction`.
1004
1005 If this attribute is ``None``, indicates this
1006 :class:`.SessionTransaction` is at the top of the stack, and
1007 corresponds to a real "COMMIT"/"ROLLBACK"
1008 block. If non-``None``, then this is either a "subtransaction"
1009 (an internal marker object used by the flush process) or a
1010 "nested" / SAVEPOINT transaction. If the
1011 :attr:`.SessionTransaction.nested` attribute is ``True``, then
1012 this is a SAVEPOINT, and if ``False``, indicates this a subtransaction.
1013
1014 """
1015 return self._parent
1016
1017 @property
1018 def is_active(self) -> bool:
1019 return (
1020 self.session is not None
1021 and self._state is SessionTransactionState.ACTIVE
1022 )
1023
1024 @property
1025 def _is_transaction_boundary(self) -> bool:
1026 return self.nested or not self._parent
1027
1028 @_StateChange.declare_states(
1029 (SessionTransactionState.ACTIVE,), _StateChangeStates.NO_CHANGE
1030 )
1031 def connection(
1032 self,
1033 bindkey: Optional[Mapper[Any]],
1034 execution_options: Optional[_ExecuteOptions] = None,
1035 **kwargs: Any,
1036 ) -> Connection:
1037 bind = self.session.get_bind(bindkey, **kwargs)
1038 return self._connection_for_bind(bind, execution_options)
1039
1040 @_StateChange.declare_states(
1041 (SessionTransactionState.ACTIVE,), _StateChangeStates.NO_CHANGE
1042 )
1043 def _begin(self, nested: bool = False) -> SessionTransaction:
1044 return SessionTransaction(
1045 self.session,
1046 (
1047 SessionTransactionOrigin.BEGIN_NESTED
1048 if nested
1049 else SessionTransactionOrigin.SUBTRANSACTION
1050 ),
1051 self,
1052 )
1053
1054 def _iterate_self_and_parents(
1055 self, upto: Optional[SessionTransaction] = None
1056 ) -> Iterable[SessionTransaction]:
1057 current = self
1058 result: Tuple[SessionTransaction, ...] = ()
1059 while current:
1060 result += (current,)
1061 if current._parent is upto:
1062 break
1063 elif current._parent is None:
1064 raise sa_exc.InvalidRequestError(
1065 "Transaction %s is not on the active transaction list"
1066 % (upto)
1067 )
1068 else:
1069 current = current._parent
1070
1071 return result
1072
1073 def _take_snapshot(self) -> None:
1074 if not self._is_transaction_boundary:
1075 parent = self._parent
1076 assert parent is not None
1077 self._new = parent._new
1078 self._deleted = parent._deleted
1079 self._dirty = parent._dirty
1080 self._key_switches = parent._key_switches
1081 return
1082
1083 is_begin = self.origin in (
1084 SessionTransactionOrigin.BEGIN,
1085 SessionTransactionOrigin.AUTOBEGIN,
1086 )
1087 if not is_begin and not self.session._flushing:
1088 self.session.flush()
1089
1090 self._new = weakref.WeakKeyDictionary()
1091 self._deleted = weakref.WeakKeyDictionary()
1092 self._dirty = weakref.WeakKeyDictionary()
1093 self._key_switches = weakref.WeakKeyDictionary()
1094
1095 def _restore_snapshot(self, dirty_only: bool = False) -> None:
1096 """Restore the restoration state taken before a transaction began.
1097
1098 Corresponds to a rollback.
1099
1100 """
1101 assert self._is_transaction_boundary
1102
1103 to_expunge = set(self._new).union(self.session._new)
1104 self.session._expunge_states(to_expunge, to_transient=True)
1105
1106 for s, (oldkey, newkey) in self._key_switches.items():
1107 # we probably can do this conditionally based on
1108 # if we expunged or not, but safe_discard does that anyway
1109 self.session.identity_map.safe_discard(s)
1110
1111 # restore the old key
1112 s.key = oldkey
1113
1114 # now restore the object, but only if we didn't expunge
1115 if s not in to_expunge:
1116 self.session.identity_map.replace(s)
1117
1118 for s in set(self._deleted).union(self.session._deleted):
1119 self.session._update_impl(s, revert_deletion=True)
1120
1121 assert not self.session._deleted
1122
1123 for s in self.session.identity_map.all_states():
1124 if not dirty_only or s.modified or s in self._dirty:
1125 s._expire(s.dict, self.session.identity_map._modified)
1126
1127 def _remove_snapshot(self) -> None:
1128 """Remove the restoration state taken before a transaction began.
1129
1130 Corresponds to a commit.
1131
1132 """
1133 assert self._is_transaction_boundary
1134
1135 if not self.nested and self.session.expire_on_commit:
1136 for s in self.session.identity_map.all_states():
1137 s._expire(s.dict, self.session.identity_map._modified)
1138
1139 statelib.InstanceState._detach_states(
1140 list(self._deleted), self.session
1141 )
1142 self._deleted.clear()
1143 elif self.nested:
1144 parent = self._parent
1145 assert parent is not None
1146 parent._new.update(self._new)
1147 parent._dirty.update(self._dirty)
1148 parent._deleted.update(self._deleted)
1149 parent._key_switches.update(self._key_switches)
1150
1151 @_StateChange.declare_states(
1152 (SessionTransactionState.ACTIVE,), _StateChangeStates.NO_CHANGE
1153 )
1154 def _connection_for_bind(
1155 self,
1156 bind: _SessionBind,
1157 execution_options: Optional[CoreExecuteOptionsParameter],
1158 ) -> Connection:
1159 if bind in self._connections:
1160 if execution_options:
1161 util.warn(
1162 "Connection is already established for the "
1163 "given bind; execution_options ignored"
1164 )
1165 return self._connections[bind][0]
1166
1167 self._state = SessionTransactionState.PROVISIONING_CONNECTION
1168
1169 local_connect = False
1170 should_commit = True
1171
1172 try:
1173 if self._parent:
1174 conn = self._parent._connection_for_bind(
1175 bind, execution_options
1176 )
1177 if not self.nested:
1178 return conn
1179 else:
1180 if isinstance(bind, engine.Connection):
1181 conn = bind
1182 if conn.engine in self._connections:
1183 raise sa_exc.InvalidRequestError(
1184 "Session already has a Connection associated "
1185 "for the given Connection's Engine"
1186 )
1187 else:
1188 conn = bind.connect()
1189 local_connect = True
1190
1191 try:
1192 if execution_options:
1193 conn = conn.execution_options(**execution_options)
1194
1195 transaction: Transaction
1196 if self.session.twophase and self._parent is None:
1197 # TODO: shouldn't we only be here if not
1198 # conn.in_transaction() ?
1199 # if twophase is set and conn.in_transaction(), validate
1200 # that it is in fact twophase.
1201 transaction = conn.begin_twophase()
1202 elif self.nested:
1203 transaction = conn.begin_nested()
1204 elif conn.in_transaction():
1205
1206 if local_connect:
1207 _trans = conn.get_transaction()
1208 assert _trans is not None
1209 transaction = _trans
1210 else:
1211 join_transaction_mode = (
1212 self.session.join_transaction_mode
1213 )
1214
1215 if join_transaction_mode == "conditional_savepoint":
1216 if conn.in_nested_transaction():
1217 join_transaction_mode = "create_savepoint"
1218 else:
1219 join_transaction_mode = "rollback_only"
1220
1221 if join_transaction_mode in (
1222 "control_fully",
1223 "rollback_only",
1224 ):
1225 if conn.in_nested_transaction():
1226 transaction = (
1227 conn._get_required_nested_transaction()
1228 )
1229 else:
1230 transaction = conn._get_required_transaction()
1231 if join_transaction_mode == "rollback_only":
1232 should_commit = False
1233 elif join_transaction_mode == "create_savepoint":
1234 transaction = conn.begin_nested()
1235 else:
1236 assert False, join_transaction_mode
1237 else:
1238 transaction = conn.begin()
1239 except:
1240 # connection will not not be associated with this Session;
1241 # close it immediately so that it isn't closed under GC
1242 if local_connect:
1243 conn.close()
1244 raise
1245 else:
1246 bind_is_connection = isinstance(bind, engine.Connection)
1247
1248 self._connections[conn] = self._connections[conn.engine] = (
1249 conn,
1250 transaction,
1251 should_commit,
1252 not bind_is_connection,
1253 )
1254 self.session.dispatch.after_begin(self.session, self, conn)
1255 return conn
1256 finally:
1257 self._state = SessionTransactionState.ACTIVE
1258
1259 def prepare(self) -> None:
1260 if self._parent is not None or not self.session.twophase:
1261 raise sa_exc.InvalidRequestError(
1262 "'twophase' mode not enabled, or not root transaction; "
1263 "can't prepare."
1264 )
1265 self._prepare_impl()
1266
1267 @_StateChange.declare_states(
1268 (SessionTransactionState.ACTIVE,), SessionTransactionState.PREPARED
1269 )
1270 def _prepare_impl(self) -> None:
1271 if self._parent is None or self.nested:
1272 self.session.dispatch.before_commit(self.session)
1273
1274 stx = self.session._transaction
1275 assert stx is not None
1276 if stx is not self:
1277 for subtransaction in stx._iterate_self_and_parents(upto=self):
1278 subtransaction.commit()
1279
1280 if not self.session._flushing:
1281 for _flush_guard in range(100):
1282 if self.session._is_clean():
1283 break
1284 self.session.flush()
1285 else:
1286 raise exc.FlushError(
1287 "Over 100 subsequent flushes have occurred within "
1288 "session.commit() - is an after_flush() hook "
1289 "creating new objects?"
1290 )
1291
1292 if self._parent is None and self.session.twophase:
1293 try:
1294 for t in set(self._connections.values()):
1295 cast("TwoPhaseTransaction", t[1]).prepare()
1296 except:
1297 with util.safe_reraise():
1298 self.rollback()
1299
1300 self._state = SessionTransactionState.PREPARED
1301
1302 @_StateChange.declare_states(
1303 (SessionTransactionState.ACTIVE, SessionTransactionState.PREPARED),
1304 SessionTransactionState.CLOSED,
1305 )
1306 def commit(self, _to_root: bool = False) -> None:
1307 if self._state is not SessionTransactionState.PREPARED:
1308 with self._expect_state(SessionTransactionState.PREPARED):
1309 self._prepare_impl()
1310
1311 if self._parent is None or self.nested:
1312 for conn, trans, should_commit, autoclose in set(
1313 self._connections.values()
1314 ):
1315 if should_commit:
1316 trans.commit()
1317
1318 self._state = SessionTransactionState.COMMITTED
1319 self.session.dispatch.after_commit(self.session)
1320
1321 self._remove_snapshot()
1322
1323 with self._expect_state(SessionTransactionState.CLOSED):
1324 self.close()
1325
1326 if _to_root and self._parent:
1327 self._parent.commit(_to_root=True)
1328
1329 @_StateChange.declare_states(
1330 (
1331 SessionTransactionState.ACTIVE,
1332 SessionTransactionState.DEACTIVE,
1333 SessionTransactionState.PREPARED,
1334 ),
1335 SessionTransactionState.CLOSED,
1336 )
1337 def rollback(
1338 self, _capture_exception: bool = False, _to_root: bool = False
1339 ) -> None:
1340 stx = self.session._transaction
1341 assert stx is not None
1342 if stx is not self:
1343 for subtransaction in stx._iterate_self_and_parents(upto=self):
1344 subtransaction.close()
1345
1346 boundary = self
1347 rollback_err = None
1348 if self._state in (
1349 SessionTransactionState.ACTIVE,
1350 SessionTransactionState.PREPARED,
1351 ):
1352 for transaction in self._iterate_self_and_parents():
1353 if transaction._parent is None or transaction.nested:
1354 try:
1355 for t in set(transaction._connections.values()):
1356 t[1].rollback()
1357
1358 transaction._state = SessionTransactionState.DEACTIVE
1359 self.session.dispatch.after_rollback(self.session)
1360 except:
1361 rollback_err = sys.exc_info()
1362 finally:
1363 transaction._state = SessionTransactionState.DEACTIVE
1364 transaction._restore_snapshot(
1365 dirty_only=transaction.nested
1366 )
1367 boundary = transaction
1368 break
1369 else:
1370 transaction._state = SessionTransactionState.DEACTIVE
1371
1372 sess = self.session
1373
1374 if not rollback_err and not sess._is_clean():
1375 # if items were added, deleted, or mutated
1376 # here, we need to re-restore the snapshot
1377 util.warn(
1378 "Session's state has been changed on "
1379 "a non-active transaction - this state "
1380 "will be discarded."
1381 )
1382 boundary._restore_snapshot(dirty_only=boundary.nested)
1383
1384 with self._expect_state(SessionTransactionState.CLOSED):
1385 self.close()
1386
1387 if self._parent and _capture_exception:
1388 self._parent._rollback_exception = sys.exc_info()[1]
1389
1390 if rollback_err and rollback_err[1]:
1391 raise rollback_err[1].with_traceback(rollback_err[2])
1392
1393 sess.dispatch.after_soft_rollback(sess, self)
1394
1395 if _to_root and self._parent:
1396 self._parent.rollback(_to_root=True)
1397
1398 @_StateChange.declare_states(
1399 _StateChangeStates.ANY, SessionTransactionState.CLOSED
1400 )
1401 def close(self, invalidate: bool = False) -> None:
1402 if self.nested:
1403 self.session._nested_transaction = (
1404 self._previous_nested_transaction
1405 )
1406
1407 self.session._transaction = self._parent
1408
1409 for connection, transaction, should_commit, autoclose in set(
1410 self._connections.values()
1411 ):
1412 if invalidate and self._parent is None:
1413 connection.invalidate()
1414 if should_commit and transaction.is_active:
1415 transaction.close()
1416 if autoclose and self._parent is None:
1417 connection.close()
1418
1419 self._state = SessionTransactionState.CLOSED
1420 sess = self.session
1421
1422 # TODO: these two None sets were historically after the
1423 # event hook below, and in 2.0 I changed it this way for some reason,
1424 # and I remember there being a reason, but not what it was.
1425 # Why do we need to get rid of them at all? test_memusage::CycleTest
1426 # passes with these commented out.
1427 # self.session = None # type: ignore
1428 # self._connections = None # type: ignore
1429
1430 sess.dispatch.after_transaction_end(sess, self)
1431
1432 def _get_subject(self) -> Session:
1433 return self.session
1434
1435 def _transaction_is_active(self) -> bool:
1436 return self._state is SessionTransactionState.ACTIVE
1437
1438 def _transaction_is_closed(self) -> bool:
1439 return self._state is SessionTransactionState.CLOSED
1440
1441 def _rollback_can_be_called(self) -> bool:
1442 return self._state not in (COMMITTED, CLOSED)
1443
1444
1445class _SessionCloseState(Enum):
1446 ACTIVE = 1
1447 CLOSED = 2
1448 CLOSE_IS_RESET = 3
1449
1450
1451class Session(_SessionClassMethods, EventTarget):
1452 """Manages persistence operations for ORM-mapped objects.
1453
1454 The :class:`_orm.Session` is **not safe for use in concurrent threads.**.
1455 See :ref:`session_faq_threadsafe` for background.
1456
1457 The Session's usage paradigm is described at :doc:`/orm/session`.
1458
1459
1460 """
1461
1462 _is_asyncio = False
1463
1464 dispatch: dispatcher[Session]
1465
1466 identity_map: IdentityMap
1467 """A mapping of object identities to objects themselves.
1468
1469 Iterating through ``Session.identity_map.values()`` provides
1470 access to the full set of persistent objects (i.e., those
1471 that have row identity) currently in the session.
1472
1473 .. seealso::
1474
1475 :func:`.identity_key` - helper function to produce the keys used
1476 in this dictionary.
1477
1478 """
1479
1480 _new: Dict[InstanceState[Any], Any]
1481 _deleted: Dict[InstanceState[Any], Any]
1482 bind: Optional[Union[Engine, Connection]]
1483 __binds: Dict[_SessionBindKey, _SessionBind]
1484 _flushing: bool
1485 _warn_on_events: bool
1486 _transaction: Optional[SessionTransaction]
1487 _nested_transaction: Optional[SessionTransaction]
1488 hash_key: int
1489 autoflush: bool
1490 expire_on_commit: bool
1491 enable_baked_queries: bool
1492 twophase: bool
1493 join_transaction_mode: JoinTransactionMode
1494 execution_options: _ExecuteOptions = util.EMPTY_DICT
1495 _query_cls: Type[Query[Any]]
1496 _close_state: _SessionCloseState
1497
1498 def __init__(
1499 self,
1500 bind: Optional[_SessionBind] = None,
1501 *,
1502 autoflush: bool = True,
1503 future: Literal[True] = True,
1504 expire_on_commit: bool = True,
1505 autobegin: bool = True,
1506 twophase: bool = False,
1507 binds: Optional[Dict[_SessionBindKey, _SessionBind]] = None,
1508 enable_baked_queries: bool = True,
1509 info: Optional[_InfoType] = None,
1510 query_cls: Optional[Type[Query[Any]]] = None,
1511 autocommit: Literal[False] = False,
1512 join_transaction_mode: JoinTransactionMode = "conditional_savepoint",
1513 close_resets_only: Union[bool, _NoArg] = _NoArg.NO_ARG,
1514 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
1515 ):
1516 r"""Construct a new :class:`_orm.Session`.
1517
1518 See also the :class:`.sessionmaker` function which is used to
1519 generate a :class:`.Session`-producing callable with a given
1520 set of arguments.
1521
1522 :param autoflush: When ``True``, all query operations will issue a
1523 :meth:`~.Session.flush` call to this ``Session`` before proceeding.
1524 This is a convenience feature so that :meth:`~.Session.flush` need
1525 not be called repeatedly in order for database queries to retrieve
1526 results.
1527
1528 .. seealso::
1529
1530 :ref:`session_flushing` - additional background on autoflush
1531
1532 :param autobegin: Automatically start transactions (i.e. equivalent to
1533 invoking :meth:`_orm.Session.begin`) when database access is
1534 requested by an operation. Defaults to ``True``. Set to
1535 ``False`` to prevent a :class:`_orm.Session` from implicitly
1536 beginning transactions after construction, as well as after any of
1537 the :meth:`_orm.Session.rollback`, :meth:`_orm.Session.commit`,
1538 or :meth:`_orm.Session.close` methods are called.
1539
1540 .. versionadded:: 2.0
1541
1542 .. seealso::
1543
1544 :ref:`session_autobegin_disable`
1545
1546 :param bind: An optional :class:`_engine.Engine` or
1547 :class:`_engine.Connection` to
1548 which this ``Session`` should be bound. When specified, all SQL
1549 operations performed by this session will execute via this
1550 connectable.
1551
1552 :param binds: A dictionary which may specify any number of
1553 :class:`_engine.Engine` or :class:`_engine.Connection`
1554 objects as the source of
1555 connectivity for SQL operations on a per-entity basis. The keys
1556 of the dictionary consist of any series of mapped classes,
1557 arbitrary Python classes that are bases for mapped classes,
1558 :class:`_schema.Table` objects and :class:`_orm.Mapper` objects.
1559 The
1560 values of the dictionary are then instances of
1561 :class:`_engine.Engine`
1562 or less commonly :class:`_engine.Connection` objects.
1563 Operations which
1564 proceed relative to a particular mapped class will consult this
1565 dictionary for the closest matching entity in order to determine
1566 which :class:`_engine.Engine` should be used for a particular SQL
1567 operation. The complete heuristics for resolution are
1568 described at :meth:`.Session.get_bind`. Usage looks like::
1569
1570 Session = sessionmaker(
1571 binds={
1572 SomeMappedClass: create_engine("postgresql+psycopg2://engine1"),
1573 SomeDeclarativeBase: create_engine(
1574 "postgresql+psycopg2://engine2"
1575 ),
1576 some_mapper: create_engine("postgresql+psycopg2://engine3"),
1577 some_table: create_engine("postgresql+psycopg2://engine4"),
1578 }
1579 )
1580
1581 .. seealso::
1582
1583 :ref:`session_partitioning`
1584
1585 :meth:`.Session.bind_mapper`
1586
1587 :meth:`.Session.bind_table`
1588
1589 :meth:`.Session.get_bind`
1590
1591
1592 :param \class_: Specify an alternate class other than
1593 ``sqlalchemy.orm.session.Session`` which should be used by the
1594 returned class. This is the only argument that is local to the
1595 :class:`.sessionmaker` function, and is not sent directly to the
1596 constructor for ``Session``.
1597
1598 :param enable_baked_queries: legacy; defaults to ``True``.
1599 A parameter consumed
1600 by the :mod:`sqlalchemy.ext.baked` extension to determine if
1601 "baked queries" should be cached, as is the normal operation
1602 of this extension. When set to ``False``, caching as used by
1603 this particular extension is disabled.
1604
1605 .. versionchanged:: 1.4 The ``sqlalchemy.ext.baked`` extension is
1606 legacy and is not used by any of SQLAlchemy's internals. This
1607 flag therefore only affects applications that are making explicit
1608 use of this extension within their own code.
1609
1610 :param execution_options: optional dictionary of execution options
1611 that will be applied to all calls to :meth:`_orm.Session.execute`,
1612 :meth:`_orm.Session.scalars`, and similar. Execution options
1613 present in statements as well as options passed to methods like
1614 :meth:`_orm.Session.execute` explicitly take precedence over
1615 the session-wide options.
1616
1617 .. versionadded:: 2.1
1618
1619 :param expire_on_commit: Defaults to ``True``. When ``True``, all
1620 instances will be fully expired after each :meth:`~.commit`,
1621 so that all attribute/object access subsequent to a completed
1622 transaction will load from the most recent database state.
1623
1624 .. seealso::
1625
1626 :ref:`session_committing`
1627
1628 :param future: Deprecated; this flag is always True.
1629
1630 .. seealso::
1631
1632 :ref:`migration_20_toplevel`
1633
1634 :param info: optional dictionary of arbitrary data to be associated
1635 with this :class:`.Session`. Is available via the
1636 :attr:`.Session.info` attribute. Note the dictionary is copied at
1637 construction time so that modifications to the per-
1638 :class:`.Session` dictionary will be local to that
1639 :class:`.Session`.
1640
1641 :param query_cls: Class which should be used to create new Query
1642 objects, as returned by the :meth:`~.Session.query` method.
1643 Defaults to :class:`_query.Query`.
1644
1645 :param twophase: When ``True``, all transactions will be started as
1646 a "two phase" transaction, i.e. using the "two phase" semantics
1647 of the database in use along with an XID. During a
1648 :meth:`~.commit`, after :meth:`~.flush` has been issued for all
1649 attached databases, the :meth:`~.TwoPhaseTransaction.prepare`
1650 method on each database's :class:`.TwoPhaseTransaction` will be
1651 called. This allows each database to roll back the entire
1652 transaction, before each transaction is committed.
1653
1654 :param autocommit: the "autocommit" keyword is present for backwards
1655 compatibility but must remain at its default value of ``False``.
1656
1657 :param join_transaction_mode: Describes the transactional behavior to
1658 take when a given bind is a :class:`_engine.Connection` that
1659 has already begun a transaction outside the scope of this
1660 :class:`_orm.Session`; in other words the
1661 :meth:`_engine.Connection.in_transaction()` method returns True.
1662
1663 The following behaviors only take effect when the :class:`_orm.Session`
1664 **actually makes use of the connection given**; that is, a method
1665 such as :meth:`_orm.Session.execute`, :meth:`_orm.Session.connection`,
1666 etc. are actually invoked:
1667
1668 * ``"conditional_savepoint"`` - this is the default. if the given
1669 :class:`_engine.Connection` is begun within a transaction but
1670 does not have a SAVEPOINT, then ``"rollback_only"`` is used.
1671 If the :class:`_engine.Connection` is additionally within
1672 a SAVEPOINT, in other words
1673 :meth:`_engine.Connection.in_nested_transaction()` method returns
1674 True, then ``"create_savepoint"`` is used.
1675
1676 ``"conditional_savepoint"`` behavior attempts to make use of
1677 savepoints in order to keep the state of the existing transaction
1678 unchanged, but only if there is already a savepoint in progress;
1679 otherwise, it is not assumed that the backend in use has adequate
1680 support for SAVEPOINT, as availability of this feature varies.
1681 ``"conditional_savepoint"`` also seeks to establish approximate
1682 backwards compatibility with previous :class:`_orm.Session`
1683 behavior, for applications that are not setting a specific mode. It
1684 is recommended that one of the explicit settings be used.
1685
1686 * ``"create_savepoint"`` - the :class:`_orm.Session` will use
1687 :meth:`_engine.Connection.begin_nested()` in all cases to create
1688 its own transaction. This transaction by its nature rides
1689 "on top" of any existing transaction that's opened on the given
1690 :class:`_engine.Connection`; if the underlying database and
1691 the driver in use has full, non-broken support for SAVEPOINT, the
1692 external transaction will remain unaffected throughout the
1693 lifespan of the :class:`_orm.Session`.
1694
1695 The ``"create_savepoint"`` mode is the most useful for integrating
1696 a :class:`_orm.Session` into a test suite where an externally
1697 initiated transaction should remain unaffected; however, it relies
1698 on proper SAVEPOINT support from the underlying driver and
1699 database.
1700
1701 .. tip:: When using SQLite, the SQLite driver included through
1702 Python 3.11 does not handle SAVEPOINTs correctly in all cases
1703 without workarounds. See the sections
1704 :ref:`pysqlite_serializable` and :ref:`aiosqlite_serializable`
1705 for details on current workarounds.
1706
1707 * ``"control_fully"`` - the :class:`_orm.Session` will take
1708 control of the given transaction as its own;
1709 :meth:`_orm.Session.commit` will call ``.commit()`` on the
1710 transaction, :meth:`_orm.Session.rollback` will call
1711 ``.rollback()`` on the transaction, :meth:`_orm.Session.close` will
1712 call ``.rollback`` on the transaction.
1713
1714 .. tip:: This mode of use is equivalent to how SQLAlchemy 1.4 would
1715 handle a :class:`_engine.Connection` given with an existing
1716 SAVEPOINT (i.e. :meth:`_engine.Connection.begin_nested`); the
1717 :class:`_orm.Session` would take full control of the existing
1718 SAVEPOINT.
1719
1720 * ``"rollback_only"`` - the :class:`_orm.Session` will take control
1721 of the given transaction for ``.rollback()`` calls only;
1722 ``.commit()`` calls will not be propagated to the given
1723 transaction. ``.close()`` calls will have no effect on the
1724 given transaction.
1725
1726 .. tip:: This mode of use is equivalent to how SQLAlchemy 1.4 would
1727 handle a :class:`_engine.Connection` given with an existing
1728 regular database transaction (i.e.
1729 :meth:`_engine.Connection.begin`); the :class:`_orm.Session`
1730 would propagate :meth:`_orm.Session.rollback` calls to the
1731 underlying transaction, but not :meth:`_orm.Session.commit` or
1732 :meth:`_orm.Session.close` calls.
1733
1734 .. versionadded:: 2.0.0rc1
1735
1736 :param close_resets_only: Defaults to ``True``. Determines if
1737 the session should reset itself after calling ``.close()``
1738 or should pass in a no longer usable state, disabling reuse.
1739
1740 .. versionadded:: 2.0.22 added flag ``close_resets_only``.
1741 A future SQLAlchemy version may change the default value of
1742 this flag to ``False``.
1743
1744 .. seealso::
1745
1746 :ref:`session_closing` - Detail on the semantics of
1747 :meth:`_orm.Session.close` and :meth:`_orm.Session.reset`.
1748
1749 """ # noqa
1750
1751 # considering allowing the "autocommit" keyword to still be accepted
1752 # as long as it's False, so that external test suites, oslo.db etc
1753 # continue to function as the argument appears to be passed in lots
1754 # of cases including in our own test suite
1755 if autocommit:
1756 raise sa_exc.ArgumentError(
1757 "autocommit=True is no longer supported"
1758 )
1759 self.identity_map = identity._WeakInstanceDict()
1760
1761 if not future:
1762 raise sa_exc.ArgumentError(
1763 "The 'future' parameter passed to "
1764 "Session() may only be set to True."
1765 )
1766
1767 self._new = {} # InstanceState->object, strong refs object
1768 self._deleted = {} # same
1769 self.bind = bind
1770 self.__binds = {}
1771 self._flushing = False
1772 self._warn_on_events = False
1773 self._transaction = None
1774 self._nested_transaction = None
1775 self.hash_key = _new_sessionid()
1776 self.autobegin = autobegin
1777 self.autoflush = autoflush
1778 self.expire_on_commit = expire_on_commit
1779 self.enable_baked_queries = enable_baked_queries
1780 if execution_options:
1781 self.execution_options = self.execution_options.union(
1782 execution_options
1783 )
1784
1785 # the idea is that at some point NO_ARG will warn that in the future
1786 # the default will switch to close_resets_only=False.
1787 if close_resets_only in (True, _NoArg.NO_ARG):
1788 self._close_state = _SessionCloseState.CLOSE_IS_RESET
1789 else:
1790 self._close_state = _SessionCloseState.ACTIVE
1791 if (
1792 join_transaction_mode
1793 and join_transaction_mode
1794 not in JoinTransactionMode.__args__ # type: ignore
1795 ):
1796 raise sa_exc.ArgumentError(
1797 f"invalid selection for join_transaction_mode: "
1798 f'"{join_transaction_mode}"'
1799 )
1800 self.join_transaction_mode = join_transaction_mode
1801
1802 self.twophase = twophase
1803 self._query_cls = query_cls if query_cls else query.Query
1804 if info:
1805 self.info.update(info)
1806
1807 if binds is not None:
1808 for key, bind in binds.items():
1809 self._add_bind(key, bind)
1810
1811 _sessions[self.hash_key] = self
1812
1813 # used by sqlalchemy.engine.util.TransactionalContext
1814 _trans_context_manager: Optional[TransactionalContext] = None
1815
1816 connection_callable: Optional[_ConnectionCallableProto] = None
1817
1818 def __enter__(self: _S) -> _S:
1819 return self
1820
1821 def __exit__(self, type_: Any, value: Any, traceback: Any) -> None:
1822 self.close()
1823
1824 @contextlib.contextmanager
1825 def _maker_context_manager(self: _S) -> Iterator[_S]:
1826 with self:
1827 with self.begin():
1828 yield self
1829
1830 def in_transaction(self) -> bool:
1831 """Return True if this :class:`_orm.Session` has begun a transaction.
1832
1833 .. versionadded:: 1.4
1834
1835 .. seealso::
1836
1837 :attr:`_orm.Session.is_active`
1838
1839
1840 """
1841 return self._transaction is not None
1842
1843 def in_nested_transaction(self) -> bool:
1844 """Return True if this :class:`_orm.Session` has begun a nested
1845 transaction, e.g. SAVEPOINT.
1846
1847 .. versionadded:: 1.4
1848
1849 """
1850 return self._nested_transaction is not None
1851
1852 def get_transaction(self) -> Optional[SessionTransaction]:
1853 """Return the current root transaction in progress, if any.
1854
1855 .. versionadded:: 1.4
1856
1857 """
1858 trans = self._transaction
1859 while trans is not None and trans._parent is not None:
1860 trans = trans._parent
1861 return trans
1862
1863 def get_nested_transaction(self) -> Optional[SessionTransaction]:
1864 """Return the current nested transaction in progress, if any.
1865
1866 .. versionadded:: 1.4
1867
1868 """
1869
1870 return self._nested_transaction
1871
1872 @util.memoized_property
1873 def info(self) -> _InfoType:
1874 """A user-modifiable dictionary.
1875
1876 The initial value of this dictionary can be populated using the
1877 ``info`` argument to the :class:`.Session` constructor or
1878 :class:`.sessionmaker` constructor or factory methods. The dictionary
1879 here is always local to this :class:`.Session` and can be modified
1880 independently of all other :class:`.Session` objects.
1881
1882 """
1883 return {}
1884
1885 def _autobegin_t(self, begin: bool = False) -> SessionTransaction:
1886 if self._transaction is None:
1887 if not begin and not self.autobegin:
1888 raise sa_exc.InvalidRequestError(
1889 "Autobegin is disabled on this Session; please call "
1890 "session.begin() to start a new transaction"
1891 )
1892 trans = SessionTransaction(
1893 self,
1894 (
1895 SessionTransactionOrigin.BEGIN
1896 if begin
1897 else SessionTransactionOrigin.AUTOBEGIN
1898 ),
1899 )
1900 assert self._transaction is trans
1901 return trans
1902
1903 return self._transaction
1904
1905 def begin(self, nested: bool = False) -> SessionTransaction:
1906 """Begin a transaction, or nested transaction,
1907 on this :class:`.Session`, if one is not already begun.
1908
1909 The :class:`_orm.Session` object features **autobegin** behavior,
1910 so that normally it is not necessary to call the
1911 :meth:`_orm.Session.begin`
1912 method explicitly. However, it may be used in order to control
1913 the scope of when the transactional state is begun.
1914
1915 When used to begin the outermost transaction, an error is raised
1916 if this :class:`.Session` is already inside of a transaction.
1917
1918 :param nested: if True, begins a SAVEPOINT transaction and is
1919 equivalent to calling :meth:`~.Session.begin_nested`. For
1920 documentation on SAVEPOINT transactions, please see
1921 :ref:`session_begin_nested`.
1922
1923 :return: the :class:`.SessionTransaction` object. Note that
1924 :class:`.SessionTransaction`
1925 acts as a Python context manager, allowing :meth:`.Session.begin`
1926 to be used in a "with" block. See :ref:`session_explicit_begin` for
1927 an example.
1928
1929 .. seealso::
1930
1931 :ref:`session_autobegin`
1932
1933 :ref:`unitofwork_transaction`
1934
1935 :meth:`.Session.begin_nested`
1936
1937
1938 """
1939
1940 trans = self._transaction
1941 if trans is None:
1942 trans = self._autobegin_t(begin=True)
1943
1944 if not nested:
1945 return trans
1946
1947 assert trans is not None
1948
1949 if nested:
1950 trans = trans._begin(nested=nested)
1951 assert self._transaction is trans
1952 self._nested_transaction = trans
1953 else:
1954 raise sa_exc.InvalidRequestError(
1955 "A transaction is already begun on this Session."
1956 )
1957
1958 return trans # needed for __enter__/__exit__ hook
1959
1960 def begin_nested(self) -> SessionTransaction:
1961 """Begin a "nested" transaction on this Session, e.g. SAVEPOINT.
1962
1963 The target database(s) and associated drivers must support SQL
1964 SAVEPOINT for this method to function correctly.
1965
1966 For documentation on SAVEPOINT
1967 transactions, please see :ref:`session_begin_nested`.
1968
1969 :return: the :class:`.SessionTransaction` object. Note that
1970 :class:`.SessionTransaction` acts as a context manager, allowing
1971 :meth:`.Session.begin_nested` to be used in a "with" block.
1972 See :ref:`session_begin_nested` for a usage example.
1973
1974 .. seealso::
1975
1976 :ref:`session_begin_nested`
1977
1978 :ref:`pysqlite_serializable` - special workarounds required
1979 with the SQLite driver in order for SAVEPOINT to work
1980 correctly. For asyncio use cases, see the section
1981 :ref:`aiosqlite_serializable`.
1982
1983 """
1984 return self.begin(nested=True)
1985
1986 def rollback(self) -> None:
1987 """Rollback the current transaction in progress.
1988
1989 If no transaction is in progress, this method is a pass-through.
1990
1991 The method always rolls back
1992 the topmost database transaction, discarding any nested
1993 transactions that may be in progress.
1994
1995 .. seealso::
1996
1997 :ref:`session_rollback`
1998
1999 :ref:`unitofwork_transaction`
2000
2001 """
2002 if self._transaction is None:
2003 pass
2004 else:
2005 self._transaction.rollback(_to_root=True)
2006
2007 def commit(self) -> None:
2008 """Flush pending changes and commit the current transaction.
2009
2010 When the COMMIT operation is complete, all objects are fully
2011 :term:`expired`, erasing their internal contents, which will be
2012 automatically re-loaded when the objects are next accessed. In the
2013 interim, these objects are in an expired state and will not function if
2014 they are :term:`detached` from the :class:`.Session`. Additionally,
2015 this re-load operation is not supported when using asyncio-oriented
2016 APIs. The :paramref:`.Session.expire_on_commit` parameter may be used
2017 to disable this behavior.
2018
2019 When there is no transaction in place for the :class:`.Session`,
2020 indicating that no operations were invoked on this :class:`.Session`
2021 since the previous call to :meth:`.Session.commit`, the method will
2022 begin and commit an internal-only "logical" transaction, that does not
2023 normally affect the database unless pending flush changes were
2024 detected, but will still invoke event handlers and object expiration
2025 rules.
2026
2027 The outermost database transaction is committed unconditionally,
2028 automatically releasing any SAVEPOINTs in effect.
2029
2030 .. seealso::
2031
2032 :ref:`session_committing`
2033
2034 :ref:`unitofwork_transaction`
2035
2036 :ref:`asyncio_orm_avoid_lazyloads`
2037
2038 """
2039 trans = self._transaction
2040 if trans is None:
2041 trans = self._autobegin_t()
2042
2043 trans.commit(_to_root=True)
2044
2045 def prepare(self) -> None:
2046 """Prepare the current transaction in progress for two phase commit.
2047
2048 If no transaction is in progress, this method raises an
2049 :exc:`~sqlalchemy.exc.InvalidRequestError`.
2050
2051 Only root transactions of two phase sessions can be prepared. If the
2052 current transaction is not such, an
2053 :exc:`~sqlalchemy.exc.InvalidRequestError` is raised.
2054
2055 """
2056 trans = self._transaction
2057 if trans is None:
2058 trans = self._autobegin_t()
2059
2060 trans.prepare()
2061
2062 def connection(
2063 self,
2064 bind_arguments: Optional[_BindArguments] = None,
2065 execution_options: Optional[CoreExecuteOptionsParameter] = None,
2066 ) -> Connection:
2067 r"""Return a :class:`_engine.Connection` object corresponding to this
2068 :class:`.Session` object's transactional state.
2069
2070 Either the :class:`_engine.Connection` corresponding to the current
2071 transaction is returned, or if no transaction is in progress, a new
2072 one is begun and the :class:`_engine.Connection`
2073 returned (note that no
2074 transactional state is established with the DBAPI until the first
2075 SQL statement is emitted).
2076
2077 Ambiguity in multi-bind or unbound :class:`.Session` objects can be
2078 resolved through any of the optional keyword arguments. This
2079 ultimately makes usage of the :meth:`.get_bind` method for resolution.
2080
2081 :param bind_arguments: dictionary of bind arguments. May include
2082 "mapper", "bind", "clause", other custom arguments that are passed
2083 to :meth:`.Session.get_bind`.
2084
2085 :param execution_options: a dictionary of execution options that will
2086 be passed to :meth:`_engine.Connection.execution_options`, **when the
2087 connection is first procured only**. If the connection is already
2088 present within the :class:`.Session`, a warning is emitted and
2089 the arguments are ignored.
2090
2091 .. seealso::
2092
2093 :ref:`session_transaction_isolation`
2094
2095 """
2096
2097 if bind_arguments:
2098 bind = bind_arguments.pop("bind", None)
2099
2100 if bind is None:
2101 bind = self.get_bind(**bind_arguments)
2102 else:
2103 bind = self.get_bind()
2104
2105 return self._connection_for_bind(
2106 bind,
2107 execution_options=execution_options,
2108 )
2109
2110 def _connection_for_bind(
2111 self,
2112 engine: _SessionBind,
2113 execution_options: Optional[CoreExecuteOptionsParameter] = None,
2114 **kw: Any,
2115 ) -> Connection:
2116 TransactionalContext._trans_ctx_check(self)
2117
2118 trans = self._transaction
2119 if trans is None:
2120 trans = self._autobegin_t()
2121 return trans._connection_for_bind(engine, execution_options)
2122
2123 @overload
2124 def _execute_internal(
2125 self,
2126 statement: Executable,
2127 params: Optional[_CoreSingleExecuteParams] = None,
2128 *,
2129 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
2130 bind_arguments: Optional[_BindArguments] = None,
2131 _parent_execute_state: Optional[Any] = None,
2132 _add_event: Optional[Any] = None,
2133 _scalar_result: Literal[True] = ...,
2134 ) -> Any: ...
2135
2136 @overload
2137 def _execute_internal(
2138 self,
2139 statement: Executable,
2140 params: Optional[_CoreAnyExecuteParams] = None,
2141 *,
2142 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
2143 bind_arguments: Optional[_BindArguments] = None,
2144 _parent_execute_state: Optional[Any] = None,
2145 _add_event: Optional[Any] = None,
2146 _scalar_result: bool = ...,
2147 ) -> Result[Unpack[TupleAny]]: ...
2148
2149 def _execute_internal(
2150 self,
2151 statement: Executable,
2152 params: Optional[_CoreAnyExecuteParams] = None,
2153 *,
2154 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
2155 bind_arguments: Optional[_BindArguments] = None,
2156 _parent_execute_state: Optional[Any] = None,
2157 _add_event: Optional[Any] = None,
2158 _scalar_result: bool = False,
2159 ) -> Any:
2160 statement = coercions.expect(roles.StatementRole, statement)
2161
2162 if not bind_arguments:
2163 bind_arguments = {}
2164 else:
2165 bind_arguments = dict(bind_arguments)
2166
2167 if (
2168 statement._propagate_attrs.get("compile_state_plugin", None)
2169 == "orm"
2170 ):
2171 compile_state_cls = CompileState._get_plugin_class_for_plugin(
2172 statement, "orm"
2173 )
2174 if TYPE_CHECKING:
2175 assert isinstance(
2176 compile_state_cls, context._AbstractORMCompileState
2177 )
2178 else:
2179 compile_state_cls = None
2180 bind_arguments.setdefault("clause", statement)
2181
2182 combined_execution_options: util.immutabledict[str, Any] = (
2183 util.coerce_to_immutabledict(execution_options)
2184 )
2185 if self.execution_options:
2186 # merge given execution options with session-wide execution
2187 # options. if the statement also has execution_options,
2188 # maintain priority of session.execution_options ->
2189 # statement.execution_options -> method passed execution_options
2190 # by omitting from the base execution options those keys that
2191 # will come from the statement
2192 if statement._execution_options:
2193 combined_execution_options = util.immutabledict(
2194 {
2195 k: v
2196 for k, v in self.execution_options.items()
2197 if k not in statement._execution_options
2198 }
2199 ).union(combined_execution_options)
2200 else:
2201 combined_execution_options = self.execution_options.union(
2202 combined_execution_options
2203 )
2204
2205 if _parent_execute_state:
2206 events_todo = _parent_execute_state._remaining_events()
2207 else:
2208 events_todo = self.dispatch.do_orm_execute
2209 if _add_event:
2210 events_todo = list(events_todo) + [_add_event]
2211
2212 if events_todo:
2213 if compile_state_cls is not None:
2214 # for event handlers, do the orm_pre_session_exec
2215 # pass ahead of the event handlers, so that things like
2216 # .load_options, .update_delete_options etc. are populated.
2217 # is_pre_event=True allows the hook to hold off on things
2218 # it doesn't want to do twice, including autoflush as well
2219 # as "pre fetch" for DML, etc.
2220 (
2221 statement,
2222 combined_execution_options,
2223 params,
2224 ) = compile_state_cls.orm_pre_session_exec(
2225 self,
2226 statement,
2227 params,
2228 combined_execution_options,
2229 bind_arguments,
2230 True,
2231 )
2232
2233 orm_exec_state = ORMExecuteState(
2234 self,
2235 statement,
2236 params,
2237 combined_execution_options,
2238 bind_arguments,
2239 compile_state_cls,
2240 events_todo,
2241 )
2242 for idx, fn in enumerate(events_todo):
2243 orm_exec_state._starting_event_idx = idx
2244 fn_result: Optional[Result[Unpack[TupleAny]]] = fn(
2245 orm_exec_state
2246 )
2247 if fn_result:
2248 if _scalar_result:
2249 return fn_result.scalar()
2250 else:
2251 return fn_result
2252
2253 statement = orm_exec_state.statement
2254 combined_execution_options = orm_exec_state.local_execution_options
2255 params = orm_exec_state.parameters
2256
2257 if compile_state_cls is not None:
2258 # now run orm_pre_session_exec() "for real". if there were
2259 # event hooks, this will re-run the steps that interpret
2260 # new execution_options into load_options / update_delete_options,
2261 # which we assume the event hook might have updated.
2262 # autoflush will also be invoked in this step if enabled.
2263 (
2264 statement,
2265 combined_execution_options,
2266 params,
2267 ) = compile_state_cls.orm_pre_session_exec(
2268 self,
2269 statement,
2270 params,
2271 combined_execution_options,
2272 bind_arguments,
2273 False,
2274 )
2275 else:
2276 # Issue #9809: unconditionally autoflush for Core statements
2277 self._autoflush()
2278
2279 bind = self.get_bind(**bind_arguments)
2280
2281 conn = self._connection_for_bind(bind)
2282
2283 if _scalar_result and not compile_state_cls:
2284 if TYPE_CHECKING:
2285 params = cast(_CoreSingleExecuteParams, params)
2286 return conn.scalar(
2287 statement,
2288 params or {},
2289 execution_options=combined_execution_options,
2290 )
2291
2292 if compile_state_cls:
2293 result: Result[Unpack[TupleAny]] = (
2294 compile_state_cls.orm_execute_statement(
2295 self,
2296 statement,
2297 params or {},
2298 combined_execution_options,
2299 bind_arguments,
2300 conn,
2301 )
2302 )
2303 else:
2304 result = conn.execute(
2305 statement, params, execution_options=combined_execution_options
2306 )
2307
2308 if _scalar_result:
2309 return result.scalar()
2310 else:
2311 return result
2312
2313 @overload
2314 def execute(
2315 self,
2316 statement: TypedReturnsRows[Unpack[_Ts]],
2317 params: Optional[_CoreAnyExecuteParams] = None,
2318 *,
2319 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
2320 bind_arguments: Optional[_BindArguments] = None,
2321 _parent_execute_state: Optional[Any] = None,
2322 _add_event: Optional[Any] = None,
2323 ) -> Result[Unpack[_Ts]]: ...
2324
2325 @overload
2326 def execute(
2327 self,
2328 statement: Executable,
2329 params: Optional[_CoreAnyExecuteParams] = None,
2330 *,
2331 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
2332 bind_arguments: Optional[_BindArguments] = None,
2333 _parent_execute_state: Optional[Any] = None,
2334 _add_event: Optional[Any] = None,
2335 ) -> Result[Unpack[TupleAny]]: ...
2336
2337 def execute(
2338 self,
2339 statement: Executable,
2340 params: Optional[_CoreAnyExecuteParams] = None,
2341 *,
2342 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
2343 bind_arguments: Optional[_BindArguments] = None,
2344 _parent_execute_state: Optional[Any] = None,
2345 _add_event: Optional[Any] = None,
2346 ) -> Result[Unpack[TupleAny]]:
2347 r"""Execute a SQL expression construct.
2348
2349 Returns a :class:`_engine.Result` object representing
2350 results of the statement execution.
2351
2352 E.g.::
2353
2354 from sqlalchemy import select
2355
2356 result = session.execute(select(User).where(User.id == 5))
2357
2358 The API contract of :meth:`_orm.Session.execute` is similar to that
2359 of :meth:`_engine.Connection.execute`, the :term:`2.0 style` version
2360 of :class:`_engine.Connection`.
2361
2362 .. versionchanged:: 1.4 the :meth:`_orm.Session.execute` method is
2363 now the primary point of ORM statement execution when using
2364 :term:`2.0 style` ORM usage.
2365
2366 :param statement:
2367 An executable statement (i.e. an :class:`.Executable` expression
2368 such as :func:`_expression.select`).
2369
2370 :param params:
2371 Optional dictionary, or list of dictionaries, containing
2372 bound parameter values. If a single dictionary, single-row
2373 execution occurs; if a list of dictionaries, an
2374 "executemany" will be invoked. The keys in each dictionary
2375 must correspond to parameter names present in the statement.
2376
2377 :param execution_options: optional dictionary of execution options,
2378 which will be associated with the statement execution. This
2379 dictionary can provide a subset of the options that are accepted
2380 by :meth:`_engine.Connection.execution_options`, and may also
2381 provide additional options understood only in an ORM context.
2382
2383 The execution_options are passed along to methods like
2384 :meth:`.Connection.execute` on :class:`.Connection` giving the
2385 highest priority to execution_options that are passed to this
2386 method explicitly, then the options that are present on the
2387 statement object if any, and finally those options present
2388 session-wide.
2389
2390 .. seealso::
2391
2392 :ref:`orm_queryguide_execution_options` - ORM-specific execution
2393 options
2394
2395 :param bind_arguments: dictionary of additional arguments to determine
2396 the bind. May include "mapper", "bind", or other custom arguments.
2397 Contents of this dictionary are passed to the
2398 :meth:`.Session.get_bind` method.
2399
2400 :return: a :class:`_engine.Result` object.
2401
2402
2403 """
2404 return self._execute_internal(
2405 statement,
2406 params,
2407 execution_options=execution_options,
2408 bind_arguments=bind_arguments,
2409 _parent_execute_state=_parent_execute_state,
2410 _add_event=_add_event,
2411 )
2412
2413 # special case to handle mypy issue:
2414 # https://github.com/python/mypy/issues/20651
2415 @overload
2416 def scalar(
2417 self,
2418 statement: TypedReturnsRows[Never],
2419 params: Optional[_CoreSingleExecuteParams] = None,
2420 *,
2421 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
2422 bind_arguments: Optional[_BindArguments] = None,
2423 **kw: Any,
2424 ) -> Optional[Any]: ...
2425
2426 @overload
2427 def scalar(
2428 self,
2429 statement: TypedReturnsRows[_T],
2430 params: Optional[_CoreSingleExecuteParams] = None,
2431 *,
2432 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
2433 bind_arguments: Optional[_BindArguments] = None,
2434 **kw: Any,
2435 ) -> Optional[_T]: ...
2436
2437 @overload
2438 def scalar(
2439 self,
2440 statement: Executable,
2441 params: Optional[_CoreSingleExecuteParams] = None,
2442 *,
2443 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
2444 bind_arguments: Optional[_BindArguments] = None,
2445 **kw: Any,
2446 ) -> Any: ...
2447
2448 def scalar(
2449 self,
2450 statement: Executable,
2451 params: Optional[_CoreSingleExecuteParams] = None,
2452 *,
2453 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
2454 bind_arguments: Optional[_BindArguments] = None,
2455 **kw: Any,
2456 ) -> Any:
2457 """Execute a statement and return a scalar result.
2458
2459 Usage and parameters are the same as that of
2460 :meth:`_orm.Session.execute`; the return result is a scalar Python
2461 value.
2462
2463 """
2464
2465 return self._execute_internal(
2466 statement,
2467 params,
2468 execution_options=execution_options,
2469 bind_arguments=bind_arguments,
2470 _scalar_result=True,
2471 **kw,
2472 )
2473
2474 @overload
2475 def scalars(
2476 self,
2477 statement: TypedReturnsRows[_T],
2478 params: Optional[_CoreAnyExecuteParams] = None,
2479 *,
2480 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
2481 bind_arguments: Optional[_BindArguments] = None,
2482 **kw: Any,
2483 ) -> ScalarResult[_T]: ...
2484
2485 @overload
2486 def scalars(
2487 self,
2488 statement: Executable,
2489 params: Optional[_CoreAnyExecuteParams] = None,
2490 *,
2491 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
2492 bind_arguments: Optional[_BindArguments] = None,
2493 **kw: Any,
2494 ) -> ScalarResult[Any]: ...
2495
2496 def scalars(
2497 self,
2498 statement: Executable,
2499 params: Optional[_CoreAnyExecuteParams] = None,
2500 *,
2501 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
2502 bind_arguments: Optional[_BindArguments] = None,
2503 **kw: Any,
2504 ) -> ScalarResult[Any]:
2505 """Execute a statement and return the results as scalars.
2506
2507 Usage and parameters are the same as that of
2508 :meth:`_orm.Session.execute`; the return result is a
2509 :class:`_result.ScalarResult` filtering object which
2510 will return single elements rather than :class:`_row.Row` objects.
2511
2512 :return: a :class:`_result.ScalarResult` object
2513
2514 .. versionadded:: 1.4.24 Added :meth:`_orm.Session.scalars`
2515
2516 .. versionadded:: 1.4.26 Added :meth:`_orm.scoped_session.scalars`
2517
2518 .. seealso::
2519
2520 :ref:`orm_queryguide_select_orm_entities` - contrasts the behavior
2521 of :meth:`_orm.Session.execute` to :meth:`_orm.Session.scalars`
2522
2523 """
2524
2525 return self._execute_internal(
2526 statement,
2527 params=params,
2528 execution_options=execution_options,
2529 bind_arguments=bind_arguments,
2530 _scalar_result=False, # mypy appreciates this
2531 **kw,
2532 ).scalars()
2533
2534 def close(self) -> None:
2535 """Close out the transactional resources and ORM objects used by this
2536 :class:`_orm.Session`.
2537
2538 This expunges all ORM objects associated with this
2539 :class:`_orm.Session`, ends any transaction in progress and
2540 :term:`releases` any :class:`_engine.Connection` objects which this
2541 :class:`_orm.Session` itself has checked out from associated
2542 :class:`_engine.Engine` objects. The operation then leaves the
2543 :class:`_orm.Session` in a state which it may be used again.
2544
2545 .. tip::
2546
2547 In the default running mode the :meth:`_orm.Session.close`
2548 method **does not prevent the Session from being used again**.
2549 The :class:`_orm.Session` itself does not actually have a
2550 distinct "closed" state; it merely means
2551 the :class:`_orm.Session` will release all database connections
2552 and ORM objects.
2553
2554 Setting the parameter :paramref:`_orm.Session.close_resets_only`
2555 to ``False`` will instead make the ``close`` final, meaning that
2556 any further action on the session will be forbidden.
2557
2558 .. versionchanged:: 1.4 The :meth:`.Session.close` method does not
2559 immediately create a new :class:`.SessionTransaction` object;
2560 instead, the new :class:`.SessionTransaction` is created only if
2561 the :class:`.Session` is used again for a database operation.
2562
2563 .. seealso::
2564
2565 :ref:`session_closing` - detail on the semantics of
2566 :meth:`_orm.Session.close` and :meth:`_orm.Session.reset`.
2567
2568 :meth:`_orm.Session.reset` - a similar method that behaves like
2569 ``close()`` with the parameter
2570 :paramref:`_orm.Session.close_resets_only` set to ``True``.
2571
2572 """
2573 self._close_impl(invalidate=False)
2574
2575 def reset(self) -> None:
2576 """Close out the transactional resources and ORM objects used by this
2577 :class:`_orm.Session`, resetting the session to its initial state.
2578
2579 This method provides for same "reset-only" behavior that the
2580 :meth:`_orm.Session.close` method has provided historically, where the
2581 state of the :class:`_orm.Session` is reset as though the object were
2582 brand new, and ready to be used again.
2583 This method may then be useful for :class:`_orm.Session` objects
2584 which set :paramref:`_orm.Session.close_resets_only` to ``False``,
2585 so that "reset only" behavior is still available.
2586
2587 .. versionadded:: 2.0.22
2588
2589 .. seealso::
2590
2591 :ref:`session_closing` - detail on the semantics of
2592 :meth:`_orm.Session.close` and :meth:`_orm.Session.reset`.
2593
2594 :meth:`_orm.Session.close` - a similar method will additionally
2595 prevent reuse of the Session when the parameter
2596 :paramref:`_orm.Session.close_resets_only` is set to ``False``.
2597 """
2598 self._close_impl(invalidate=False, is_reset=True)
2599
2600 def invalidate(self) -> None:
2601 """Close this Session, using connection invalidation.
2602
2603 This is a variant of :meth:`.Session.close` that will additionally
2604 ensure that the :meth:`_engine.Connection.invalidate`
2605 method will be called on each :class:`_engine.Connection` object
2606 that is currently in use for a transaction (typically there is only
2607 one connection unless the :class:`_orm.Session` is used with
2608 multiple engines).
2609
2610 This can be called when the database is known to be in a state where
2611 the connections are no longer safe to be used.
2612
2613 Below illustrates a scenario when using `gevent
2614 <https://www.gevent.org/>`_, which can produce ``Timeout`` exceptions
2615 that may mean the underlying connection should be discarded::
2616
2617 import gevent
2618
2619 try:
2620 sess = Session()
2621 sess.add(User())
2622 sess.commit()
2623 except gevent.Timeout:
2624 sess.invalidate()
2625 raise
2626 except:
2627 sess.rollback()
2628 raise
2629
2630 The method additionally does everything that :meth:`_orm.Session.close`
2631 does, including that all ORM objects are expunged.
2632
2633 """
2634 self._close_impl(invalidate=True)
2635
2636 def _close_impl(self, invalidate: bool, is_reset: bool = False) -> None:
2637 if not is_reset and self._close_state is _SessionCloseState.ACTIVE:
2638 self._close_state = _SessionCloseState.CLOSED
2639 self.expunge_all()
2640 if self._transaction is not None:
2641 for transaction in self._transaction._iterate_self_and_parents():
2642 transaction.close(invalidate)
2643
2644 def expunge_all(self) -> None:
2645 """Remove all object instances from this ``Session``.
2646
2647 This is equivalent to calling ``expunge(obj)`` on all objects in this
2648 ``Session``.
2649
2650 """
2651
2652 all_states = self.identity_map.all_states() + list(self._new)
2653 self.identity_map._kill()
2654 self.identity_map = identity._WeakInstanceDict()
2655 self._new = {}
2656 self._deleted = {}
2657
2658 statelib.InstanceState._detach_states(all_states, self)
2659
2660 def _add_bind(self, key: _SessionBindKey, bind: _SessionBind) -> None:
2661 try:
2662 insp = inspect(key)
2663 except sa_exc.NoInspectionAvailable as err:
2664 if not isinstance(key, type):
2665 raise sa_exc.ArgumentError(
2666 "Not an acceptable bind target: %s" % key
2667 ) from err
2668 else:
2669 self.__binds[key] = bind
2670 else:
2671 if TYPE_CHECKING:
2672 assert isinstance(insp, Inspectable)
2673
2674 if isinstance(insp, TableClause):
2675 self.__binds[insp] = bind
2676 elif insp_is_mapper(insp):
2677 self.__binds[insp.class_] = bind
2678 for _selectable in insp._all_tables:
2679 self.__binds[_selectable] = bind
2680 else:
2681 raise sa_exc.ArgumentError(
2682 "Not an acceptable bind target: %s" % key
2683 )
2684
2685 def bind_mapper(
2686 self, mapper: _EntityBindKey[_O], bind: _SessionBind
2687 ) -> None:
2688 """Associate a :class:`_orm.Mapper` or arbitrary Python class with a
2689 "bind", e.g. an :class:`_engine.Engine` or
2690 :class:`_engine.Connection`.
2691
2692 The given entity is added to a lookup used by the
2693 :meth:`.Session.get_bind` method.
2694
2695 :param mapper: a :class:`_orm.Mapper` object,
2696 or an instance of a mapped
2697 class, or any Python class that is the base of a set of mapped
2698 classes.
2699
2700 :param bind: an :class:`_engine.Engine` or :class:`_engine.Connection`
2701 object.
2702
2703 .. seealso::
2704
2705 :ref:`session_partitioning`
2706
2707 :paramref:`.Session.binds`
2708
2709 :meth:`.Session.bind_table`
2710
2711
2712 """
2713 self._add_bind(mapper, bind)
2714
2715 def bind_table(self, table: TableClause, bind: _SessionBind) -> None:
2716 """Associate a :class:`_schema.Table` with a "bind", e.g. an
2717 :class:`_engine.Engine`
2718 or :class:`_engine.Connection`.
2719
2720 The given :class:`_schema.Table` is added to a lookup used by the
2721 :meth:`.Session.get_bind` method.
2722
2723 :param table: a :class:`_schema.Table` object,
2724 which is typically the target
2725 of an ORM mapping, or is present within a selectable that is
2726 mapped.
2727
2728 :param bind: an :class:`_engine.Engine` or :class:`_engine.Connection`
2729 object.
2730
2731 .. seealso::
2732
2733 :ref:`session_partitioning`
2734
2735 :paramref:`.Session.binds`
2736
2737 :meth:`.Session.bind_mapper`
2738
2739
2740 """
2741 self._add_bind(table, bind)
2742
2743 def get_bind(
2744 self,
2745 mapper: Optional[_EntityBindKey[_O]] = None,
2746 *,
2747 clause: Optional[ClauseElement] = None,
2748 bind: Optional[_SessionBind] = None,
2749 _sa_skip_events: Optional[bool] = None,
2750 _sa_skip_for_implicit_returning: bool = False,
2751 **kw: Any,
2752 ) -> Union[Engine, Connection]:
2753 """Return a "bind" to which this :class:`.Session` is bound.
2754
2755 The "bind" is usually an instance of :class:`_engine.Engine`,
2756 except in the case where the :class:`.Session` has been
2757 explicitly bound directly to a :class:`_engine.Connection`.
2758
2759 For a multiply-bound or unbound :class:`.Session`, the
2760 ``mapper`` or ``clause`` arguments are used to determine the
2761 appropriate bind to return.
2762
2763 Note that the "mapper" argument is usually present
2764 when :meth:`.Session.get_bind` is called via an ORM
2765 operation such as a :meth:`.Session.query`, each
2766 individual INSERT/UPDATE/DELETE operation within a
2767 :meth:`.Session.flush`, call, etc.
2768
2769 The order of resolution is:
2770
2771 1. if mapper given and :paramref:`.Session.binds` is present,
2772 locate a bind based first on the mapper in use, then
2773 on the mapped class in use, then on any base classes that are
2774 present in the ``__mro__`` of the mapped class, from more specific
2775 superclasses to more general.
2776 2. if clause given and ``Session.binds`` is present,
2777 locate a bind based on :class:`_schema.Table` objects
2778 found in the given clause present in ``Session.binds``.
2779 3. if ``Session.binds`` is present, return that.
2780 4. if clause given, attempt to return a bind
2781 linked to the :class:`_schema.MetaData` ultimately
2782 associated with the clause.
2783 5. if mapper given, attempt to return a bind
2784 linked to the :class:`_schema.MetaData` ultimately
2785 associated with the :class:`_schema.Table` or other
2786 selectable to which the mapper is mapped.
2787 6. No bind can be found, :exc:`~sqlalchemy.exc.UnboundExecutionError`
2788 is raised.
2789
2790 Note that the :meth:`.Session.get_bind` method can be overridden on
2791 a user-defined subclass of :class:`.Session` to provide any kind
2792 of bind resolution scheme. See the example at
2793 :ref:`session_custom_partitioning`.
2794
2795 :param mapper:
2796 Optional mapped class or corresponding :class:`_orm.Mapper` instance.
2797 The bind can be derived from a :class:`_orm.Mapper` first by
2798 consulting the "binds" map associated with this :class:`.Session`,
2799 and secondly by consulting the :class:`_schema.MetaData` associated
2800 with the :class:`_schema.Table` to which the :class:`_orm.Mapper` is
2801 mapped for a bind.
2802
2803 :param clause:
2804 A :class:`_expression.ClauseElement` (i.e.
2805 :func:`_expression.select`,
2806 :func:`_expression.text`,
2807 etc.). If the ``mapper`` argument is not present or could not
2808 produce a bind, the given expression construct will be searched
2809 for a bound element, typically a :class:`_schema.Table`
2810 associated with
2811 bound :class:`_schema.MetaData`.
2812
2813 .. seealso::
2814
2815 :ref:`session_partitioning`
2816
2817 :paramref:`.Session.binds`
2818
2819 :meth:`.Session.bind_mapper`
2820
2821 :meth:`.Session.bind_table`
2822
2823 """
2824
2825 # this function is documented as a subclassing hook, so we have
2826 # to call this method even if the return is simple
2827 if bind:
2828 return bind
2829 elif not self.__binds and self.bind:
2830 # simplest and most common case, we have a bind and no
2831 # per-mapper/table binds, we're done
2832 return self.bind
2833
2834 # we don't have self.bind and either have self.__binds
2835 # or we don't have self.__binds (which is legacy). Look at the
2836 # mapper and the clause
2837 if mapper is None and clause is None:
2838 if self.bind:
2839 return self.bind
2840 else:
2841 raise sa_exc.UnboundExecutionError(
2842 "This session is not bound to a single Engine or "
2843 "Connection, and no context was provided to locate "
2844 "a binding."
2845 )
2846
2847 # look more closely at the mapper.
2848 if mapper is not None:
2849 try:
2850 inspected_mapper = inspect(mapper)
2851 except sa_exc.NoInspectionAvailable as err:
2852 if isinstance(mapper, type):
2853 raise exc.UnmappedClassError(mapper) from err
2854 else:
2855 raise
2856 else:
2857 inspected_mapper = None
2858
2859 # match up the mapper or clause in the __binds
2860 if self.__binds:
2861 # matching mappers and selectables to entries in the
2862 # binds dictionary; supported use case.
2863 if inspected_mapper:
2864 for cls in inspected_mapper.class_.__mro__:
2865 if cls in self.__binds:
2866 return self.__binds[cls]
2867 if clause is None:
2868 clause = inspected_mapper.persist_selectable
2869
2870 if clause is not None:
2871 plugin_subject = clause._propagate_attrs.get(
2872 "plugin_subject", None
2873 )
2874
2875 if plugin_subject is not None:
2876 for cls in plugin_subject.mapper.class_.__mro__:
2877 if cls in self.__binds:
2878 return self.__binds[cls]
2879
2880 for obj in visitors.iterate(clause):
2881 if obj in self.__binds:
2882 if TYPE_CHECKING:
2883 assert isinstance(obj, Table)
2884 return self.__binds[obj]
2885
2886 # none of the __binds matched, but we have a fallback bind.
2887 # return that
2888 if self.bind:
2889 return self.bind
2890
2891 context = []
2892 if inspected_mapper is not None:
2893 context.append(f"mapper {inspected_mapper}")
2894 if clause is not None:
2895 context.append("SQL expression")
2896
2897 raise sa_exc.UnboundExecutionError(
2898 f"Could not locate a bind configured on "
2899 f'{", ".join(context)} or this Session.'
2900 )
2901
2902 @overload
2903 def query(self, _entity: _EntityType[_O]) -> Query[_O]: ...
2904
2905 @overload
2906 def query(
2907 self, _colexpr: TypedColumnsClauseRole[_T]
2908 ) -> RowReturningQuery[_T]: ...
2909
2910 # START OVERLOADED FUNCTIONS self.query RowReturningQuery 2-8
2911
2912 # code within this block is **programmatically,
2913 # statically generated** by tools/generate_tuple_map_overloads.py
2914
2915 @overload
2916 def query(
2917 self, __ent0: _TCCA[_T0], __ent1: _TCCA[_T1], /
2918 ) -> RowReturningQuery[_T0, _T1]: ...
2919
2920 @overload
2921 def query(
2922 self, __ent0: _TCCA[_T0], __ent1: _TCCA[_T1], __ent2: _TCCA[_T2], /
2923 ) -> RowReturningQuery[_T0, _T1, _T2]: ...
2924
2925 @overload
2926 def query(
2927 self,
2928 __ent0: _TCCA[_T0],
2929 __ent1: _TCCA[_T1],
2930 __ent2: _TCCA[_T2],
2931 __ent3: _TCCA[_T3],
2932 /,
2933 ) -> RowReturningQuery[_T0, _T1, _T2, _T3]: ...
2934
2935 @overload
2936 def query(
2937 self,
2938 __ent0: _TCCA[_T0],
2939 __ent1: _TCCA[_T1],
2940 __ent2: _TCCA[_T2],
2941 __ent3: _TCCA[_T3],
2942 __ent4: _TCCA[_T4],
2943 /,
2944 ) -> RowReturningQuery[_T0, _T1, _T2, _T3, _T4]: ...
2945
2946 @overload
2947 def query(
2948 self,
2949 __ent0: _TCCA[_T0],
2950 __ent1: _TCCA[_T1],
2951 __ent2: _TCCA[_T2],
2952 __ent3: _TCCA[_T3],
2953 __ent4: _TCCA[_T4],
2954 __ent5: _TCCA[_T5],
2955 /,
2956 ) -> RowReturningQuery[_T0, _T1, _T2, _T3, _T4, _T5]: ...
2957
2958 @overload
2959 def query(
2960 self,
2961 __ent0: _TCCA[_T0],
2962 __ent1: _TCCA[_T1],
2963 __ent2: _TCCA[_T2],
2964 __ent3: _TCCA[_T3],
2965 __ent4: _TCCA[_T4],
2966 __ent5: _TCCA[_T5],
2967 __ent6: _TCCA[_T6],
2968 /,
2969 ) -> RowReturningQuery[_T0, _T1, _T2, _T3, _T4, _T5, _T6]: ...
2970
2971 @overload
2972 def query(
2973 self,
2974 __ent0: _TCCA[_T0],
2975 __ent1: _TCCA[_T1],
2976 __ent2: _TCCA[_T2],
2977 __ent3: _TCCA[_T3],
2978 __ent4: _TCCA[_T4],
2979 __ent5: _TCCA[_T5],
2980 __ent6: _TCCA[_T6],
2981 __ent7: _TCCA[_T7],
2982 /,
2983 *entities: _ColumnsClauseArgument[Any],
2984 ) -> RowReturningQuery[
2985 _T0, _T1, _T2, _T3, _T4, _T5, _T6, _T7, Unpack[TupleAny]
2986 ]: ...
2987
2988 # END OVERLOADED FUNCTIONS self.query
2989
2990 @overload
2991 def query(
2992 self, *entities: _ColumnsClauseArgument[Any], **kwargs: Any
2993 ) -> Query[Any]: ...
2994
2995 def query(
2996 self, *entities: _ColumnsClauseArgument[Any], **kwargs: Any
2997 ) -> Query[Any]:
2998 """Return a new :class:`_query.Query` object corresponding to this
2999 :class:`_orm.Session`.
3000
3001 Note that the :class:`_query.Query` object is legacy as of
3002 SQLAlchemy 2.0; the :func:`_sql.select` construct is now used
3003 to construct ORM queries.
3004
3005 .. seealso::
3006
3007 :ref:`unified_tutorial`
3008
3009 :ref:`queryguide_toplevel`
3010
3011 :ref:`query_api_toplevel` - legacy API doc
3012
3013 """
3014
3015 return self._query_cls(entities, self, **kwargs)
3016
3017 def _identity_lookup(
3018 self,
3019 mapper: Mapper[_O],
3020 primary_key_identity: Union[Any, Tuple[Any, ...]],
3021 identity_token: Any = None,
3022 passive: PassiveFlag = PassiveFlag.PASSIVE_OFF,
3023 lazy_loaded_from: Optional[InstanceState[Any]] = None,
3024 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
3025 bind_arguments: Optional[_BindArguments] = None,
3026 ) -> Union[Optional[_O], LoaderCallableStatus]:
3027 """Locate an object in the identity map.
3028
3029 Given a primary key identity, constructs an identity key and then
3030 looks in the session's identity map. If present, the object may
3031 be run through unexpiration rules (e.g. load unloaded attributes,
3032 check if was deleted).
3033
3034 e.g.::
3035
3036 obj = session._identity_lookup(inspect(SomeClass), (1,))
3037
3038 :param mapper: mapper in use
3039 :param primary_key_identity: the primary key we are searching for, as
3040 a tuple.
3041 :param identity_token: identity token that should be used to create
3042 the identity key. Used as is, however overriding subclasses can
3043 repurpose this in order to interpret the value in a special way,
3044 such as if None then look among multiple target tokens.
3045 :param passive: passive load flag passed to
3046 :func:`.loading.get_from_identity`, which impacts the behavior if
3047 the object is found; the object may be validated and/or unexpired
3048 if the flag allows for SQL to be emitted.
3049 :param lazy_loaded_from: an :class:`.InstanceState` that is
3050 specifically asking for this identity as a related identity. Used
3051 for sharding schemes where there is a correspondence between an object
3052 and a related object being lazy-loaded (or otherwise
3053 relationship-loaded).
3054
3055 :return: None if the object is not found in the identity map, *or*
3056 if the object was unexpired and found to have been deleted.
3057 if passive flags disallow SQL and the object is expired, returns
3058 PASSIVE_NO_RESULT. In all other cases the instance is returned.
3059
3060 .. versionchanged:: 1.4.0 - the :meth:`.Session._identity_lookup`
3061 method was moved from :class:`_query.Query` to
3062 :class:`.Session`, to avoid having to instantiate the
3063 :class:`_query.Query` object.
3064
3065
3066 """
3067
3068 key = mapper.identity_key_from_primary_key(
3069 primary_key_identity, identity_token=identity_token
3070 )
3071
3072 # work around: https://github.com/python/typing/discussions/1143
3073 return_value = loading.get_from_identity(self, mapper, key, passive)
3074 return return_value
3075
3076 @util.non_memoized_property
3077 @contextlib.contextmanager
3078 def no_autoflush(self) -> Iterator[Session]:
3079 """Return a context manager that disables autoflush.
3080
3081 e.g.::
3082
3083 with session.no_autoflush:
3084
3085 some_object = SomeClass()
3086 session.add(some_object)
3087 # won't autoflush
3088 some_object.related_thing = session.query(SomeRelated).first()
3089
3090 Operations that proceed within the ``with:`` block
3091 will not be subject to flushes occurring upon query
3092 access. This is useful when initializing a series
3093 of objects which involve existing database queries,
3094 where the uncompleted object should not yet be flushed.
3095
3096 """
3097 autoflush = self.autoflush
3098 self.autoflush = False
3099 try:
3100 yield self
3101 finally:
3102 self.autoflush = autoflush
3103
3104 @util.langhelpers.tag_method_for_warnings(
3105 "This warning originated from the Session 'autoflush' process, "
3106 "which was invoked automatically in response to a user-initiated "
3107 "operation. Consider using ``no_autoflush`` context manager if this "
3108 "warning happened while initializing objects.",
3109 sa_exc.SAWarning,
3110 )
3111 def _autoflush(self) -> None:
3112 if self.autoflush and not self._flushing:
3113 try:
3114 self.flush()
3115 except sa_exc.StatementError as e:
3116 # note we are reraising StatementError as opposed to
3117 # raising FlushError with "chaining" to remain compatible
3118 # with code that catches StatementError, IntegrityError,
3119 # etc.
3120 e.add_detail(
3121 "raised as a result of Query-invoked autoflush; "
3122 "consider using a session.no_autoflush block if this "
3123 "flush is occurring prematurely"
3124 )
3125 raise e.with_traceback(sys.exc_info()[2])
3126
3127 def refresh(
3128 self,
3129 instance: object,
3130 attribute_names: Optional[Iterable[str]] = None,
3131 with_for_update: ForUpdateParameter = None,
3132 ) -> None:
3133 """Expire and refresh attributes on the given instance.
3134
3135 The selected attributes will first be expired as they would when using
3136 :meth:`_orm.Session.expire`; then a SELECT statement will be issued to
3137 the database to refresh column-oriented attributes with the current
3138 value available in the current transaction.
3139
3140 :func:`_orm.relationship` oriented attributes will also be immediately
3141 loaded if they were already eagerly loaded on the object, using the
3142 same eager loading strategy that they were loaded with originally.
3143
3144 .. versionadded:: 1.4 - the :meth:`_orm.Session.refresh` method
3145 can also refresh eagerly loaded attributes.
3146
3147 :func:`_orm.relationship` oriented attributes that would normally
3148 load using the ``select`` (or "lazy") loader strategy will also
3149 load **if they are named explicitly in the attribute_names
3150 collection**, emitting a SELECT statement for the attribute using the
3151 ``immediate`` loader strategy. If lazy-loaded relationships are not
3152 named in :paramref:`_orm.Session.refresh.attribute_names`, then
3153 they remain as "lazy loaded" attributes and are not implicitly
3154 refreshed.
3155
3156 .. versionchanged:: 2.0.4 The :meth:`_orm.Session.refresh` method
3157 will now refresh lazy-loaded :func:`_orm.relationship` oriented
3158 attributes for those which are named explicitly in the
3159 :paramref:`_orm.Session.refresh.attribute_names` collection.
3160
3161 .. tip::
3162
3163 While the :meth:`_orm.Session.refresh` method is capable of
3164 refreshing both column and relationship oriented attributes, its
3165 primary focus is on refreshing of local column-oriented attributes
3166 on a single instance. For more open ended "refresh" functionality,
3167 including the ability to refresh the attributes on many objects at
3168 once while having explicit control over relationship loader
3169 strategies, use the
3170 :ref:`populate existing <orm_queryguide_populate_existing>` feature
3171 instead.
3172
3173 Note that a highly isolated transaction will return the same values as
3174 were previously read in that same transaction, regardless of changes
3175 in database state outside of that transaction. Refreshing
3176 attributes usually only makes sense at the start of a transaction
3177 where database rows have not yet been accessed.
3178
3179 :param attribute_names: optional. An iterable collection of
3180 string attribute names indicating a subset of attributes to
3181 be refreshed.
3182
3183 :param with_for_update: optional boolean ``True`` indicating FOR UPDATE
3184 should be used, or may be a dictionary containing flags to
3185 indicate a more specific set of FOR UPDATE flags for the SELECT;
3186 flags should match the parameters of
3187 :meth:`_query.Query.with_for_update`.
3188 Supersedes the :paramref:`.Session.refresh.lockmode` parameter.
3189
3190 .. seealso::
3191
3192 :ref:`session_expire` - introductory material
3193
3194 :meth:`.Session.expire`
3195
3196 :meth:`.Session.expire_all`
3197
3198 :ref:`orm_queryguide_populate_existing` - allows any ORM query
3199 to refresh objects as they would be loaded normally.
3200
3201 """
3202 try:
3203 state = attributes.instance_state(instance)
3204 except exc.NO_STATE as err:
3205 raise exc.UnmappedInstanceError(instance) from err
3206
3207 self._expire_state(state, attribute_names)
3208
3209 # this autoflush previously used to occur as a secondary effect
3210 # of the load_on_ident below. Meaning we'd organize the SELECT
3211 # based on current DB pks, then flush, then if pks changed in that
3212 # flush, crash. this was unticketed but discovered as part of
3213 # #8703. So here, autoflush up front, dont autoflush inside
3214 # load_on_ident.
3215 self._autoflush()
3216
3217 if with_for_update == {}:
3218 raise sa_exc.ArgumentError(
3219 "with_for_update should be the boolean value "
3220 "True, or a dictionary with options. "
3221 "A blank dictionary is ambiguous."
3222 )
3223
3224 with_for_update = ForUpdateArg._from_argument(with_for_update)
3225
3226 stmt: Select[Unpack[TupleAny]] = sql.select(object_mapper(instance))
3227 if (
3228 loading._load_on_ident(
3229 self,
3230 stmt,
3231 state.key,
3232 refresh_state=state,
3233 with_for_update=with_for_update,
3234 only_load_props=attribute_names,
3235 require_pk_cols=True,
3236 # technically unnecessary as we just did autoflush
3237 # above, however removes the additional unnecessary
3238 # call to _autoflush()
3239 no_autoflush=True,
3240 is_user_refresh=True,
3241 )
3242 is None
3243 ):
3244 raise sa_exc.InvalidRequestError(
3245 "Could not refresh instance '%s'" % instance_str(instance)
3246 )
3247
3248 def expire_all(self) -> None:
3249 """Expires all persistent instances within this Session.
3250
3251 When any attributes on a persistent instance is next accessed,
3252 a query will be issued using the
3253 :class:`.Session` object's current transactional context in order to
3254 load all expired attributes for the given instance. Note that
3255 a highly isolated transaction will return the same values as were
3256 previously read in that same transaction, regardless of changes
3257 in database state outside of that transaction.
3258
3259 To expire individual objects and individual attributes
3260 on those objects, use :meth:`Session.expire`.
3261
3262 The :class:`.Session` object's default behavior is to
3263 expire all state whenever the :meth:`Session.rollback`
3264 or :meth:`Session.commit` methods are called, so that new
3265 state can be loaded for the new transaction. For this reason,
3266 calling :meth:`Session.expire_all` is not usually needed,
3267 assuming the transaction is isolated.
3268
3269 .. seealso::
3270
3271 :ref:`session_expire` - introductory material
3272
3273 :meth:`.Session.expire`
3274
3275 :meth:`.Session.refresh`
3276
3277 :meth:`_orm.Query.populate_existing`
3278
3279 """
3280 for state in self.identity_map.all_states():
3281 state._expire(state.dict, self.identity_map._modified)
3282
3283 def expire(
3284 self, instance: object, attribute_names: Optional[Iterable[str]] = None
3285 ) -> None:
3286 """Expire the attributes on an instance.
3287
3288 Marks the attributes of an instance as out of date. When an expired
3289 attribute is next accessed, a query will be issued to the
3290 :class:`.Session` object's current transactional context in order to
3291 load all expired attributes for the given instance. Note that
3292 a highly isolated transaction will return the same values as were
3293 previously read in that same transaction, regardless of changes
3294 in database state outside of that transaction.
3295
3296 To expire all objects in the :class:`.Session` simultaneously,
3297 use :meth:`Session.expire_all`.
3298
3299 The :class:`.Session` object's default behavior is to
3300 expire all state whenever the :meth:`Session.rollback`
3301 or :meth:`Session.commit` methods are called, so that new
3302 state can be loaded for the new transaction. For this reason,
3303 calling :meth:`Session.expire` only makes sense for the specific
3304 case that a non-ORM SQL statement was emitted in the current
3305 transaction.
3306
3307 :param instance: The instance to be refreshed.
3308 :param attribute_names: optional list of string attribute names
3309 indicating a subset of attributes to be expired.
3310
3311 .. seealso::
3312
3313 :ref:`session_expire` - introductory material
3314
3315 :meth:`.Session.expire`
3316
3317 :meth:`.Session.refresh`
3318
3319 :meth:`_orm.Query.populate_existing`
3320
3321 """
3322 try:
3323 state = attributes.instance_state(instance)
3324 except exc.NO_STATE as err:
3325 raise exc.UnmappedInstanceError(instance) from err
3326 self._expire_state(state, attribute_names)
3327
3328 def _expire_state(
3329 self,
3330 state: InstanceState[Any],
3331 attribute_names: Optional[Iterable[str]],
3332 ) -> None:
3333 self._validate_persistent(state)
3334 if attribute_names:
3335 state._expire_attributes(state.dict, attribute_names)
3336 else:
3337 # pre-fetch the full cascade since the expire is going to
3338 # remove associations
3339 cascaded = list(
3340 state.manager.mapper.cascade_iterator("refresh-expire", state)
3341 )
3342 self._conditional_expire(state)
3343 for o, m, st_, dct_ in cascaded:
3344 self._conditional_expire(st_)
3345
3346 def _conditional_expire(
3347 self, state: InstanceState[Any], autoflush: Optional[bool] = None
3348 ) -> None:
3349 """Expire a state if persistent, else expunge if pending"""
3350
3351 if state.key:
3352 state._expire(state.dict, self.identity_map._modified)
3353 elif state in self._new:
3354 self._new.pop(state)
3355 state._detach(self)
3356
3357 def expunge(self, instance: object) -> None:
3358 """Remove the `instance` from this ``Session``.
3359
3360 This will free all internal references to the instance. Cascading
3361 will be applied according to the *expunge* cascade rule.
3362
3363 """
3364 try:
3365 state = attributes.instance_state(instance)
3366 except exc.NO_STATE as err:
3367 raise exc.UnmappedInstanceError(instance) from err
3368 if state.session_id is not self.hash_key:
3369 raise sa_exc.InvalidRequestError(
3370 "Instance %s is not present in this Session" % state_str(state)
3371 )
3372
3373 cascaded = list(
3374 state.manager.mapper.cascade_iterator("expunge", state)
3375 )
3376 self._expunge_states([state] + [st_ for o, m, st_, dct_ in cascaded])
3377
3378 def _expunge_states(
3379 self, states: Iterable[InstanceState[Any]], to_transient: bool = False
3380 ) -> None:
3381 for state in states:
3382 if state in self._new:
3383 self._new.pop(state)
3384 elif self.identity_map.contains_state(state):
3385 self.identity_map.safe_discard(state)
3386 self._deleted.pop(state, None)
3387 elif self._transaction:
3388 # state is "detached" from being deleted, but still present
3389 # in the transaction snapshot
3390 self._transaction._deleted.pop(state, None)
3391 statelib.InstanceState._detach_states(
3392 states, self, to_transient=to_transient
3393 )
3394
3395 def _register_persistent(self, states: Set[InstanceState[Any]]) -> None:
3396 """Register all persistent objects from a flush.
3397
3398 This is used both for pending objects moving to the persistent
3399 state as well as already persistent objects.
3400
3401 """
3402
3403 pending_to_persistent = self.dispatch.pending_to_persistent or None
3404 for state in states:
3405 mapper = _state_mapper(state)
3406
3407 # prevent against last minute dereferences of the object
3408 obj = state.obj()
3409 if obj is not None:
3410 instance_key = mapper._identity_key_from_state(state)
3411
3412 if (
3413 _none_set.intersection(instance_key[1])
3414 and not mapper.allow_partial_pks
3415 or _none_set.issuperset(instance_key[1])
3416 ):
3417 raise exc.FlushError(
3418 "Instance %s has a NULL identity key. If this is an "
3419 "auto-generated value, check that the database table "
3420 "allows generation of new primary key values, and "
3421 "that the mapped Column object is configured to "
3422 "expect these generated values. Ensure also that "
3423 "this flush() is not occurring at an inappropriate "
3424 "time, such as within a load() event."
3425 % state_str(state)
3426 )
3427
3428 if state.key is None:
3429 state.key = instance_key
3430 elif state.key != instance_key:
3431 # primary key switch. use safe_discard() in case another
3432 # state has already replaced this one in the identity
3433 # map (see test/orm/test_naturalpks.py ReversePKsTest)
3434 self.identity_map.safe_discard(state)
3435 trans = self._transaction
3436 assert trans is not None
3437 if state in trans._key_switches:
3438 orig_key = trans._key_switches[state][0]
3439 else:
3440 orig_key = state.key
3441 trans._key_switches[state] = (
3442 orig_key,
3443 instance_key,
3444 )
3445 state.key = instance_key
3446
3447 # there can be an existing state in the identity map
3448 # that is replaced when the primary keys of two instances
3449 # are swapped; see test/orm/test_naturalpks.py -> test_reverse
3450 old = self.identity_map.replace(state)
3451 if (
3452 old is not None
3453 and mapper._identity_key_from_state(old) == instance_key
3454 and old.obj() is not None
3455 ):
3456 util.warn(
3457 "Identity map already had an identity for %s, "
3458 "replacing it with newly flushed object. Are there "
3459 "load operations occurring inside of an event handler "
3460 "within the flush?" % (instance_key,)
3461 )
3462 state._orphaned_outside_of_session = False
3463
3464 statelib.InstanceState._commit_all_states(
3465 ((state, state.dict) for state in states), self.identity_map
3466 )
3467
3468 self._register_altered(states)
3469
3470 if pending_to_persistent is not None:
3471 for state in states.intersection(self._new):
3472 pending_to_persistent(self, state)
3473
3474 # remove from new last, might be the last strong ref
3475 for state in set(states).intersection(self._new):
3476 self._new.pop(state)
3477
3478 def _register_altered(self, states: Iterable[InstanceState[Any]]) -> None:
3479 if self._transaction:
3480 for state in states:
3481 if state in self._new:
3482 self._transaction._new[state] = True
3483 else:
3484 self._transaction._dirty[state] = True
3485
3486 def _remove_newly_deleted(
3487 self, states: Iterable[InstanceState[Any]]
3488 ) -> None:
3489 persistent_to_deleted = self.dispatch.persistent_to_deleted or None
3490 for state in states:
3491 if self._transaction:
3492 self._transaction._deleted[state] = True
3493
3494 if persistent_to_deleted is not None:
3495 # get a strong reference before we pop out of
3496 # self._deleted
3497 obj = state.obj() # noqa
3498
3499 self.identity_map.safe_discard(state)
3500 self._deleted.pop(state, None)
3501 state._deleted = True
3502 # can't call state._detach() here, because this state
3503 # is still in the transaction snapshot and needs to be
3504 # tracked as part of that
3505 if persistent_to_deleted is not None:
3506 persistent_to_deleted(self, state)
3507
3508 def add(self, instance: object, *, _warn: bool = True) -> None:
3509 """Place an object into this :class:`_orm.Session`.
3510
3511 Objects that are in the :term:`transient` state when passed to the
3512 :meth:`_orm.Session.add` method will move to the
3513 :term:`pending` state, until the next flush, at which point they
3514 will move to the :term:`persistent` state.
3515
3516 Objects that are in the :term:`detached` state when passed to the
3517 :meth:`_orm.Session.add` method will move to the :term:`persistent`
3518 state directly.
3519
3520 If the transaction used by the :class:`_orm.Session` is rolled back,
3521 objects which were transient when they were passed to
3522 :meth:`_orm.Session.add` will be moved back to the
3523 :term:`transient` state, and will no longer be present within this
3524 :class:`_orm.Session`.
3525
3526 .. seealso::
3527
3528 :meth:`_orm.Session.add_all`
3529
3530 :ref:`session_adding` - at :ref:`session_basics`
3531
3532 """
3533 if _warn and self._warn_on_events:
3534 self._flush_warning("Session.add()")
3535
3536 try:
3537 state = attributes.instance_state(instance)
3538 except exc.NO_STATE as err:
3539 raise exc.UnmappedInstanceError(instance) from err
3540
3541 self._save_or_update_state(state)
3542
3543 def add_all(self, instances: Iterable[object]) -> None:
3544 """Add the given collection of instances to this :class:`_orm.Session`.
3545
3546 See the documentation for :meth:`_orm.Session.add` for a general
3547 behavioral description.
3548
3549 .. seealso::
3550
3551 :meth:`_orm.Session.add`
3552
3553 :ref:`session_adding` - at :ref:`session_basics`
3554
3555 """
3556
3557 if self._warn_on_events:
3558 self._flush_warning("Session.add_all()")
3559
3560 for instance in instances:
3561 self.add(instance, _warn=False)
3562
3563 def _save_or_update_state(self, state: InstanceState[Any]) -> None:
3564 state._orphaned_outside_of_session = False
3565 self._save_or_update_impl(state)
3566
3567 mapper = _state_mapper(state)
3568 for o, m, st_, dct_ in mapper.cascade_iterator(
3569 "save-update", state, halt_on=self._contains_state
3570 ):
3571 self._save_or_update_impl(st_)
3572
3573 def delete(self, instance: object) -> None:
3574 """Mark an instance as deleted.
3575
3576 The object is assumed to be either :term:`persistent` or
3577 :term:`detached` when passed; after the method is called, the
3578 object will remain in the :term:`persistent` state until the next
3579 flush proceeds. During this time, the object will also be a member
3580 of the :attr:`_orm.Session.deleted` collection.
3581
3582 When the next flush proceeds, the object will move to the
3583 :term:`deleted` state, indicating a ``DELETE`` statement was emitted
3584 for its row within the current transaction. When the transaction
3585 is successfully committed,
3586 the deleted object is moved to the :term:`detached` state and is
3587 no longer present within this :class:`_orm.Session`.
3588
3589 .. seealso::
3590
3591 :ref:`session_deleting` - at :ref:`session_basics`
3592
3593 :meth:`.Session.delete_all` - multiple instance version
3594
3595 """
3596 if self._warn_on_events:
3597 self._flush_warning("Session.delete()")
3598
3599 self._delete_impl(object_state(instance), instance, head=True)
3600
3601 def delete_all(self, instances: Iterable[object]) -> None:
3602 """Calls :meth:`.Session.delete` on multiple instances.
3603
3604 .. seealso::
3605
3606 :meth:`.Session.delete` - main documentation on delete
3607
3608 .. versionadded:: 2.1
3609
3610 """
3611
3612 if self._warn_on_events:
3613 self._flush_warning("Session.delete_all()")
3614
3615 for instance in instances:
3616 self._delete_impl(object_state(instance), instance, head=True)
3617
3618 def _delete_impl(
3619 self, state: InstanceState[Any], obj: object, head: bool
3620 ) -> None:
3621 if state.key is None:
3622 if head:
3623 raise sa_exc.InvalidRequestError(
3624 "Instance '%s' is not persisted" % state_str(state)
3625 )
3626 else:
3627 return
3628
3629 to_attach = self._before_attach(state, obj)
3630
3631 if state in self._deleted:
3632 return
3633
3634 self.identity_map.add(state)
3635
3636 if to_attach:
3637 self._after_attach(state, obj)
3638
3639 if head:
3640 # grab the cascades before adding the item to the deleted list
3641 # so that autoflush does not delete the item
3642 # the strong reference to the instance itself is significant here
3643 cascade_states = list(
3644 state.manager.mapper.cascade_iterator("delete", state)
3645 )
3646 else:
3647 cascade_states = None
3648
3649 self._deleted[state] = obj
3650
3651 if head:
3652 if TYPE_CHECKING:
3653 assert cascade_states is not None
3654 for o, m, st_, dct_ in cascade_states:
3655 self._delete_impl(st_, o, False)
3656
3657 def get(
3658 self,
3659 entity: _EntityBindKey[_O],
3660 ident: _PKIdentityArgument,
3661 *,
3662 options: Optional[Sequence[ORMOption]] = None,
3663 populate_existing: bool | None = None,
3664 with_for_update: ForUpdateParameter = None,
3665 identity_token: Optional[Any] = None,
3666 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
3667 bind_arguments: Optional[_BindArguments] = None,
3668 ) -> Optional[_O]:
3669 """Return an instance based on the given primary key identifier,
3670 or ``None`` if not found.
3671
3672 E.g.::
3673
3674 my_user = session.get(User, 5)
3675
3676 some_object = session.get(VersionedFoo, (5, 10))
3677
3678 some_object = session.get(VersionedFoo, {"id": 5, "version_id": 10})
3679
3680 .. versionadded:: 1.4 Added :meth:`_orm.Session.get`, which is moved
3681 from the now legacy :meth:`_orm.Query.get` method.
3682
3683 :meth:`_orm.Session.get` is special in that it provides direct
3684 access to the identity map of the :class:`.Session`.
3685 If the given primary key identifier is present
3686 in the local identity map, the object is returned
3687 directly from this collection and no SQL is emitted,
3688 unless the object has been marked fully expired.
3689 If not present,
3690 a SELECT is performed in order to locate the object.
3691
3692 :meth:`_orm.Session.get` also will perform a check if
3693 the object is present in the identity map and
3694 marked as expired - a SELECT
3695 is emitted to refresh the object as well as to
3696 ensure that the row is still present.
3697 If not, :class:`~sqlalchemy.orm.exc.ObjectDeletedError` is raised.
3698
3699 :param entity: a mapped class or :class:`.Mapper` indicating the
3700 type of entity to be loaded.
3701
3702 :param ident: A scalar, tuple, or dictionary representing the
3703 primary key. For a composite (e.g. multiple column) primary key,
3704 a tuple or dictionary should be passed.
3705
3706 For a single-column primary key, the scalar calling form is typically
3707 the most expedient. If the primary key of a row is the value "5",
3708 the call looks like::
3709
3710 my_object = session.get(SomeClass, 5)
3711
3712 The tuple form contains primary key values typically in
3713 the order in which they correspond to the mapped
3714 :class:`_schema.Table`
3715 object's primary key columns, or if the
3716 :paramref:`_orm.Mapper.primary_key` configuration parameter were
3717 used, in
3718 the order used for that parameter. For example, if the primary key
3719 of a row is represented by the integer
3720 digits "5, 10" the call would look like::
3721
3722 my_object = session.get(SomeClass, (5, 10))
3723
3724 The dictionary form should include as keys the mapped attribute names
3725 corresponding to each element of the primary key. If the mapped class
3726 has the attributes ``id``, ``version_id`` as the attributes which
3727 store the object's primary key value, the call would look like::
3728
3729 my_object = session.get(SomeClass, {"id": 5, "version_id": 10})
3730
3731 :param options: optional sequence of loader options which will be
3732 applied to the query, if one is emitted.
3733
3734 :param populate_existing: causes the method to unconditionally emit
3735 a SQL query and refresh the object with the newly loaded data,
3736 regardless of whether or not the object is already present.
3737 Setting this flag takes precedence over passing it as an
3738 execution option.
3739
3740 :param with_for_update: optional boolean ``True`` indicating FOR UPDATE
3741 should be used, or may be a dictionary containing flags to
3742 indicate a more specific set of FOR UPDATE flags for the SELECT;
3743 flags should match the parameters of
3744 :meth:`_query.Query.with_for_update`.
3745 Supersedes the :paramref:`.Session.refresh.lockmode` parameter.
3746
3747 :param execution_options: optional dictionary of execution options,
3748 which will be associated with the query execution if one is emitted.
3749 This dictionary can provide a subset of the options that are
3750 accepted by :meth:`_engine.Connection.execution_options`, and may
3751 also provide additional options understood only in an ORM context.
3752
3753 .. versionadded:: 1.4.29
3754
3755 .. seealso::
3756
3757 :ref:`orm_queryguide_execution_options` - ORM-specific execution
3758 options
3759
3760 :param bind_arguments: dictionary of additional arguments to determine
3761 the bind. May include "mapper", "bind", or other custom arguments.
3762 Contents of this dictionary are passed to the
3763 :meth:`.Session.get_bind` method.
3764
3765 .. versionadded:: 2.0.0rc1
3766
3767 :return: The object instance, or ``None``.
3768
3769 """ # noqa: E501
3770 return self._get_impl(
3771 entity,
3772 ident,
3773 loading._load_on_pk_identity,
3774 options=options,
3775 populate_existing=populate_existing,
3776 with_for_update=with_for_update,
3777 identity_token=identity_token,
3778 execution_options=execution_options,
3779 bind_arguments=bind_arguments,
3780 )
3781
3782 def get_one(
3783 self,
3784 entity: _EntityBindKey[_O],
3785 ident: _PKIdentityArgument,
3786 *,
3787 options: Optional[Sequence[ORMOption]] = None,
3788 populate_existing: bool | None = None,
3789 with_for_update: ForUpdateParameter = None,
3790 identity_token: Optional[Any] = None,
3791 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
3792 bind_arguments: Optional[_BindArguments] = None,
3793 ) -> _O:
3794 """Return exactly one instance based on the given primary key
3795 identifier, or raise an exception if not found.
3796
3797 Raises :class:`_exc.NoResultFound` if the query selects no rows.
3798
3799 For a detailed documentation of the arguments see the
3800 method :meth:`.Session.get`.
3801
3802 .. versionadded:: 2.0.22
3803
3804 :return: The object instance.
3805
3806 .. seealso::
3807
3808 :meth:`.Session.get` - equivalent method that instead
3809 returns ``None`` if no row was found with the provided primary
3810 key
3811
3812 """
3813
3814 instance = self.get(
3815 entity,
3816 ident,
3817 options=options,
3818 populate_existing=populate_existing,
3819 with_for_update=with_for_update,
3820 identity_token=identity_token,
3821 execution_options=execution_options,
3822 bind_arguments=bind_arguments,
3823 )
3824
3825 if instance is None:
3826 raise sa_exc.NoResultFound(
3827 "No row was found when one was required"
3828 )
3829
3830 return instance
3831
3832 def _get_impl(
3833 self,
3834 entity: _EntityBindKey[_O],
3835 primary_key_identity: _PKIdentityArgument,
3836 db_load_fn: Callable[..., _O],
3837 *,
3838 options: Optional[Sequence[ExecutableOption]] = None,
3839 populate_existing: bool | None = None,
3840 with_for_update: ForUpdateParameter = None,
3841 identity_token: Optional[Any] = None,
3842 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
3843 bind_arguments: Optional[_BindArguments] = None,
3844 ) -> Optional[_O]:
3845 # set populate_existing value; direct parameter
3846 # takes precedence over execution_options
3847 if populate_existing is not None:
3848 execution_options = {
3849 **execution_options, # type: ignore[typeddict-item]
3850 "populate_existing": populate_existing,
3851 }
3852 else:
3853 populate_existing = execution_options.get(
3854 "populate_existing", False
3855 )
3856
3857 # convert composite types to individual args
3858 if (
3859 is_composite_class(primary_key_identity)
3860 and type(primary_key_identity)
3861 in descriptor_props._composite_getters
3862 ):
3863 getter = descriptor_props._composite_getters[
3864 type(primary_key_identity)
3865 ]
3866 primary_key_identity = getter(primary_key_identity)
3867
3868 mapper: Optional[Mapper[_O]] = inspect(entity)
3869
3870 if mapper is None or not mapper.is_mapper:
3871 raise sa_exc.ArgumentError(
3872 "Expected mapped class or mapper, got: %r" % entity
3873 )
3874
3875 is_dict = isinstance(primary_key_identity, dict)
3876 if not is_dict:
3877 primary_key_identity = util.to_list(
3878 primary_key_identity, default=[None]
3879 )
3880
3881 if len(primary_key_identity) != len(mapper.primary_key):
3882 raise sa_exc.InvalidRequestError(
3883 "Incorrect number of values in identifier to formulate "
3884 "primary key for session.get(); primary key columns "
3885 "are %s" % ",".join("'%s'" % c for c in mapper.primary_key)
3886 )
3887
3888 if is_dict:
3889 pk_synonyms = mapper._pk_synonyms
3890
3891 if pk_synonyms:
3892 correct_keys = set(pk_synonyms).intersection(
3893 primary_key_identity
3894 )
3895
3896 if correct_keys:
3897 primary_key_identity = dict(primary_key_identity)
3898 for k in correct_keys:
3899 primary_key_identity[pk_synonyms[k]] = (
3900 primary_key_identity[k]
3901 )
3902
3903 try:
3904 primary_key_identity = list(
3905 primary_key_identity[prop.key]
3906 for prop in mapper._identity_key_props
3907 )
3908
3909 except KeyError as err:
3910 raise sa_exc.InvalidRequestError(
3911 "Incorrect names of values in identifier to formulate "
3912 "primary key for session.get(); primary key attribute "
3913 "names are %s (synonym names are also accepted)"
3914 % ",".join(
3915 "'%s'" % prop.key
3916 for prop in mapper._identity_key_props
3917 )
3918 ) from err
3919
3920 for_update_arg = ForUpdateArg._from_argument(with_for_update)
3921
3922 if (
3923 not populate_existing
3924 and not mapper.always_refresh
3925 and for_update_arg is None
3926 ):
3927 instance = self._identity_lookup(
3928 mapper,
3929 primary_key_identity,
3930 identity_token=identity_token,
3931 execution_options=execution_options,
3932 bind_arguments=bind_arguments,
3933 )
3934
3935 if instance is not None:
3936 # reject calls for id in identity map but class
3937 # mismatch.
3938 if not isinstance(instance, mapper.class_):
3939 return None
3940 return instance
3941
3942 # TODO: this was being tested before, but this is not possible
3943 assert instance is not LoaderCallableStatus.PASSIVE_CLASS_MISMATCH
3944
3945 load_options = context.QueryContext.default_load_options
3946
3947 if populate_existing:
3948 load_options += {"_populate_existing": populate_existing}
3949 statement = sql.select(mapper)
3950 if for_update_arg is not None:
3951 statement._for_update_arg = for_update_arg
3952
3953 if options:
3954 statement = statement.options(*options)
3955 if self.execution_options:
3956 execution_options = self.execution_options.union(execution_options)
3957 return db_load_fn(
3958 self,
3959 statement,
3960 primary_key_identity,
3961 load_options=load_options,
3962 identity_token=identity_token,
3963 execution_options=execution_options,
3964 bind_arguments=bind_arguments,
3965 )
3966
3967 def merge(
3968 self,
3969 instance: _O,
3970 *,
3971 load: bool = True,
3972 options: Optional[Sequence[ORMOption]] = None,
3973 ) -> _O:
3974 """Copy the state of a given instance into a corresponding instance
3975 within this :class:`.Session`.
3976
3977 :meth:`.Session.merge` examines the primary key attributes of the
3978 source instance, and attempts to reconcile it with an instance of the
3979 same primary key in the session. If not found locally, it attempts
3980 to load the object from the database based on primary key, and if
3981 none can be located, creates a new instance. The state of each
3982 attribute on the source instance is then copied to the target
3983 instance. The resulting target instance is then returned by the
3984 method; the original source instance is left unmodified, and
3985 un-associated with the :class:`.Session` if not already.
3986
3987 This operation cascades to associated instances if the association is
3988 mapped with ``cascade="merge"``.
3989
3990 See :ref:`unitofwork_merging` for a detailed discussion of merging.
3991
3992 :param instance: Instance to be merged.
3993 :param load: Boolean, when False, :meth:`.merge` switches into
3994 a "high performance" mode which causes it to forego emitting history
3995 events as well as all database access. This flag is used for
3996 cases such as transferring graphs of objects into a :class:`.Session`
3997 from a second level cache, or to transfer just-loaded objects
3998 into the :class:`.Session` owned by a worker thread or process
3999 without re-querying the database.
4000
4001 The ``load=False`` use case adds the caveat that the given
4002 object has to be in a "clean" state, that is, has no pending changes
4003 to be flushed - even if the incoming object is detached from any
4004 :class:`.Session`. This is so that when
4005 the merge operation populates local attributes and
4006 cascades to related objects and
4007 collections, the values can be "stamped" onto the
4008 target object as is, without generating any history or attribute
4009 events, and without the need to reconcile the incoming data with
4010 any existing related objects or collections that might not
4011 be loaded. The resulting objects from ``load=False`` are always
4012 produced as "clean", so it is only appropriate that the given objects
4013 should be "clean" as well, else this suggests a mis-use of the
4014 method.
4015 :param options: optional sequence of loader options which will be
4016 applied to the :meth:`_orm.Session.get` method when the merge
4017 operation loads the existing version of the object from the database.
4018
4019 .. versionadded:: 1.4.24
4020
4021
4022 .. seealso::
4023
4024 :func:`.make_transient_to_detached` - provides for an alternative
4025 means of "merging" a single object into the :class:`.Session`
4026
4027 :meth:`.Session.merge_all` - multiple instance version
4028
4029 """
4030
4031 if self._warn_on_events:
4032 self._flush_warning("Session.merge()")
4033
4034 if load:
4035 # flush current contents if we expect to load data
4036 self._autoflush()
4037
4038 with self.no_autoflush:
4039 return self._merge(
4040 object_state(instance),
4041 attributes.instance_dict(instance),
4042 load=load,
4043 options=options,
4044 _recursive={},
4045 _resolve_conflict_map={},
4046 )
4047
4048 def merge_all(
4049 self,
4050 instances: Iterable[_O],
4051 *,
4052 load: bool = True,
4053 options: Optional[Sequence[ORMOption]] = None,
4054 ) -> Sequence[_O]:
4055 """Calls :meth:`.Session.merge` on multiple instances.
4056
4057 .. seealso::
4058
4059 :meth:`.Session.merge` - main documentation on merge
4060
4061 .. versionadded:: 2.1
4062
4063 """
4064
4065 if self._warn_on_events:
4066 self._flush_warning("Session.merge_all()")
4067
4068 if load:
4069 # flush current contents if we expect to load data
4070 self._autoflush()
4071
4072 return [
4073 self._merge(
4074 object_state(instance),
4075 attributes.instance_dict(instance),
4076 load=load,
4077 options=options,
4078 _recursive={},
4079 _resolve_conflict_map={},
4080 )
4081 for instance in instances
4082 ]
4083
4084 def _merge(
4085 self,
4086 state: InstanceState[_O],
4087 state_dict: _InstanceDict,
4088 *,
4089 options: Optional[Sequence[ORMOption]] = None,
4090 load: bool,
4091 _recursive: Dict[Any, object],
4092 _resolve_conflict_map: Dict[_IdentityKeyType[Any], object],
4093 ) -> _O:
4094 mapper: Mapper[_O] = _state_mapper(state)
4095 if state in _recursive:
4096 return cast(_O, _recursive[state])
4097
4098 new_instance = False
4099 key = state.key
4100
4101 merged: Optional[_O]
4102
4103 if key is None:
4104 if state in self._new:
4105 util.warn(
4106 "Instance %s is already pending in this Session yet is "
4107 "being merged again; this is probably not what you want "
4108 "to do" % state_str(state)
4109 )
4110
4111 if not load:
4112 raise sa_exc.InvalidRequestError(
4113 "merge() with load=False option does not support "
4114 "objects transient (i.e. unpersisted) objects. flush() "
4115 "all changes on mapped instances before merging with "
4116 "load=False."
4117 )
4118 key = mapper._identity_key_from_state(state)
4119 key_is_persistent = LoaderCallableStatus.NEVER_SET not in key[
4120 1
4121 ] and (
4122 not _none_set.intersection(key[1])
4123 or (
4124 mapper.allow_partial_pks
4125 and not _none_set.issuperset(key[1])
4126 )
4127 )
4128 else:
4129 key_is_persistent = True
4130
4131 merged = self.identity_map.get(key)
4132
4133 if merged is None:
4134 if key_is_persistent and key in _resolve_conflict_map:
4135 merged = cast(_O, _resolve_conflict_map[key])
4136
4137 elif not load:
4138 if state.modified:
4139 raise sa_exc.InvalidRequestError(
4140 "merge() with load=False option does not support "
4141 "objects marked as 'dirty'. flush() all changes on "
4142 "mapped instances before merging with load=False."
4143 )
4144 merged = mapper.class_manager.new_instance()
4145 merged_state = attributes.instance_state(merged)
4146 merged_state.key = key
4147 self._update_impl(merged_state)
4148 new_instance = True
4149
4150 elif key_is_persistent:
4151 merged = self.get(
4152 mapper.class_,
4153 key[1],
4154 identity_token=key[2],
4155 options=options,
4156 )
4157
4158 if merged is None:
4159 merged = mapper.class_manager.new_instance()
4160 merged_state = attributes.instance_state(merged)
4161 merged_dict = attributes.instance_dict(merged)
4162 new_instance = True
4163 self._save_or_update_state(merged_state)
4164 else:
4165 merged_state = attributes.instance_state(merged)
4166 merged_dict = attributes.instance_dict(merged)
4167
4168 _recursive[state] = merged
4169 _resolve_conflict_map[key] = merged
4170
4171 # check that we didn't just pull the exact same
4172 # state out.
4173 if state is not merged_state:
4174 # version check if applicable
4175 if mapper.version_id_col is not None:
4176 existing_version = mapper._get_state_attr_by_column(
4177 state,
4178 state_dict,
4179 mapper.version_id_col,
4180 passive=PassiveFlag.PASSIVE_NO_INITIALIZE,
4181 )
4182
4183 merged_version = mapper._get_state_attr_by_column(
4184 merged_state,
4185 merged_dict,
4186 mapper.version_id_col,
4187 passive=PassiveFlag.PASSIVE_NO_INITIALIZE,
4188 )
4189
4190 if (
4191 existing_version
4192 is not LoaderCallableStatus.PASSIVE_NO_RESULT
4193 and merged_version
4194 is not LoaderCallableStatus.PASSIVE_NO_RESULT
4195 and existing_version != merged_version
4196 ):
4197 raise exc.StaleDataError(
4198 "Version id '%s' on merged state %s "
4199 "does not match existing version '%s'. "
4200 "Leave the version attribute unset when "
4201 "merging to update the most recent version."
4202 % (
4203 existing_version,
4204 state_str(merged_state),
4205 merged_version,
4206 )
4207 )
4208
4209 merged_state.load_path = state.load_path
4210 merged_state.load_options = state.load_options
4211
4212 # since we are copying load_options, we need to copy
4213 # the callables_ that would have been generated by those
4214 # load_options.
4215 # assumes that the callables we put in state.callables_
4216 # are not instance-specific (which they should not be)
4217 merged_state._copy_callables(state)
4218
4219 for prop in mapper.iterate_properties:
4220 prop.merge(
4221 self,
4222 state,
4223 state_dict,
4224 merged_state,
4225 merged_dict,
4226 load,
4227 _recursive,
4228 _resolve_conflict_map,
4229 )
4230
4231 if not load:
4232 # remove any history
4233 merged_state._commit_all(merged_dict, self.identity_map)
4234 merged_state.manager.dispatch._sa_event_merge_wo_load(
4235 merged_state, None
4236 )
4237
4238 if new_instance:
4239 merged_state.manager.dispatch.load(merged_state, None)
4240
4241 return merged
4242
4243 def _validate_persistent(self, state: InstanceState[Any]) -> None:
4244 if not self.identity_map.contains_state(state):
4245 raise sa_exc.InvalidRequestError(
4246 "Instance '%s' is not persistent within this Session"
4247 % state_str(state)
4248 )
4249
4250 def _save_impl(self, state: InstanceState[Any]) -> None:
4251 if state.key is not None:
4252 raise sa_exc.InvalidRequestError(
4253 "Object '%s' already has an identity - "
4254 "it can't be registered as pending" % state_str(state)
4255 )
4256
4257 obj = state.obj()
4258 to_attach = self._before_attach(state, obj)
4259 if state not in self._new:
4260 self._new[state] = obj
4261 state.insert_order = len(self._new)
4262 if to_attach:
4263 self._after_attach(state, obj)
4264
4265 def _update_impl(
4266 self, state: InstanceState[Any], revert_deletion: bool = False
4267 ) -> None:
4268 if state.key is None:
4269 raise sa_exc.InvalidRequestError(
4270 "Instance '%s' is not persisted" % state_str(state)
4271 )
4272
4273 if state._deleted:
4274 if revert_deletion:
4275 if not state._attached:
4276 return
4277 del state._deleted
4278 else:
4279 raise sa_exc.InvalidRequestError(
4280 "Instance '%s' has been deleted. "
4281 "Use the make_transient() "
4282 "function to send this object back "
4283 "to the transient state." % state_str(state)
4284 )
4285
4286 obj = state.obj()
4287
4288 # check for late gc
4289 if obj is None:
4290 return
4291
4292 to_attach = self._before_attach(state, obj)
4293
4294 self._deleted.pop(state, None)
4295 if revert_deletion:
4296 self.identity_map.replace(state)
4297 else:
4298 self.identity_map.add(state)
4299
4300 if to_attach:
4301 self._after_attach(state, obj)
4302 elif revert_deletion:
4303 self.dispatch.deleted_to_persistent(self, state)
4304
4305 def _save_or_update_impl(self, state: InstanceState[Any]) -> None:
4306 if state.key is None:
4307 self._save_impl(state)
4308 else:
4309 self._update_impl(state)
4310
4311 def enable_relationship_loading(self, obj: object) -> None:
4312 """Associate an object with this :class:`.Session` for related
4313 object loading.
4314
4315 .. warning::
4316
4317 :meth:`.enable_relationship_loading` exists to serve special
4318 use cases and is not recommended for general use.
4319
4320 Accesses of attributes mapped with :func:`_orm.relationship`
4321 will attempt to load a value from the database using this
4322 :class:`.Session` as the source of connectivity. The values
4323 will be loaded based on foreign key and primary key values
4324 present on this object - if not present, then those relationships
4325 will be unavailable.
4326
4327 The object will be attached to this session, but will
4328 **not** participate in any persistence operations; its state
4329 for almost all purposes will remain either "transient" or
4330 "detached", except for the case of relationship loading.
4331
4332 Also note that backrefs will often not work as expected.
4333 Altering a relationship-bound attribute on the target object
4334 may not fire off a backref event, if the effective value
4335 is what was already loaded from a foreign-key-holding value.
4336
4337 The :meth:`.Session.enable_relationship_loading` method is
4338 similar to the ``load_on_pending`` flag on :func:`_orm.relationship`.
4339 Unlike that flag, :meth:`.Session.enable_relationship_loading` allows
4340 an object to remain transient while still being able to load
4341 related items.
4342
4343 To make a transient object associated with a :class:`.Session`
4344 via :meth:`.Session.enable_relationship_loading` pending, add
4345 it to the :class:`.Session` using :meth:`.Session.add` normally.
4346 If the object instead represents an existing identity in the database,
4347 it should be merged using :meth:`.Session.merge`.
4348
4349 :meth:`.Session.enable_relationship_loading` does not improve
4350 behavior when the ORM is used normally - object references should be
4351 constructed at the object level, not at the foreign key level, so
4352 that they are present in an ordinary way before flush()
4353 proceeds. This method is not intended for general use.
4354
4355 .. seealso::
4356
4357 :paramref:`_orm.relationship.load_on_pending` - this flag
4358 allows per-relationship loading of many-to-ones on items that
4359 are pending.
4360
4361 :func:`.make_transient_to_detached` - allows for an object to
4362 be added to a :class:`.Session` without SQL emitted, which then
4363 will unexpire attributes on access.
4364
4365 """
4366 try:
4367 state = attributes.instance_state(obj)
4368 except exc.NO_STATE as err:
4369 raise exc.UnmappedInstanceError(obj) from err
4370
4371 to_attach = self._before_attach(state, obj)
4372 state._load_pending = True
4373 if to_attach:
4374 self._after_attach(state, obj)
4375
4376 def _before_attach(self, state: InstanceState[Any], obj: object) -> bool:
4377 self._autobegin_t()
4378
4379 if state.session_id == self.hash_key:
4380 return False
4381
4382 if state.session_id and state.session_id in _sessions:
4383 raise sa_exc.InvalidRequestError(
4384 "Object '%s' is already attached to session '%s' "
4385 "(this is '%s')"
4386 % (state_str(state), state.session_id, self.hash_key)
4387 )
4388
4389 self.dispatch.before_attach(self, state)
4390
4391 return True
4392
4393 def _after_attach(self, state: InstanceState[Any], obj: object) -> None:
4394 state.session_id = self.hash_key
4395 if state.modified and state._strong_obj is None:
4396 state._strong_obj = obj
4397 self.dispatch.after_attach(self, state)
4398
4399 if state.key:
4400 self.dispatch.detached_to_persistent(self, state)
4401 else:
4402 self.dispatch.transient_to_pending(self, state)
4403
4404 def __contains__(self, instance: object) -> bool:
4405 """Return True if the instance is associated with this session.
4406
4407 The instance may be pending or persistent within the Session for a
4408 result of True.
4409
4410 """
4411 try:
4412 state = attributes.instance_state(instance)
4413 except exc.NO_STATE as err:
4414 raise exc.UnmappedInstanceError(instance) from err
4415 return self._contains_state(state)
4416
4417 def __iter__(self) -> Iterator[object]:
4418 """Iterate over all pending or persistent instances within this
4419 Session.
4420
4421 """
4422 return iter(
4423 list(self._new.values()) + list(self.identity_map.values())
4424 )
4425
4426 def _contains_state(self, state: InstanceState[Any]) -> bool:
4427 return state in self._new or self.identity_map.contains_state(state)
4428
4429 def flush(self, objects: Optional[Sequence[Any]] = None) -> None:
4430 """Flush all the object changes to the database.
4431
4432 Writes out all pending object creations, deletions and modifications
4433 to the database as INSERTs, DELETEs, UPDATEs, etc. Operations are
4434 automatically ordered by the Session's unit of work dependency
4435 solver.
4436
4437 Database operations will be issued in the current transactional
4438 context and do not affect the state of the transaction, unless an
4439 error occurs, in which case the entire transaction is rolled back.
4440 You may flush() as often as you like within a transaction to move
4441 changes from Python to the database's transaction buffer.
4442
4443 :param objects: Optional; restricts the flush operation to operate
4444 only on elements that are in the given collection.
4445
4446 This feature is for an extremely narrow set of use cases where
4447 particular objects may need to be operated upon before the
4448 full flush() occurs. It is not intended for general use.
4449
4450 .. deprecated:: 2.1
4451
4452 """
4453
4454 if self._flushing:
4455 raise sa_exc.InvalidRequestError("Session is already flushing")
4456
4457 if self._is_clean():
4458 return
4459 try:
4460 self._flushing = True
4461 self._flush(objects)
4462 finally:
4463 self._flushing = False
4464
4465 def _flush_warning(self, method: Any) -> None:
4466 util.warn(
4467 "Usage of the '%s' operation is not currently supported "
4468 "within the execution stage of the flush process. "
4469 "Results may not be consistent. Consider using alternative "
4470 "event listeners or connection-level operations instead." % method
4471 )
4472
4473 def _is_clean(self) -> bool:
4474 return (
4475 not self.identity_map.check_modified()
4476 and not self._deleted
4477 and not self._new
4478 )
4479
4480 # have this here since it otherwise causes issues with the proxy
4481 # method generation
4482 @deprecated_params(
4483 objects=(
4484 "2.1",
4485 "The `objects` parameter of `Session.flush` is deprecated",
4486 )
4487 )
4488 def _flush(self, objects: Optional[Sequence[object]] = None) -> None:
4489 dirty = self._dirty_states
4490 if not dirty and not self._deleted and not self._new:
4491 self.identity_map._modified.clear()
4492 return
4493
4494 flush_context = UOWTransaction(self)
4495
4496 if self.dispatch.before_flush:
4497 self.dispatch.before_flush(self, flush_context, objects)
4498 # re-establish "dirty states" in case the listeners
4499 # added
4500 dirty = self._dirty_states
4501
4502 deleted = set(self._deleted)
4503 new = set(self._new)
4504
4505 dirty = set(dirty).difference(deleted)
4506
4507 # create the set of all objects we want to operate upon
4508 if objects:
4509 # specific list passed in
4510 objset = set()
4511 for o in objects:
4512 try:
4513 state = attributes.instance_state(o)
4514
4515 except exc.NO_STATE as err:
4516 raise exc.UnmappedInstanceError(o) from err
4517 objset.add(state)
4518 else:
4519 objset = None
4520
4521 # store objects whose fate has been decided
4522 processed = set()
4523
4524 # put all saves/updates into the flush context. detect top-level
4525 # orphans and throw them into deleted.
4526 if objset:
4527 proc = new.union(dirty).intersection(objset).difference(deleted)
4528 else:
4529 proc = new.union(dirty).difference(deleted)
4530
4531 for state in proc:
4532 is_orphan = _state_mapper(state)._is_orphan(state)
4533
4534 is_persistent_orphan = is_orphan and state.has_identity
4535
4536 if (
4537 is_orphan
4538 and not is_persistent_orphan
4539 and state._orphaned_outside_of_session
4540 ):
4541 self._expunge_states([state])
4542 else:
4543 _reg = flush_context.register_object(
4544 state, isdelete=is_persistent_orphan
4545 )
4546 assert _reg, "Failed to add object to the flush context!"
4547 processed.add(state)
4548
4549 # put all remaining deletes into the flush context.
4550 if objset:
4551 proc = deleted.intersection(objset).difference(processed)
4552 else:
4553 proc = deleted.difference(processed)
4554 for state in proc:
4555 _reg = flush_context.register_object(state, isdelete=True)
4556 assert _reg, "Failed to add object to the flush context!"
4557
4558 if not flush_context.has_work:
4559 return
4560
4561 flush_context.transaction = transaction = self._autobegin_t()._begin()
4562 try:
4563 self._warn_on_events = True
4564 try:
4565 flush_context.execute()
4566 finally:
4567 self._warn_on_events = False
4568
4569 self.dispatch.after_flush(self, flush_context)
4570
4571 flush_context.finalize_flush_changes()
4572
4573 if not objects and self.identity_map._modified:
4574 len_ = len(self.identity_map._modified)
4575
4576 statelib.InstanceState._commit_all_states(
4577 [
4578 (state, state.dict)
4579 for state in self.identity_map._modified
4580 ],
4581 instance_dict=self.identity_map,
4582 )
4583 util.warn(
4584 "Attribute history events accumulated on %d "
4585 "previously clean instances "
4586 "within inner-flush event handlers have been "
4587 "reset, and will not result in database updates. "
4588 "Consider using set_committed_value() within "
4589 "inner-flush event handlers to avoid this warning." % len_
4590 )
4591
4592 # useful assertions:
4593 # if not objects:
4594 # assert not self.identity_map._modified
4595 # else:
4596 # assert self.identity_map._modified == \
4597 # self.identity_map._modified.difference(objects)
4598
4599 self.dispatch.after_flush_postexec(self, flush_context)
4600
4601 transaction.commit()
4602
4603 except:
4604 with util.safe_reraise():
4605 transaction.rollback(_capture_exception=True)
4606
4607 def bulk_save_objects(
4608 self,
4609 objects: Iterable[object],
4610 return_defaults: bool = False,
4611 update_changed_only: bool = True,
4612 preserve_order: bool = True,
4613 ) -> None:
4614 """Perform a bulk save of the given list of objects.
4615
4616 .. legacy::
4617
4618 This method is a legacy feature as of the 2.0 series of
4619 SQLAlchemy. For modern bulk INSERT and UPDATE, see
4620 the sections :ref:`orm_queryguide_bulk_insert` and
4621 :ref:`orm_queryguide_bulk_update`.
4622
4623 For general INSERT and UPDATE of existing ORM mapped objects,
4624 prefer standard :term:`unit of work` data management patterns,
4625 introduced in the :ref:`unified_tutorial` at
4626 :ref:`tutorial_orm_data_manipulation`. SQLAlchemy 2.0
4627 now uses :ref:`engine_insertmanyvalues` with modern dialects
4628 which solves previous issues of bulk INSERT slowness.
4629
4630 :param objects: a sequence of mapped object instances. The mapped
4631 objects are persisted as is, and are **not** associated with the
4632 :class:`.Session` afterwards.
4633
4634 For each object, whether the object is sent as an INSERT or an
4635 UPDATE is dependent on the same rules used by the :class:`.Session`
4636 in traditional operation; if the object has the
4637 :attr:`.InstanceState.key`
4638 attribute set, then the object is assumed to be "detached" and
4639 will result in an UPDATE. Otherwise, an INSERT is used.
4640
4641 In the case of an UPDATE, statements are grouped based on which
4642 attributes have changed, and are thus to be the subject of each
4643 SET clause. If ``update_changed_only`` is False, then all
4644 attributes present within each object are applied to the UPDATE
4645 statement, which may help in allowing the statements to be grouped
4646 together into a larger executemany(), and will also reduce the
4647 overhead of checking history on attributes.
4648
4649 :param return_defaults: when True, rows that are missing values which
4650 generate defaults, namely integer primary key defaults and sequences,
4651 will be inserted **one at a time**, so that the primary key value
4652 is available. In particular this will allow joined-inheritance
4653 and other multi-table mappings to insert correctly without the need
4654 to provide primary key values ahead of time; however,
4655 :paramref:`.Session.bulk_save_objects.return_defaults` **greatly
4656 reduces the performance gains** of the method overall. It is strongly
4657 advised to please use the standard :meth:`_orm.Session.add_all`
4658 approach.
4659
4660 :param update_changed_only: when True, UPDATE statements are rendered
4661 based on those attributes in each state that have logged changes.
4662 When False, all attributes present are rendered into the SET clause
4663 with the exception of primary key attributes.
4664
4665 :param preserve_order: when True, the order of inserts and updates
4666 matches exactly the order in which the objects are given. When
4667 False, common types of objects are grouped into inserts
4668 and updates, to allow for more batching opportunities.
4669
4670 .. seealso::
4671
4672 :doc:`queryguide/dml`
4673
4674 :meth:`.Session.bulk_insert_mappings`
4675
4676 :meth:`.Session.bulk_update_mappings`
4677
4678 """
4679
4680 obj_states: Iterable[InstanceState[Any]]
4681
4682 obj_states = (attributes.instance_state(obj) for obj in objects)
4683
4684 if not preserve_order:
4685 # the purpose of this sort is just so that common mappers
4686 # and persistence states are grouped together, so that groupby
4687 # will return a single group for a particular type of mapper.
4688 # it's not trying to be deterministic beyond that.
4689 obj_states = sorted(
4690 obj_states,
4691 key=lambda state: (id(state.mapper), state.key is not None),
4692 )
4693
4694 def grouping_key(
4695 state: InstanceState[_O],
4696 ) -> Tuple[Mapper[_O], bool]:
4697 return (state.mapper, state.key is not None)
4698
4699 for (mapper, isupdate), states in itertools.groupby(
4700 obj_states, grouping_key
4701 ):
4702 self._bulk_save_mappings(
4703 mapper,
4704 states,
4705 isupdate=isupdate,
4706 isstates=True,
4707 return_defaults=return_defaults,
4708 update_changed_only=update_changed_only,
4709 render_nulls=False,
4710 )
4711
4712 def bulk_insert_mappings(
4713 self,
4714 mapper: Mapper[Any],
4715 mappings: Iterable[Dict[str, Any]],
4716 return_defaults: bool = False,
4717 render_nulls: bool = False,
4718 ) -> None:
4719 """Perform a bulk insert of the given list of mapping dictionaries.
4720
4721 .. legacy::
4722
4723 This method is a legacy feature as of the 2.0 series of
4724 SQLAlchemy. For modern bulk INSERT and UPDATE, see
4725 the sections :ref:`orm_queryguide_bulk_insert` and
4726 :ref:`orm_queryguide_bulk_update`. The 2.0 API shares
4727 implementation details with this method and adds new features
4728 as well.
4729
4730 :param mapper: a mapped class, or the actual :class:`_orm.Mapper`
4731 object,
4732 representing the single kind of object represented within the mapping
4733 list.
4734
4735 :param mappings: a sequence of dictionaries, each one containing the
4736 state of the mapped row to be inserted, in terms of the attribute
4737 names on the mapped class. If the mapping refers to multiple tables,
4738 such as a joined-inheritance mapping, each dictionary must contain all
4739 keys to be populated into all tables.
4740
4741 :param return_defaults: when True, the INSERT process will be altered
4742 to ensure that newly generated primary key values will be fetched.
4743 The rationale for this parameter is typically to enable
4744 :ref:`Joined Table Inheritance <joined_inheritance>` mappings to
4745 be bulk inserted.
4746
4747 .. note:: for backends that don't support RETURNING, the
4748 :paramref:`_orm.Session.bulk_insert_mappings.return_defaults`
4749 parameter can significantly decrease performance as INSERT
4750 statements can no longer be batched. See
4751 :ref:`engine_insertmanyvalues`
4752 for background on which backends are affected.
4753
4754 :param render_nulls: When True, a value of ``None`` will result
4755 in a NULL value being included in the INSERT statement, rather
4756 than the column being omitted from the INSERT. This allows all
4757 the rows being INSERTed to have the identical set of columns which
4758 allows the full set of rows to be batched to the DBAPI. Normally,
4759 each column-set that contains a different combination of NULL values
4760 than the previous row must omit a different series of columns from
4761 the rendered INSERT statement, which means it must be emitted as a
4762 separate statement. By passing this flag, the full set of rows
4763 are guaranteed to be batchable into one batch; the cost however is
4764 that server-side defaults which are invoked by an omitted column will
4765 be skipped, so care must be taken to ensure that these are not
4766 necessary.
4767
4768 .. warning::
4769
4770 When this flag is set, **server side default SQL values will
4771 not be invoked** for those columns that are inserted as NULL;
4772 the NULL value will be sent explicitly. Care must be taken
4773 to ensure that no server-side default functions need to be
4774 invoked for the operation as a whole.
4775
4776 .. seealso::
4777
4778 :doc:`queryguide/dml`
4779
4780 :meth:`.Session.bulk_save_objects`
4781
4782 :meth:`.Session.bulk_update_mappings`
4783
4784 """
4785 self._bulk_save_mappings(
4786 mapper,
4787 mappings,
4788 isupdate=False,
4789 isstates=False,
4790 return_defaults=return_defaults,
4791 update_changed_only=False,
4792 render_nulls=render_nulls,
4793 )
4794
4795 def bulk_update_mappings(
4796 self, mapper: Mapper[Any], mappings: Iterable[Dict[str, Any]]
4797 ) -> None:
4798 """Perform a bulk update of the given list of mapping dictionaries.
4799
4800 .. legacy::
4801
4802 This method is a legacy feature as of the 2.0 series of
4803 SQLAlchemy. For modern bulk INSERT and UPDATE, see
4804 the sections :ref:`orm_queryguide_bulk_insert` and
4805 :ref:`orm_queryguide_bulk_update`. The 2.0 API shares
4806 implementation details with this method and adds new features
4807 as well.
4808
4809 :param mapper: a mapped class, or the actual :class:`_orm.Mapper`
4810 object,
4811 representing the single kind of object represented within the mapping
4812 list.
4813
4814 :param mappings: a sequence of dictionaries, each one containing the
4815 state of the mapped row to be updated, in terms of the attribute names
4816 on the mapped class. If the mapping refers to multiple tables, such
4817 as a joined-inheritance mapping, each dictionary may contain keys
4818 corresponding to all tables. All those keys which are present and
4819 are not part of the primary key are applied to the SET clause of the
4820 UPDATE statement; the primary key values, which are required, are
4821 applied to the WHERE clause.
4822
4823
4824 .. seealso::
4825
4826 :doc:`queryguide/dml`
4827
4828 :meth:`.Session.bulk_insert_mappings`
4829
4830 :meth:`.Session.bulk_save_objects`
4831
4832 """
4833 self._bulk_save_mappings(
4834 mapper,
4835 mappings,
4836 isupdate=True,
4837 isstates=False,
4838 return_defaults=False,
4839 update_changed_only=False,
4840 render_nulls=False,
4841 )
4842
4843 def _bulk_save_mappings(
4844 self,
4845 mapper: Mapper[_O],
4846 mappings: Union[Iterable[InstanceState[_O]], Iterable[Dict[str, Any]]],
4847 *,
4848 isupdate: bool,
4849 isstates: bool,
4850 return_defaults: bool,
4851 update_changed_only: bool,
4852 render_nulls: bool,
4853 ) -> None:
4854 mapper = _class_to_mapper(mapper)
4855 self._flushing = True
4856
4857 transaction = self._autobegin_t()._begin()
4858 try:
4859 if isupdate:
4860 bulk_persistence._bulk_update(
4861 mapper,
4862 mappings,
4863 transaction,
4864 isstates=isstates,
4865 update_changed_only=update_changed_only,
4866 )
4867 else:
4868 bulk_persistence._bulk_insert(
4869 mapper,
4870 mappings,
4871 transaction,
4872 isstates=isstates,
4873 return_defaults=return_defaults,
4874 render_nulls=render_nulls,
4875 )
4876 transaction.commit()
4877
4878 except:
4879 with util.safe_reraise():
4880 transaction.rollback(_capture_exception=True)
4881 finally:
4882 self._flushing = False
4883
4884 def is_modified(
4885 self, instance: object, include_collections: bool = True
4886 ) -> bool:
4887 r"""Return ``True`` if the given instance has locally
4888 modified attributes.
4889
4890 This method retrieves the history for each instrumented
4891 attribute on the instance and performs a comparison of the current
4892 value to its previously flushed or committed value, if any.
4893
4894 It is in effect a more expensive and accurate
4895 version of checking for the given instance in the
4896 :attr:`.Session.dirty` collection; a full test for
4897 each attribute's net "dirty" status is performed.
4898
4899 E.g.::
4900
4901 return session.is_modified(someobject)
4902
4903 A few caveats to this method apply:
4904
4905 * Instances present in the :attr:`.Session.dirty` collection may
4906 report ``False`` when tested with this method. This is because
4907 the object may have received change events via attribute mutation,
4908 thus placing it in :attr:`.Session.dirty`, but ultimately the state
4909 is the same as that loaded from the database, resulting in no net
4910 change here.
4911 * Scalar attributes may not have recorded the previously set
4912 value when a new value was applied, if the attribute was not loaded,
4913 or was expired, at the time the new value was received - in these
4914 cases, the attribute is assumed to have a change, even if there is
4915 ultimately no net change against its database value. SQLAlchemy in
4916 most cases does not need the "old" value when a set event occurs, so
4917 it skips the expense of a SQL call if the old value isn't present,
4918 based on the assumption that an UPDATE of the scalar value is
4919 usually needed, and in those few cases where it isn't, is less
4920 expensive on average than issuing a defensive SELECT.
4921
4922 The "old" value is fetched unconditionally upon set only if the
4923 attribute container has the ``active_history`` flag set to ``True``.
4924 This flag is set typically for primary key attributes and scalar
4925 object references that are not a simple many-to-one. To set this
4926 flag for any arbitrary mapped column, use the ``active_history``
4927 argument with :func:`.column_property`.
4928
4929 :param instance: mapped instance to be tested for pending changes.
4930 :param include_collections: Indicates if multivalued collections
4931 should be included in the operation. Setting this to ``False`` is a
4932 way to detect only local-column based properties (i.e. scalar columns
4933 or many-to-one foreign keys) that would result in an UPDATE for this
4934 instance upon flush.
4935
4936 """
4937 state = object_state(instance)
4938
4939 if not state.modified:
4940 return False
4941
4942 dict_ = state.dict
4943
4944 for attr in state.manager.attributes:
4945 if (
4946 not include_collections
4947 and hasattr(attr.impl, "get_collection")
4948 ) or not hasattr(attr.impl, "get_history"):
4949 continue
4950
4951 added, unchanged, deleted = attr.impl.get_history(
4952 state, dict_, passive=PassiveFlag.NO_CHANGE
4953 )
4954
4955 if added or deleted:
4956 return True
4957 else:
4958 return False
4959
4960 @property
4961 def is_active(self) -> bool:
4962 """True if this :class:`.Session` not in "partial rollback" state.
4963
4964 .. versionchanged:: 1.4 The :class:`_orm.Session` no longer begins
4965 a new transaction immediately, so this attribute will be False
4966 when the :class:`_orm.Session` is first instantiated.
4967
4968 "partial rollback" state typically indicates that the flush process
4969 of the :class:`_orm.Session` has failed, and that the
4970 :meth:`_orm.Session.rollback` method must be emitted in order to
4971 fully roll back the transaction.
4972
4973 If this :class:`_orm.Session` is not in a transaction at all, the
4974 :class:`_orm.Session` will autobegin when it is first used, so in this
4975 case :attr:`_orm.Session.is_active` will return True.
4976
4977 Otherwise, if this :class:`_orm.Session` is within a transaction,
4978 and that transaction has not been rolled back internally, the
4979 :attr:`_orm.Session.is_active` will also return True.
4980
4981 .. seealso::
4982
4983 :ref:`faq_session_rollback`
4984
4985 :meth:`_orm.Session.in_transaction`
4986
4987 """
4988 return self._transaction is None or self._transaction.is_active
4989
4990 @property
4991 def _dirty_states(self) -> Iterable[InstanceState[Any]]:
4992 """The set of all persistent states considered dirty.
4993
4994 This method returns all states that were modified including
4995 those that were possibly deleted.
4996
4997 """
4998 return self.identity_map._dirty_states()
4999
5000 @property
5001 def dirty(self) -> IdentitySet:
5002 """The set of all persistent instances considered dirty.
5003
5004 E.g.::
5005
5006 some_mapped_object in session.dirty
5007
5008 Instances are considered dirty when they were modified but not
5009 deleted.
5010
5011 Note that this 'dirty' calculation is 'optimistic'; most
5012 attribute-setting or collection modification operations will
5013 mark an instance as 'dirty' and place it in this set, even if
5014 there is no net change to the attribute's value. At flush
5015 time, the value of each attribute is compared to its
5016 previously saved value, and if there's no net change, no SQL
5017 operation will occur (this is a more expensive operation so
5018 it's only done at flush time).
5019
5020 To check if an instance has actionable net changes to its
5021 attributes, use the :meth:`.Session.is_modified` method.
5022
5023 """
5024 return IdentitySet(
5025 [
5026 state.obj()
5027 for state in self._dirty_states
5028 if state not in self._deleted
5029 ]
5030 )
5031
5032 @property
5033 def deleted(self) -> IdentitySet:
5034 "The set of all instances marked as 'deleted' within this ``Session``"
5035
5036 return util.IdentitySet(list(self._deleted.values()))
5037
5038 @property
5039 def new(self) -> IdentitySet:
5040 "The set of all instances marked as 'new' within this ``Session``."
5041
5042 return util.IdentitySet(list(self._new.values()))
5043
5044
5045_S = TypeVar("_S", bound="Session")
5046
5047
5048class sessionmaker(_SessionClassMethods, Generic[_S]):
5049 """A configurable :class:`.Session` factory.
5050
5051 The :class:`.sessionmaker` factory generates new
5052 :class:`.Session` objects when called, creating them given
5053 the configurational arguments established here.
5054
5055 e.g.::
5056
5057 from sqlalchemy import create_engine
5058 from sqlalchemy.orm import sessionmaker
5059
5060 # an Engine, which the Session will use for connection
5061 # resources
5062 engine = create_engine("postgresql+psycopg2://scott:tiger@localhost/")
5063
5064 Session = sessionmaker(engine)
5065
5066 with Session() as session:
5067 session.add(some_object)
5068 session.add(some_other_object)
5069 session.commit()
5070
5071 Context manager use is optional; otherwise, the returned
5072 :class:`_orm.Session` object may be closed explicitly via the
5073 :meth:`_orm.Session.close` method. Using a
5074 ``try:/finally:`` block is optional, however will ensure that the close
5075 takes place even if there are database errors::
5076
5077 session = Session()
5078 try:
5079 session.add(some_object)
5080 session.add(some_other_object)
5081 session.commit()
5082 finally:
5083 session.close()
5084
5085 :class:`.sessionmaker` acts as a factory for :class:`_orm.Session`
5086 objects in the same way as an :class:`_engine.Engine` acts as a factory
5087 for :class:`_engine.Connection` objects. In this way it also includes
5088 a :meth:`_orm.sessionmaker.begin` method, that provides a context
5089 manager which both begins and commits a transaction, as well as closes
5090 out the :class:`_orm.Session` when complete, rolling back the transaction
5091 if any errors occur::
5092
5093 Session = sessionmaker(engine)
5094
5095 with Session.begin() as session:
5096 session.add(some_object)
5097 session.add(some_other_object)
5098 # commits transaction, closes session
5099
5100 .. versionadded:: 1.4
5101
5102 When calling upon :class:`_orm.sessionmaker` to construct a
5103 :class:`_orm.Session`, keyword arguments may also be passed to the
5104 method; these arguments will override that of the globally configured
5105 parameters. Below we use a :class:`_orm.sessionmaker` bound to a certain
5106 :class:`_engine.Engine` to produce a :class:`_orm.Session` that is instead
5107 bound to a specific :class:`_engine.Connection` procured from that engine::
5108
5109 Session = sessionmaker(engine)
5110
5111 # bind an individual session to a connection
5112
5113 with engine.connect() as connection:
5114 with Session(bind=connection) as session:
5115 ... # work with session
5116
5117 The class also includes a method :meth:`_orm.sessionmaker.configure`, which
5118 can be used to specify additional keyword arguments to the factory, which
5119 will take effect for subsequent :class:`.Session` objects generated. This
5120 is usually used to associate one or more :class:`_engine.Engine` objects
5121 with an existing
5122 :class:`.sessionmaker` factory before it is first used::
5123
5124 # application starts, sessionmaker does not have
5125 # an engine bound yet
5126 Session = sessionmaker()
5127
5128 # ... later, when an engine URL is read from a configuration
5129 # file or other events allow the engine to be created
5130 engine = create_engine("sqlite:///foo.db")
5131 Session.configure(bind=engine)
5132
5133 sess = Session()
5134 # work with session
5135
5136 .. seealso::
5137
5138 :ref:`session_getting` - introductory text on creating
5139 sessions using :class:`.sessionmaker`.
5140
5141 """
5142
5143 class_: Type[_S]
5144
5145 @overload
5146 def __init__(
5147 self,
5148 bind: Optional[_SessionBind] = ...,
5149 *,
5150 class_: Type[_S],
5151 autoflush: bool = ...,
5152 expire_on_commit: bool = ...,
5153 info: Optional[_InfoType] = ...,
5154 **kw: Any,
5155 ): ...
5156
5157 @overload
5158 def __init__(
5159 self: "sessionmaker[Session]",
5160 bind: Optional[_SessionBind] = ...,
5161 *,
5162 autoflush: bool = ...,
5163 expire_on_commit: bool = ...,
5164 info: Optional[_InfoType] = ...,
5165 **kw: Any,
5166 ): ...
5167
5168 def __init__(
5169 self,
5170 bind: Optional[_SessionBind] = None,
5171 *,
5172 class_: Type[_S] = Session, # type: ignore
5173 autoflush: bool = True,
5174 expire_on_commit: bool = True,
5175 info: Optional[_InfoType] = None,
5176 **kw: Any,
5177 ):
5178 r"""Construct a new :class:`.sessionmaker`.
5179
5180 All arguments here except for ``class_`` correspond to arguments
5181 accepted by :class:`.Session` directly. See the
5182 :meth:`.Session.__init__` docstring for more details on parameters.
5183
5184 :param bind: a :class:`_engine.Engine` or other :class:`.Connectable`
5185 with
5186 which newly created :class:`.Session` objects will be associated.
5187 :param class\_: class to use in order to create new :class:`.Session`
5188 objects. Defaults to :class:`.Session`.
5189 :param autoflush: The autoflush setting to use with newly created
5190 :class:`.Session` objects.
5191
5192 .. seealso::
5193
5194 :ref:`session_flushing` - additional background on autoflush
5195
5196 :param expire_on_commit=True: the
5197 :paramref:`_orm.Session.expire_on_commit` setting to use
5198 with newly created :class:`.Session` objects.
5199
5200 :param info: optional dictionary of information that will be available
5201 via :attr:`.Session.info`. Note this dictionary is *updated*, not
5202 replaced, when the ``info`` parameter is specified to the specific
5203 :class:`.Session` construction operation.
5204
5205 :param \**kw: all other keyword arguments are passed to the
5206 constructor of newly created :class:`.Session` objects.
5207
5208 """
5209 kw["bind"] = bind
5210 kw["autoflush"] = autoflush
5211 kw["expire_on_commit"] = expire_on_commit
5212 if info is not None:
5213 kw["info"] = info
5214 self.kw = kw
5215 # make our own subclass of the given class, so that
5216 # events can be associated with it specifically.
5217 self.class_ = type(class_.__name__, (class_,), {})
5218
5219 def begin(self) -> contextlib.AbstractContextManager[_S]:
5220 """Produce a context manager that both provides a new
5221 :class:`_orm.Session` as well as a transaction that commits.
5222
5223
5224 e.g.::
5225
5226 Session = sessionmaker(some_engine)
5227
5228 with Session.begin() as session:
5229 session.add(some_object)
5230
5231 # commits transaction, closes session
5232
5233 .. versionadded:: 1.4
5234
5235
5236 """
5237
5238 session = self()
5239 return session._maker_context_manager()
5240
5241 def __call__(self, **local_kw: Any) -> _S:
5242 """Produce a new :class:`.Session` object using the configuration
5243 established in this :class:`.sessionmaker`.
5244
5245 In Python, the ``__call__`` method is invoked on an object when
5246 it is "called" in the same way as a function::
5247
5248 Session = sessionmaker(some_engine)
5249 session = Session() # invokes sessionmaker.__call__()
5250
5251 """
5252 for k, v in self.kw.items():
5253 if k == "info" and "info" in local_kw:
5254 d = v.copy()
5255 d.update(local_kw["info"])
5256 local_kw["info"] = d
5257 else:
5258 local_kw.setdefault(k, v)
5259 return self.class_(**local_kw)
5260
5261 def configure(self, **new_kw: Any) -> None:
5262 """(Re)configure the arguments for this sessionmaker.
5263
5264 e.g.::
5265
5266 Session = sessionmaker()
5267
5268 Session.configure(bind=create_engine("sqlite://"))
5269 """
5270 self.kw.update(new_kw)
5271
5272 def __repr__(self) -> str:
5273 return "%s(class_=%r, %s)" % (
5274 self.__class__.__name__,
5275 self.class_.__name__,
5276 ", ".join("%s=%r" % (k, v) for k, v in self.kw.items()),
5277 )
5278
5279
5280def close_all_sessions() -> None:
5281 """Close all sessions in memory.
5282
5283 This function consults a global registry of all :class:`.Session` objects
5284 and calls :meth:`.Session.close` on them, which resets them to a clean
5285 state.
5286
5287 This function is not for general use but may be useful for test suites
5288 within the teardown scheme.
5289
5290 """
5291
5292 for sess in _sessions.values():
5293 sess.close()
5294
5295
5296def make_transient(instance: object) -> None:
5297 """Alter the state of the given instance so that it is :term:`transient`.
5298
5299 .. note::
5300
5301 :func:`.make_transient` is a special-case function for
5302 advanced use cases only.
5303
5304 The given mapped instance is assumed to be in the :term:`persistent` or
5305 :term:`detached` state. The function will remove its association with any
5306 :class:`.Session` as well as its :attr:`.InstanceState.identity`. The
5307 effect is that the object will behave as though it were newly constructed,
5308 except retaining any attribute / collection values that were loaded at the
5309 time of the call. The :attr:`.InstanceState.deleted` flag is also reset
5310 if this object had been deleted as a result of using
5311 :meth:`.Session.delete`.
5312
5313 .. warning::
5314
5315 :func:`.make_transient` does **not** "unexpire" or otherwise eagerly
5316 load ORM-mapped attributes that are not currently loaded at the time
5317 the function is called. This includes attributes which:
5318
5319 * were expired via :meth:`.Session.expire`
5320
5321 * were expired as the natural effect of committing a session
5322 transaction, e.g. :meth:`.Session.commit`
5323
5324 * are normally :term:`lazy loaded` but are not currently loaded
5325
5326 * are "deferred" (see :ref:`orm_queryguide_column_deferral`) and are
5327 not yet loaded
5328
5329 * were not present in the query which loaded this object, such as that
5330 which is common in joined table inheritance and other scenarios.
5331
5332 After :func:`.make_transient` is called, unloaded attributes such
5333 as those above will normally resolve to the value ``None`` when
5334 accessed, or an empty collection for a collection-oriented attribute.
5335 As the object is transient and un-associated with any database
5336 identity, it will no longer retrieve these values.
5337
5338 .. seealso::
5339
5340 :func:`.make_transient_to_detached`
5341
5342 """
5343 state = attributes.instance_state(instance)
5344 s = _state_session(state)
5345 if s:
5346 s._expunge_states([state])
5347
5348 # remove expired state
5349 state.expired_attributes.clear()
5350
5351 # remove deferred callables
5352 if state.callables:
5353 del state.callables
5354
5355 if state.key:
5356 del state.key
5357 if state._deleted:
5358 del state._deleted
5359
5360
5361def make_transient_to_detached(instance: object) -> None:
5362 """Make the given transient instance :term:`detached`.
5363
5364 .. note::
5365
5366 :func:`.make_transient_to_detached` is a special-case function for
5367 advanced use cases only.
5368
5369 All attribute history on the given instance
5370 will be reset as though the instance were freshly loaded
5371 from a query. Missing attributes will be marked as expired.
5372 The primary key attributes of the object, which are required, will be made
5373 into the "key" of the instance.
5374
5375 The object can then be added to a session, or merged
5376 possibly with the load=False flag, at which point it will look
5377 as if it were loaded that way, without emitting SQL.
5378
5379 This is a special use case function that differs from a normal
5380 call to :meth:`.Session.merge` in that a given persistent state
5381 can be manufactured without any SQL calls.
5382
5383 .. seealso::
5384
5385 :func:`.make_transient`
5386
5387 :meth:`.Session.enable_relationship_loading`
5388
5389 """
5390 state = attributes.instance_state(instance)
5391 if state.session_id or state.key:
5392 raise sa_exc.InvalidRequestError("Given object must be transient")
5393 state.key = state.mapper._identity_key_from_state(state)
5394 if state._deleted:
5395 del state._deleted
5396 state._commit_all(state.dict)
5397 state._expire_attributes(state.dict, state.unloaded)
5398
5399
5400def object_session(instance: object) -> Optional[Session]:
5401 """Return the :class:`.Session` to which the given instance belongs.
5402
5403 This is essentially the same as the :attr:`.InstanceState.session`
5404 accessor. See that attribute for details.
5405
5406 """
5407
5408 try:
5409 state = attributes.instance_state(instance)
5410 except exc.NO_STATE as err:
5411 raise exc.UnmappedInstanceError(instance) from err
5412 else:
5413 return _state_session(state)
5414
5415
5416_new_sessionid = util.counter()