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