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