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