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