Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/sqlalchemy/orm/session.py: 19%
1198 statements
« prev ^ index » next coverage.py v7.0.1, created at 2022-12-25 06:11 +0000
« prev ^ index » next coverage.py v7.0.1, created at 2022-12-25 06:11 +0000
1# orm/session.py
2# Copyright (C) 2005-2022 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"""Provides the Session class and related utilities."""
10import itertools
11import sys
12import weakref
14from . import attributes
15from . import context
16from . import exc
17from . import identity
18from . import loading
19from . import persistence
20from . import query
21from . import state as statelib
22from .base import _class_to_mapper
23from .base import _none_set
24from .base import _state_mapper
25from .base import instance_str
26from .base import object_mapper
27from .base import object_state
28from .base import state_str
29from .unitofwork import UOWTransaction
30from .. import engine
31from .. import exc as sa_exc
32from .. import sql
33from .. import util
34from ..engine.util import TransactionalContext
35from ..inspection import inspect
36from ..sql import coercions
37from ..sql import dml
38from ..sql import roles
39from ..sql import visitors
40from ..sql.base import CompileState
41from ..sql.selectable import LABEL_STYLE_TABLENAME_PLUS_COL
43__all__ = [
44 "Session",
45 "SessionTransaction",
46 "sessionmaker",
47 "ORMExecuteState",
48 "close_all_sessions",
49 "make_transient",
50 "make_transient_to_detached",
51 "object_session",
52]
54_sessions = weakref.WeakValueDictionary()
55"""Weak-referencing dictionary of :class:`.Session` objects.
56"""
58statelib._sessions = _sessions
61def _state_session(state):
62 """Given an :class:`.InstanceState`, return the :class:`.Session`
63 associated, if any.
64 """
65 return state.session
68class _SessionClassMethods(object):
69 """Class-level methods for :class:`.Session`, :class:`.sessionmaker`."""
71 @classmethod
72 @util.deprecated(
73 "1.3",
74 "The :meth:`.Session.close_all` method is deprecated and will be "
75 "removed in a future release. Please refer to "
76 ":func:`.session.close_all_sessions`.",
77 )
78 def close_all(cls):
79 """Close *all* sessions in memory."""
81 close_all_sessions()
83 @classmethod
84 @util.preload_module("sqlalchemy.orm.util")
85 def identity_key(cls, *args, **kwargs):
86 """Return an identity key.
88 This is an alias of :func:`.util.identity_key`.
90 """
91 return util.preloaded.orm_util.identity_key(*args, **kwargs)
93 @classmethod
94 def object_session(cls, instance):
95 """Return the :class:`.Session` to which an object belongs.
97 This is an alias of :func:`.object_session`.
99 """
101 return object_session(instance)
104ACTIVE = util.symbol("ACTIVE")
105PREPARED = util.symbol("PREPARED")
106COMMITTED = util.symbol("COMMITTED")
107DEACTIVE = util.symbol("DEACTIVE")
108CLOSED = util.symbol("CLOSED")
111class ORMExecuteState(util.MemoizedSlots):
112 """Represents a call to the :meth:`_orm.Session.execute` method, as passed
113 to the :meth:`.SessionEvents.do_orm_execute` event hook.
115 .. versionadded:: 1.4
117 .. seealso::
119 :ref:`session_execute_events` - top level documentation on how
120 to use :meth:`_orm.SessionEvents.do_orm_execute`
122 """
124 __slots__ = (
125 "session",
126 "statement",
127 "parameters",
128 "execution_options",
129 "local_execution_options",
130 "bind_arguments",
131 "_compile_state_cls",
132 "_starting_event_idx",
133 "_events_todo",
134 "_update_execution_options",
135 )
137 def __init__(
138 self,
139 session,
140 statement,
141 parameters,
142 execution_options,
143 bind_arguments,
144 compile_state_cls,
145 events_todo,
146 ):
147 self.session = session
148 self.statement = statement
149 self.parameters = parameters
150 self.local_execution_options = execution_options
151 self.execution_options = statement._execution_options.union(
152 execution_options
153 )
154 self.bind_arguments = bind_arguments
155 self._compile_state_cls = compile_state_cls
156 self._events_todo = list(events_todo)
158 def _remaining_events(self):
159 return self._events_todo[self._starting_event_idx + 1 :]
161 def invoke_statement(
162 self,
163 statement=None,
164 params=None,
165 execution_options=None,
166 bind_arguments=None,
167 ):
168 """Execute the statement represented by this
169 :class:`.ORMExecuteState`, without re-invoking events that have
170 already proceeded.
172 This method essentially performs a re-entrant execution of the current
173 statement for which the :meth:`.SessionEvents.do_orm_execute` event is
174 being currently invoked. The use case for this is for event handlers
175 that want to override how the ultimate
176 :class:`_engine.Result` object is returned, such as for schemes that
177 retrieve results from an offline cache or which concatenate results
178 from multiple executions.
180 When the :class:`_engine.Result` object is returned by the actual
181 handler function within :meth:`_orm.SessionEvents.do_orm_execute` and
182 is propagated to the calling
183 :meth:`_orm.Session.execute` method, the remainder of the
184 :meth:`_orm.Session.execute` method is preempted and the
185 :class:`_engine.Result` object is returned to the caller of
186 :meth:`_orm.Session.execute` immediately.
188 :param statement: optional statement to be invoked, in place of the
189 statement currently represented by :attr:`.ORMExecuteState.statement`.
191 :param params: optional dictionary of parameters which will be merged
192 into the existing :attr:`.ORMExecuteState.parameters` of this
193 :class:`.ORMExecuteState`.
195 :param execution_options: optional dictionary of execution options
196 will be merged into the existing
197 :attr:`.ORMExecuteState.execution_options` of this
198 :class:`.ORMExecuteState`.
200 :param bind_arguments: optional dictionary of bind_arguments
201 which will be merged amongst the current
202 :attr:`.ORMExecuteState.bind_arguments`
203 of this :class:`.ORMExecuteState`.
205 :return: a :class:`_engine.Result` object with ORM-level results.
207 .. seealso::
209 :ref:`do_orm_execute_re_executing` - background and examples on the
210 appropriate usage of :meth:`_orm.ORMExecuteState.invoke_statement`.
213 """
215 if statement is None:
216 statement = self.statement
218 _bind_arguments = dict(self.bind_arguments)
219 if bind_arguments:
220 _bind_arguments.update(bind_arguments)
221 _bind_arguments["_sa_skip_events"] = True
223 if params:
224 _params = dict(self.parameters)
225 _params.update(params)
226 else:
227 _params = self.parameters
229 _execution_options = self.local_execution_options
230 if execution_options:
231 _execution_options = _execution_options.union(execution_options)
233 return self.session.execute(
234 statement,
235 _params,
236 _execution_options,
237 _bind_arguments,
238 _parent_execute_state=self,
239 )
241 @property
242 def bind_mapper(self):
243 """Return the :class:`_orm.Mapper` that is the primary "bind" mapper.
245 For an :class:`_orm.ORMExecuteState` object invoking an ORM
246 statement, that is, the :attr:`_orm.ORMExecuteState.is_orm_statement`
247 attribute is ``True``, this attribute will return the
248 :class:`_orm.Mapper` that is considered to be the "primary" mapper
249 of the statement. The term "bind mapper" refers to the fact that
250 a :class:`_orm.Session` object may be "bound" to multiple
251 :class:`_engine.Engine` objects keyed to mapped classes, and the
252 "bind mapper" determines which of those :class:`_engine.Engine` objects
253 would be selected.
255 For a statement that is invoked against a single mapped class,
256 :attr:`_orm.ORMExecuteState.bind_mapper` is intended to be a reliable
257 way of getting this mapper.
259 .. versionadded:: 1.4.0b2
261 .. seealso::
263 :attr:`_orm.ORMExecuteState.all_mappers`
266 """
267 return self.bind_arguments.get("mapper", None)
269 @property
270 def all_mappers(self):
271 """Return a sequence of all :class:`_orm.Mapper` objects that are
272 involved at the top level of this statement.
274 By "top level" we mean those :class:`_orm.Mapper` objects that would
275 be represented in the result set rows for a :func:`_sql.select`
276 query, or for a :func:`_dml.update` or :func:`_dml.delete` query,
277 the mapper that is the main subject of the UPDATE or DELETE.
279 .. versionadded:: 1.4.0b2
281 .. seealso::
283 :attr:`_orm.ORMExecuteState.bind_mapper`
287 """
288 if not self.is_orm_statement:
289 return []
290 elif self.is_select:
291 result = []
292 seen = set()
293 for d in self.statement.column_descriptions:
294 ent = d["entity"]
295 if ent:
296 insp = inspect(ent, raiseerr=False)
297 if insp and insp.mapper and insp.mapper not in seen:
298 seen.add(insp.mapper)
299 result.append(insp.mapper)
300 return result
301 elif self.is_update or self.is_delete:
302 return [self.bind_mapper]
303 else:
304 return []
306 @property
307 def is_orm_statement(self):
308 """return True if the operation is an ORM statement.
310 This indicates that the select(), update(), or delete() being
311 invoked contains ORM entities as subjects. For a statement
312 that does not have ORM entities and instead refers only to
313 :class:`.Table` metadata, it is invoked as a Core SQL statement
314 and no ORM-level automation takes place.
316 """
317 return self._compile_state_cls is not None
319 @property
320 def is_select(self):
321 """return True if this is a SELECT operation."""
322 return self.statement.is_select
324 @property
325 def is_insert(self):
326 """return True if this is an INSERT operation."""
327 return self.statement.is_dml and self.statement.is_insert
329 @property
330 def is_update(self):
331 """return True if this is an UPDATE operation."""
332 return self.statement.is_dml and self.statement.is_update
334 @property
335 def is_delete(self):
336 """return True if this is a DELETE operation."""
337 return self.statement.is_dml and self.statement.is_delete
339 @property
340 def _is_crud(self):
341 return isinstance(self.statement, (dml.Update, dml.Delete))
343 def update_execution_options(self, **opts):
344 # TODO: no coverage
345 self.local_execution_options = self.local_execution_options.union(opts)
347 def _orm_compile_options(self):
348 if not self.is_select:
349 return None
350 opts = self.statement._compile_options
351 if opts.isinstance(context.ORMCompileState.default_compile_options):
352 return opts
353 else:
354 return None
356 @property
357 def lazy_loaded_from(self):
358 """An :class:`.InstanceState` that is using this statement execution
359 for a lazy load operation.
361 The primary rationale for this attribute is to support the horizontal
362 sharding extension, where it is available within specific query
363 execution time hooks created by this extension. To that end, the
364 attribute is only intended to be meaningful at **query execution
365 time**, and importantly not any time prior to that, including query
366 compilation time.
368 """
369 return self.load_options._lazy_loaded_from
371 @property
372 def loader_strategy_path(self):
373 """Return the :class:`.PathRegistry` for the current load path.
375 This object represents the "path" in a query along relationships
376 when a particular object or collection is being loaded.
378 """
379 opts = self._orm_compile_options()
380 if opts is not None:
381 return opts._current_path
382 else:
383 return None
385 @property
386 def is_column_load(self):
387 """Return True if the operation is refreshing column-oriented
388 attributes on an existing ORM object.
390 This occurs during operations such as :meth:`_orm.Session.refresh`,
391 as well as when an attribute deferred by :func:`_orm.defer` is
392 being loaded, or an attribute that was expired either directly
393 by :meth:`_orm.Session.expire` or via a commit operation is being
394 loaded.
396 Handlers will very likely not want to add any options to queries
397 when such an operation is occurring as the query should be a straight
398 primary key fetch which should not have any additional WHERE criteria,
399 and loader options travelling with the instance
400 will have already been added to the query.
402 .. versionadded:: 1.4.0b2
404 .. seealso::
406 :attr:`_orm.ORMExecuteState.is_relationship_load`
408 """
409 opts = self._orm_compile_options()
410 return opts is not None and opts._for_refresh_state
412 @property
413 def is_relationship_load(self):
414 """Return True if this load is loading objects on behalf of a
415 relationship.
417 This means, the loader in effect is either a LazyLoader,
418 SelectInLoader, SubqueryLoader, or similar, and the entire
419 SELECT statement being emitted is on behalf of a relationship
420 load.
422 Handlers will very likely not want to add any options to queries
423 when such an operation is occurring, as loader options are already
424 capable of being propagated to relationship loaders and should
425 be already present.
427 .. seealso::
429 :attr:`_orm.ORMExecuteState.is_column_load`
431 """
432 opts = self._orm_compile_options()
433 if opts is None:
434 return False
435 path = self.loader_strategy_path
436 return path is not None and not path.is_root
438 @property
439 def load_options(self):
440 """Return the load_options that will be used for this execution."""
442 if not self.is_select:
443 raise sa_exc.InvalidRequestError(
444 "This ORM execution is not against a SELECT statement "
445 "so there are no load options."
446 )
447 return self.execution_options.get(
448 "_sa_orm_load_options", context.QueryContext.default_load_options
449 )
451 @property
452 def update_delete_options(self):
453 """Return the update_delete_options that will be used for this
454 execution."""
456 if not self._is_crud:
457 raise sa_exc.InvalidRequestError(
458 "This ORM execution is not against an UPDATE or DELETE "
459 "statement so there are no update options."
460 )
461 return self.execution_options.get(
462 "_sa_orm_update_options",
463 persistence.BulkUDCompileState.default_update_options,
464 )
466 @property
467 def user_defined_options(self):
468 """The sequence of :class:`.UserDefinedOptions` that have been
469 associated with the statement being invoked.
471 """
472 return [
473 opt
474 for opt in self.statement._with_options
475 if not opt._is_compile_state and not opt._is_legacy_option
476 ]
479class SessionTransaction(TransactionalContext):
480 """A :class:`.Session`-level transaction.
482 :class:`.SessionTransaction` is produced from the
483 :meth:`_orm.Session.begin`
484 and :meth:`_orm.Session.begin_nested` methods. It's largely an internal
485 object that in modern use provides a context manager for session
486 transactions.
488 Documentation on interacting with :class:`_orm.SessionTransaction` is
489 at: :ref:`unitofwork_transaction`.
492 .. versionchanged:: 1.4 The scoping and API methods to work with the
493 :class:`_orm.SessionTransaction` object directly have been simplified.
495 .. seealso::
497 :ref:`unitofwork_transaction`
499 :meth:`.Session.begin`
501 :meth:`.Session.begin_nested`
503 :meth:`.Session.rollback`
505 :meth:`.Session.commit`
507 :meth:`.Session.in_transaction`
509 :meth:`.Session.in_nested_transaction`
511 :meth:`.Session.get_transaction`
513 :meth:`.Session.get_nested_transaction`
516 """
518 _rollback_exception = None
520 def __init__(
521 self,
522 session,
523 parent=None,
524 nested=False,
525 autobegin=False,
526 ):
527 TransactionalContext._trans_ctx_check(session)
529 self.session = session
530 self._connections = {}
531 self._parent = parent
532 self.nested = nested
533 if nested:
534 self._previous_nested_transaction = session._nested_transaction
535 self._state = ACTIVE
536 if not parent and nested:
537 raise sa_exc.InvalidRequestError(
538 "Can't start a SAVEPOINT transaction when no existing "
539 "transaction is in progress"
540 )
542 self._take_snapshot(autobegin=autobegin)
544 # make sure transaction is assigned before we call the
545 # dispatch
546 self.session._transaction = self
548 self.session.dispatch.after_transaction_create(self.session, self)
550 @property
551 def parent(self):
552 """The parent :class:`.SessionTransaction` of this
553 :class:`.SessionTransaction`.
555 If this attribute is ``None``, indicates this
556 :class:`.SessionTransaction` is at the top of the stack, and
557 corresponds to a real "COMMIT"/"ROLLBACK"
558 block. If non-``None``, then this is either a "subtransaction"
559 or a "nested" / SAVEPOINT transaction. If the
560 :attr:`.SessionTransaction.nested` attribute is ``True``, then
561 this is a SAVEPOINT, and if ``False``, indicates this a subtransaction.
563 .. versionadded:: 1.0.16 - use ._parent for previous versions
565 """
566 return self._parent
568 nested = False
569 """Indicates if this is a nested, or SAVEPOINT, transaction.
571 When :attr:`.SessionTransaction.nested` is True, it is expected
572 that :attr:`.SessionTransaction.parent` will be True as well.
574 """
576 @property
577 def is_active(self):
578 return self.session is not None and self._state is ACTIVE
580 def _assert_active(
581 self,
582 prepared_ok=False,
583 rollback_ok=False,
584 deactive_ok=False,
585 closed_msg="This transaction is closed",
586 ):
587 if self._state is COMMITTED:
588 raise sa_exc.InvalidRequestError(
589 "This session is in 'committed' state; no further "
590 "SQL can be emitted within this transaction."
591 )
592 elif self._state is PREPARED:
593 if not prepared_ok:
594 raise sa_exc.InvalidRequestError(
595 "This session is in 'prepared' state; no further "
596 "SQL can be emitted within this transaction."
597 )
598 elif self._state is DEACTIVE:
599 if not deactive_ok and not rollback_ok:
600 if self._rollback_exception:
601 raise sa_exc.PendingRollbackError(
602 "This Session's transaction has been rolled back "
603 "due to a previous exception during flush."
604 " To begin a new transaction with this Session, "
605 "first issue Session.rollback()."
606 " Original exception was: %s"
607 % self._rollback_exception,
608 code="7s2a",
609 )
610 elif not deactive_ok:
611 raise sa_exc.InvalidRequestError(
612 "This session is in 'inactive' state, due to the "
613 "SQL transaction being rolled back; no further "
614 "SQL can be emitted within this transaction."
615 )
616 elif self._state is CLOSED:
617 raise sa_exc.ResourceClosedError(closed_msg)
619 @property
620 def _is_transaction_boundary(self):
621 return self.nested or not self._parent
623 def connection(self, bindkey, execution_options=None, **kwargs):
624 self._assert_active()
625 bind = self.session.get_bind(bindkey, **kwargs)
626 return self._connection_for_bind(bind, execution_options)
628 def _begin(self, nested=False):
629 self._assert_active()
630 return SessionTransaction(self.session, self, nested=nested)
632 def _iterate_self_and_parents(self, upto=None):
634 current = self
635 result = ()
636 while current:
637 result += (current,)
638 if current._parent is upto:
639 break
640 elif current._parent is None:
641 raise sa_exc.InvalidRequestError(
642 "Transaction %s is not on the active transaction list"
643 % (upto)
644 )
645 else:
646 current = current._parent
648 return result
650 def _take_snapshot(self, autobegin=False):
651 if not self._is_transaction_boundary:
652 self._new = self._parent._new
653 self._deleted = self._parent._deleted
654 self._dirty = self._parent._dirty
655 self._key_switches = self._parent._key_switches
656 return
658 if not autobegin and not self.session._flushing:
659 self.session.flush()
661 self._new = weakref.WeakKeyDictionary()
662 self._deleted = weakref.WeakKeyDictionary()
663 self._dirty = weakref.WeakKeyDictionary()
664 self._key_switches = weakref.WeakKeyDictionary()
666 def _restore_snapshot(self, dirty_only=False):
667 """Restore the restoration state taken before a transaction began.
669 Corresponds to a rollback.
671 """
672 assert self._is_transaction_boundary
674 to_expunge = set(self._new).union(self.session._new)
675 self.session._expunge_states(to_expunge, to_transient=True)
677 for s, (oldkey, newkey) in self._key_switches.items():
678 # we probably can do this conditionally based on
679 # if we expunged or not, but safe_discard does that anyway
680 self.session.identity_map.safe_discard(s)
682 # restore the old key
683 s.key = oldkey
685 # now restore the object, but only if we didn't expunge
686 if s not in to_expunge:
687 self.session.identity_map.replace(s)
689 for s in set(self._deleted).union(self.session._deleted):
690 self.session._update_impl(s, revert_deletion=True)
692 assert not self.session._deleted
694 for s in self.session.identity_map.all_states():
695 if not dirty_only or s.modified or s in self._dirty:
696 s._expire(s.dict, self.session.identity_map._modified)
698 def _remove_snapshot(self):
699 """Remove the restoration state taken before a transaction began.
701 Corresponds to a commit.
703 """
704 assert self._is_transaction_boundary
706 if not self.nested and self.session.expire_on_commit:
707 for s in self.session.identity_map.all_states():
708 s._expire(s.dict, self.session.identity_map._modified)
710 statelib.InstanceState._detach_states(
711 list(self._deleted), self.session
712 )
713 self._deleted.clear()
714 elif self.nested:
715 self._parent._new.update(self._new)
716 self._parent._dirty.update(self._dirty)
717 self._parent._deleted.update(self._deleted)
718 self._parent._key_switches.update(self._key_switches)
720 def _connection_for_bind(self, bind, execution_options):
721 self._assert_active()
723 if bind in self._connections:
724 if execution_options:
725 util.warn(
726 "Connection is already established for the "
727 "given bind; execution_options ignored"
728 )
729 return self._connections[bind][0]
731 local_connect = False
732 should_commit = True
734 if self._parent:
735 conn = self._parent._connection_for_bind(bind, execution_options)
736 if not self.nested:
737 return conn
738 else:
739 if isinstance(bind, engine.Connection):
740 conn = bind
741 if conn.engine in self._connections:
742 raise sa_exc.InvalidRequestError(
743 "Session already has a Connection associated for the "
744 "given Connection's Engine"
745 )
746 else:
747 conn = bind.connect()
748 local_connect = True
750 try:
751 if execution_options:
752 conn = conn.execution_options(**execution_options)
754 if self.session.twophase and self._parent is None:
755 transaction = conn.begin_twophase()
756 elif self.nested:
757 transaction = conn.begin_nested()
758 elif conn.in_transaction():
759 # if given a future connection already in a transaction, don't
760 # commit that transaction unless it is a savepoint
761 if conn.in_nested_transaction():
762 transaction = conn.get_nested_transaction()
763 else:
764 transaction = conn.get_transaction()
765 should_commit = False
766 else:
767 transaction = conn.begin()
768 except:
769 # connection will not not be associated with this Session;
770 # close it immediately so that it isn't closed under GC
771 if local_connect:
772 conn.close()
773 raise
774 else:
775 bind_is_connection = isinstance(bind, engine.Connection)
777 self._connections[conn] = self._connections[conn.engine] = (
778 conn,
779 transaction,
780 should_commit,
781 not bind_is_connection,
782 )
783 self.session.dispatch.after_begin(self.session, self, conn)
784 return conn
786 def prepare(self):
787 if self._parent is not None or not self.session.twophase:
788 raise sa_exc.InvalidRequestError(
789 "'twophase' mode not enabled, or not root transaction; "
790 "can't prepare."
791 )
792 self._prepare_impl()
794 def _prepare_impl(self):
795 self._assert_active()
796 if self._parent is None or self.nested:
797 self.session.dispatch.before_commit(self.session)
799 stx = self.session._transaction
800 if stx is not self:
801 for subtransaction in stx._iterate_self_and_parents(upto=self):
802 subtransaction.commit()
804 if not self.session._flushing:
805 for _flush_guard in range(100):
806 if self.session._is_clean():
807 break
808 self.session.flush()
809 else:
810 raise exc.FlushError(
811 "Over 100 subsequent flushes have occurred within "
812 "session.commit() - is an after_flush() hook "
813 "creating new objects?"
814 )
816 if self._parent is None and self.session.twophase:
817 try:
818 for t in set(self._connections.values()):
819 t[1].prepare()
820 except:
821 with util.safe_reraise():
822 self.rollback()
824 self._state = PREPARED
826 def commit(self, _to_root=False):
827 self._assert_active(prepared_ok=True)
828 if self._state is not PREPARED:
829 self._prepare_impl()
831 if self._parent is None or self.nested:
832 for conn, trans, should_commit, autoclose in set(
833 self._connections.values()
834 ):
835 if should_commit:
836 trans.commit()
838 self._state = COMMITTED
839 self.session.dispatch.after_commit(self.session)
841 self._remove_snapshot()
843 self.close()
845 if _to_root and self._parent:
846 return self._parent.commit(_to_root=True)
848 return self._parent
850 def rollback(self, _capture_exception=False, _to_root=False):
851 self._assert_active(prepared_ok=True, rollback_ok=True)
853 stx = self.session._transaction
854 if stx is not self:
855 for subtransaction in stx._iterate_self_and_parents(upto=self):
856 subtransaction.close()
858 boundary = self
859 rollback_err = None
860 if self._state in (ACTIVE, PREPARED):
861 for transaction in self._iterate_self_and_parents():
862 if transaction._parent is None or transaction.nested:
863 try:
864 for t in set(transaction._connections.values()):
865 t[1].rollback()
867 transaction._state = DEACTIVE
868 self.session.dispatch.after_rollback(self.session)
869 except:
870 rollback_err = sys.exc_info()
871 finally:
872 transaction._state = DEACTIVE
873 transaction._restore_snapshot(
874 dirty_only=transaction.nested
875 )
876 boundary = transaction
877 break
878 else:
879 transaction._state = DEACTIVE
881 sess = self.session
883 if not rollback_err and not sess._is_clean():
885 # if items were added, deleted, or mutated
886 # here, we need to re-restore the snapshot
887 util.warn(
888 "Session's state has been changed on "
889 "a non-active transaction - this state "
890 "will be discarded."
891 )
892 boundary._restore_snapshot(dirty_only=boundary.nested)
894 self.close()
896 if self._parent and _capture_exception:
897 self._parent._rollback_exception = sys.exc_info()[1]
899 if rollback_err:
900 util.raise_(rollback_err[1], with_traceback=rollback_err[2])
902 sess.dispatch.after_soft_rollback(sess, self)
904 if _to_root and self._parent:
905 return self._parent.rollback(_to_root=True)
906 return self._parent
908 def close(self, invalidate=False):
909 if self.nested:
910 self.session._nested_transaction = (
911 self._previous_nested_transaction
912 )
914 self.session._transaction = self._parent
916 if self._parent is None:
917 for connection, transaction, should_commit, autoclose in set(
918 self._connections.values()
919 ):
920 if invalidate:
921 connection.invalidate()
922 if should_commit and transaction.is_active:
923 transaction.close()
924 if autoclose:
925 connection.close()
927 self._state = CLOSED
928 self.session.dispatch.after_transaction_end(self.session, self)
930 self.session = None
931 self._connections = None
933 def _get_subject(self):
934 return self.session
936 def _transaction_is_active(self):
937 return self._state is ACTIVE
939 def _transaction_is_closed(self):
940 return self._state is CLOSED
942 def _rollback_can_be_called(self):
943 return self._state not in (COMMITTED, CLOSED)
946class Session(_SessionClassMethods):
947 """Manages persistence operations for ORM-mapped objects.
949 The Session's usage paradigm is described at :doc:`/orm/session`.
952 """
954 _is_asyncio = False
956 @util.deprecated_params(
957 autocommit=(
958 "2.0",
959 "The :paramref:`.Session.autocommit` parameter is deprecated "
960 "and will be removed in SQLAlchemy version 2.0. The "
961 ':class:`_orm.Session` now features "autobegin" behavior '
962 "such that the :meth:`.Session.begin` method may be called "
963 "if a transaction has not yet been started yet. See the section "
964 ":ref:`session_explicit_begin` for background.",
965 ),
966 )
967 def __init__(
968 self,
969 bind=None,
970 autoflush=True,
971 future=False,
972 expire_on_commit=True,
973 autocommit=False,
974 twophase=False,
975 binds=None,
976 enable_baked_queries=True,
977 info=None,
978 query_cls=None,
979 ):
980 r"""Construct a new Session.
982 See also the :class:`.sessionmaker` function which is used to
983 generate a :class:`.Session`-producing callable with a given
984 set of arguments.
986 :param autocommit:
987 Defaults to ``False``. When ``True``, the
988 :class:`.Session` does not automatically begin transactions for
989 individual statement executions, will acquire connections from the
990 engine on an as-needed basis, releasing to the connection pool
991 after each statement. Flushes will begin and commit (or possibly
992 rollback) their own transaction if no transaction is present.
993 When using this mode, the
994 :meth:`.Session.begin` method may be used to explicitly start
995 transactions, but the usual "autobegin" behavior is not present.
997 :param autoflush: When ``True``, all query operations will issue a
998 :meth:`~.Session.flush` call to this ``Session`` before proceeding.
999 This is a convenience feature so that :meth:`~.Session.flush` need
1000 not be called repeatedly in order for database queries to retrieve
1001 results. It's typical that ``autoflush`` is used in conjunction
1002 with ``autocommit=False``. In this scenario, explicit calls to
1003 :meth:`~.Session.flush` are rarely needed; you usually only need to
1004 call :meth:`~.Session.commit` (which flushes) to finalize changes.
1006 .. seealso::
1008 :ref:`session_flushing` - additional background on autoflush
1010 :param bind: An optional :class:`_engine.Engine` or
1011 :class:`_engine.Connection` to
1012 which this ``Session`` should be bound. When specified, all SQL
1013 operations performed by this session will execute via this
1014 connectable.
1016 :param binds: A dictionary which may specify any number of
1017 :class:`_engine.Engine` or :class:`_engine.Connection`
1018 objects as the source of
1019 connectivity for SQL operations on a per-entity basis. The keys
1020 of the dictionary consist of any series of mapped classes,
1021 arbitrary Python classes that are bases for mapped classes,
1022 :class:`_schema.Table` objects and :class:`_orm.Mapper` objects.
1023 The
1024 values of the dictionary are then instances of
1025 :class:`_engine.Engine`
1026 or less commonly :class:`_engine.Connection` objects.
1027 Operations which
1028 proceed relative to a particular mapped class will consult this
1029 dictionary for the closest matching entity in order to determine
1030 which :class:`_engine.Engine` should be used for a particular SQL
1031 operation. The complete heuristics for resolution are
1032 described at :meth:`.Session.get_bind`. Usage looks like::
1034 Session = sessionmaker(binds={
1035 SomeMappedClass: create_engine('postgresql://engine1'),
1036 SomeDeclarativeBase: create_engine('postgresql://engine2'),
1037 some_mapper: create_engine('postgresql://engine3'),
1038 some_table: create_engine('postgresql://engine4'),
1039 })
1041 .. seealso::
1043 :ref:`session_partitioning`
1045 :meth:`.Session.bind_mapper`
1047 :meth:`.Session.bind_table`
1049 :meth:`.Session.get_bind`
1052 :param \class_: Specify an alternate class other than
1053 ``sqlalchemy.orm.session.Session`` which should be used by the
1054 returned class. This is the only argument that is local to the
1055 :class:`.sessionmaker` function, and is not sent directly to the
1056 constructor for ``Session``.
1058 :param enable_baked_queries: defaults to ``True``. A flag consumed
1059 by the :mod:`sqlalchemy.ext.baked` extension to determine if
1060 "baked queries" should be cached, as is the normal operation
1061 of this extension. When set to ``False``, caching as used by
1062 this particular extension is disabled.
1064 .. versionchanged:: 1.4 The ``sqlalchemy.ext.baked`` extension is
1065 legacy and is not used by any of SQLAlchemy's internals. This
1066 flag therefore only affects applications that are making explicit
1067 use of this extension within their own code.
1069 :param expire_on_commit: Defaults to ``True``. When ``True``, all
1070 instances will be fully expired after each :meth:`~.commit`,
1071 so that all attribute/object access subsequent to a completed
1072 transaction will load from the most recent database state.
1074 .. seealso::
1076 :ref:`session_committing`
1078 :param future: if True, use 2.0 style transactional and engine
1079 behavior. Future mode includes the following behaviors:
1081 * The :class:`_orm.Session` will not use "bound" metadata in order
1082 to locate an :class:`_engine.Engine`; the engine or engines in use
1083 must be specified to the constructor of :class:`_orm.Session` or
1084 otherwise be configured against the :class:`_orm.sessionmaker`
1085 in use
1087 * The "subtransactions" feature of :meth:`_orm.Session.begin` is
1088 removed in version 2.0 and is disabled when the future flag is
1089 set.
1091 * The behavior of the :paramref:`_orm.relationship.cascade_backrefs`
1092 flag on a :func:`_orm.relationship` will always assume
1093 "False" behavior.
1095 .. versionadded:: 1.4
1097 .. seealso::
1099 :ref:`migration_20_toplevel`
1101 :param info: optional dictionary of arbitrary data to be associated
1102 with this :class:`.Session`. Is available via the
1103 :attr:`.Session.info` attribute. Note the dictionary is copied at
1104 construction time so that modifications to the per-
1105 :class:`.Session` dictionary will be local to that
1106 :class:`.Session`.
1108 :param query_cls: Class which should be used to create new Query
1109 objects, as returned by the :meth:`~.Session.query` method.
1110 Defaults to :class:`_query.Query`.
1112 :param twophase: When ``True``, all transactions will be started as
1113 a "two phase" transaction, i.e. using the "two phase" semantics
1114 of the database in use along with an XID. During a
1115 :meth:`~.commit`, after :meth:`~.flush` has been issued for all
1116 attached databases, the :meth:`~.TwoPhaseTransaction.prepare`
1117 method on each database's :class:`.TwoPhaseTransaction` will be
1118 called. This allows each database to roll back the entire
1119 transaction, before each transaction is committed.
1121 """
1122 self.identity_map = identity.WeakInstanceDict()
1124 self._new = {} # InstanceState->object, strong refs object
1125 self._deleted = {} # same
1126 self.bind = bind
1127 self.__binds = {}
1128 self._flushing = False
1129 self._warn_on_events = False
1130 self._transaction = None
1131 self._nested_transaction = None
1132 self.future = future
1133 self.hash_key = _new_sessionid()
1134 self.autoflush = autoflush
1135 self.expire_on_commit = expire_on_commit
1136 self.enable_baked_queries = enable_baked_queries
1138 if autocommit:
1139 if future:
1140 raise sa_exc.ArgumentError(
1141 "Cannot use autocommit mode with future=True."
1142 )
1143 self.autocommit = True
1144 else:
1145 self.autocommit = False
1147 self.twophase = twophase
1148 self._query_cls = query_cls if query_cls else query.Query
1149 if info:
1150 self.info.update(info)
1152 if binds is not None:
1153 for key, bind in binds.items():
1154 self._add_bind(key, bind)
1156 _sessions[self.hash_key] = self
1158 # used by sqlalchemy.engine.util.TransactionalContext
1159 _trans_context_manager = None
1161 connection_callable = None
1163 def __enter__(self):
1164 return self
1166 def __exit__(self, type_, value, traceback):
1167 self.close()
1169 @util.contextmanager
1170 def _maker_context_manager(self):
1171 with self:
1172 with self.begin():
1173 yield self
1175 @property
1176 @util.deprecated_20(
1177 ":attr:`_orm.Session.transaction`",
1178 alternative="For context manager use, use "
1179 ":meth:`_orm.Session.begin`. To access "
1180 "the current root transaction, use "
1181 ":meth:`_orm.Session.get_transaction`.",
1182 warn_on_attribute_access=True,
1183 )
1184 def transaction(self):
1185 """The current active or inactive :class:`.SessionTransaction`.
1187 May be None if no transaction has begun yet.
1189 .. versionchanged:: 1.4 the :attr:`.Session.transaction` attribute
1190 is now a read-only descriptor that also may return None if no
1191 transaction has begun yet.
1194 """
1195 return self._legacy_transaction()
1197 def _legacy_transaction(self):
1198 if not self.future:
1199 self._autobegin()
1200 return self._transaction
1202 def in_transaction(self):
1203 """Return True if this :class:`_orm.Session` has begun a transaction.
1205 .. versionadded:: 1.4
1207 .. seealso::
1209 :attr:`_orm.Session.is_active`
1212 """
1213 return self._transaction is not None
1215 def in_nested_transaction(self):
1216 """Return True if this :class:`_orm.Session` has begun a nested
1217 transaction, e.g. SAVEPOINT.
1219 .. versionadded:: 1.4
1221 """
1222 return self._nested_transaction is not None
1224 def get_transaction(self):
1225 """Return the current root transaction in progress, if any.
1227 .. versionadded:: 1.4
1229 """
1230 trans = self._transaction
1231 while trans is not None and trans._parent is not None:
1232 trans = trans._parent
1233 return trans
1235 def get_nested_transaction(self):
1236 """Return the current nested transaction in progress, if any.
1238 .. versionadded:: 1.4
1240 """
1242 return self._nested_transaction
1244 @util.memoized_property
1245 def info(self):
1246 """A user-modifiable dictionary.
1248 The initial value of this dictionary can be populated using the
1249 ``info`` argument to the :class:`.Session` constructor or
1250 :class:`.sessionmaker` constructor or factory methods. The dictionary
1251 here is always local to this :class:`.Session` and can be modified
1252 independently of all other :class:`.Session` objects.
1254 """
1255 return {}
1257 def _autobegin(self):
1258 if not self.autocommit and self._transaction is None:
1260 trans = SessionTransaction(self, autobegin=True)
1261 assert self._transaction is trans
1262 return True
1264 return False
1266 @util.deprecated_params(
1267 subtransactions=(
1268 "2.0",
1269 "The :paramref:`_orm.Session.begin.subtransactions` flag is "
1270 "deprecated and "
1271 "will be removed in SQLAlchemy version 2.0. See "
1272 "the documentation at :ref:`session_subtransactions` for "
1273 "background on a compatible alternative pattern.",
1274 )
1275 )
1276 def begin(self, subtransactions=False, nested=False, _subtrans=False):
1277 """Begin a transaction, or nested transaction,
1278 on this :class:`.Session`, if one is not already begun.
1280 The :class:`_orm.Session` object features **autobegin** behavior,
1281 so that normally it is not necessary to call the
1282 :meth:`_orm.Session.begin`
1283 method explicitly. However, it may be used in order to control
1284 the scope of when the transactional state is begun.
1286 When used to begin the outermost transaction, an error is raised
1287 if this :class:`.Session` is already inside of a transaction.
1289 :param nested: if True, begins a SAVEPOINT transaction and is
1290 equivalent to calling :meth:`~.Session.begin_nested`. For
1291 documentation on SAVEPOINT transactions, please see
1292 :ref:`session_begin_nested`.
1294 :param subtransactions: if True, indicates that this
1295 :meth:`~.Session.begin` can create a "subtransaction".
1297 :return: the :class:`.SessionTransaction` object. Note that
1298 :class:`.SessionTransaction`
1299 acts as a Python context manager, allowing :meth:`.Session.begin`
1300 to be used in a "with" block. See :ref:`session_autocommit` for
1301 an example.
1303 .. seealso::
1305 :ref:`session_autobegin`
1307 :ref:`unitofwork_transaction`
1309 :meth:`.Session.begin_nested`
1312 """
1314 if subtransactions and self.future:
1315 raise NotImplementedError(
1316 "subtransactions are not implemented in future "
1317 "Session objects."
1318 )
1320 if self._autobegin():
1321 if not subtransactions and not nested and not _subtrans:
1322 return self._transaction
1324 if self._transaction is not None:
1325 if subtransactions or _subtrans or nested:
1326 trans = self._transaction._begin(nested=nested)
1327 assert self._transaction is trans
1328 if nested:
1329 self._nested_transaction = trans
1330 else:
1331 raise sa_exc.InvalidRequestError(
1332 "A transaction is already begun on this Session."
1333 )
1334 elif not self.autocommit:
1335 # outermost transaction. must be a not nested and not
1336 # a subtransaction
1338 assert not nested and not _subtrans and not subtransactions
1339 trans = SessionTransaction(self)
1340 assert self._transaction is trans
1341 else:
1342 # legacy autocommit mode
1343 assert not self.future
1344 trans = SessionTransaction(self, nested=nested)
1345 assert self._transaction is trans
1347 return self._transaction # needed for __enter__/__exit__ hook
1349 def begin_nested(self):
1350 """Begin a "nested" transaction on this Session, e.g. SAVEPOINT.
1352 The target database(s) and associated drivers must support SQL
1353 SAVEPOINT for this method to function correctly.
1355 For documentation on SAVEPOINT
1356 transactions, please see :ref:`session_begin_nested`.
1358 :return: the :class:`.SessionTransaction` object. Note that
1359 :class:`.SessionTransaction` acts as a context manager, allowing
1360 :meth:`.Session.begin_nested` to be used in a "with" block.
1361 See :ref:`session_begin_nested` for a usage example.
1363 .. seealso::
1365 :ref:`session_begin_nested`
1367 :ref:`pysqlite_serializable` - special workarounds required
1368 with the SQLite driver in order for SAVEPOINT to work
1369 correctly.
1371 """
1372 return self.begin(nested=True)
1374 def rollback(self):
1375 """Rollback the current transaction in progress.
1377 If no transaction is in progress, this method is a pass-through.
1379 In :term:`1.x-style` use, this method rolls back the topmost
1380 database transaction if no nested transactions are in effect, or
1381 to the current nested transaction if one is in effect.
1383 When
1384 :term:`2.0-style` use is in effect via the
1385 :paramref:`_orm.Session.future` flag, the method always rolls back
1386 the topmost database transaction, discarding any nested
1387 transactions that may be in progress.
1389 .. seealso::
1391 :ref:`session_rollback`
1393 :ref:`unitofwork_transaction`
1395 """
1396 if self._transaction is None:
1397 pass
1398 else:
1399 self._transaction.rollback(_to_root=self.future)
1401 def commit(self):
1402 """Flush pending changes and commit the current transaction.
1404 When the COMMIT operation is complete, all objects are fully
1405 :term:`expired`, erasing their internal contents, which will be
1406 automatically re-loaded when the objects are next accessed. In the
1407 interim, these objects are in an expired state and will not function if
1408 they are :term:`detached` from the :class:`.Session`. Additionally,
1409 this re-load operation is not supported when using asyncio-oriented
1410 APIs. The :paramref:`.Session.expire_on_commit` parameter may be used
1411 to disable this behavior.
1413 When there is no transaction in place for the :class:`.Session`,
1414 indicating that no operations were invoked on this :class:`.Session`
1415 since the previous call to :meth:`.Session.commit`, the method will
1416 begin and commit an internal-only "logical" transaction, that does not
1417 normally affect the database unless pending flush changes were
1418 detected, but will still invoke event handlers and object expiration
1419 rules.
1421 If :term:`1.x-style` use is in effect and there are currently
1422 SAVEPOINTs in progress via :meth:`_orm.Session.begin_nested`,
1423 the operation will release the current SAVEPOINT but not commit
1424 the outermost database transaction.
1426 If :term:`2.0-style` use is in effect via the
1427 :paramref:`_orm.Session.future` flag, the outermost database
1428 transaction is committed unconditionally, automatically releasing any
1429 SAVEPOINTs in effect.
1431 When using legacy "autocommit" mode, this method is only
1432 valid to call if a transaction is actually in progress, else
1433 an error is raised. Similarly, when using legacy "subtransactions",
1434 the method will instead close out the current "subtransaction",
1435 rather than the actual database transaction, if a transaction
1436 is in progress.
1438 .. seealso::
1440 :ref:`session_committing`
1442 :ref:`unitofwork_transaction`
1444 :ref:`asyncio_orm_avoid_lazyloads`
1446 """
1447 if self._transaction is None:
1448 if not self._autobegin():
1449 raise sa_exc.InvalidRequestError("No transaction is begun.")
1451 self._transaction.commit(_to_root=self.future)
1453 def prepare(self):
1454 """Prepare the current transaction in progress for two phase commit.
1456 If no transaction is in progress, this method raises an
1457 :exc:`~sqlalchemy.exc.InvalidRequestError`.
1459 Only root transactions of two phase sessions can be prepared. If the
1460 current transaction is not such, an
1461 :exc:`~sqlalchemy.exc.InvalidRequestError` is raised.
1463 """
1464 if self._transaction is None:
1465 if not self._autobegin():
1466 raise sa_exc.InvalidRequestError("No transaction is begun.")
1468 self._transaction.prepare()
1470 def connection(
1471 self,
1472 bind_arguments=None,
1473 close_with_result=False,
1474 execution_options=None,
1475 **kw
1476 ):
1477 r"""Return a :class:`_engine.Connection` object corresponding to this
1478 :class:`.Session` object's transactional state.
1480 If this :class:`.Session` is configured with ``autocommit=False``,
1481 either the :class:`_engine.Connection` corresponding to the current
1482 transaction is returned, or if no transaction is in progress, a new
1483 one is begun and the :class:`_engine.Connection`
1484 returned (note that no
1485 transactional state is established with the DBAPI until the first
1486 SQL statement is emitted).
1488 Alternatively, if this :class:`.Session` is configured with
1489 ``autocommit=True``, an ad-hoc :class:`_engine.Connection` is returned
1490 using :meth:`_engine.Engine.connect` on the underlying
1491 :class:`_engine.Engine`.
1493 Ambiguity in multi-bind or unbound :class:`.Session` objects can be
1494 resolved through any of the optional keyword arguments. This
1495 ultimately makes usage of the :meth:`.get_bind` method for resolution.
1497 :param bind_arguments: dictionary of bind arguments. May include
1498 "mapper", "bind", "clause", other custom arguments that are passed
1499 to :meth:`.Session.get_bind`.
1501 :param bind:
1502 deprecated; use bind_arguments
1504 :param mapper:
1505 deprecated; use bind_arguments
1507 :param clause:
1508 deprecated; use bind_arguments
1510 :param close_with_result: Passed to :meth:`_engine.Engine.connect`,
1511 indicating the :class:`_engine.Connection` should be considered
1512 "single use", automatically closing when the first result set is
1513 closed. This flag only has an effect if this :class:`.Session` is
1514 configured with ``autocommit=True`` and does not already have a
1515 transaction in progress.
1517 .. deprecated:: 1.4 this parameter is deprecated and will be removed
1518 in SQLAlchemy 2.0
1520 :param execution_options: a dictionary of execution options that will
1521 be passed to :meth:`_engine.Connection.execution_options`, **when the
1522 connection is first procured only**. If the connection is already
1523 present within the :class:`.Session`, a warning is emitted and
1524 the arguments are ignored.
1526 .. seealso::
1528 :ref:`session_transaction_isolation`
1530 :param \**kw:
1531 deprecated; use bind_arguments
1533 """
1535 if not bind_arguments:
1536 bind_arguments = kw
1538 bind = bind_arguments.pop("bind", None)
1539 if bind is None:
1540 bind = self.get_bind(**bind_arguments)
1542 return self._connection_for_bind(
1543 bind,
1544 close_with_result=close_with_result,
1545 execution_options=execution_options,
1546 )
1548 def _connection_for_bind(self, engine, execution_options=None, **kw):
1549 TransactionalContext._trans_ctx_check(self)
1551 if self._transaction is not None or self._autobegin():
1552 return self._transaction._connection_for_bind(
1553 engine, execution_options
1554 )
1556 assert self._transaction is None
1557 assert self.autocommit
1558 conn = engine.connect(**kw)
1559 if execution_options:
1560 conn = conn.execution_options(**execution_options)
1561 return conn
1563 def execute(
1564 self,
1565 statement,
1566 params=None,
1567 execution_options=util.EMPTY_DICT,
1568 bind_arguments=None,
1569 _parent_execute_state=None,
1570 _add_event=None,
1571 **kw
1572 ):
1573 r"""Execute a SQL expression construct.
1575 Returns a :class:`_engine.Result` object representing
1576 results of the statement execution.
1578 E.g.::
1580 from sqlalchemy import select
1581 result = session.execute(
1582 select(User).where(User.id == 5)
1583 )
1585 The API contract of :meth:`_orm.Session.execute` is similar to that
1586 of :meth:`_future.Connection.execute`, the :term:`2.0 style` version
1587 of :class:`_future.Connection`.
1589 .. versionchanged:: 1.4 the :meth:`_orm.Session.execute` method is
1590 now the primary point of ORM statement execution when using
1591 :term:`2.0 style` ORM usage.
1593 :param statement:
1594 An executable statement (i.e. an :class:`.Executable` expression
1595 such as :func:`_expression.select`).
1597 :param params:
1598 Optional dictionary, or list of dictionaries, containing
1599 bound parameter values. If a single dictionary, single-row
1600 execution occurs; if a list of dictionaries, an
1601 "executemany" will be invoked. The keys in each dictionary
1602 must correspond to parameter names present in the statement.
1604 :param execution_options: optional dictionary of execution options,
1605 which will be associated with the statement execution. This
1606 dictionary can provide a subset of the options that are accepted
1607 by :meth:`_engine.Connection.execution_options`, and may also
1608 provide additional options understood only in an ORM context.
1610 :param bind_arguments: dictionary of additional arguments to determine
1611 the bind. May include "mapper", "bind", or other custom arguments.
1612 Contents of this dictionary are passed to the
1613 :meth:`.Session.get_bind` method.
1615 :param mapper:
1616 deprecated; use the bind_arguments dictionary
1618 :param bind:
1619 deprecated; use the bind_arguments dictionary
1621 :param \**kw:
1622 deprecated; use the bind_arguments dictionary
1624 :return: a :class:`_engine.Result` object.
1627 """
1628 statement = coercions.expect(roles.StatementRole, statement)
1630 if kw:
1631 util.warn_deprecated_20(
1632 "Passing bind arguments to Session.execute() as keyword "
1633 "arguments is deprecated and will be removed SQLAlchemy 2.0. "
1634 "Please use the bind_arguments parameter."
1635 )
1636 if not bind_arguments:
1637 bind_arguments = kw
1638 else:
1639 bind_arguments.update(kw)
1640 elif not bind_arguments:
1641 bind_arguments = {}
1642 else:
1643 bind_arguments = dict(bind_arguments)
1645 if (
1646 statement._propagate_attrs.get("compile_state_plugin", None)
1647 == "orm"
1648 ):
1649 # note that even without "future" mode, we need
1650 compile_state_cls = CompileState._get_plugin_class_for_plugin(
1651 statement, "orm"
1652 )
1653 else:
1654 compile_state_cls = None
1656 execution_options = util.coerce_to_immutabledict(execution_options)
1658 if compile_state_cls is not None:
1659 (
1660 statement,
1661 execution_options,
1662 ) = compile_state_cls.orm_pre_session_exec(
1663 self,
1664 statement,
1665 params,
1666 execution_options,
1667 bind_arguments,
1668 _parent_execute_state is not None,
1669 )
1670 else:
1671 bind_arguments.setdefault("clause", statement)
1672 execution_options = execution_options.union(
1673 {"future_result": True}
1674 )
1676 if _parent_execute_state:
1677 events_todo = _parent_execute_state._remaining_events()
1678 else:
1679 events_todo = self.dispatch.do_orm_execute
1680 if _add_event:
1681 events_todo = list(events_todo) + [_add_event]
1683 if events_todo:
1684 orm_exec_state = ORMExecuteState(
1685 self,
1686 statement,
1687 params,
1688 execution_options,
1689 bind_arguments,
1690 compile_state_cls,
1691 events_todo,
1692 )
1693 for idx, fn in enumerate(events_todo):
1694 orm_exec_state._starting_event_idx = idx
1695 result = fn(orm_exec_state)
1696 if result:
1697 return result
1699 statement = orm_exec_state.statement
1700 execution_options = orm_exec_state.local_execution_options
1702 bind = self.get_bind(**bind_arguments)
1704 if self.autocommit:
1705 # legacy stuff, we can't use future_result w/ autocommit because
1706 # we rely upon close_with_result, also legacy. it's all
1707 # interrelated
1708 conn = self._connection_for_bind(bind, close_with_result=True)
1709 execution_options = execution_options.union(
1710 dict(future_result=False)
1711 )
1712 else:
1713 conn = self._connection_for_bind(bind)
1714 result = conn._execute_20(statement, params or {}, execution_options)
1716 if compile_state_cls:
1717 result = compile_state_cls.orm_setup_cursor_result(
1718 self,
1719 statement,
1720 params,
1721 execution_options,
1722 bind_arguments,
1723 result,
1724 )
1726 return result
1728 def scalar(
1729 self,
1730 statement,
1731 params=None,
1732 execution_options=util.EMPTY_DICT,
1733 bind_arguments=None,
1734 **kw
1735 ):
1736 """Execute a statement and return a scalar result.
1738 Usage and parameters are the same as that of
1739 :meth:`_orm.Session.execute`; the return result is a scalar Python
1740 value.
1742 """
1744 return self.execute(
1745 statement,
1746 params=params,
1747 execution_options=execution_options,
1748 bind_arguments=bind_arguments,
1749 **kw
1750 ).scalar()
1752 def scalars(
1753 self,
1754 statement,
1755 params=None,
1756 execution_options=util.EMPTY_DICT,
1757 bind_arguments=None,
1758 **kw
1759 ):
1760 """Execute a statement and return the results as scalars.
1762 Usage and parameters are the same as that of
1763 :meth:`_orm.Session.execute`; the return result is a
1764 :class:`_result.ScalarResult` filtering object which
1765 will return single elements rather than :class:`_row.Row` objects.
1767 :return: a :class:`_result.ScalarResult` object
1769 .. versionadded:: 1.4.24
1771 """
1773 return self.execute(
1774 statement,
1775 params=params,
1776 execution_options=execution_options,
1777 bind_arguments=bind_arguments,
1778 **kw
1779 ).scalars()
1781 def close(self):
1782 """Close out the transactional resources and ORM objects used by this
1783 :class:`_orm.Session`.
1785 This expunges all ORM objects associated with this
1786 :class:`_orm.Session`, ends any transaction in progress and
1787 :term:`releases` any :class:`_engine.Connection` objects which this
1788 :class:`_orm.Session` itself has checked out from associated
1789 :class:`_engine.Engine` objects. The operation then leaves the
1790 :class:`_orm.Session` in a state which it may be used again.
1792 .. tip::
1794 The :meth:`_orm.Session.close` method **does not prevent the
1795 Session from being used again**. The :class:`_orm.Session` itself
1796 does not actually have a distinct "closed" state; it merely means
1797 the :class:`_orm.Session` will release all database connections
1798 and ORM objects.
1800 .. versionchanged:: 1.4 The :meth:`.Session.close` method does not
1801 immediately create a new :class:`.SessionTransaction` object;
1802 instead, the new :class:`.SessionTransaction` is created only if
1803 the :class:`.Session` is used again for a database operation.
1805 .. seealso::
1807 :ref:`session_closing` - detail on the semantics of
1808 :meth:`_orm.Session.close`
1810 """
1811 self._close_impl(invalidate=False)
1813 def invalidate(self):
1814 """Close this Session, using connection invalidation.
1816 This is a variant of :meth:`.Session.close` that will additionally
1817 ensure that the :meth:`_engine.Connection.invalidate`
1818 method will be called on each :class:`_engine.Connection` object
1819 that is currently in use for a transaction (typically there is only
1820 one connection unless the :class:`_orm.Session` is used with
1821 multiple engines).
1823 This can be called when the database is known to be in a state where
1824 the connections are no longer safe to be used.
1826 Below illustrates a scenario when using `gevent
1827 <https://www.gevent.org/>`_, which can produce ``Timeout`` exceptions
1828 that may mean the underlying connection should be discarded::
1830 import gevent
1832 try:
1833 sess = Session()
1834 sess.add(User())
1835 sess.commit()
1836 except gevent.Timeout:
1837 sess.invalidate()
1838 raise
1839 except:
1840 sess.rollback()
1841 raise
1843 The method additionally does everything that :meth:`_orm.Session.close`
1844 does, including that all ORM objects are expunged.
1846 """
1847 self._close_impl(invalidate=True)
1849 def _close_impl(self, invalidate):
1850 self.expunge_all()
1851 if self._transaction is not None:
1852 for transaction in self._transaction._iterate_self_and_parents():
1853 transaction.close(invalidate)
1855 def expunge_all(self):
1856 """Remove all object instances from this ``Session``.
1858 This is equivalent to calling ``expunge(obj)`` on all objects in this
1859 ``Session``.
1861 """
1863 all_states = self.identity_map.all_states() + list(self._new)
1864 self.identity_map._kill()
1865 self.identity_map = identity.WeakInstanceDict()
1866 self._new = {}
1867 self._deleted = {}
1869 statelib.InstanceState._detach_states(all_states, self)
1871 def _add_bind(self, key, bind):
1872 try:
1873 insp = inspect(key)
1874 except sa_exc.NoInspectionAvailable as err:
1875 if not isinstance(key, type):
1876 util.raise_(
1877 sa_exc.ArgumentError(
1878 "Not an acceptable bind target: %s" % key
1879 ),
1880 replace_context=err,
1881 )
1882 else:
1883 self.__binds[key] = bind
1884 else:
1885 if insp.is_selectable:
1886 self.__binds[insp] = bind
1887 elif insp.is_mapper:
1888 self.__binds[insp.class_] = bind
1889 for _selectable in insp._all_tables:
1890 self.__binds[_selectable] = bind
1891 else:
1892 raise sa_exc.ArgumentError(
1893 "Not an acceptable bind target: %s" % key
1894 )
1896 def bind_mapper(self, mapper, bind):
1897 """Associate a :class:`_orm.Mapper` or arbitrary Python class with a
1898 "bind", e.g. an :class:`_engine.Engine` or
1899 :class:`_engine.Connection`.
1901 The given entity is added to a lookup used by the
1902 :meth:`.Session.get_bind` method.
1904 :param mapper: a :class:`_orm.Mapper` object,
1905 or an instance of a mapped
1906 class, or any Python class that is the base of a set of mapped
1907 classes.
1909 :param bind: an :class:`_engine.Engine` or :class:`_engine.Connection`
1910 object.
1912 .. seealso::
1914 :ref:`session_partitioning`
1916 :paramref:`.Session.binds`
1918 :meth:`.Session.bind_table`
1921 """
1922 self._add_bind(mapper, bind)
1924 def bind_table(self, table, bind):
1925 """Associate a :class:`_schema.Table` with a "bind", e.g. an
1926 :class:`_engine.Engine`
1927 or :class:`_engine.Connection`.
1929 The given :class:`_schema.Table` is added to a lookup used by the
1930 :meth:`.Session.get_bind` method.
1932 :param table: a :class:`_schema.Table` object,
1933 which is typically the target
1934 of an ORM mapping, or is present within a selectable that is
1935 mapped.
1937 :param bind: an :class:`_engine.Engine` or :class:`_engine.Connection`
1938 object.
1940 .. seealso::
1942 :ref:`session_partitioning`
1944 :paramref:`.Session.binds`
1946 :meth:`.Session.bind_mapper`
1949 """
1950 self._add_bind(table, bind)
1952 def get_bind(
1953 self,
1954 mapper=None,
1955 clause=None,
1956 bind=None,
1957 _sa_skip_events=None,
1958 _sa_skip_for_implicit_returning=False,
1959 ):
1960 """Return a "bind" to which this :class:`.Session` is bound.
1962 The "bind" is usually an instance of :class:`_engine.Engine`,
1963 except in the case where the :class:`.Session` has been
1964 explicitly bound directly to a :class:`_engine.Connection`.
1966 For a multiply-bound or unbound :class:`.Session`, the
1967 ``mapper`` or ``clause`` arguments are used to determine the
1968 appropriate bind to return.
1970 Note that the "mapper" argument is usually present
1971 when :meth:`.Session.get_bind` is called via an ORM
1972 operation such as a :meth:`.Session.query`, each
1973 individual INSERT/UPDATE/DELETE operation within a
1974 :meth:`.Session.flush`, call, etc.
1976 The order of resolution is:
1978 1. if mapper given and :paramref:`.Session.binds` is present,
1979 locate a bind based first on the mapper in use, then
1980 on the mapped class in use, then on any base classes that are
1981 present in the ``__mro__`` of the mapped class, from more specific
1982 superclasses to more general.
1983 2. if clause given and ``Session.binds`` is present,
1984 locate a bind based on :class:`_schema.Table` objects
1985 found in the given clause present in ``Session.binds``.
1986 3. if ``Session.binds`` is present, return that.
1987 4. if clause given, attempt to return a bind
1988 linked to the :class:`_schema.MetaData` ultimately
1989 associated with the clause.
1990 5. if mapper given, attempt to return a bind
1991 linked to the :class:`_schema.MetaData` ultimately
1992 associated with the :class:`_schema.Table` or other
1993 selectable to which the mapper is mapped.
1994 6. No bind can be found, :exc:`~sqlalchemy.exc.UnboundExecutionError`
1995 is raised.
1997 Note that the :meth:`.Session.get_bind` method can be overridden on
1998 a user-defined subclass of :class:`.Session` to provide any kind
1999 of bind resolution scheme. See the example at
2000 :ref:`session_custom_partitioning`.
2002 :param mapper:
2003 Optional :func:`.mapper` mapped class or instance of
2004 :class:`_orm.Mapper`. The bind can be derived from a
2005 :class:`_orm.Mapper`
2006 first by consulting the "binds" map associated with this
2007 :class:`.Session`, and secondly by consulting the
2008 :class:`_schema.MetaData`
2009 associated with the :class:`_schema.Table` to which the
2010 :class:`_orm.Mapper`
2011 is mapped for a bind.
2013 :param clause:
2014 A :class:`_expression.ClauseElement` (i.e.
2015 :func:`_expression.select`,
2016 :func:`_expression.text`,
2017 etc.). If the ``mapper`` argument is not present or could not
2018 produce a bind, the given expression construct will be searched
2019 for a bound element, typically a :class:`_schema.Table`
2020 associated with
2021 bound :class:`_schema.MetaData`.
2023 .. seealso::
2025 :ref:`session_partitioning`
2027 :paramref:`.Session.binds`
2029 :meth:`.Session.bind_mapper`
2031 :meth:`.Session.bind_table`
2033 """
2035 # this function is documented as a subclassing hook, so we have
2036 # to call this method even if the return is simple
2037 if bind:
2038 return bind
2039 elif not self.__binds and self.bind:
2040 # simplest and most common case, we have a bind and no
2041 # per-mapper/table binds, we're done
2042 return self.bind
2044 # we don't have self.bind and either have self.__binds
2045 # or we don't have self.__binds (which is legacy). Look at the
2046 # mapper and the clause
2047 if mapper is clause is None:
2048 if self.bind:
2049 return self.bind
2050 else:
2051 raise sa_exc.UnboundExecutionError(
2052 "This session is not bound to a single Engine or "
2053 "Connection, and no context was provided to locate "
2054 "a binding."
2055 )
2057 # look more closely at the mapper.
2058 if mapper is not None:
2059 try:
2060 mapper = inspect(mapper)
2061 except sa_exc.NoInspectionAvailable as err:
2062 if isinstance(mapper, type):
2063 util.raise_(
2064 exc.UnmappedClassError(mapper),
2065 replace_context=err,
2066 )
2067 else:
2068 raise
2070 # match up the mapper or clause in the __binds
2071 if self.__binds:
2072 # matching mappers and selectables to entries in the
2073 # binds dictionary; supported use case.
2074 if mapper:
2075 for cls in mapper.class_.__mro__:
2076 if cls in self.__binds:
2077 return self.__binds[cls]
2078 if clause is None:
2079 clause = mapper.persist_selectable
2081 if clause is not None:
2082 plugin_subject = clause._propagate_attrs.get(
2083 "plugin_subject", None
2084 )
2086 if plugin_subject is not None:
2087 for cls in plugin_subject.mapper.class_.__mro__:
2088 if cls in self.__binds:
2089 return self.__binds[cls]
2091 for obj in visitors.iterate(clause):
2092 if obj in self.__binds:
2093 return self.__binds[obj]
2095 # none of the __binds matched, but we have a fallback bind.
2096 # return that
2097 if self.bind:
2098 return self.bind
2100 # now we are in legacy territory. looking for "bind" on tables
2101 # that are via bound metadata. this goes away in 2.0.
2103 future_msg = ""
2104 future_code = ""
2106 if mapper and clause is None:
2107 clause = mapper.persist_selectable
2109 if clause is not None:
2110 if clause.bind:
2111 if self.future:
2112 future_msg = (
2113 " A bind was located via legacy bound metadata, but "
2114 "since future=True is set on this Session, this "
2115 "bind is ignored."
2116 )
2117 else:
2118 util.warn_deprecated_20(
2119 "This Session located a target engine via bound "
2120 "metadata; as this functionality will be removed in "
2121 "SQLAlchemy 2.0, an Engine object should be passed "
2122 "to the Session() constructor directly."
2123 )
2124 return clause.bind
2126 if mapper:
2127 if mapper.persist_selectable.bind:
2128 if self.future:
2129 future_msg = (
2130 " A bind was located via legacy bound metadata, but "
2131 "since future=True is set on this Session, this "
2132 "bind is ignored."
2133 )
2134 else:
2135 util.warn_deprecated_20(
2136 "This Session located a target engine via bound "
2137 "metadata; as this functionality will be removed in "
2138 "SQLAlchemy 2.0, an Engine object should be passed "
2139 "to the Session() constructor directly."
2140 )
2141 return mapper.persist_selectable.bind
2143 context = []
2144 if mapper is not None:
2145 context.append("mapper %s" % mapper)
2146 if clause is not None:
2147 context.append("SQL expression")
2149 raise sa_exc.UnboundExecutionError(
2150 "Could not locate a bind configured on %s or this Session.%s"
2151 % (", ".join(context), future_msg),
2152 code=future_code,
2153 )
2155 def query(self, *entities, **kwargs):
2156 """Return a new :class:`_query.Query` object corresponding to this
2157 :class:`_orm.Session`.
2159 """
2161 return self._query_cls(entities, self, **kwargs)
2163 def _identity_lookup(
2164 self,
2165 mapper,
2166 primary_key_identity,
2167 identity_token=None,
2168 passive=attributes.PASSIVE_OFF,
2169 lazy_loaded_from=None,
2170 ):
2171 """Locate an object in the identity map.
2173 Given a primary key identity, constructs an identity key and then
2174 looks in the session's identity map. If present, the object may
2175 be run through unexpiration rules (e.g. load unloaded attributes,
2176 check if was deleted).
2178 e.g.::
2180 obj = session._identity_lookup(inspect(SomeClass), (1, ))
2182 :param mapper: mapper in use
2183 :param primary_key_identity: the primary key we are searching for, as
2184 a tuple.
2185 :param identity_token: identity token that should be used to create
2186 the identity key. Used as is, however overriding subclasses can
2187 repurpose this in order to interpret the value in a special way,
2188 such as if None then look among multiple target tokens.
2189 :param passive: passive load flag passed to
2190 :func:`.loading.get_from_identity`, which impacts the behavior if
2191 the object is found; the object may be validated and/or unexpired
2192 if the flag allows for SQL to be emitted.
2193 :param lazy_loaded_from: an :class:`.InstanceState` that is
2194 specifically asking for this identity as a related identity. Used
2195 for sharding schemes where there is a correspondence between an object
2196 and a related object being lazy-loaded (or otherwise
2197 relationship-loaded).
2199 :return: None if the object is not found in the identity map, *or*
2200 if the object was unexpired and found to have been deleted.
2201 if passive flags disallow SQL and the object is expired, returns
2202 PASSIVE_NO_RESULT. In all other cases the instance is returned.
2204 .. versionchanged:: 1.4.0 - the :meth:`.Session._identity_lookup`
2205 method was moved from :class:`_query.Query` to
2206 :class:`.Session`, to avoid having to instantiate the
2207 :class:`_query.Query` object.
2210 """
2212 key = mapper.identity_key_from_primary_key(
2213 primary_key_identity, identity_token=identity_token
2214 )
2215 return loading.get_from_identity(self, mapper, key, passive)
2217 @property
2218 @util.contextmanager
2219 def no_autoflush(self):
2220 """Return a context manager that disables autoflush.
2222 e.g.::
2224 with session.no_autoflush:
2226 some_object = SomeClass()
2227 session.add(some_object)
2228 # won't autoflush
2229 some_object.related_thing = session.query(SomeRelated).first()
2231 Operations that proceed within the ``with:`` block
2232 will not be subject to flushes occurring upon query
2233 access. This is useful when initializing a series
2234 of objects which involve existing database queries,
2235 where the uncompleted object should not yet be flushed.
2237 """
2238 autoflush = self.autoflush
2239 self.autoflush = False
2240 try:
2241 yield self
2242 finally:
2243 self.autoflush = autoflush
2245 def _autoflush(self):
2246 if self.autoflush and not self._flushing:
2247 try:
2248 self.flush()
2249 except sa_exc.StatementError as e:
2250 # note we are reraising StatementError as opposed to
2251 # raising FlushError with "chaining" to remain compatible
2252 # with code that catches StatementError, IntegrityError,
2253 # etc.
2254 e.add_detail(
2255 "raised as a result of Query-invoked autoflush; "
2256 "consider using a session.no_autoflush block if this "
2257 "flush is occurring prematurely"
2258 )
2259 util.raise_(e, with_traceback=sys.exc_info()[2])
2261 def refresh(self, instance, attribute_names=None, with_for_update=None):
2262 """Expire and refresh attributes on the given instance.
2264 The selected attributes will first be expired as they would when using
2265 :meth:`_orm.Session.expire`; then a SELECT statement will be issued to
2266 the database to refresh column-oriented attributes with the current
2267 value available in the current transaction.
2269 :func:`_orm.relationship` oriented attributes will also be immediately
2270 loaded if they were already eagerly loaded on the object, using the
2271 same eager loading strategy that they were loaded with originally.
2272 Unloaded relationship attributes will remain unloaded, as will
2273 relationship attributes that were originally lazy loaded.
2275 .. versionadded:: 1.4 - the :meth:`_orm.Session.refresh` method
2276 can also refresh eagerly loaded attributes.
2278 .. tip::
2280 While the :meth:`_orm.Session.refresh` method is capable of
2281 refreshing both column and relationship oriented attributes, its
2282 primary focus is on refreshing of local column-oriented attributes
2283 on a single instance. For more open ended "refresh" functionality,
2284 including the ability to refresh the attributes on many objects at
2285 once while having explicit control over relationship loader
2286 strategies, use the
2287 :ref:`populate existing <orm_queryguide_populate_existing>` feature
2288 instead.
2290 Note that a highly isolated transaction will return the same values as
2291 were previously read in that same transaction, regardless of changes
2292 in database state outside of that transaction. Refreshing
2293 attributes usually only makes sense at the start of a transaction
2294 where database rows have not yet been accessed.
2296 :param attribute_names: optional. An iterable collection of
2297 string attribute names indicating a subset of attributes to
2298 be refreshed.
2300 :param with_for_update: optional boolean ``True`` indicating FOR UPDATE
2301 should be used, or may be a dictionary containing flags to
2302 indicate a more specific set of FOR UPDATE flags for the SELECT;
2303 flags should match the parameters of
2304 :meth:`_query.Query.with_for_update`.
2305 Supersedes the :paramref:`.Session.refresh.lockmode` parameter.
2307 .. seealso::
2309 :ref:`session_expire` - introductory material
2311 :meth:`.Session.expire`
2313 :meth:`.Session.expire_all`
2315 :ref:`orm_queryguide_populate_existing` - allows any ORM query
2316 to refresh objects as they would be loaded normally.
2318 """
2319 try:
2320 state = attributes.instance_state(instance)
2321 except exc.NO_STATE as err:
2322 util.raise_(
2323 exc.UnmappedInstanceError(instance),
2324 replace_context=err,
2325 )
2327 self._expire_state(state, attribute_names)
2329 if with_for_update == {}:
2330 raise sa_exc.ArgumentError(
2331 "with_for_update should be the boolean value "
2332 "True, or a dictionary with options. "
2333 "A blank dictionary is ambiguous."
2334 )
2336 with_for_update = query.ForUpdateArg._from_argument(with_for_update)
2338 stmt = sql.select(object_mapper(instance))
2339 if (
2340 loading.load_on_ident(
2341 self,
2342 stmt,
2343 state.key,
2344 refresh_state=state,
2345 with_for_update=with_for_update,
2346 only_load_props=attribute_names,
2347 )
2348 is None
2349 ):
2350 raise sa_exc.InvalidRequestError(
2351 "Could not refresh instance '%s'" % instance_str(instance)
2352 )
2354 def expire_all(self):
2355 """Expires all persistent instances within this Session.
2357 When any attributes on a persistent instance is next accessed,
2358 a query will be issued using the
2359 :class:`.Session` object's current transactional context in order to
2360 load all expired attributes for the given instance. Note that
2361 a highly isolated transaction will return the same values as were
2362 previously read in that same transaction, regardless of changes
2363 in database state outside of that transaction.
2365 To expire individual objects and individual attributes
2366 on those objects, use :meth:`Session.expire`.
2368 The :class:`.Session` object's default behavior is to
2369 expire all state whenever the :meth:`Session.rollback`
2370 or :meth:`Session.commit` methods are called, so that new
2371 state can be loaded for the new transaction. For this reason,
2372 calling :meth:`Session.expire_all` should not be needed when
2373 autocommit is ``False``, assuming the transaction is isolated.
2375 .. seealso::
2377 :ref:`session_expire` - introductory material
2379 :meth:`.Session.expire`
2381 :meth:`.Session.refresh`
2383 :meth:`_orm.Query.populate_existing`
2385 """
2386 for state in self.identity_map.all_states():
2387 state._expire(state.dict, self.identity_map._modified)
2389 def expire(self, instance, attribute_names=None):
2390 """Expire the attributes on an instance.
2392 Marks the attributes of an instance as out of date. When an expired
2393 attribute is next accessed, a query will be issued to the
2394 :class:`.Session` object's current transactional context in order to
2395 load all expired attributes for the given instance. Note that
2396 a highly isolated transaction will return the same values as were
2397 previously read in that same transaction, regardless of changes
2398 in database state outside of that transaction.
2400 To expire all objects in the :class:`.Session` simultaneously,
2401 use :meth:`Session.expire_all`.
2403 The :class:`.Session` object's default behavior is to
2404 expire all state whenever the :meth:`Session.rollback`
2405 or :meth:`Session.commit` methods are called, so that new
2406 state can be loaded for the new transaction. For this reason,
2407 calling :meth:`Session.expire` only makes sense for the specific
2408 case that a non-ORM SQL statement was emitted in the current
2409 transaction.
2411 :param instance: The instance to be refreshed.
2412 :param attribute_names: optional list of string attribute names
2413 indicating a subset of attributes to be expired.
2415 .. seealso::
2417 :ref:`session_expire` - introductory material
2419 :meth:`.Session.expire`
2421 :meth:`.Session.refresh`
2423 :meth:`_orm.Query.populate_existing`
2425 """
2426 try:
2427 state = attributes.instance_state(instance)
2428 except exc.NO_STATE as err:
2429 util.raise_(
2430 exc.UnmappedInstanceError(instance),
2431 replace_context=err,
2432 )
2433 self._expire_state(state, attribute_names)
2435 def _expire_state(self, state, attribute_names):
2436 self._validate_persistent(state)
2437 if attribute_names:
2438 state._expire_attributes(state.dict, attribute_names)
2439 else:
2440 # pre-fetch the full cascade since the expire is going to
2441 # remove associations
2442 cascaded = list(
2443 state.manager.mapper.cascade_iterator("refresh-expire", state)
2444 )
2445 self._conditional_expire(state)
2446 for o, m, st_, dct_ in cascaded:
2447 self._conditional_expire(st_)
2449 def _conditional_expire(self, state, autoflush=None):
2450 """Expire a state if persistent, else expunge if pending"""
2452 if state.key:
2453 state._expire(state.dict, self.identity_map._modified)
2454 elif state in self._new:
2455 self._new.pop(state)
2456 state._detach(self)
2458 def expunge(self, instance):
2459 """Remove the `instance` from this ``Session``.
2461 This will free all internal references to the instance. Cascading
2462 will be applied according to the *expunge* cascade rule.
2464 """
2465 try:
2466 state = attributes.instance_state(instance)
2467 except exc.NO_STATE as err:
2468 util.raise_(
2469 exc.UnmappedInstanceError(instance),
2470 replace_context=err,
2471 )
2472 if state.session_id is not self.hash_key:
2473 raise sa_exc.InvalidRequestError(
2474 "Instance %s is not present in this Session" % state_str(state)
2475 )
2477 cascaded = list(
2478 state.manager.mapper.cascade_iterator("expunge", state)
2479 )
2480 self._expunge_states([state] + [st_ for o, m, st_, dct_ in cascaded])
2482 def _expunge_states(self, states, to_transient=False):
2483 for state in states:
2484 if state in self._new:
2485 self._new.pop(state)
2486 elif self.identity_map.contains_state(state):
2487 self.identity_map.safe_discard(state)
2488 self._deleted.pop(state, None)
2489 elif self._transaction:
2490 # state is "detached" from being deleted, but still present
2491 # in the transaction snapshot
2492 self._transaction._deleted.pop(state, None)
2493 statelib.InstanceState._detach_states(
2494 states, self, to_transient=to_transient
2495 )
2497 def _register_persistent(self, states):
2498 """Register all persistent objects from a flush.
2500 This is used both for pending objects moving to the persistent
2501 state as well as already persistent objects.
2503 """
2505 pending_to_persistent = self.dispatch.pending_to_persistent or None
2506 for state in states:
2507 mapper = _state_mapper(state)
2509 # prevent against last minute dereferences of the object
2510 obj = state.obj()
2511 if obj is not None:
2513 instance_key = mapper._identity_key_from_state(state)
2515 if (
2516 _none_set.intersection(instance_key[1])
2517 and not mapper.allow_partial_pks
2518 or _none_set.issuperset(instance_key[1])
2519 ):
2520 raise exc.FlushError(
2521 "Instance %s has a NULL identity key. If this is an "
2522 "auto-generated value, check that the database table "
2523 "allows generation of new primary key values, and "
2524 "that the mapped Column object is configured to "
2525 "expect these generated values. Ensure also that "
2526 "this flush() is not occurring at an inappropriate "
2527 "time, such as within a load() event."
2528 % state_str(state)
2529 )
2531 if state.key is None:
2532 state.key = instance_key
2533 elif state.key != instance_key:
2534 # primary key switch. use safe_discard() in case another
2535 # state has already replaced this one in the identity
2536 # map (see test/orm/test_naturalpks.py ReversePKsTest)
2537 self.identity_map.safe_discard(state)
2538 if state in self._transaction._key_switches:
2539 orig_key = self._transaction._key_switches[state][0]
2540 else:
2541 orig_key = state.key
2542 self._transaction._key_switches[state] = (
2543 orig_key,
2544 instance_key,
2545 )
2546 state.key = instance_key
2548 # there can be an existing state in the identity map
2549 # that is replaced when the primary keys of two instances
2550 # are swapped; see test/orm/test_naturalpks.py -> test_reverse
2551 old = self.identity_map.replace(state)
2552 if (
2553 old is not None
2554 and mapper._identity_key_from_state(old) == instance_key
2555 and old.obj() is not None
2556 ):
2557 util.warn(
2558 "Identity map already had an identity for %s, "
2559 "replacing it with newly flushed object. Are there "
2560 "load operations occurring inside of an event handler "
2561 "within the flush?" % (instance_key,)
2562 )
2563 state._orphaned_outside_of_session = False
2565 statelib.InstanceState._commit_all_states(
2566 ((state, state.dict) for state in states), self.identity_map
2567 )
2569 self._register_altered(states)
2571 if pending_to_persistent is not None:
2572 for state in states.intersection(self._new):
2573 pending_to_persistent(self, state)
2575 # remove from new last, might be the last strong ref
2576 for state in set(states).intersection(self._new):
2577 self._new.pop(state)
2579 def _register_altered(self, states):
2580 if self._transaction:
2581 for state in states:
2582 if state in self._new:
2583 self._transaction._new[state] = True
2584 else:
2585 self._transaction._dirty[state] = True
2587 def _remove_newly_deleted(self, states):
2588 persistent_to_deleted = self.dispatch.persistent_to_deleted or None
2589 for state in states:
2590 if self._transaction:
2591 self._transaction._deleted[state] = True
2593 if persistent_to_deleted is not None:
2594 # get a strong reference before we pop out of
2595 # self._deleted
2596 obj = state.obj() # noqa
2598 self.identity_map.safe_discard(state)
2599 self._deleted.pop(state, None)
2600 state._deleted = True
2601 # can't call state._detach() here, because this state
2602 # is still in the transaction snapshot and needs to be
2603 # tracked as part of that
2604 if persistent_to_deleted is not None:
2605 persistent_to_deleted(self, state)
2607 def add(self, instance, _warn=True):
2608 """Place an object into this :class:`_orm.Session`.
2610 Objects that are in the :term:`transient` state when passed to the
2611 :meth:`_orm.Session.add` method will move to the
2612 :term:`pending` state, until the next flush, at which point they
2613 will move to the :term:`persistent` state.
2615 Objects that are in the :term:`detached` state when passed to the
2616 :meth:`_orm.Session.add` method will move to the :term:`persistent`
2617 state directly.
2619 If the transaction used by the :class:`_orm.Session` is rolled back,
2620 objects which were transient when they were passed to
2621 :meth:`_orm.Session.add` will be moved back to the
2622 :term:`transient` state, and will no longer be present within this
2623 :class:`_orm.Session`.
2625 .. seealso::
2627 :meth:`_orm.Session.add_all`
2629 :ref:`session_adding` - at :ref:`session_basics`
2631 """
2632 if _warn and self._warn_on_events:
2633 self._flush_warning("Session.add()")
2635 try:
2636 state = attributes.instance_state(instance)
2637 except exc.NO_STATE as err:
2638 util.raise_(
2639 exc.UnmappedInstanceError(instance),
2640 replace_context=err,
2641 )
2643 self._save_or_update_state(state)
2645 def add_all(self, instances):
2646 """Add the given collection of instances to this :class:`_orm.Session`.
2648 See the documentation for :meth:`_orm.Session.add` for a general
2649 behavioral description.
2651 .. seealso::
2653 :meth:`_orm.Session.add`
2655 :ref:`session_adding` - at :ref:`session_basics`
2657 """
2659 if self._warn_on_events:
2660 self._flush_warning("Session.add_all()")
2662 for instance in instances:
2663 self.add(instance, _warn=False)
2665 def _save_or_update_state(self, state):
2666 state._orphaned_outside_of_session = False
2667 self._save_or_update_impl(state)
2669 mapper = _state_mapper(state)
2670 for o, m, st_, dct_ in mapper.cascade_iterator(
2671 "save-update", state, halt_on=self._contains_state
2672 ):
2673 self._save_or_update_impl(st_)
2675 def delete(self, instance):
2676 """Mark an instance as deleted.
2678 The object is assumed to be either :term:`persistent` or
2679 :term:`detached` when passed; after the method is called, the
2680 object will remain in the :term:`persistent` state until the next
2681 flush proceeds. During this time, the object will also be a member
2682 of the :attr:`_orm.Session.deleted` collection.
2684 When the next flush proceeds, the object will move to the
2685 :term:`deleted` state, indicating a ``DELETE`` statement was emitted
2686 for its row within the current transaction. When the transaction
2687 is successfully committed,
2688 the deleted object is moved to the :term:`detached` state and is
2689 no longer present within this :class:`_orm.Session`.
2691 .. seealso::
2693 :ref:`session_deleting` - at :ref:`session_basics`
2695 """
2696 if self._warn_on_events:
2697 self._flush_warning("Session.delete()")
2699 try:
2700 state = attributes.instance_state(instance)
2701 except exc.NO_STATE as err:
2702 util.raise_(
2703 exc.UnmappedInstanceError(instance),
2704 replace_context=err,
2705 )
2707 self._delete_impl(state, instance, head=True)
2709 def _delete_impl(self, state, obj, head):
2711 if state.key is None:
2712 if head:
2713 raise sa_exc.InvalidRequestError(
2714 "Instance '%s' is not persisted" % state_str(state)
2715 )
2716 else:
2717 return
2719 to_attach = self._before_attach(state, obj)
2721 if state in self._deleted:
2722 return
2724 self.identity_map.add(state)
2726 if to_attach:
2727 self._after_attach(state, obj)
2729 if head:
2730 # grab the cascades before adding the item to the deleted list
2731 # so that autoflush does not delete the item
2732 # the strong reference to the instance itself is significant here
2733 cascade_states = list(
2734 state.manager.mapper.cascade_iterator("delete", state)
2735 )
2737 self._deleted[state] = obj
2739 if head:
2740 for o, m, st_, dct_ in cascade_states:
2741 self._delete_impl(st_, o, False)
2743 def get(
2744 self,
2745 entity,
2746 ident,
2747 options=None,
2748 populate_existing=False,
2749 with_for_update=None,
2750 identity_token=None,
2751 execution_options=None,
2752 ):
2753 """Return an instance based on the given primary key identifier,
2754 or ``None`` if not found.
2756 E.g.::
2758 my_user = session.get(User, 5)
2760 some_object = session.get(VersionedFoo, (5, 10))
2762 some_object = session.get(
2763 VersionedFoo,
2764 {"id": 5, "version_id": 10}
2765 )
2767 .. versionadded:: 1.4 Added :meth:`_orm.Session.get`, which is moved
2768 from the now deprecated :meth:`_orm.Query.get` method.
2770 :meth:`_orm.Session.get` is special in that it provides direct
2771 access to the identity map of the :class:`.Session`.
2772 If the given primary key identifier is present
2773 in the local identity map, the object is returned
2774 directly from this collection and no SQL is emitted,
2775 unless the object has been marked fully expired.
2776 If not present,
2777 a SELECT is performed in order to locate the object.
2779 :meth:`_orm.Session.get` also will perform a check if
2780 the object is present in the identity map and
2781 marked as expired - a SELECT
2782 is emitted to refresh the object as well as to
2783 ensure that the row is still present.
2784 If not, :class:`~sqlalchemy.orm.exc.ObjectDeletedError` is raised.
2786 :param entity: a mapped class or :class:`.Mapper` indicating the
2787 type of entity to be loaded.
2789 :param ident: A scalar, tuple, or dictionary representing the
2790 primary key. For a composite (e.g. multiple column) primary key,
2791 a tuple or dictionary should be passed.
2793 For a single-column primary key, the scalar calling form is typically
2794 the most expedient. If the primary key of a row is the value "5",
2795 the call looks like::
2797 my_object = session.get(SomeClass, 5)
2799 The tuple form contains primary key values typically in
2800 the order in which they correspond to the mapped
2801 :class:`_schema.Table`
2802 object's primary key columns, or if the
2803 :paramref:`_orm.Mapper.primary_key` configuration parameter were
2804 used, in
2805 the order used for that parameter. For example, if the primary key
2806 of a row is represented by the integer
2807 digits "5, 10" the call would look like::
2809 my_object = session.get(SomeClass, (5, 10))
2811 The dictionary form should include as keys the mapped attribute names
2812 corresponding to each element of the primary key. If the mapped class
2813 has the attributes ``id``, ``version_id`` as the attributes which
2814 store the object's primary key value, the call would look like::
2816 my_object = session.get(SomeClass, {"id": 5, "version_id": 10})
2818 :param options: optional sequence of loader options which will be
2819 applied to the query, if one is emitted.
2821 :param populate_existing: causes the method to unconditionally emit
2822 a SQL query and refresh the object with the newly loaded data,
2823 regardless of whether or not the object is already present.
2825 :param with_for_update: optional boolean ``True`` indicating FOR UPDATE
2826 should be used, or may be a dictionary containing flags to
2827 indicate a more specific set of FOR UPDATE flags for the SELECT;
2828 flags should match the parameters of
2829 :meth:`_query.Query.with_for_update`.
2830 Supersedes the :paramref:`.Session.refresh.lockmode` parameter.
2832 :param execution_options: optional dictionary of execution options,
2833 which will be associated with the query execution if one is emitted.
2834 This dictionary can provide a subset of the options that are
2835 accepted by :meth:`_engine.Connection.execution_options`, and may
2836 also provide additional options understood only in an ORM context.
2838 .. versionadded:: 1.4.29
2840 .. seealso::
2842 :ref:`orm_queryguide_execution_options` - ORM-specific execution
2843 options
2845 :return: The object instance, or ``None``.
2847 """
2848 return self._get_impl(
2849 entity,
2850 ident,
2851 loading.load_on_pk_identity,
2852 options,
2853 populate_existing=populate_existing,
2854 with_for_update=with_for_update,
2855 identity_token=identity_token,
2856 execution_options=execution_options,
2857 )
2859 def _get_impl(
2860 self,
2861 entity,
2862 primary_key_identity,
2863 db_load_fn,
2864 options=None,
2865 populate_existing=False,
2866 with_for_update=None,
2867 identity_token=None,
2868 execution_options=None,
2869 ):
2871 # convert composite types to individual args
2872 if hasattr(primary_key_identity, "__composite_values__"):
2873 primary_key_identity = primary_key_identity.__composite_values__()
2875 mapper = inspect(entity)
2877 if not mapper or not mapper.is_mapper:
2878 raise sa_exc.ArgumentError(
2879 "Expected mapped class or mapper, got: %r" % entity
2880 )
2882 is_dict = isinstance(primary_key_identity, dict)
2883 if not is_dict:
2884 primary_key_identity = util.to_list(
2885 primary_key_identity, default=(None,)
2886 )
2888 if len(primary_key_identity) != len(mapper.primary_key):
2889 raise sa_exc.InvalidRequestError(
2890 "Incorrect number of values in identifier to formulate "
2891 "primary key for session.get(); primary key columns "
2892 "are %s" % ",".join("'%s'" % c for c in mapper.primary_key)
2893 )
2895 if is_dict:
2897 pk_synonyms = mapper._pk_synonyms
2899 if pk_synonyms:
2900 correct_keys = set(pk_synonyms).intersection(
2901 primary_key_identity
2902 )
2904 if correct_keys:
2905 primary_key_identity = dict(primary_key_identity)
2906 for k in correct_keys:
2907 primary_key_identity[
2908 pk_synonyms[k]
2909 ] = primary_key_identity[k]
2911 try:
2912 primary_key_identity = list(
2913 primary_key_identity[prop.key]
2914 for prop in mapper._identity_key_props
2915 )
2917 except KeyError as err:
2918 util.raise_(
2919 sa_exc.InvalidRequestError(
2920 "Incorrect names of values in identifier to formulate "
2921 "primary key for session.get(); primary key attribute "
2922 "names are %s (synonym names are also accepted)"
2923 % ",".join(
2924 "'%s'" % prop.key
2925 for prop in mapper._identity_key_props
2926 )
2927 ),
2928 replace_context=err,
2929 )
2931 if (
2932 not populate_existing
2933 and not mapper.always_refresh
2934 and with_for_update is None
2935 ):
2937 instance = self._identity_lookup(
2938 mapper, primary_key_identity, identity_token=identity_token
2939 )
2941 if instance is not None:
2942 # reject calls for id in identity map but class
2943 # mismatch.
2944 if not issubclass(instance.__class__, mapper.class_):
2945 return None
2946 return instance
2947 elif instance is attributes.PASSIVE_CLASS_MISMATCH:
2948 return None
2950 # set_label_style() not strictly necessary, however this will ensure
2951 # that tablename_colname style is used which at the moment is
2952 # asserted in a lot of unit tests :)
2954 load_options = context.QueryContext.default_load_options
2956 if populate_existing:
2957 load_options += {"_populate_existing": populate_existing}
2958 statement = sql.select(mapper).set_label_style(
2959 LABEL_STYLE_TABLENAME_PLUS_COL
2960 )
2961 if with_for_update is not None:
2962 statement._for_update_arg = query.ForUpdateArg._from_argument(
2963 with_for_update
2964 )
2966 if options:
2967 statement = statement.options(*options)
2968 if execution_options:
2969 statement = statement.execution_options(**execution_options)
2970 return db_load_fn(
2971 self,
2972 statement,
2973 primary_key_identity,
2974 load_options=load_options,
2975 )
2977 def merge(self, instance, load=True, options=None):
2978 """Copy the state of a given instance into a corresponding instance
2979 within this :class:`.Session`.
2981 :meth:`.Session.merge` examines the primary key attributes of the
2982 source instance, and attempts to reconcile it with an instance of the
2983 same primary key in the session. If not found locally, it attempts
2984 to load the object from the database based on primary key, and if
2985 none can be located, creates a new instance. The state of each
2986 attribute on the source instance is then copied to the target
2987 instance. The resulting target instance is then returned by the
2988 method; the original source instance is left unmodified, and
2989 un-associated with the :class:`.Session` if not already.
2991 This operation cascades to associated instances if the association is
2992 mapped with ``cascade="merge"``.
2994 See :ref:`unitofwork_merging` for a detailed discussion of merging.
2996 .. versionchanged:: 1.1 - :meth:`.Session.merge` will now reconcile
2997 pending objects with overlapping primary keys in the same way
2998 as persistent. See :ref:`change_3601` for discussion.
3000 :param instance: Instance to be merged.
3001 :param load: Boolean, when False, :meth:`.merge` switches into
3002 a "high performance" mode which causes it to forego emitting history
3003 events as well as all database access. This flag is used for
3004 cases such as transferring graphs of objects into a :class:`.Session`
3005 from a second level cache, or to transfer just-loaded objects
3006 into the :class:`.Session` owned by a worker thread or process
3007 without re-querying the database.
3009 The ``load=False`` use case adds the caveat that the given
3010 object has to be in a "clean" state, that is, has no pending changes
3011 to be flushed - even if the incoming object is detached from any
3012 :class:`.Session`. This is so that when
3013 the merge operation populates local attributes and
3014 cascades to related objects and
3015 collections, the values can be "stamped" onto the
3016 target object as is, without generating any history or attribute
3017 events, and without the need to reconcile the incoming data with
3018 any existing related objects or collections that might not
3019 be loaded. The resulting objects from ``load=False`` are always
3020 produced as "clean", so it is only appropriate that the given objects
3021 should be "clean" as well, else this suggests a mis-use of the
3022 method.
3023 :param options: optional sequence of loader options which will be
3024 applied to the :meth:`_orm.Session.get` method when the merge
3025 operation loads the existing version of the object from the database.
3027 .. versionadded:: 1.4.24
3030 .. seealso::
3032 :func:`.make_transient_to_detached` - provides for an alternative
3033 means of "merging" a single object into the :class:`.Session`
3035 """
3037 if self._warn_on_events:
3038 self._flush_warning("Session.merge()")
3040 _recursive = {}
3041 _resolve_conflict_map = {}
3043 if load:
3044 # flush current contents if we expect to load data
3045 self._autoflush()
3047 object_mapper(instance) # verify mapped
3048 autoflush = self.autoflush
3049 try:
3050 self.autoflush = False
3051 return self._merge(
3052 attributes.instance_state(instance),
3053 attributes.instance_dict(instance),
3054 load=load,
3055 options=options,
3056 _recursive=_recursive,
3057 _resolve_conflict_map=_resolve_conflict_map,
3058 )
3059 finally:
3060 self.autoflush = autoflush
3062 def _merge(
3063 self,
3064 state,
3065 state_dict,
3066 load=True,
3067 options=None,
3068 _recursive=None,
3069 _resolve_conflict_map=None,
3070 ):
3071 mapper = _state_mapper(state)
3072 if state in _recursive:
3073 return _recursive[state]
3075 new_instance = False
3076 key = state.key
3078 if key is None:
3079 if state in self._new:
3080 util.warn(
3081 "Instance %s is already pending in this Session yet is "
3082 "being merged again; this is probably not what you want "
3083 "to do" % state_str(state)
3084 )
3086 if not load:
3087 raise sa_exc.InvalidRequestError(
3088 "merge() with load=False option does not support "
3089 "objects transient (i.e. unpersisted) objects. flush() "
3090 "all changes on mapped instances before merging with "
3091 "load=False."
3092 )
3093 key = mapper._identity_key_from_state(state)
3094 key_is_persistent = attributes.NEVER_SET not in key[1] and (
3095 not _none_set.intersection(key[1])
3096 or (
3097 mapper.allow_partial_pks
3098 and not _none_set.issuperset(key[1])
3099 )
3100 )
3101 else:
3102 key_is_persistent = True
3104 if key in self.identity_map:
3105 try:
3106 merged = self.identity_map[key]
3107 except KeyError:
3108 # object was GC'ed right as we checked for it
3109 merged = None
3110 else:
3111 merged = None
3113 if merged is None:
3114 if key_is_persistent and key in _resolve_conflict_map:
3115 merged = _resolve_conflict_map[key]
3117 elif not load:
3118 if state.modified:
3119 raise sa_exc.InvalidRequestError(
3120 "merge() with load=False option does not support "
3121 "objects marked as 'dirty'. flush() all changes on "
3122 "mapped instances before merging with load=False."
3123 )
3124 merged = mapper.class_manager.new_instance()
3125 merged_state = attributes.instance_state(merged)
3126 merged_state.key = key
3127 self._update_impl(merged_state)
3128 new_instance = True
3130 elif key_is_persistent:
3131 merged = self.get(
3132 mapper.class_,
3133 key[1],
3134 identity_token=key[2],
3135 options=options,
3136 )
3138 if merged is None:
3139 merged = mapper.class_manager.new_instance()
3140 merged_state = attributes.instance_state(merged)
3141 merged_dict = attributes.instance_dict(merged)
3142 new_instance = True
3143 self._save_or_update_state(merged_state)
3144 else:
3145 merged_state = attributes.instance_state(merged)
3146 merged_dict = attributes.instance_dict(merged)
3148 _recursive[state] = merged
3149 _resolve_conflict_map[key] = merged
3151 # check that we didn't just pull the exact same
3152 # state out.
3153 if state is not merged_state:
3154 # version check if applicable
3155 if mapper.version_id_col is not None:
3156 existing_version = mapper._get_state_attr_by_column(
3157 state,
3158 state_dict,
3159 mapper.version_id_col,
3160 passive=attributes.PASSIVE_NO_INITIALIZE,
3161 )
3163 merged_version = mapper._get_state_attr_by_column(
3164 merged_state,
3165 merged_dict,
3166 mapper.version_id_col,
3167 passive=attributes.PASSIVE_NO_INITIALIZE,
3168 )
3170 if (
3171 existing_version is not attributes.PASSIVE_NO_RESULT
3172 and merged_version is not attributes.PASSIVE_NO_RESULT
3173 and existing_version != merged_version
3174 ):
3175 raise exc.StaleDataError(
3176 "Version id '%s' on merged state %s "
3177 "does not match existing version '%s'. "
3178 "Leave the version attribute unset when "
3179 "merging to update the most recent version."
3180 % (
3181 existing_version,
3182 state_str(merged_state),
3183 merged_version,
3184 )
3185 )
3187 merged_state.load_path = state.load_path
3188 merged_state.load_options = state.load_options
3190 # since we are copying load_options, we need to copy
3191 # the callables_ that would have been generated by those
3192 # load_options.
3193 # assumes that the callables we put in state.callables_
3194 # are not instance-specific (which they should not be)
3195 merged_state._copy_callables(state)
3197 for prop in mapper.iterate_properties:
3198 prop.merge(
3199 self,
3200 state,
3201 state_dict,
3202 merged_state,
3203 merged_dict,
3204 load,
3205 _recursive,
3206 _resolve_conflict_map,
3207 )
3209 if not load:
3210 # remove any history
3211 merged_state._commit_all(merged_dict, self.identity_map)
3212 merged_state.manager.dispatch._sa_event_merge_wo_load(
3213 merged_state, None
3214 )
3216 if new_instance:
3217 merged_state.manager.dispatch.load(merged_state, None)
3218 return merged
3220 def _validate_persistent(self, state):
3221 if not self.identity_map.contains_state(state):
3222 raise sa_exc.InvalidRequestError(
3223 "Instance '%s' is not persistent within this Session"
3224 % state_str(state)
3225 )
3227 def _save_impl(self, state):
3228 if state.key is not None:
3229 raise sa_exc.InvalidRequestError(
3230 "Object '%s' already has an identity - "
3231 "it can't be registered as pending" % state_str(state)
3232 )
3234 obj = state.obj()
3235 to_attach = self._before_attach(state, obj)
3236 if state not in self._new:
3237 self._new[state] = obj
3238 state.insert_order = len(self._new)
3239 if to_attach:
3240 self._after_attach(state, obj)
3242 def _update_impl(self, state, revert_deletion=False):
3243 if state.key is None:
3244 raise sa_exc.InvalidRequestError(
3245 "Instance '%s' is not persisted" % state_str(state)
3246 )
3248 if state._deleted:
3249 if revert_deletion:
3250 if not state._attached:
3251 return
3252 del state._deleted
3253 else:
3254 raise sa_exc.InvalidRequestError(
3255 "Instance '%s' has been deleted. "
3256 "Use the make_transient() "
3257 "function to send this object back "
3258 "to the transient state." % state_str(state)
3259 )
3261 obj = state.obj()
3263 # check for late gc
3264 if obj is None:
3265 return
3267 to_attach = self._before_attach(state, obj)
3269 self._deleted.pop(state, None)
3270 if revert_deletion:
3271 self.identity_map.replace(state)
3272 else:
3273 self.identity_map.add(state)
3275 if to_attach:
3276 self._after_attach(state, obj)
3277 elif revert_deletion:
3278 self.dispatch.deleted_to_persistent(self, state)
3280 def _save_or_update_impl(self, state):
3281 if state.key is None:
3282 self._save_impl(state)
3283 else:
3284 self._update_impl(state)
3286 def enable_relationship_loading(self, obj):
3287 """Associate an object with this :class:`.Session` for related
3288 object loading.
3290 .. warning::
3292 :meth:`.enable_relationship_loading` exists to serve special
3293 use cases and is not recommended for general use.
3295 Accesses of attributes mapped with :func:`_orm.relationship`
3296 will attempt to load a value from the database using this
3297 :class:`.Session` as the source of connectivity. The values
3298 will be loaded based on foreign key and primary key values
3299 present on this object - if not present, then those relationships
3300 will be unavailable.
3302 The object will be attached to this session, but will
3303 **not** participate in any persistence operations; its state
3304 for almost all purposes will remain either "transient" or
3305 "detached", except for the case of relationship loading.
3307 Also note that backrefs will often not work as expected.
3308 Altering a relationship-bound attribute on the target object
3309 may not fire off a backref event, if the effective value
3310 is what was already loaded from a foreign-key-holding value.
3312 The :meth:`.Session.enable_relationship_loading` method is
3313 similar to the ``load_on_pending`` flag on :func:`_orm.relationship`.
3314 Unlike that flag, :meth:`.Session.enable_relationship_loading` allows
3315 an object to remain transient while still being able to load
3316 related items.
3318 To make a transient object associated with a :class:`.Session`
3319 via :meth:`.Session.enable_relationship_loading` pending, add
3320 it to the :class:`.Session` using :meth:`.Session.add` normally.
3321 If the object instead represents an existing identity in the database,
3322 it should be merged using :meth:`.Session.merge`.
3324 :meth:`.Session.enable_relationship_loading` does not improve
3325 behavior when the ORM is used normally - object references should be
3326 constructed at the object level, not at the foreign key level, so
3327 that they are present in an ordinary way before flush()
3328 proceeds. This method is not intended for general use.
3330 .. seealso::
3332 :paramref:`_orm.relationship.load_on_pending` - this flag
3333 allows per-relationship loading of many-to-ones on items that
3334 are pending.
3336 :func:`.make_transient_to_detached` - allows for an object to
3337 be added to a :class:`.Session` without SQL emitted, which then
3338 will unexpire attributes on access.
3340 """
3341 try:
3342 state = attributes.instance_state(obj)
3343 except exc.NO_STATE as err:
3344 util.raise_(
3345 exc.UnmappedInstanceError(obj),
3346 replace_context=err,
3347 )
3349 to_attach = self._before_attach(state, obj)
3350 state._load_pending = True
3351 if to_attach:
3352 self._after_attach(state, obj)
3354 def _before_attach(self, state, obj):
3355 self._autobegin()
3357 if state.session_id == self.hash_key:
3358 return False
3360 if state.session_id and state.session_id in _sessions:
3361 raise sa_exc.InvalidRequestError(
3362 "Object '%s' is already attached to session '%s' "
3363 "(this is '%s')"
3364 % (state_str(state), state.session_id, self.hash_key)
3365 )
3367 self.dispatch.before_attach(self, state)
3369 return True
3371 def _after_attach(self, state, obj):
3372 state.session_id = self.hash_key
3373 if state.modified and state._strong_obj is None:
3374 state._strong_obj = obj
3375 self.dispatch.after_attach(self, state)
3377 if state.key:
3378 self.dispatch.detached_to_persistent(self, state)
3379 else:
3380 self.dispatch.transient_to_pending(self, state)
3382 def __contains__(self, instance):
3383 """Return True if the instance is associated with this session.
3385 The instance may be pending or persistent within the Session for a
3386 result of True.
3388 """
3389 try:
3390 state = attributes.instance_state(instance)
3391 except exc.NO_STATE as err:
3392 util.raise_(
3393 exc.UnmappedInstanceError(instance),
3394 replace_context=err,
3395 )
3396 return self._contains_state(state)
3398 def __iter__(self):
3399 """Iterate over all pending or persistent instances within this
3400 Session.
3402 """
3403 return iter(
3404 list(self._new.values()) + list(self.identity_map.values())
3405 )
3407 def _contains_state(self, state):
3408 return state in self._new or self.identity_map.contains_state(state)
3410 def flush(self, objects=None):
3411 """Flush all the object changes to the database.
3413 Writes out all pending object creations, deletions and modifications
3414 to the database as INSERTs, DELETEs, UPDATEs, etc. Operations are
3415 automatically ordered by the Session's unit of work dependency
3416 solver.
3418 Database operations will be issued in the current transactional
3419 context and do not affect the state of the transaction, unless an
3420 error occurs, in which case the entire transaction is rolled back.
3421 You may flush() as often as you like within a transaction to move
3422 changes from Python to the database's transaction buffer.
3424 For ``autocommit`` Sessions with no active manual transaction, flush()
3425 will create a transaction on the fly that surrounds the entire set of
3426 operations into the flush.
3428 :param objects: Optional; restricts the flush operation to operate
3429 only on elements that are in the given collection.
3431 This feature is for an extremely narrow set of use cases where
3432 particular objects may need to be operated upon before the
3433 full flush() occurs. It is not intended for general use.
3435 """
3437 if self._flushing:
3438 raise sa_exc.InvalidRequestError("Session is already flushing")
3440 if self._is_clean():
3441 return
3442 try:
3443 self._flushing = True
3444 self._flush(objects)
3445 finally:
3446 self._flushing = False
3448 def _flush_warning(self, method):
3449 util.warn(
3450 "Usage of the '%s' operation is not currently supported "
3451 "within the execution stage of the flush process. "
3452 "Results may not be consistent. Consider using alternative "
3453 "event listeners or connection-level operations instead." % method
3454 )
3456 def _is_clean(self):
3457 return (
3458 not self.identity_map.check_modified()
3459 and not self._deleted
3460 and not self._new
3461 )
3463 def _flush(self, objects=None):
3465 dirty = self._dirty_states
3466 if not dirty and not self._deleted and not self._new:
3467 self.identity_map._modified.clear()
3468 return
3470 flush_context = UOWTransaction(self)
3472 if self.dispatch.before_flush:
3473 self.dispatch.before_flush(self, flush_context, objects)
3474 # re-establish "dirty states" in case the listeners
3475 # added
3476 dirty = self._dirty_states
3478 deleted = set(self._deleted)
3479 new = set(self._new)
3481 dirty = set(dirty).difference(deleted)
3483 # create the set of all objects we want to operate upon
3484 if objects:
3485 # specific list passed in
3486 objset = set()
3487 for o in objects:
3488 try:
3489 state = attributes.instance_state(o)
3491 except exc.NO_STATE as err:
3492 util.raise_(
3493 exc.UnmappedInstanceError(o),
3494 replace_context=err,
3495 )
3496 objset.add(state)
3497 else:
3498 objset = None
3500 # store objects whose fate has been decided
3501 processed = set()
3503 # put all saves/updates into the flush context. detect top-level
3504 # orphans and throw them into deleted.
3505 if objset:
3506 proc = new.union(dirty).intersection(objset).difference(deleted)
3507 else:
3508 proc = new.union(dirty).difference(deleted)
3510 for state in proc:
3511 is_orphan = _state_mapper(state)._is_orphan(state)
3513 is_persistent_orphan = is_orphan and state.has_identity
3515 if (
3516 is_orphan
3517 and not is_persistent_orphan
3518 and state._orphaned_outside_of_session
3519 ):
3520 self._expunge_states([state])
3521 else:
3522 _reg = flush_context.register_object(
3523 state, isdelete=is_persistent_orphan
3524 )
3525 assert _reg, "Failed to add object to the flush context!"
3526 processed.add(state)
3528 # put all remaining deletes into the flush context.
3529 if objset:
3530 proc = deleted.intersection(objset).difference(processed)
3531 else:
3532 proc = deleted.difference(processed)
3533 for state in proc:
3534 _reg = flush_context.register_object(state, isdelete=True)
3535 assert _reg, "Failed to add object to the flush context!"
3537 if not flush_context.has_work:
3538 return
3540 flush_context.transaction = transaction = self.begin(_subtrans=True)
3541 try:
3542 self._warn_on_events = True
3543 try:
3544 flush_context.execute()
3545 finally:
3546 self._warn_on_events = False
3548 self.dispatch.after_flush(self, flush_context)
3550 flush_context.finalize_flush_changes()
3552 if not objects and self.identity_map._modified:
3553 len_ = len(self.identity_map._modified)
3555 statelib.InstanceState._commit_all_states(
3556 [
3557 (state, state.dict)
3558 for state in self.identity_map._modified
3559 ],
3560 instance_dict=self.identity_map,
3561 )
3562 util.warn(
3563 "Attribute history events accumulated on %d "
3564 "previously clean instances "
3565 "within inner-flush event handlers have been "
3566 "reset, and will not result in database updates. "
3567 "Consider using set_committed_value() within "
3568 "inner-flush event handlers to avoid this warning." % len_
3569 )
3571 # useful assertions:
3572 # if not objects:
3573 # assert not self.identity_map._modified
3574 # else:
3575 # assert self.identity_map._modified == \
3576 # self.identity_map._modified.difference(objects)
3578 self.dispatch.after_flush_postexec(self, flush_context)
3580 transaction.commit()
3582 except:
3583 with util.safe_reraise():
3584 transaction.rollback(_capture_exception=True)
3586 def bulk_save_objects(
3587 self,
3588 objects,
3589 return_defaults=False,
3590 update_changed_only=True,
3591 preserve_order=True,
3592 ):
3593 """Perform a bulk save of the given list of objects.
3595 The bulk save feature allows mapped objects to be used as the
3596 source of simple INSERT and UPDATE operations which can be more easily
3597 grouped together into higher performing "executemany"
3598 operations; the extraction of data from the objects is also performed
3599 using a lower-latency process that ignores whether or not attributes
3600 have actually been modified in the case of UPDATEs, and also ignores
3601 SQL expressions.
3603 The objects as given are not added to the session and no additional
3604 state is established on them. If the
3605 :paramref:`_orm.Session.bulk_save_objects.return_defaults` flag is set,
3606 then server-generated primary key values will be assigned to the
3607 returned objects, but **not server side defaults**; this is a
3608 limitation in the implementation. If stateful objects are desired,
3609 please use the standard :meth:`_orm.Session.add_all` approach or
3610 as an alternative newer mass-insert features such as
3611 :ref:`orm_dml_returning_objects`.
3613 .. legacy::
3615 The bulk save feature allows for a lower-latency INSERT/UPDATE
3616 of rows at the expense of most other unit-of-work features.
3617 Features such as object management, relationship handling,
3618 and SQL clause support are silently omitted in favor of raw
3619 INSERT/UPDATES of records.
3621 In SQLAlchemy 2.0, improved versions of the bulk insert/update
3622 methods are introduced, with clearer behavior and
3623 documentation, new capabilities, and much better performance.
3625 For 1.4 use, **please read the list of caveats at**
3626 :ref:`bulk_operations_caveats` **before using this method, and
3627 fully test and confirm the functionality of all code developed
3628 using these systems.**
3630 :param objects: a sequence of mapped object instances. The mapped
3631 objects are persisted as is, and are **not** associated with the
3632 :class:`.Session` afterwards.
3634 For each object, whether the object is sent as an INSERT or an
3635 UPDATE is dependent on the same rules used by the :class:`.Session`
3636 in traditional operation; if the object has the
3637 :attr:`.InstanceState.key`
3638 attribute set, then the object is assumed to be "detached" and
3639 will result in an UPDATE. Otherwise, an INSERT is used.
3641 In the case of an UPDATE, statements are grouped based on which
3642 attributes have changed, and are thus to be the subject of each
3643 SET clause. If ``update_changed_only`` is False, then all
3644 attributes present within each object are applied to the UPDATE
3645 statement, which may help in allowing the statements to be grouped
3646 together into a larger executemany(), and will also reduce the
3647 overhead of checking history on attributes.
3649 :param return_defaults: when True, rows that are missing values which
3650 generate defaults, namely integer primary key defaults and sequences,
3651 will be inserted **one at a time**, so that the primary key value
3652 is available. In particular this will allow joined-inheritance
3653 and other multi-table mappings to insert correctly without the need
3654 to provide primary key values ahead of time; however,
3655 :paramref:`.Session.bulk_save_objects.return_defaults` **greatly
3656 reduces the performance gains** of the method overall. It is strongly
3657 advised to please use the standard :meth:`_orm.Session.add_all`
3658 approach.
3660 :param update_changed_only: when True, UPDATE statements are rendered
3661 based on those attributes in each state that have logged changes.
3662 When False, all attributes present are rendered into the SET clause
3663 with the exception of primary key attributes.
3665 :param preserve_order: when True, the order of inserts and updates
3666 matches exactly the order in which the objects are given. When
3667 False, common types of objects are grouped into inserts
3668 and updates, to allow for more batching opportunities.
3670 .. versionadded:: 1.3
3672 .. seealso::
3674 :ref:`bulk_operations`
3676 :meth:`.Session.bulk_insert_mappings`
3678 :meth:`.Session.bulk_update_mappings`
3680 """
3682 obj_states = (attributes.instance_state(obj) for obj in objects)
3684 if not preserve_order:
3685 # the purpose of this sort is just so that common mappers
3686 # and persistence states are grouped together, so that groupby
3687 # will return a single group for a particular type of mapper.
3688 # it's not trying to be deterministic beyond that.
3689 obj_states = sorted(
3690 obj_states,
3691 key=lambda state: (id(state.mapper), state.key is not None),
3692 )
3694 def grouping_key(state):
3695 return (state.mapper, state.key is not None)
3697 for (mapper, isupdate), states in itertools.groupby(
3698 obj_states, grouping_key
3699 ):
3700 self._bulk_save_mappings(
3701 mapper,
3702 states,
3703 isupdate,
3704 True,
3705 return_defaults,
3706 update_changed_only,
3707 False,
3708 )
3710 def bulk_insert_mappings(
3711 self, mapper, mappings, return_defaults=False, render_nulls=False
3712 ):
3713 """Perform a bulk insert of the given list of mapping dictionaries.
3715 The bulk insert feature allows plain Python dictionaries to be used as
3716 the source of simple INSERT operations which can be more easily
3717 grouped together into higher performing "executemany"
3718 operations. Using dictionaries, there is no "history" or session
3719 state management features in use, reducing latency when inserting
3720 large numbers of simple rows.
3722 The values within the dictionaries as given are typically passed
3723 without modification into Core :meth:`_expression.Insert` constructs,
3724 after
3725 organizing the values within them across the tables to which
3726 the given mapper is mapped.
3728 .. versionadded:: 1.0.0
3730 .. legacy::
3732 The bulk insert feature allows for a lower-latency INSERT
3733 of rows at the expense of most other unit-of-work features.
3734 Features such as object management, relationship handling,
3735 and SQL clause support are silently omitted in favor of raw
3736 INSERT of records.
3738 In SQLAlchemy 2.0, improved versions of the bulk insert/update
3739 methods are introduced, with clearer behavior and
3740 documentation, new capabilities, and much better performance.
3742 For 1.4 use, **please read the list of caveats at**
3743 :ref:`bulk_operations_caveats` **before using this method, and
3744 fully test and confirm the functionality of all code developed
3745 using these systems.**
3747 :param mapper: a mapped class, or the actual :class:`_orm.Mapper`
3748 object,
3749 representing the single kind of object represented within the mapping
3750 list.
3752 :param mappings: a sequence of dictionaries, each one containing the
3753 state of the mapped row to be inserted, in terms of the attribute
3754 names on the mapped class. If the mapping refers to multiple tables,
3755 such as a joined-inheritance mapping, each dictionary must contain all
3756 keys to be populated into all tables.
3758 :param return_defaults: when True, rows that are missing values which
3759 generate defaults, namely integer primary key defaults and sequences,
3760 will be inserted **one at a time**, so that the primary key value
3761 is available. In particular this will allow joined-inheritance
3762 and other multi-table mappings to insert correctly without the need
3763 to provide primary
3764 key values ahead of time; however,
3765 :paramref:`.Session.bulk_insert_mappings.return_defaults`
3766 **greatly reduces the performance gains** of the method overall.
3767 If the rows
3768 to be inserted only refer to a single table, then there is no
3769 reason this flag should be set as the returned default information
3770 is not used.
3772 :param render_nulls: When True, a value of ``None`` will result
3773 in a NULL value being included in the INSERT statement, rather
3774 than the column being omitted from the INSERT. This allows all
3775 the rows being INSERTed to have the identical set of columns which
3776 allows the full set of rows to be batched to the DBAPI. Normally,
3777 each column-set that contains a different combination of NULL values
3778 than the previous row must omit a different series of columns from
3779 the rendered INSERT statement, which means it must be emitted as a
3780 separate statement. By passing this flag, the full set of rows
3781 are guaranteed to be batchable into one batch; the cost however is
3782 that server-side defaults which are invoked by an omitted column will
3783 be skipped, so care must be taken to ensure that these are not
3784 necessary.
3786 .. warning::
3788 When this flag is set, **server side default SQL values will
3789 not be invoked** for those columns that are inserted as NULL;
3790 the NULL value will be sent explicitly. Care must be taken
3791 to ensure that no server-side default functions need to be
3792 invoked for the operation as a whole.
3794 .. versionadded:: 1.1
3796 .. seealso::
3798 :ref:`bulk_operations`
3800 :meth:`.Session.bulk_save_objects`
3802 :meth:`.Session.bulk_update_mappings`
3804 """
3805 self._bulk_save_mappings(
3806 mapper,
3807 mappings,
3808 False,
3809 False,
3810 return_defaults,
3811 False,
3812 render_nulls,
3813 )
3815 def bulk_update_mappings(self, mapper, mappings):
3816 """Perform a bulk update of the given list of mapping dictionaries.
3818 The bulk update feature allows plain Python dictionaries to be used as
3819 the source of simple UPDATE operations which can be more easily
3820 grouped together into higher performing "executemany"
3821 operations. Using dictionaries, there is no "history" or session
3822 state management features in use, reducing latency when updating
3823 large numbers of simple rows.
3825 .. versionadded:: 1.0.0
3827 .. legacy::
3829 The bulk update feature allows for a lower-latency UPDATE
3830 of rows at the expense of most other unit-of-work features.
3831 Features such as object management, relationship handling,
3832 and SQL clause support are silently omitted in favor of raw
3833 UPDATES of records.
3835 In SQLAlchemy 2.0, improved versions of the bulk insert/update
3836 methods are introduced, with clearer behavior and
3837 documentation, new capabilities, and much better performance.
3839 For 1.4 use, **please read the list of caveats at**
3840 :ref:`bulk_operations_caveats` **before using this method, and
3841 fully test and confirm the functionality of all code developed
3842 using these systems.**
3844 :param mapper: a mapped class, or the actual :class:`_orm.Mapper`
3845 object,
3846 representing the single kind of object represented within the mapping
3847 list.
3849 :param mappings: a sequence of dictionaries, each one containing the
3850 state of the mapped row to be updated, in terms of the attribute names
3851 on the mapped class. If the mapping refers to multiple tables, such
3852 as a joined-inheritance mapping, each dictionary may contain keys
3853 corresponding to all tables. All those keys which are present and
3854 are not part of the primary key are applied to the SET clause of the
3855 UPDATE statement; the primary key values, which are required, are
3856 applied to the WHERE clause.
3859 .. seealso::
3861 :ref:`bulk_operations`
3863 :meth:`.Session.bulk_insert_mappings`
3865 :meth:`.Session.bulk_save_objects`
3867 """
3868 self._bulk_save_mappings(
3869 mapper, mappings, True, False, False, False, False
3870 )
3872 def _bulk_save_mappings(
3873 self,
3874 mapper,
3875 mappings,
3876 isupdate,
3877 isstates,
3878 return_defaults,
3879 update_changed_only,
3880 render_nulls,
3881 ):
3882 mapper = _class_to_mapper(mapper)
3883 self._flushing = True
3885 transaction = self.begin(_subtrans=True)
3886 try:
3887 if isupdate:
3888 persistence._bulk_update(
3889 mapper,
3890 mappings,
3891 transaction,
3892 isstates,
3893 update_changed_only,
3894 )
3895 else:
3896 persistence._bulk_insert(
3897 mapper,
3898 mappings,
3899 transaction,
3900 isstates,
3901 return_defaults,
3902 render_nulls,
3903 )
3904 transaction.commit()
3906 except:
3907 with util.safe_reraise():
3908 transaction.rollback(_capture_exception=True)
3909 finally:
3910 self._flushing = False
3912 def is_modified(self, instance, include_collections=True):
3913 r"""Return ``True`` if the given instance has locally
3914 modified attributes.
3916 This method retrieves the history for each instrumented
3917 attribute on the instance and performs a comparison of the current
3918 value to its previously committed value, if any.
3920 It is in effect a more expensive and accurate
3921 version of checking for the given instance in the
3922 :attr:`.Session.dirty` collection; a full test for
3923 each attribute's net "dirty" status is performed.
3925 E.g.::
3927 return session.is_modified(someobject)
3929 A few caveats to this method apply:
3931 * Instances present in the :attr:`.Session.dirty` collection may
3932 report ``False`` when tested with this method. This is because
3933 the object may have received change events via attribute mutation,
3934 thus placing it in :attr:`.Session.dirty`, but ultimately the state
3935 is the same as that loaded from the database, resulting in no net
3936 change here.
3937 * Scalar attributes may not have recorded the previously set
3938 value when a new value was applied, if the attribute was not loaded,
3939 or was expired, at the time the new value was received - in these
3940 cases, the attribute is assumed to have a change, even if there is
3941 ultimately no net change against its database value. SQLAlchemy in
3942 most cases does not need the "old" value when a set event occurs, so
3943 it skips the expense of a SQL call if the old value isn't present,
3944 based on the assumption that an UPDATE of the scalar value is
3945 usually needed, and in those few cases where it isn't, is less
3946 expensive on average than issuing a defensive SELECT.
3948 The "old" value is fetched unconditionally upon set only if the
3949 attribute container has the ``active_history`` flag set to ``True``.
3950 This flag is set typically for primary key attributes and scalar
3951 object references that are not a simple many-to-one. To set this
3952 flag for any arbitrary mapped column, use the ``active_history``
3953 argument with :func:`.column_property`.
3955 :param instance: mapped instance to be tested for pending changes.
3956 :param include_collections: Indicates if multivalued collections
3957 should be included in the operation. Setting this to ``False`` is a
3958 way to detect only local-column based properties (i.e. scalar columns
3959 or many-to-one foreign keys) that would result in an UPDATE for this
3960 instance upon flush.
3962 """
3963 state = object_state(instance)
3965 if not state.modified:
3966 return False
3968 dict_ = state.dict
3970 for attr in state.manager.attributes:
3971 if (
3972 not include_collections
3973 and hasattr(attr.impl, "get_collection")
3974 ) or not hasattr(attr.impl, "get_history"):
3975 continue
3977 (added, unchanged, deleted) = attr.impl.get_history(
3978 state, dict_, passive=attributes.NO_CHANGE
3979 )
3981 if added or deleted:
3982 return True
3983 else:
3984 return False
3986 @property
3987 def is_active(self):
3988 """True if this :class:`.Session` not in "partial rollback" state.
3990 .. versionchanged:: 1.4 The :class:`_orm.Session` no longer begins
3991 a new transaction immediately, so this attribute will be False
3992 when the :class:`_orm.Session` is first instantiated.
3994 "partial rollback" state typically indicates that the flush process
3995 of the :class:`_orm.Session` has failed, and that the
3996 :meth:`_orm.Session.rollback` method must be emitted in order to
3997 fully roll back the transaction.
3999 If this :class:`_orm.Session` is not in a transaction at all, the
4000 :class:`_orm.Session` will autobegin when it is first used, so in this
4001 case :attr:`_orm.Session.is_active` will return True.
4003 Otherwise, if this :class:`_orm.Session` is within a transaction,
4004 and that transaction has not been rolled back internally, the
4005 :attr:`_orm.Session.is_active` will also return True.
4007 .. seealso::
4009 :ref:`faq_session_rollback`
4011 :meth:`_orm.Session.in_transaction`
4013 """
4014 if self.autocommit:
4015 return (
4016 self._transaction is not None and self._transaction.is_active
4017 )
4018 else:
4019 return self._transaction is None or self._transaction.is_active
4021 identity_map = None
4022 """A mapping of object identities to objects themselves.
4024 Iterating through ``Session.identity_map.values()`` provides
4025 access to the full set of persistent objects (i.e., those
4026 that have row identity) currently in the session.
4028 .. seealso::
4030 :func:`.identity_key` - helper function to produce the keys used
4031 in this dictionary.
4033 """
4035 @property
4036 def _dirty_states(self):
4037 """The set of all persistent states considered dirty.
4039 This method returns all states that were modified including
4040 those that were possibly deleted.
4042 """
4043 return self.identity_map._dirty_states()
4045 @property
4046 def dirty(self):
4047 """The set of all persistent instances considered dirty.
4049 E.g.::
4051 some_mapped_object in session.dirty
4053 Instances are considered dirty when they were modified but not
4054 deleted.
4056 Note that this 'dirty' calculation is 'optimistic'; most
4057 attribute-setting or collection modification operations will
4058 mark an instance as 'dirty' and place it in this set, even if
4059 there is no net change to the attribute's value. At flush
4060 time, the value of each attribute is compared to its
4061 previously saved value, and if there's no net change, no SQL
4062 operation will occur (this is a more expensive operation so
4063 it's only done at flush time).
4065 To check if an instance has actionable net changes to its
4066 attributes, use the :meth:`.Session.is_modified` method.
4068 """
4069 return util.IdentitySet(
4070 [
4071 state.obj()
4072 for state in self._dirty_states
4073 if state not in self._deleted
4074 ]
4075 )
4077 @property
4078 def deleted(self):
4079 "The set of all instances marked as 'deleted' within this ``Session``"
4081 return util.IdentitySet(list(self._deleted.values()))
4083 @property
4084 def new(self):
4085 "The set of all instances marked as 'new' within this ``Session``."
4087 return util.IdentitySet(list(self._new.values()))
4090class sessionmaker(_SessionClassMethods):
4091 """A configurable :class:`.Session` factory.
4093 The :class:`.sessionmaker` factory generates new
4094 :class:`.Session` objects when called, creating them given
4095 the configurational arguments established here.
4097 e.g.::
4099 from sqlalchemy import create_engine
4100 from sqlalchemy.orm import sessionmaker
4102 # an Engine, which the Session will use for connection
4103 # resources
4104 engine = create_engine('postgresql://scott:tiger@localhost/')
4106 Session = sessionmaker(engine)
4108 with Session() as session:
4109 session.add(some_object)
4110 session.add(some_other_object)
4111 session.commit()
4113 Context manager use is optional; otherwise, the returned
4114 :class:`_orm.Session` object may be closed explicitly via the
4115 :meth:`_orm.Session.close` method. Using a
4116 ``try:/finally:`` block is optional, however will ensure that the close
4117 takes place even if there are database errors::
4119 session = Session()
4120 try:
4121 session.add(some_object)
4122 session.add(some_other_object)
4123 session.commit()
4124 finally:
4125 session.close()
4127 :class:`.sessionmaker` acts as a factory for :class:`_orm.Session`
4128 objects in the same way as an :class:`_engine.Engine` acts as a factory
4129 for :class:`_engine.Connection` objects. In this way it also includes
4130 a :meth:`_orm.sessionmaker.begin` method, that provides a context
4131 manager which both begins and commits a transaction, as well as closes
4132 out the :class:`_orm.Session` when complete, rolling back the transaction
4133 if any errors occur::
4135 Session = sessionmaker(engine)
4137 with Session.begin() as session:
4138 session.add(some_object)
4139 session.add(some_other_object)
4140 # commits transaction, closes session
4142 .. versionadded:: 1.4
4144 When calling upon :class:`_orm.sessionmaker` to construct a
4145 :class:`_orm.Session`, keyword arguments may also be passed to the
4146 method; these arguments will override that of the globally configured
4147 parameters. Below we use a :class:`_orm.sessionmaker` bound to a certain
4148 :class:`_engine.Engine` to produce a :class:`_orm.Session` that is instead
4149 bound to a specific :class:`_engine.Connection` procured from that engine::
4151 Session = sessionmaker(engine)
4153 # bind an individual session to a connection
4155 with engine.connect() as connection:
4156 with Session(bind=connection) as session:
4157 # work with session
4159 The class also includes a method :meth:`_orm.sessionmaker.configure`, which
4160 can be used to specify additional keyword arguments to the factory, which
4161 will take effect for subsequent :class:`.Session` objects generated. This
4162 is usually used to associate one or more :class:`_engine.Engine` objects
4163 with an existing
4164 :class:`.sessionmaker` factory before it is first used::
4166 # application starts, sessionmaker does not have
4167 # an engine bound yet
4168 Session = sessionmaker()
4170 # ... later, when an engine URL is read from a configuration
4171 # file or other events allow the engine to be created
4172 engine = create_engine('sqlite:///foo.db')
4173 Session.configure(bind=engine)
4175 sess = Session()
4176 # work with session
4178 .. seealso::
4180 :ref:`session_getting` - introductory text on creating
4181 sessions using :class:`.sessionmaker`.
4183 """
4185 def __init__(
4186 self,
4187 bind=None,
4188 class_=Session,
4189 autoflush=True,
4190 autocommit=False,
4191 expire_on_commit=True,
4192 info=None,
4193 **kw
4194 ):
4195 r"""Construct a new :class:`.sessionmaker`.
4197 All arguments here except for ``class_`` correspond to arguments
4198 accepted by :class:`.Session` directly. See the
4199 :meth:`.Session.__init__` docstring for more details on parameters.
4201 :param bind: a :class:`_engine.Engine` or other :class:`.Connectable`
4202 with
4203 which newly created :class:`.Session` objects will be associated.
4204 :param class\_: class to use in order to create new :class:`.Session`
4205 objects. Defaults to :class:`.Session`.
4206 :param autoflush: The autoflush setting to use with newly created
4207 :class:`.Session` objects.
4208 :param autocommit: The autocommit setting to use with newly created
4209 :class:`.Session` objects.
4210 :param expire_on_commit=True: the
4211 :paramref:`_orm.Session.expire_on_commit` setting to use
4212 with newly created :class:`.Session` objects.
4214 :param info: optional dictionary of information that will be available
4215 via :attr:`.Session.info`. Note this dictionary is *updated*, not
4216 replaced, when the ``info`` parameter is specified to the specific
4217 :class:`.Session` construction operation.
4219 :param \**kw: all other keyword arguments are passed to the
4220 constructor of newly created :class:`.Session` objects.
4222 """
4223 kw["bind"] = bind
4224 kw["autoflush"] = autoflush
4225 kw["autocommit"] = autocommit
4226 kw["expire_on_commit"] = expire_on_commit
4227 if info is not None:
4228 kw["info"] = info
4229 self.kw = kw
4230 # make our own subclass of the given class, so that
4231 # events can be associated with it specifically.
4232 self.class_ = type(class_.__name__, (class_,), {})
4234 def begin(self):
4235 """Produce a context manager that both provides a new
4236 :class:`_orm.Session` as well as a transaction that commits.
4239 e.g.::
4241 Session = sessionmaker(some_engine)
4243 with Session.begin() as session:
4244 session.add(some_object)
4246 # commits transaction, closes session
4248 .. versionadded:: 1.4
4251 """
4253 session = self()
4254 return session._maker_context_manager()
4256 def __call__(self, **local_kw):
4257 """Produce a new :class:`.Session` object using the configuration
4258 established in this :class:`.sessionmaker`.
4260 In Python, the ``__call__`` method is invoked on an object when
4261 it is "called" in the same way as a function::
4263 Session = sessionmaker()
4264 session = Session() # invokes sessionmaker.__call__()
4266 """
4267 for k, v in self.kw.items():
4268 if k == "info" and "info" in local_kw:
4269 d = v.copy()
4270 d.update(local_kw["info"])
4271 local_kw["info"] = d
4272 else:
4273 local_kw.setdefault(k, v)
4274 return self.class_(**local_kw)
4276 def configure(self, **new_kw):
4277 """(Re)configure the arguments for this sessionmaker.
4279 e.g.::
4281 Session = sessionmaker()
4283 Session.configure(bind=create_engine('sqlite://'))
4284 """
4285 self.kw.update(new_kw)
4287 def __repr__(self):
4288 return "%s(class_=%r, %s)" % (
4289 self.__class__.__name__,
4290 self.class_.__name__,
4291 ", ".join("%s=%r" % (k, v) for k, v in self.kw.items()),
4292 )
4295def close_all_sessions():
4296 """Close all sessions in memory.
4298 This function consults a global registry of all :class:`.Session` objects
4299 and calls :meth:`.Session.close` on them, which resets them to a clean
4300 state.
4302 This function is not for general use but may be useful for test suites
4303 within the teardown scheme.
4305 .. versionadded:: 1.3
4307 """
4309 for sess in _sessions.values():
4310 sess.close()
4313def make_transient(instance):
4314 """Alter the state of the given instance so that it is :term:`transient`.
4316 .. note::
4318 :func:`.make_transient` is a special-case function for
4319 advanced use cases only.
4321 The given mapped instance is assumed to be in the :term:`persistent` or
4322 :term:`detached` state. The function will remove its association with any
4323 :class:`.Session` as well as its :attr:`.InstanceState.identity`. The
4324 effect is that the object will behave as though it were newly constructed,
4325 except retaining any attribute / collection values that were loaded at the
4326 time of the call. The :attr:`.InstanceState.deleted` flag is also reset
4327 if this object had been deleted as a result of using
4328 :meth:`.Session.delete`.
4330 .. warning::
4332 :func:`.make_transient` does **not** "unexpire" or otherwise eagerly
4333 load ORM-mapped attributes that are not currently loaded at the time
4334 the function is called. This includes attributes which:
4336 * were expired via :meth:`.Session.expire`
4338 * were expired as the natural effect of committing a session
4339 transaction, e.g. :meth:`.Session.commit`
4341 * are normally :term:`lazy loaded` but are not currently loaded
4343 * are "deferred" via :ref:`deferred` and are not yet loaded
4345 * were not present in the query which loaded this object, such as that
4346 which is common in joined table inheritance and other scenarios.
4348 After :func:`.make_transient` is called, unloaded attributes such
4349 as those above will normally resolve to the value ``None`` when
4350 accessed, or an empty collection for a collection-oriented attribute.
4351 As the object is transient and un-associated with any database
4352 identity, it will no longer retrieve these values.
4354 .. seealso::
4356 :func:`.make_transient_to_detached`
4358 """
4359 state = attributes.instance_state(instance)
4360 s = _state_session(state)
4361 if s:
4362 s._expunge_states([state])
4364 # remove expired state
4365 state.expired_attributes.clear()
4367 # remove deferred callables
4368 if state.callables:
4369 del state.callables
4371 if state.key:
4372 del state.key
4373 if state._deleted:
4374 del state._deleted
4377def make_transient_to_detached(instance):
4378 """Make the given transient instance :term:`detached`.
4380 .. note::
4382 :func:`.make_transient_to_detached` is a special-case function for
4383 advanced use cases only.
4385 All attribute history on the given instance
4386 will be reset as though the instance were freshly loaded
4387 from a query. Missing attributes will be marked as expired.
4388 The primary key attributes of the object, which are required, will be made
4389 into the "key" of the instance.
4391 The object can then be added to a session, or merged
4392 possibly with the load=False flag, at which point it will look
4393 as if it were loaded that way, without emitting SQL.
4395 This is a special use case function that differs from a normal
4396 call to :meth:`.Session.merge` in that a given persistent state
4397 can be manufactured without any SQL calls.
4399 .. seealso::
4401 :func:`.make_transient`
4403 :meth:`.Session.enable_relationship_loading`
4405 """
4406 state = attributes.instance_state(instance)
4407 if state.session_id or state.key:
4408 raise sa_exc.InvalidRequestError("Given object must be transient")
4409 state.key = state.mapper._identity_key_from_state(state)
4410 if state._deleted:
4411 del state._deleted
4412 state._commit_all(state.dict)
4413 state._expire_attributes(state.dict, state.unloaded_expirable)
4416def object_session(instance):
4417 """Return the :class:`.Session` to which the given instance belongs.
4419 This is essentially the same as the :attr:`.InstanceState.session`
4420 accessor. See that attribute for details.
4422 """
4424 try:
4425 state = attributes.instance_state(instance)
4426 except exc.NO_STATE as err:
4427 util.raise_(
4428 exc.UnmappedInstanceError(instance),
4429 replace_context=err,
4430 )
4431 else:
4432 return _state_session(state)
4435_new_sessionid = util.counter()