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