· 7 years ago · Feb 19, 2019, 12:44 AM
1# orm/session.py
2# Copyright (C) 2005-2019 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: http://www.opensource.org/licenses/mit-license.php
7"""Provides the Session class and related utilities."""
8
9
10import itertools
11import sys
12import weakref
13
14from . import attributes
15from . import exc
16from . import identity
17from . import loading
18from . import persistence
19from . import query
20from . import state as statelib
21from .base import _class_to_mapper
22from .base import _none_set
23from .base import _state_mapper
24from .base import instance_str
25from .base import object_mapper
26from .base import object_state
27from .base import state_str
28from .deprecated_interfaces import SessionExtension
29from .unitofwork import UOWTransaction
30from .. import engine
31from .. import exc as sa_exc
32from .. import sql
33from .. import util
34from ..inspection import inspect
35from ..sql import expression
36from ..sql import util as sql_util
37
38
39__all__ = ["Session", "SessionTransaction", "SessionExtension", "sessionmaker"]
40
41_sessions = weakref.WeakValueDictionary()
42"""Weak-referencing dictionary of :class:`.Session` objects.
43"""
44
45
46def _state_session(state):
47 """Given an :class:`.InstanceState`, return the :class:`.Session`
48 associated, if any.
49 """
50 if state.session_id:
51 try:
52 return _sessions[state.session_id]
53 except KeyError:
54 pass
55 return None
56
57
58class _SessionClassMethods(object):
59 """Class-level methods for :class:`.Session`, :class:`.sessionmaker`."""
60
61 @classmethod
62 @util.deprecated(
63 "1.3",
64 "The :meth:`.Session.close_all` method is deprecated and will be "
65 "removed in a future release. Please refer to "
66 ":func:`.session.close_all_sessions`.",
67 )
68 def close_all(cls):
69 """Close *all* sessions in memory."""
70
71 close_all_sessions()
72
73 @classmethod
74 @util.dependencies("sqlalchemy.orm.util")
75 def identity_key(cls, orm_util, *args, **kwargs):
76 """Return an identity key.
77
78 This is an alias of :func:`.util.identity_key`.
79
80 """
81 return orm_util.identity_key(*args, **kwargs)
82
83 @classmethod
84 def object_session(cls, instance):
85 """Return the :class:`.Session` to which an object belongs.
86
87 This is an alias of :func:`.object_session`.
88
89 """
90
91 return object_session(instance)
92
93
94ACTIVE = util.symbol("ACTIVE")
95PREPARED = util.symbol("PREPARED")
96COMMITTED = util.symbol("COMMITTED")
97DEACTIVE = util.symbol("DEACTIVE")
98CLOSED = util.symbol("CLOSED")
99
100
101class SessionTransaction(object):
102 """A :class:`.Session`-level transaction.
103
104 :class:`.SessionTransaction` is a mostly behind-the-scenes object
105 not normally referenced directly by application code. It coordinates
106 among multiple :class:`.Connection` objects, maintaining a database
107 transaction for each one individually, committing or rolling them
108 back all at once. It also provides optional two-phase commit behavior
109 which can augment this coordination operation.
110
111 The :attr:`.Session.transaction` attribute of :class:`.Session`
112 refers to the current :class:`.SessionTransaction` object in use, if any.
113 The :attr:`.SessionTransaction.parent` attribute refers to the parent
114 :class:`.SessionTransaction` in the stack of :class:`.SessionTransaction`
115 objects. If this attribute is ``None``, then this is the top of the stack.
116 If non-``None``, then this :class:`.SessionTransaction` refers either
117 to a so-called "subtransaction" or a "nested" transaction. A
118 "subtransaction" is a scoping concept that demarcates an inner portion
119 of the outermost "real" transaction. A nested transaction, which
120 is indicated when the :attr:`.SessionTransaction.nested`
121 attribute is also True, indicates that this :class:`.SessionTransaction`
122 corresponds to a SAVEPOINT.
123
124 **Life Cycle**
125
126 A :class:`.SessionTransaction` is associated with a :class:`.Session`
127 in its default mode of ``autocommit=False`` immediately, associated
128 with no database connections. As the :class:`.Session` is called upon
129 to emit SQL on behalf of various :class:`.Engine` or :class:`.Connection`
130 objects, a corresponding :class:`.Connection` and associated
131 :class:`.Transaction` is added to a collection within the
132 :class:`.SessionTransaction` object, becoming one of the
133 connection/transaction pairs maintained by the
134 :class:`.SessionTransaction`. The start of a :class:`.SessionTransaction`
135 can be tracked using the :meth:`.SessionEvents.after_transaction_create`
136 event.
137
138 The lifespan of the :class:`.SessionTransaction` ends when the
139 :meth:`.Session.commit`, :meth:`.Session.rollback` or
140 :meth:`.Session.close` methods are called. At this point, the
141 :class:`.SessionTransaction` removes its association with its parent
142 :class:`.Session`. A :class:`.Session` that is in ``autocommit=False``
143 mode will create a new :class:`.SessionTransaction` to replace it
144 immediately, whereas a :class:`.Session` that's in ``autocommit=True``
145 mode will remain without a :class:`.SessionTransaction` until the
146 :meth:`.Session.begin` method is called. The end of a
147 :class:`.SessionTransaction` can be tracked using the
148 :meth:`.SessionEvents.after_transaction_end` event.
149
150 **Nesting and Subtransactions**
151
152 Another detail of :class:`.SessionTransaction` behavior is that it is
153 capable of "nesting". This means that the :meth:`.Session.begin` method
154 can be called while an existing :class:`.SessionTransaction` is already
155 present, producing a new :class:`.SessionTransaction` that temporarily
156 replaces the parent :class:`.SessionTransaction`. When a
157 :class:`.SessionTransaction` is produced as nested, it assigns itself to
158 the :attr:`.Session.transaction` attribute, and it additionally will assign
159 the previous :class:`.SessionTransaction` to its :attr:`.Session.parent`
160 attribute. The behavior is effectively a
161 stack, where :attr:`.Session.transaction` refers to the current head of
162 the stack, and the :attr:`.SessionTransaction.parent` attribute allows
163 traversal up the stack until :attr:`.SessionTransaction.parent` is
164 ``None``, indicating the top of the stack.
165
166 When the scope of :class:`.SessionTransaction` is ended via
167 :meth:`.Session.commit` or :meth:`.Session.rollback`, it restores its
168 parent :class:`.SessionTransaction` back onto the
169 :attr:`.Session.transaction` attribute.
170
171 The purpose of this stack is to allow nesting of
172 :meth:`.Session.rollback` or :meth:`.Session.commit` calls in context
173 with various flavors of :meth:`.Session.begin`. This nesting behavior
174 applies to when :meth:`.Session.begin_nested` is used to emit a
175 SAVEPOINT transaction, and is also used to produce a so-called
176 "subtransaction" which allows a block of code to use a
177 begin/rollback/commit sequence regardless of whether or not its enclosing
178 code block has begun a transaction. The :meth:`.flush` method, whether
179 called explicitly or via autoflush, is the primary consumer of the
180 "subtransaction" feature, in that it wishes to guarantee that it works
181 within in a transaction block regardless of whether or not the
182 :class:`.Session` is in transactional mode when the method is called.
183
184 Note that the flush process that occurs within the "autoflush" feature
185 as well as when the :meth:`.Session.flush` method is used **always**
186 creates a :class:`.SessionTransaction` object. This object is normally
187 a subtransaction, unless the :class:`.Session` is in autocommit mode
188 and no transaction exists at all, in which case it's the outermost
189 transaction. Any event-handling logic or other inspection logic
190 needs to take into account whether a :class:`.SessionTransaction`
191 is the outermost transaction, a subtransaction, or a "nested" / SAVEPOINT
192 transaction.
193
194 .. seealso::
195
196 :meth:`.Session.rollback`
197
198 :meth:`.Session.commit`
199
200 :meth:`.Session.begin`
201
202 :meth:`.Session.begin_nested`
203
204 :attr:`.Session.is_active`
205
206 :meth:`.SessionEvents.after_transaction_create`
207
208 :meth:`.SessionEvents.after_transaction_end`
209
210 :meth:`.SessionEvents.after_commit`
211
212 :meth:`.SessionEvents.after_rollback`
213
214 :meth:`.SessionEvents.after_soft_rollback`
215
216 """
217
218 _rollback_exception = None
219
220 def __init__(self, session, parent=None, nested=False):
221 self.session = session
222 self._connections = {}
223 self._parent = parent
224 self.nested = nested
225 self._state = ACTIVE
226 if not parent and nested:
227 raise sa_exc.InvalidRequestError(
228 "Can't start a SAVEPOINT transaction when no existing "
229 "transaction is in progress"
230 )
231
232 if self.session._enable_transaction_accounting:
233 self._take_snapshot()
234
235 self.session.dispatch.after_transaction_create(self.session, self)
236
237 @property
238 def parent(self):
239 """The parent :class:`.SessionTransaction` of this
240 :class:`.SessionTransaction`.
241
242 If this attribute is ``None``, indicates this
243 :class:`.SessionTransaction` is at the top of the stack, and
244 corresponds to a real "COMMIT"/"ROLLBACK"
245 block. If non-``None``, then this is either a "subtransaction"
246 or a "nested" / SAVEPOINT transaction. If the
247 :attr:`.SessionTransaction.nested` attribute is ``True``, then
248 this is a SAVEPOINT, and if ``False``, indicates this a subtransaction.
249
250 .. versionadded:: 1.0.16 - use ._parent for previous versions
251
252 """
253 return self._parent
254
255 nested = False
256 """Indicates if this is a nested, or SAVEPOINT, transaction.
257
258 When :attr:`.SessionTransaction.nested` is True, it is expected
259 that :attr:`.SessionTransaction.parent` will be True as well.
260
261 """
262
263 @property
264 def is_active(self):
265 return self.session is not None and self._state is ACTIVE
266
267 def _assert_active(
268 self,
269 prepared_ok=False,
270 rollback_ok=False,
271 deactive_ok=False,
272 closed_msg="This transaction is closed",
273 ):
274 if self._state is COMMITTED:
275 raise sa_exc.InvalidRequestError(
276 "This session is in 'committed' state; no further "
277 "SQL can be emitted within this transaction."
278 )
279 elif self._state is PREPARED:
280 if not prepared_ok:
281 raise sa_exc.InvalidRequestError(
282 "This session is in 'prepared' state; no further "
283 "SQL can be emitted within this transaction."
284 )
285 elif self._state is DEACTIVE:
286 if not deactive_ok and not rollback_ok:
287 if self._rollback_exception:
288 raise sa_exc.InvalidRequestError(
289 "This Session's transaction has been rolled back "
290 "due to a previous exception during flush."
291 " To begin a new transaction with this Session, "
292 "first issue Session.rollback()."
293 " Original exception was: %s"
294 % self._rollback_exception
295 )
296 elif not deactive_ok:
297 raise sa_exc.InvalidRequestError(
298 "This session is in 'inactive' state, due to the "
299 "SQL transaction being rolled back; no further "
300 "SQL can be emitted within this transaction."
301 )
302 elif self._state is CLOSED:
303 raise sa_exc.ResourceClosedError(closed_msg)
304
305 @property
306 def _is_transaction_boundary(self):
307 return self.nested or not self._parent
308
309 def connection(self, bindkey, execution_options=None, **kwargs):
310 self._assert_active()
311 bind = self.session.get_bind(bindkey, **kwargs)
312 return self._connection_for_bind(bind, execution_options)
313
314 def _begin(self, nested=False):
315 self._assert_active()
316 return SessionTransaction(self.session, self, nested=nested)
317
318 def _iterate_self_and_parents(self, upto=None):
319
320 current = self
321 result = ()
322 while current:
323 result += (current,)
324 if current._parent is upto:
325 break
326 elif current._parent is None:
327 raise sa_exc.InvalidRequestError(
328 "Transaction %s is not on the active transaction list"
329 % (upto)
330 )
331 else:
332 current = current._parent
333
334 return result
335
336 def _take_snapshot(self):
337 if not self._is_transaction_boundary:
338 self._new = self._parent._new
339 self._deleted = self._parent._deleted
340 self._dirty = self._parent._dirty
341 self._key_switches = self._parent._key_switches
342 return
343
344 if not self.session._flushing:
345 self.session.flush()
346
347 self._new = weakref.WeakKeyDictionary()
348 self._deleted = weakref.WeakKeyDictionary()
349 self._dirty = weakref.WeakKeyDictionary()
350 self._key_switches = weakref.WeakKeyDictionary()
351
352 def _restore_snapshot(self, dirty_only=False):
353 """Restore the restoration state taken before a transaction began.
354
355 Corresponds to a rollback.
356
357 """
358 assert self._is_transaction_boundary
359
360 to_expunge = set(self._new).union(self.session._new)
361 self.session._expunge_states(to_expunge, to_transient=True)
362
363 for s, (oldkey, newkey) in self._key_switches.items():
364 # we probably can do this conditionally based on
365 # if we expunged or not, but safe_discard does that anyway
366 self.session.identity_map.safe_discard(s)
367
368 # restore the old key
369 s.key = oldkey
370
371 # now restore the object, but only if we didn't expunge
372 if s not in to_expunge:
373 self.session.identity_map.replace(s)
374
375 for s in set(self._deleted).union(self.session._deleted):
376 self.session._update_impl(s, revert_deletion=True)
377
378 assert not self.session._deleted
379
380 for s in self.session.identity_map.all_states():
381 if not dirty_only or s.modified or s in self._dirty:
382 s._expire(s.dict, self.session.identity_map._modified)
383
384 def _remove_snapshot(self):
385 """Remove the restoration state taken before a transaction began.
386
387 Corresponds to a commit.
388
389 """
390 assert self._is_transaction_boundary
391
392 if not self.nested and self.session.expire_on_commit:
393 for s in self.session.identity_map.all_states():
394 s._expire(s.dict, self.session.identity_map._modified)
395
396 statelib.InstanceState._detach_states(
397 list(self._deleted), self.session
398 )
399 self._deleted.clear()
400 elif self.nested:
401 self._parent._new.update(self._new)
402 self._parent._dirty.update(self._dirty)
403 self._parent._deleted.update(self._deleted)
404 self._parent._key_switches.update(self._key_switches)
405
406 def _connection_for_bind(self, bind, execution_options):
407 self._assert_active()
408
409 if bind in self._connections:
410 if execution_options:
411 util.warn(
412 "Connection is already established for the "
413 "given bind; execution_options ignored"
414 )
415 return self._connections[bind][0]
416
417 if self._parent:
418 conn = self._parent._connection_for_bind(bind, execution_options)
419 if not self.nested:
420 return conn
421 else:
422 if isinstance(bind, engine.Connection):
423 conn = bind
424 if conn.engine in self._connections:
425 raise sa_exc.InvalidRequestError(
426 "Session already has a Connection associated for the "
427 "given Connection's Engine"
428 )
429 else:
430 conn = bind._contextual_connect()
431
432 if execution_options:
433 conn = conn.execution_options(**execution_options)
434
435 if self.session.twophase and self._parent is None:
436 transaction = conn.begin_twophase()
437 elif self.nested:
438 transaction = conn.begin_nested()
439 else:
440 transaction = conn.begin()
441
442 self._connections[conn] = self._connections[conn.engine] = (
443 conn,
444 transaction,
445 conn is not bind,
446 )
447 self.session.dispatch.after_begin(self.session, self, conn)
448 return conn
449
450 def prepare(self):
451 if self._parent is not None or not self.session.twophase:
452 raise sa_exc.InvalidRequestError(
453 "'twophase' mode not enabled, or not root transaction; "
454 "can't prepare."
455 )
456 self._prepare_impl()
457
458 def _prepare_impl(self):
459 self._assert_active()
460 if self._parent is None or self.nested:
461 self.session.dispatch.before_commit(self.session)
462
463 stx = self.session.transaction
464 if stx is not self:
465 for subtransaction in stx._iterate_self_and_parents(upto=self):
466 subtransaction.commit()
467
468 if not self.session._flushing:
469 for _flush_guard in range(100):
470 if self.session._is_clean():
471 break
472 self.session.flush()
473 else:
474 raise exc.FlushError(
475 "Over 100 subsequent flushes have occurred within "
476 "session.commit() - is an after_flush() hook "
477 "creating new objects?"
478 )
479
480 if self._parent is None and self.session.twophase:
481 try:
482 for t in set(self._connections.values()):
483 t[1].prepare()
484 except:
485 with util.safe_reraise():
486 self.rollback()
487
488 self._state = PREPARED
489
490 def commit(self):
491 self._assert_active(prepared_ok=True)
492 if self._state is not PREPARED:
493 self._prepare_impl()
494
495 if self._parent is None or self.nested:
496 for t in set(self._connections.values()):
497 t[1].commit()
498
499 self._state = COMMITTED
500 self.session.dispatch.after_commit(self.session)
501
502 if self.session._enable_transaction_accounting:
503 self._remove_snapshot()
504
505 self.close()
506 return self._parent
507
508 def rollback(self, _capture_exception=False):
509 self._assert_active(prepared_ok=True, rollback_ok=True)
510
511 stx = self.session.transaction
512 if stx is not self:
513 for subtransaction in stx._iterate_self_and_parents(upto=self):
514 subtransaction.close()
515
516 boundary = self
517 rollback_err = None
518 if self._state in (ACTIVE, PREPARED):
519 for transaction in self._iterate_self_and_parents():
520 if transaction._parent is None or transaction.nested:
521 try:
522 for t in set(transaction._connections.values()):
523 t[1].rollback()
524
525 transaction._state = DEACTIVE
526 self.session.dispatch.after_rollback(self.session)
527 except:
528 rollback_err = sys.exc_info()
529 finally:
530 transaction._state = DEACTIVE
531 if self.session._enable_transaction_accounting:
532 transaction._restore_snapshot(
533 dirty_only=transaction.nested
534 )
535 boundary = transaction
536 break
537 else:
538 transaction._state = DEACTIVE
539
540 sess = self.session
541
542 if (
543 not rollback_err
544 and sess._enable_transaction_accounting
545 and not sess._is_clean()
546 ):
547
548 # if items were added, deleted, or mutated
549 # here, we need to re-restore the snapshot
550 util.warn(
551 "Session's state has been changed on "
552 "a non-active transaction - this state "
553 "will be discarded."
554 )
555 boundary._restore_snapshot(dirty_only=boundary.nested)
556
557 self.close()
558
559 if self._parent and _capture_exception:
560 self._parent._rollback_exception = sys.exc_info()[1]
561
562 if rollback_err:
563 util.reraise(*rollback_err)
564
565 sess.dispatch.after_soft_rollback(sess, self)
566
567 return self._parent
568
569 def close(self, invalidate=False):
570 self.session.transaction = self._parent
571 if self._parent is None:
572 for connection, transaction, autoclose in set(
573 self._connections.values()
574 ):
575 if invalidate:
576 connection.invalidate()
577 if autoclose:
578 connection.close()
579 else:
580 transaction.close()
581
582 self._state = CLOSED
583 self.session.dispatch.after_transaction_end(self.session, self)
584
585 if self._parent is None:
586 if not self.session.autocommit:
587 self.session.begin()
588 self.session = None
589 self._connections = None
590
591 def __enter__(self):
592 return self
593
594 def __exit__(self, type_, value, traceback):
595 self._assert_active(deactive_ok=True, prepared_ok=True)
596 if self.session.transaction is None:
597 return
598 if type_ is None:
599 try:
600 self.commit()
601 except:
602 with util.safe_reraise():
603 self.rollback()
604 else:
605 self.rollback()
606
607
608class Session(_SessionClassMethods):
609 """Manages persistence operations for ORM-mapped objects.
610
611 The Session's usage paradigm is described at :doc:`/orm/session`.
612
613
614 """
615
616 public_methods = (
617 "__contains__",
618 "__iter__",
619 "add",
620 "add_all",
621 "begin",
622 "begin_nested",
623 "close",
624 "commit",
625 "connection",
626 "delete",
627 "execute",
628 "expire",
629 "expire_all",
630 "expunge",
631 "expunge_all",
632 "flush",
633 "get_bind",
634 "is_modified",
635 "bulk_save_objects",
636 "bulk_insert_mappings",
637 "bulk_update_mappings",
638 "merge",
639 "query",
640 "refresh",
641 "rollback",
642 "scalar",
643 )
644
645 @util.deprecated_params(
646 weak_identity_map=(
647 "1.0",
648 "The :paramref:`.Session.weak_identity_map` parameter as well as "
649 "the strong-referencing identity map are deprecated, and will be "
650 "removed in a future release. For the use case where objects "
651 "present in a :class:`.Session` need to be automatically strong "
652 "referenced, see the recipe at "
653 ":ref:`session_referencing_behavior` for an event-based approach "
654 "to maintaining strong identity references. ",
655 ),
656 _enable_transaction_accounting=(
657 "0.7",
658 "The :paramref:`.Session._enable_transaction_accounting` "
659 "parameter is deprecated and will be removed in a future release.",
660 ),
661 extension=(
662 "0.7",
663 ":class:`.SessionExtension` is deprecated in favor of the "
664 ":class:`.SessionEvents` listener interface. The "
665 ":paramref:`.Session.extension` parameter will be "
666 "removed in a future release.",
667 ),
668 )
669 def __init__(
670 self,
671 bind=None,
672 autoflush=True,
673 expire_on_commit=True,
674 _enable_transaction_accounting=True,
675 autocommit=False,
676 twophase=False,
677 weak_identity_map=None,
678 binds=None,
679 extension=None,
680 enable_baked_queries=True,
681 info=None,
682 query_cls=None,
683 ):
684 r"""Construct a new Session.
685
686 See also the :class:`.sessionmaker` function which is used to
687 generate a :class:`.Session`-producing callable with a given
688 set of arguments.
689
690 :param autocommit:
691
692 .. warning::
693
694 The autocommit flag is **not for general use**, and if it is
695 used, queries should only be invoked within the span of a
696 :meth:`.Session.begin` / :meth:`.Session.commit` pair. Executing
697 queries outside of a demarcated transaction is a legacy mode
698 of usage, and can in some cases lead to concurrent connection
699 checkouts.
700
701 Defaults to ``False``. When ``True``, the
702 :class:`.Session` does not keep a persistent transaction running,
703 and will acquire connections from the engine on an as-needed basis,
704 returning them immediately after their use. Flushes will begin and
705 commit (or possibly rollback) their own transaction if no
706 transaction is present. When using this mode, the
707 :meth:`.Session.begin` method is used to explicitly start
708 transactions.
709
710 .. seealso::
711
712 :ref:`session_autocommit`
713
714 :param autoflush: When ``True``, all query operations will issue a
715 :meth:`~.Session.flush` call to this ``Session`` before proceeding.
716 This is a convenience feature so that :meth:`~.Session.flush` need
717 not be called repeatedly in order for database queries to retrieve
718 results. It's typical that ``autoflush`` is used in conjunction
719 with ``autocommit=False``. In this scenario, explicit calls to
720 :meth:`~.Session.flush` are rarely needed; you usually only need to
721 call :meth:`~.Session.commit` (which flushes) to finalize changes.
722
723 :param bind: An optional :class:`.Engine` or :class:`.Connection` to
724 which this ``Session`` should be bound. When specified, all SQL
725 operations performed by this session will execute via this
726 connectable.
727
728 :param binds: A dictionary which may specify any number of
729 :class:`.Engine` or :class:`.Connection` objects as the source of
730 connectivity for SQL operations on a per-entity basis. The keys
731 of the dictionary consist of any series of mapped classes,
732 arbitrary Python classes that are bases for mapped classes,
733 :class:`.Table` objects and :class:`.Mapper` objects. The
734 values of the dictionary are then instances of :class:`.Engine`
735 or less commonly :class:`.Connection` objects. Operations which
736 proceed relative to a particular mapped class will consult this
737 dictionary for the closest matching entity in order to determine
738 which :class:`.Engine` should be used for a particular SQL
739 operation. The complete heuristics for resolution are
740 described at :meth:`.Session.get_bind`. Usage looks like::
741
742 Session = sessionmaker(binds={
743 SomeMappedClass: create_engine('postgresql://engine1'),
744 SomeDeclarativeBase: create_engine('postgresql://engine2'),
745 some_mapper: create_engine('postgresql://engine3'),
746 some_table: create_engine('postgresql://engine4'),
747 })
748
749 .. seealso::
750
751 :ref:`session_partitioning`
752
753 :meth:`.Session.bind_mapper`
754
755 :meth:`.Session.bind_table`
756
757 :meth:`.Session.get_bind`
758
759
760 :param \class_: Specify an alternate class other than
761 ``sqlalchemy.orm.session.Session`` which should be used by the
762 returned class. This is the only argument that is local to the
763 :class:`.sessionmaker` function, and is not sent directly to the
764 constructor for ``Session``.
765
766 :param enable_baked_queries: defaults to ``True``. A flag consumed
767 by the :mod:`sqlalchemy.ext.baked` extension to determine if
768 "baked queries" should be cached, as is the normal operation
769 of this extension. When set to ``False``, all caching is disabled,
770 including baked queries defined by the calling application as
771 well as those used internally. Setting this flag to ``False``
772 can significantly reduce memory use, however will also degrade
773 performance for those areas that make use of baked queries
774 (such as relationship loaders). Additionally, baked query
775 logic in the calling application or potentially within the ORM
776 that may be malfunctioning due to cache key collisions or similar
777 can be flagged by observing if this flag resolves the issue.
778
779 .. versionadded:: 1.2
780
781 :param _enable_transaction_accounting: A
782 legacy-only flag which when ``False`` disables *all* 0.5-style
783 object accounting on transaction boundaries.
784
785 :param expire_on_commit: Defaults to ``True``. When ``True``, all
786 instances will be fully expired after each :meth:`~.commit`,
787 so that all attribute/object access subsequent to a completed
788 transaction will load from the most recent database state.
789
790 :param extension: An optional
791 :class:`~.SessionExtension` instance, or a list
792 of such instances, which will receive pre- and post- commit and
793 flush events, as well as a post-rollback event.
794
795 :param info: optional dictionary of arbitrary data to be associated
796 with this :class:`.Session`. Is available via the
797 :attr:`.Session.info` attribute. Note the dictionary is copied at
798 construction time so that modifications to the per-
799 :class:`.Session` dictionary will be local to that
800 :class:`.Session`.
801
802 .. versionadded:: 0.9.0
803
804 :param query_cls: Class which should be used to create new Query
805 objects, as returned by the :meth:`~.Session.query` method.
806 Defaults to :class:`.Query`.
807
808 :param twophase: When ``True``, all transactions will be started as
809 a "two phase" transaction, i.e. using the "two phase" semantics
810 of the database in use along with an XID. During a
811 :meth:`~.commit`, after :meth:`~.flush` has been issued for all
812 attached databases, the :meth:`~.TwoPhaseTransaction.prepare`
813 method on each database's :class:`.TwoPhaseTransaction` will be
814 called. This allows each database to roll back the entire
815 transaction, before each transaction is committed.
816
817 :param weak_identity_map: Defaults to ``True`` - when set to
818 ``False``, objects placed in the :class:`.Session` will be
819 strongly referenced until explicitly removed or the
820 :class:`.Session` is closed.
821
822
823 """
824
825 if weak_identity_map in (True, None):
826 self._identity_cls = identity.WeakInstanceDict
827 else:
828 self._identity_cls = identity.StrongInstanceDict
829
830 self.identity_map = self._identity_cls()
831
832 self._new = {} # InstanceState->object, strong refs object
833 self._deleted = {} # same
834 self.bind = bind
835 self.__binds = {}
836 self._flushing = False
837 self._warn_on_events = False
838 self.transaction = None
839 self.hash_key = _new_sessionid()
840 self.autoflush = autoflush
841 self.autocommit = autocommit
842 self.expire_on_commit = expire_on_commit
843 self.enable_baked_queries = enable_baked_queries
844 self._enable_transaction_accounting = _enable_transaction_accounting
845
846 self.twophase = twophase
847 self._query_cls = query_cls if query_cls else query.Query
848 if info:
849 self.info.update(info)
850
851 if extension:
852 for ext in util.to_list(extension):
853 SessionExtension._adapt_listener(self, ext)
854
855 if binds is not None:
856 for key, bind in binds.items():
857 self._add_bind(key, bind)
858
859 if not self.autocommit:
860 self.begin()
861 _sessions[self.hash_key] = self
862
863 connection_callable = None
864
865 transaction = None
866 """The current active or inactive :class:`.SessionTransaction`."""
867
868 @util.memoized_property
869 def info(self):
870 """A user-modifiable dictionary.
871
872 The initial value of this dictionary can be populated using the
873 ``info`` argument to the :class:`.Session` constructor or
874 :class:`.sessionmaker` constructor or factory methods. The dictionary
875 here is always local to this :class:`.Session` and can be modified
876 independently of all other :class:`.Session` objects.
877
878 .. versionadded:: 0.9.0
879
880 """
881 return {}
882
883 def begin(self, subtransactions=False, nested=False):
884 """Begin a transaction on this :class:`.Session`.
885
886 .. warning::
887
888 The :meth:`.Session.begin` method is part of a larger pattern
889 of use with the :class:`.Session` known as **autocommit mode**.
890 This is essentially a **legacy mode of use** and is
891 not necessary for new applications. The :class:`.Session`
892 normally handles the work of "begin" transparently, which in
893 turn relies upon the Python DBAPI to transparently "begin"
894 transactions; there is **no need to explicitly begin transactions**
895 when using modern :class:`.Session` programming patterns.
896 In its default mode of ``autocommit=False``, the
897 :class:`.Session` does all of its work within
898 the context of a transaction, so as soon as you call
899 :meth:`.Session.commit`, the next transaction is implicitly
900 started when the next database operation is invoked. See
901 :ref:`session_autocommit` for further background.
902
903 The method will raise an error if this :class:`.Session` is already
904 inside of a transaction, unless
905 :paramref:`~.Session.begin.subtransactions` or
906 :paramref:`~.Session.begin.nested` are specified. A "subtransaction"
907 is essentially a code embedding pattern that does not affect the
908 transactional state of the database connection unless a rollback is
909 emitted, in which case the whole transaction is rolled back. For
910 documentation on subtransactions, please see
911 :ref:`session_subtransactions`.
912
913 :param subtransactions: if True, indicates that this
914 :meth:`~.Session.begin` can create a "subtransaction".
915
916 :param nested: if True, begins a SAVEPOINT transaction and is
917 equivalent to calling :meth:`~.Session.begin_nested`. For
918 documentation on SAVEPOINT transactions, please see
919 :ref:`session_begin_nested`.
920
921 :return: the :class:`.SessionTransaction` object. Note that
922 :class:`.SessionTransaction`
923 acts as a Python context manager, allowing :meth:`.Session.begin`
924 to be used in a "with" block. See :ref:`session_autocommit` for
925 an example.
926
927 .. seealso::
928
929 :ref:`session_autocommit`
930
931 :meth:`.Session.begin_nested`
932
933
934 """
935 if self.transaction is not None:
936 if subtransactions or nested:
937 self.transaction = self.transaction._begin(nested=nested)
938 else:
939 raise sa_exc.InvalidRequestError(
940 "A transaction is already begun. Use "
941 "subtransactions=True to allow subtransactions."
942 )
943 else:
944 self.transaction = SessionTransaction(self, nested=nested)
945 return self.transaction # needed for __enter__/__exit__ hook
946
947 def begin_nested(self):
948 """Begin a "nested" transaction on this Session, e.g. SAVEPOINT.
949
950 The target database(s) and associated drivers must support SQL
951 SAVEPOINT for this method to function correctly.
952
953 For documentation on SAVEPOINT
954 transactions, please see :ref:`session_begin_nested`.
955
956 :return: the :class:`.SessionTransaction` object. Note that
957 :class:`.SessionTransaction` acts as a context manager, allowing
958 :meth:`.Session.begin_nested` to be used in a "with" block.
959 See :ref:`session_begin_nested` for a usage example.
960
961 .. seealso::
962
963 :ref:`session_begin_nested`
964
965 :ref:`pysqlite_serializable` - special workarounds required
966 with the SQLite driver in order for SAVEPOINT to work
967 correctly.
968
969 """
970 return self.begin(nested=True)
971
972 def rollback(self):
973 """Rollback the current transaction in progress.
974
975 If no transaction is in progress, this method is a pass-through.
976
977 This method rolls back the current transaction or nested transaction
978 regardless of subtransactions being in effect. All subtransactions up
979 to the first real transaction are closed. Subtransactions occur when
980 :meth:`.begin` is called multiple times.
981
982 .. seealso::
983
984 :ref:`session_rollback`
985
986 """
987 if self.transaction is None:
988 pass
989 else:
990 self.transaction.rollback()
991
992 def commit(self):
993 """Flush pending changes and commit the current transaction.
994
995 If no transaction is in progress, this method raises an
996 :exc:`~sqlalchemy.exc.InvalidRequestError`.
997
998 By default, the :class:`.Session` also expires all database
999 loaded state on all ORM-managed attributes after transaction commit.
1000 This so that subsequent operations load the most recent
1001 data from the database. This behavior can be disabled using
1002 the ``expire_on_commit=False`` option to :class:`.sessionmaker` or
1003 the :class:`.Session` constructor.
1004
1005 If a subtransaction is in effect (which occurs when begin() is called
1006 multiple times), the subtransaction will be closed, and the next call
1007 to ``commit()`` will operate on the enclosing transaction.
1008
1009 When using the :class:`.Session` in its default mode of
1010 ``autocommit=False``, a new transaction will
1011 be begun immediately after the commit, but note that the newly begun
1012 transaction does *not* use any connection resources until the first
1013 SQL is actually emitted.
1014
1015 .. seealso::
1016
1017 :ref:`session_committing`
1018
1019 """
1020 if self.transaction is None:
1021 if not self.autocommit:
1022 self.begin()
1023 else:
1024 raise sa_exc.InvalidRequestError("No transaction is begun.")
1025
1026 self.transaction.commit()
1027
1028 def prepare(self):
1029 """Prepare the current transaction in progress for two phase commit.
1030
1031 If no transaction is in progress, this method raises an
1032 :exc:`~sqlalchemy.exc.InvalidRequestError`.
1033
1034 Only root transactions of two phase sessions can be prepared. If the
1035 current transaction is not such, an
1036 :exc:`~sqlalchemy.exc.InvalidRequestError` is raised.
1037
1038 """
1039 if self.transaction is None:
1040 if not self.autocommit:
1041 self.begin()
1042 else:
1043 raise sa_exc.InvalidRequestError("No transaction is begun.")
1044
1045 self.transaction.prepare()
1046
1047 def connection(
1048 self,
1049 mapper=None,
1050 clause=None,
1051 bind=None,
1052 close_with_result=False,
1053 execution_options=None,
1054 **kw
1055 ):
1056 r"""Return a :class:`.Connection` object corresponding to this
1057 :class:`.Session` object's transactional state.
1058
1059 If this :class:`.Session` is configured with ``autocommit=False``,
1060 either the :class:`.Connection` corresponding to the current
1061 transaction is returned, or if no transaction is in progress, a new
1062 one is begun and the :class:`.Connection` returned (note that no
1063 transactional state is established with the DBAPI until the first
1064 SQL statement is emitted).
1065
1066 Alternatively, if this :class:`.Session` is configured with
1067 ``autocommit=True``, an ad-hoc :class:`.Connection` is returned
1068 using :meth:`.Engine.connect` on the underlying
1069 :class:`.Engine`.
1070
1071 Ambiguity in multi-bind or unbound :class:`.Session` objects can be
1072 resolved through any of the optional keyword arguments. This
1073 ultimately makes usage of the :meth:`.get_bind` method for resolution.
1074
1075 :param bind:
1076 Optional :class:`.Engine` to be used as the bind. If
1077 this engine is already involved in an ongoing transaction,
1078 that connection will be used. This argument takes precedence
1079 over ``mapper``, ``clause``.
1080
1081 :param mapper:
1082 Optional :func:`.mapper` mapped class, used to identify
1083 the appropriate bind. This argument takes precedence over
1084 ``clause``.
1085
1086 :param clause:
1087 A :class:`.ClauseElement` (i.e. :func:`~.sql.expression.select`,
1088 :func:`~.sql.expression.text`,
1089 etc.) which will be used to locate a bind, if a bind
1090 cannot otherwise be identified.
1091
1092 :param close_with_result: Passed to :meth:`.Engine.connect`,
1093 indicating the :class:`.Connection` should be considered
1094 "single use", automatically closing when the first result set is
1095 closed. This flag only has an effect if this :class:`.Session` is
1096 configured with ``autocommit=True`` and does not already have a
1097 transaction in progress.
1098
1099 :param execution_options: a dictionary of execution options that will
1100 be passed to :meth:`.Connection.execution_options`, **when the
1101 connection is first procured only**. If the connection is already
1102 present within the :class:`.Session`, a warning is emitted and
1103 the arguments are ignored.
1104
1105 .. versionadded:: 0.9.9
1106
1107 .. seealso::
1108
1109 :ref:`session_transaction_isolation`
1110
1111 :param \**kw:
1112 Additional keyword arguments are sent to :meth:`get_bind()`,
1113 allowing additional arguments to be passed to custom
1114 implementations of :meth:`get_bind`.
1115
1116 """
1117 if bind is None:
1118 bind = self.get_bind(mapper, clause=clause, **kw)
1119
1120 return self._connection_for_bind(
1121 bind,
1122 close_with_result=close_with_result,
1123 execution_options=execution_options,
1124 )
1125
1126 def _connection_for_bind(self, engine, execution_options=None, **kw):
1127 if self.transaction is not None:
1128 return self.transaction._connection_for_bind(
1129 engine, execution_options
1130 )
1131 else:
1132 conn = engine._contextual_connect(**kw)
1133 if execution_options:
1134 conn = conn.execution_options(**execution_options)
1135 return conn
1136
1137 def execute(self, clause, params=None, mapper=None, bind=None, **kw):
1138 r"""Execute a SQL expression construct or string statement within
1139 the current transaction.
1140
1141 Returns a :class:`.ResultProxy` representing
1142 results of the statement execution, in the same manner as that of an
1143 :class:`.Engine` or
1144 :class:`.Connection`.
1145
1146 E.g.::
1147
1148 result = session.execute(
1149 user_table.select().where(user_table.c.id == 5)
1150 )
1151
1152 :meth:`~.Session.execute` accepts any executable clause construct,
1153 such as :func:`~.sql.expression.select`,
1154 :func:`~.sql.expression.insert`,
1155 :func:`~.sql.expression.update`,
1156 :func:`~.sql.expression.delete`, and
1157 :func:`~.sql.expression.text`. Plain SQL strings can be passed
1158 as well, which in the case of :meth:`.Session.execute` only
1159 will be interpreted the same as if it were passed via a
1160 :func:`~.expression.text` construct. That is, the following usage::
1161
1162 result = session.execute(
1163 "SELECT * FROM user WHERE id=:param",
1164 {"param":5}
1165 )
1166
1167 is equivalent to::
1168
1169 from sqlalchemy import text
1170 result = session.execute(
1171 text("SELECT * FROM user WHERE id=:param"),
1172 {"param":5}
1173 )
1174
1175 The second positional argument to :meth:`.Session.execute` is an
1176 optional parameter set. Similar to that of
1177 :meth:`.Connection.execute`, whether this is passed as a single
1178 dictionary, or a list of dictionaries, determines whether the DBAPI
1179 cursor's ``execute()`` or ``executemany()`` is used to execute the
1180 statement. An INSERT construct may be invoked for a single row::
1181
1182 result = session.execute(
1183 users.insert(), {"id": 7, "name": "somename"})
1184
1185 or for multiple rows::
1186
1187 result = session.execute(users.insert(), [
1188 {"id": 7, "name": "somename7"},
1189 {"id": 8, "name": "somename8"},
1190 {"id": 9, "name": "somename9"}
1191 ])
1192
1193 The statement is executed within the current transactional context of
1194 this :class:`.Session`. The :class:`.Connection` which is used
1195 to execute the statement can also be acquired directly by
1196 calling the :meth:`.Session.connection` method. Both methods use
1197 a rule-based resolution scheme in order to determine the
1198 :class:`.Connection`, which in the average case is derived directly
1199 from the "bind" of the :class:`.Session` itself, and in other cases
1200 can be based on the :func:`.mapper`
1201 and :class:`.Table` objects passed to the method; see the
1202 documentation for :meth:`.Session.get_bind` for a full description of
1203 this scheme.
1204
1205 The :meth:`.Session.execute` method does *not* invoke autoflush.
1206
1207 The :class:`.ResultProxy` returned by the :meth:`.Session.execute`
1208 method is returned with the "close_with_result" flag set to true;
1209 the significance of this flag is that if this :class:`.Session` is
1210 autocommitting and does not have a transaction-dedicated
1211 :class:`.Connection` available, a temporary :class:`.Connection` is
1212 established for the statement execution, which is closed (meaning,
1213 returned to the connection pool) when the :class:`.ResultProxy` has
1214 consumed all available data. This applies *only* when the
1215 :class:`.Session` is configured with autocommit=True and no
1216 transaction has been started.
1217
1218 :param clause:
1219 An executable statement (i.e. an :class:`.Executable` expression
1220 such as :func:`.expression.select`) or string SQL statement
1221 to be executed.
1222
1223 :param params:
1224 Optional dictionary, or list of dictionaries, containing
1225 bound parameter values. If a single dictionary, single-row
1226 execution occurs; if a list of dictionaries, an
1227 "executemany" will be invoked. The keys in each dictionary
1228 must correspond to parameter names present in the statement.
1229
1230 :param mapper:
1231 Optional :func:`.mapper` or mapped class, used to identify
1232 the appropriate bind. This argument takes precedence over
1233 ``clause`` when locating a bind. See :meth:`.Session.get_bind`
1234 for more details.
1235
1236 :param bind:
1237 Optional :class:`.Engine` to be used as the bind. If
1238 this engine is already involved in an ongoing transaction,
1239 that connection will be used. This argument takes
1240 precedence over ``mapper`` and ``clause`` when locating
1241 a bind.
1242
1243 :param \**kw:
1244 Additional keyword arguments are sent to :meth:`.Session.get_bind()`
1245 to allow extensibility of "bind" schemes.
1246
1247 .. seealso::
1248
1249 :ref:`sqlexpression_toplevel` - Tutorial on using Core SQL
1250 constructs.
1251
1252 :ref:`connections_toplevel` - Further information on direct
1253 statement execution.
1254
1255 :meth:`.Connection.execute` - core level statement execution
1256 method, which is :meth:`.Session.execute` ultimately uses
1257 in order to execute the statement.
1258
1259 """
1260 clause = expression._literal_as_text(
1261 clause, allow_coercion_to_text=True
1262 )
1263
1264 if bind is None:
1265 bind = self.get_bind(mapper, clause=clause, **kw)
1266
1267 return self._connection_for_bind(bind, close_with_result=True).execute(
1268 clause, params or {}
1269 )
1270
1271 def scalar(self, clause, params=None, mapper=None, bind=None, **kw):
1272 """Like :meth:`~.Session.execute` but return a scalar result."""
1273
1274 return self.execute(
1275 clause, params=params, mapper=mapper, bind=bind, **kw
1276 ).scalar()
1277
1278 def close(self):
1279 """Close this Session.
1280
1281 This clears all items and ends any transaction in progress.
1282
1283 If this session were created with ``autocommit=False``, a new
1284 transaction is immediately begun. Note that this new transaction does
1285 not use any connection resources until they are first needed.
1286
1287 """
1288 self._close_impl(invalidate=False)
1289
1290 def invalidate(self):
1291 """Close this Session, using connection invalidation.
1292
1293 This is a variant of :meth:`.Session.close` that will additionally
1294 ensure that the :meth:`.Connection.invalidate` method will be called
1295 on all :class:`.Connection` objects. This can be called when
1296 the database is known to be in a state where the connections are
1297 no longer safe to be used.
1298
1299 E.g.::
1300
1301 try:
1302 sess = Session()
1303 sess.add(User())
1304 sess.commit()
1305 except gevent.Timeout:
1306 sess.invalidate()
1307 raise
1308 except:
1309 sess.rollback()
1310 raise
1311
1312 This clears all items and ends any transaction in progress.
1313
1314 If this session were created with ``autocommit=False``, a new
1315 transaction is immediately begun. Note that this new transaction does
1316 not use any connection resources until they are first needed.
1317
1318 .. versionadded:: 0.9.9
1319
1320 """
1321 self._close_impl(invalidate=True)
1322
1323 def _close_impl(self, invalidate):
1324 self.expunge_all()
1325 if self.transaction is not None:
1326 for transaction in self.transaction._iterate_self_and_parents():
1327 transaction.close(invalidate)
1328
1329 def expunge_all(self):
1330 """Remove all object instances from this ``Session``.
1331
1332 This is equivalent to calling ``expunge(obj)`` on all objects in this
1333 ``Session``.
1334
1335 """
1336
1337 all_states = self.identity_map.all_states() + list(self._new)
1338 self.identity_map = self._identity_cls()
1339 self._new = {}
1340 self._deleted = {}
1341
1342 statelib.InstanceState._detach_states(all_states, self)
1343
1344 def _add_bind(self, key, bind):
1345 try:
1346 insp = inspect(key)
1347 except sa_exc.NoInspectionAvailable:
1348 if not isinstance(key, type):
1349 raise sa_exc.ArgumentError(
1350 "Not an acceptable bind target: %s" % key
1351 )
1352 else:
1353 self.__binds[key] = bind
1354 else:
1355 if insp.is_selectable:
1356 self.__binds[insp] = bind
1357 elif insp.is_mapper:
1358 self.__binds[insp.class_] = bind
1359 for selectable in insp._all_tables:
1360 self.__binds[selectable] = bind
1361 else:
1362 raise sa_exc.ArgumentError(
1363 "Not an acceptable bind target: %s" % key
1364 )
1365
1366 def bind_mapper(self, mapper, bind):
1367 """Associate a :class:`.Mapper` or arbitrary Python class with a
1368 "bind", e.g. an :class:`.Engine` or :class:`.Connection`.
1369
1370 The given entity is added to a lookup used by the
1371 :meth:`.Session.get_bind` method.
1372
1373 :param mapper: a :class:`.Mapper` object, or an instance of a mapped
1374 class, or any Python class that is the base of a set of mapped
1375 classes.
1376
1377 :param bind: an :class:`.Engine` or :class:`.Connection` object.
1378
1379 .. seealso::
1380
1381 :ref:`session_partitioning`
1382
1383 :paramref:`.Session.binds`
1384
1385 :meth:`.Session.bind_table`
1386
1387
1388 """
1389 self._add_bind(mapper, bind)
1390
1391 def bind_table(self, table, bind):
1392 """Associate a :class:`.Table` with a "bind", e.g. an :class:`.Engine`
1393 or :class:`.Connection`.
1394
1395 The given :class:`.Table` is added to a lookup used by the
1396 :meth:`.Session.get_bind` method.
1397
1398 :param table: a :class:`.Table` object, which is typically the target
1399 of an ORM mapping, or is present within a selectable that is
1400 mapped.
1401
1402 :param bind: an :class:`.Engine` or :class:`.Connection` object.
1403
1404 .. seealso::
1405
1406 :ref:`session_partitioning`
1407
1408 :paramref:`.Session.binds`
1409
1410 :meth:`.Session.bind_mapper`
1411
1412
1413 """
1414 self._add_bind(table, bind)
1415
1416 def get_bind(self, mapper=None, clause=None):
1417 """Return a "bind" to which this :class:`.Session` is bound.
1418
1419 The "bind" is usually an instance of :class:`.Engine`,
1420 except in the case where the :class:`.Session` has been
1421 explicitly bound directly to a :class:`.Connection`.
1422
1423 For a multiply-bound or unbound :class:`.Session`, the
1424 ``mapper`` or ``clause`` arguments are used to determine the
1425 appropriate bind to return.
1426
1427 Note that the "mapper" argument is usually present
1428 when :meth:`.Session.get_bind` is called via an ORM
1429 operation such as a :meth:`.Session.query`, each
1430 individual INSERT/UPDATE/DELETE operation within a
1431 :meth:`.Session.flush`, call, etc.
1432
1433 The order of resolution is:
1434
1435 1. if mapper given and session.binds is present,
1436 locate a bind based first on the mapper in use, then
1437 on the mapped class in use, then on any base classes that are
1438 present in the ``__mro__`` of the mapped class, from more specific
1439 superclasses to more general.
1440 2. if clause given and session.binds is present,
1441 locate a bind based on :class:`.Table` objects
1442 found in the given clause present in session.binds.
1443 3. if session.bind is present, return that.
1444 4. if clause given, attempt to return a bind
1445 linked to the :class:`.MetaData` ultimately
1446 associated with the clause.
1447 5. if mapper given, attempt to return a bind
1448 linked to the :class:`.MetaData` ultimately
1449 associated with the :class:`.Table` or other
1450 selectable to which the mapper is mapped.
1451 6. No bind can be found, :exc:`~sqlalchemy.exc.UnboundExecutionError`
1452 is raised.
1453
1454 Note that the :meth:`.Session.get_bind` method can be overridden on
1455 a user-defined subclass of :class:`.Session` to provide any kind
1456 of bind resolution scheme. See the example at
1457 :ref:`session_custom_partitioning`.
1458
1459 :param mapper:
1460 Optional :func:`.mapper` mapped class or instance of
1461 :class:`.Mapper`. The bind can be derived from a :class:`.Mapper`
1462 first by consulting the "binds" map associated with this
1463 :class:`.Session`, and secondly by consulting the :class:`.MetaData`
1464 associated with the :class:`.Table` to which the :class:`.Mapper`
1465 is mapped for a bind.
1466
1467 :param clause:
1468 A :class:`.ClauseElement` (i.e. :func:`~.sql.expression.select`,
1469 :func:`~.sql.expression.text`,
1470 etc.). If the ``mapper`` argument is not present or could not
1471 produce a bind, the given expression construct will be searched
1472 for a bound element, typically a :class:`.Table` associated with
1473 bound :class:`.MetaData`.
1474
1475 .. seealso::
1476
1477 :ref:`session_partitioning`
1478
1479 :paramref:`.Session.binds`
1480
1481 :meth:`.Session.bind_mapper`
1482
1483 :meth:`.Session.bind_table`
1484
1485 """
1486
1487 if mapper is clause is None:
1488 if self.bind:
1489 return self.bind
1490 else:
1491 raise sa_exc.UnboundExecutionError(
1492 "This session is not bound to a single Engine or "
1493 "Connection, and no context was provided to locate "
1494 "a binding."
1495 )
1496
1497 if mapper is not None:
1498 try:
1499 mapper = inspect(mapper)
1500 except sa_exc.NoInspectionAvailable:
1501 if isinstance(mapper, type):
1502 raise exc.UnmappedClassError(mapper)
1503 else:
1504 raise
1505
1506 if self.__binds:
1507 if mapper:
1508 for cls in mapper.class_.__mro__:
1509 if cls in self.__binds:
1510 return self.__binds[cls]
1511 if clause is None:
1512 clause = mapper.persist_selectable
1513
1514 if clause is not None:
1515 for t in sql_util.find_tables(clause, include_crud=True):
1516 if t in self.__binds:
1517 return self.__binds[t]
1518
1519 if self.bind:
1520 return self.bind
1521
1522 if isinstance(clause, sql.expression.ClauseElement) and clause.bind:
1523 return clause.bind
1524
1525 if mapper and mapper.persist_selectable.bind:
1526 return mapper.persist_selectable.bind
1527
1528 context = []
1529 if mapper is not None:
1530 context.append("mapper %s" % mapper)
1531 if clause is not None:
1532 context.append("SQL expression")
1533
1534 raise sa_exc.UnboundExecutionError(
1535 "Could not locate a bind configured on %s or this Session"
1536 % (", ".join(context))
1537 )
1538
1539 def query(self, *entities, **kwargs):
1540 """Return a new :class:`.Query` object corresponding to this
1541 :class:`.Session`."""
1542
1543 return self._query_cls(entities, self, **kwargs)
1544
1545 @property
1546 @util.contextmanager
1547 def no_autoflush(self):
1548 """Return a context manager that disables autoflush.
1549
1550 e.g.::
1551
1552 with session.no_autoflush:
1553
1554 some_object = SomeClass()
1555 session.add(some_object)
1556 # won't autoflush
1557 some_object.related_thing = session.query(SomeRelated).first()
1558
1559 Operations that proceed within the ``with:`` block
1560 will not be subject to flushes occurring upon query
1561 access. This is useful when initializing a series
1562 of objects which involve existing database queries,
1563 where the uncompleted object should not yet be flushed.
1564
1565 """
1566 autoflush = self.autoflush
1567 self.autoflush = False
1568 try:
1569 yield self
1570 finally:
1571 self.autoflush = autoflush
1572
1573 def _autoflush(self):
1574 if self.autoflush and not self._flushing:
1575 try:
1576 self.flush()
1577 except sa_exc.StatementError as e:
1578 # note we are reraising StatementError as opposed to
1579 # raising FlushError with "chaining" to remain compatible
1580 # with code that catches StatementError, IntegrityError,
1581 # etc.
1582 e.add_detail(
1583 "raised as a result of Query-invoked autoflush; "
1584 "consider using a session.no_autoflush block if this "
1585 "flush is occurring prematurely"
1586 )
1587 util.raise_from_cause(e)
1588
1589 def refresh(
1590 self,
1591 instance,
1592 attribute_names=None,
1593 with_for_update=None,
1594 lockmode=None,
1595 ):
1596 """Expire and refresh the attributes on the given instance.
1597
1598 A query will be issued to the database and all attributes will be
1599 refreshed with their current database value.
1600
1601 Lazy-loaded relational attributes will remain lazily loaded, so that
1602 the instance-wide refresh operation will be followed immediately by
1603 the lazy load of that attribute.
1604
1605 Eagerly-loaded relational attributes will eagerly load within the
1606 single refresh operation.
1607
1608 Note that a highly isolated transaction will return the same values as
1609 were previously read in that same transaction, regardless of changes
1610 in database state outside of that transaction - usage of
1611 :meth:`~Session.refresh` usually only makes sense if non-ORM SQL
1612 statement were emitted in the ongoing transaction, or if autocommit
1613 mode is turned on.
1614
1615 :param attribute_names: optional. An iterable collection of
1616 string attribute names indicating a subset of attributes to
1617 be refreshed.
1618
1619 :param with_for_update: optional boolean ``True`` indicating FOR UPDATE
1620 should be used, or may be a dictionary containing flags to
1621 indicate a more specific set of FOR UPDATE flags for the SELECT;
1622 flags should match the parameters of :meth:`.Query.with_for_update`.
1623 Supersedes the :paramref:`.Session.refresh.lockmode` parameter.
1624
1625 .. versionadded:: 1.2
1626
1627 :param lockmode: Passed to the :class:`~sqlalchemy.orm.query.Query`
1628 as used by :meth:`~sqlalchemy.orm.query.Query.with_lockmode`.
1629 Superseded by :paramref:`.Session.refresh.with_for_update`.
1630
1631 .. seealso::
1632
1633 :ref:`session_expire` - introductory material
1634
1635 :meth:`.Session.expire`
1636
1637 :meth:`.Session.expire_all`
1638
1639 """
1640 try:
1641 state = attributes.instance_state(instance)
1642 except exc.NO_STATE:
1643 raise exc.UnmappedInstanceError(instance)
1644
1645 self._expire_state(state, attribute_names)
1646
1647 if with_for_update == {}:
1648 raise sa_exc.ArgumentError(
1649 "with_for_update should be the boolean value "
1650 "True, or a dictionary with options. "
1651 "A blank dictionary is ambiguous."
1652 )
1653
1654 if lockmode:
1655 with_for_update = query.LockmodeArg.parse_legacy_query(lockmode)
1656 elif with_for_update is not None:
1657 if with_for_update is True:
1658 with_for_update = query.LockmodeArg()
1659 elif with_for_update:
1660 with_for_update = query.LockmodeArg(**with_for_update)
1661 else:
1662 with_for_update = None
1663
1664 if (
1665 loading.load_on_ident(
1666 self.query(object_mapper(instance)),
1667 state.key,
1668 refresh_state=state,
1669 with_for_update=with_for_update,
1670 only_load_props=attribute_names,
1671 )
1672 is None
1673 ):
1674 raise sa_exc.InvalidRequestError(
1675 "Could not refresh instance '%s'" % instance_str(instance)
1676 )
1677
1678 def expire_all(self):
1679 """Expires all persistent instances within this Session.
1680
1681 When any attributes on a persistent instance is next accessed,
1682 a query will be issued using the
1683 :class:`.Session` object's current transactional context in order to
1684 load all expired attributes for the given instance. Note that
1685 a highly isolated transaction will return the same values as were
1686 previously read in that same transaction, regardless of changes
1687 in database state outside of that transaction.
1688
1689 To expire individual objects and individual attributes
1690 on those objects, use :meth:`Session.expire`.
1691
1692 The :class:`.Session` object's default behavior is to
1693 expire all state whenever the :meth:`Session.rollback`
1694 or :meth:`Session.commit` methods are called, so that new
1695 state can be loaded for the new transaction. For this reason,
1696 calling :meth:`Session.expire_all` should not be needed when
1697 autocommit is ``False``, assuming the transaction is isolated.
1698
1699 .. seealso::
1700
1701 :ref:`session_expire` - introductory material
1702
1703 :meth:`.Session.expire`
1704
1705 :meth:`.Session.refresh`
1706
1707 """
1708 for state in self.identity_map.all_states():
1709 state._expire(state.dict, self.identity_map._modified)
1710
1711 def expire(self, instance, attribute_names=None):
1712 """Expire the attributes on an instance.
1713
1714 Marks the attributes of an instance as out of date. When an expired
1715 attribute is next accessed, a query will be issued to the
1716 :class:`.Session` object's current transactional context in order to
1717 load all expired attributes for the given instance. Note that
1718 a highly isolated transaction will return the same values as were
1719 previously read in that same transaction, regardless of changes
1720 in database state outside of that transaction.
1721
1722 To expire all objects in the :class:`.Session` simultaneously,
1723 use :meth:`Session.expire_all`.
1724
1725 The :class:`.Session` object's default behavior is to
1726 expire all state whenever the :meth:`Session.rollback`
1727 or :meth:`Session.commit` methods are called, so that new
1728 state can be loaded for the new transaction. For this reason,
1729 calling :meth:`Session.expire` only makes sense for the specific
1730 case that a non-ORM SQL statement was emitted in the current
1731 transaction.
1732
1733 :param instance: The instance to be refreshed.
1734 :param attribute_names: optional list of string attribute names
1735 indicating a subset of attributes to be expired.
1736
1737 .. seealso::
1738
1739 :ref:`session_expire` - introductory material
1740
1741 :meth:`.Session.expire`
1742
1743 :meth:`.Session.refresh`
1744
1745 """
1746 try:
1747 state = attributes.instance_state(instance)
1748 except exc.NO_STATE:
1749 raise exc.UnmappedInstanceError(instance)
1750 self._expire_state(state, attribute_names)
1751
1752 def _expire_state(self, state, attribute_names):
1753 self._validate_persistent(state)
1754 if attribute_names:
1755 state._expire_attributes(state.dict, attribute_names)
1756 else:
1757 # pre-fetch the full cascade since the expire is going to
1758 # remove associations
1759 cascaded = list(
1760 state.manager.mapper.cascade_iterator("refresh-expire", state)
1761 )
1762 self._conditional_expire(state)
1763 for o, m, st_, dct_ in cascaded:
1764 self._conditional_expire(st_)
1765
1766 def _conditional_expire(self, state):
1767 """Expire a state if persistent, else expunge if pending"""
1768
1769 if state.key:
1770 state._expire(state.dict, self.identity_map._modified)
1771 elif state in self._new:
1772 self._new.pop(state)
1773 state._detach(self)
1774
1775 @util.deprecated(
1776 "0.7",
1777 "The :meth:`.Session.prune` method is deprecated along with "
1778 ":paramref:`.Session.weak_identity_map`. This method will be "
1779 "removed in a future release.",
1780 )
1781 def prune(self):
1782 """Remove unreferenced instances cached in the identity map.
1783
1784 Note that this method is only meaningful if "weak_identity_map" is set
1785 to False. The default weak identity map is self-pruning.
1786
1787 Removes any object in this Session's identity map that is not
1788 referenced in user code, modified, new or scheduled for deletion.
1789 Returns the number of objects pruned.
1790
1791 """
1792 return self.identity_map.prune()
1793
1794 def expunge(self, instance):
1795 """Remove the `instance` from this ``Session``.
1796
1797 This will free all internal references to the instance. Cascading
1798 will be applied according to the *expunge* cascade rule.
1799
1800 """
1801 try:
1802 state = attributes.instance_state(instance)
1803 except exc.NO_STATE:
1804 raise exc.UnmappedInstanceError(instance)
1805 if state.session_id is not self.hash_key:
1806 raise sa_exc.InvalidRequestError(
1807 "Instance %s is not present in this Session" % state_str(state)
1808 )
1809
1810 cascaded = list(
1811 state.manager.mapper.cascade_iterator("expunge", state)
1812 )
1813 self._expunge_states([state] + [st_ for o, m, st_, dct_ in cascaded])
1814
1815 def _expunge_states(self, states, to_transient=False):
1816 for state in states:
1817 if state in self._new:
1818 self._new.pop(state)
1819 elif self.identity_map.contains_state(state):
1820 self.identity_map.safe_discard(state)
1821 self._deleted.pop(state, None)
1822 elif self.transaction:
1823 # state is "detached" from being deleted, but still present
1824 # in the transaction snapshot
1825 self.transaction._deleted.pop(state, None)
1826 statelib.InstanceState._detach_states(
1827 states, self, to_transient=to_transient
1828 )
1829
1830 def _register_persistent(self, states):
1831 """Register all persistent objects from a flush.
1832
1833 This is used both for pending objects moving to the persistent
1834 state as well as already persistent objects.
1835
1836 """
1837
1838 pending_to_persistent = self.dispatch.pending_to_persistent or None
1839 for state in states:
1840 mapper = _state_mapper(state)
1841
1842 # prevent against last minute dereferences of the object
1843 obj = state.obj()
1844 if obj is not None:
1845
1846 instance_key = mapper._identity_key_from_state(state)
1847
1848 if (
1849 _none_set.intersection(instance_key[1])
1850 and not mapper.allow_partial_pks
1851 or _none_set.issuperset(instance_key[1])
1852 ):
1853 raise exc.FlushError(
1854 "Instance %s has a NULL identity key. If this is an "
1855 "auto-generated value, check that the database table "
1856 "allows generation of new primary key values, and "
1857 "that the mapped Column object is configured to "
1858 "expect these generated values. Ensure also that "
1859 "this flush() is not occurring at an inappropriate "
1860 "time, such as within a load() event."
1861 % state_str(state)
1862 )
1863
1864 if state.key is None:
1865 state.key = instance_key
1866 elif state.key != instance_key:
1867 # primary key switch. use safe_discard() in case another
1868 # state has already replaced this one in the identity
1869 # map (see test/orm/test_naturalpks.py ReversePKsTest)
1870 self.identity_map.safe_discard(state)
1871 if state in self.transaction._key_switches:
1872 orig_key = self.transaction._key_switches[state][0]
1873 else:
1874 orig_key = state.key
1875 self.transaction._key_switches[state] = (
1876 orig_key,
1877 instance_key,
1878 )
1879 state.key = instance_key
1880
1881 # there can be an existing state in the identity map
1882 # that is replaced when the primary keys of two instances
1883 # are swapped; see test/orm/test_naturalpks.py -> test_reverse
1884 self.identity_map.replace(state)
1885 state._orphaned_outside_of_session = False
1886
1887 statelib.InstanceState._commit_all_states(
1888 ((state, state.dict) for state in states), self.identity_map
1889 )
1890
1891 self._register_altered(states)
1892
1893 if pending_to_persistent is not None:
1894 for state in states.intersection(self._new):
1895 pending_to_persistent(self, state.obj())
1896
1897 # remove from new last, might be the last strong ref
1898 for state in set(states).intersection(self._new):
1899 self._new.pop(state)
1900
1901 def _register_altered(self, states):
1902 if self._enable_transaction_accounting and self.transaction:
1903 for state in states:
1904 if state in self._new:
1905 self.transaction._new[state] = True
1906 else:
1907 self.transaction._dirty[state] = True
1908
1909 def _remove_newly_deleted(self, states):
1910 persistent_to_deleted = self.dispatch.persistent_to_deleted or None
1911 for state in states:
1912 if self._enable_transaction_accounting and self.transaction:
1913 self.transaction._deleted[state] = True
1914
1915 if persistent_to_deleted is not None:
1916 # get a strong reference before we pop out of
1917 # self._deleted
1918 obj = state.obj()
1919
1920 self.identity_map.safe_discard(state)
1921 self._deleted.pop(state, None)
1922 state._deleted = True
1923 # can't call state._detach() here, because this state
1924 # is still in the transaction snapshot and needs to be
1925 # tracked as part of that
1926 if persistent_to_deleted is not None:
1927 persistent_to_deleted(self, obj)
1928
1929 def add(self, instance, _warn=True):
1930 """Place an object in the ``Session``.
1931
1932 Its state will be persisted to the database on the next flush
1933 operation.
1934
1935 Repeated calls to ``add()`` will be ignored. The opposite of ``add()``
1936 is ``expunge()``.
1937
1938 """
1939 if _warn and self._warn_on_events:
1940 self._flush_warning("Session.add()")
1941
1942 try:
1943 state = attributes.instance_state(instance)
1944 except exc.NO_STATE:
1945 raise exc.UnmappedInstanceError(instance)
1946
1947 self._save_or_update_state(state)
1948
1949 def add_all(self, instances):
1950 """Add the given collection of instances to this ``Session``."""
1951
1952 if self._warn_on_events:
1953 self._flush_warning("Session.add_all()")
1954
1955 for instance in instances:
1956 self.add(instance, _warn=False)
1957
1958 def _save_or_update_state(self, state):
1959 state._orphaned_outside_of_session = False
1960 self._save_or_update_impl(state)
1961
1962 mapper = _state_mapper(state)
1963 for o, m, st_, dct_ in mapper.cascade_iterator(
1964 "save-update", state, halt_on=self._contains_state
1965 ):
1966 self._save_or_update_impl(st_)
1967
1968 def delete(self, instance):
1969 """Mark an instance as deleted.
1970
1971 The database delete operation occurs upon ``flush()``.
1972
1973 """
1974 if self._warn_on_events:
1975 self._flush_warning("Session.delete()")
1976
1977 try:
1978 state = attributes.instance_state(instance)
1979 except exc.NO_STATE:
1980 raise exc.UnmappedInstanceError(instance)
1981
1982 self._delete_impl(state, instance, head=True)
1983
1984 def _delete_impl(self, state, obj, head):
1985
1986 if state.key is None:
1987 if head:
1988 raise sa_exc.InvalidRequestError(
1989 "Instance '%s' is not persisted" % state_str(state)
1990 )
1991 else:
1992 return
1993
1994 to_attach = self._before_attach(state, obj)
1995
1996 if state in self._deleted:
1997 return
1998
1999 self.identity_map.add(state)
2000
2001 if to_attach:
2002 self._after_attach(state, obj)
2003
2004 if head:
2005 # grab the cascades before adding the item to the deleted list
2006 # so that autoflush does not delete the item
2007 # the strong reference to the instance itself is significant here
2008 cascade_states = list(
2009 state.manager.mapper.cascade_iterator("delete", state)
2010 )
2011
2012 self._deleted[state] = obj
2013
2014 if head:
2015 for o, m, st_, dct_ in cascade_states:
2016 self._delete_impl(st_, o, False)
2017
2018 def merge(self, instance, load=True):
2019 """Copy the state of a given instance into a corresponding instance
2020 within this :class:`.Session`.
2021
2022 :meth:`.Session.merge` examines the primary key attributes of the
2023 source instance, and attempts to reconcile it with an instance of the
2024 same primary key in the session. If not found locally, it attempts
2025 to load the object from the database based on primary key, and if
2026 none can be located, creates a new instance. The state of each
2027 attribute on the source instance is then copied to the target
2028 instance. The resulting target instance is then returned by the
2029 method; the original source instance is left unmodified, and
2030 un-associated with the :class:`.Session` if not already.
2031
2032 This operation cascades to associated instances if the association is
2033 mapped with ``cascade="merge"``.
2034
2035 See :ref:`unitofwork_merging` for a detailed discussion of merging.
2036
2037 .. versionchanged:: 1.1 - :meth:`.Session.merge` will now reconcile
2038 pending objects with overlapping primary keys in the same way
2039 as persistent. See :ref:`change_3601` for discussion.
2040
2041 :param instance: Instance to be merged.
2042 :param load: Boolean, when False, :meth:`.merge` switches into
2043 a "high performance" mode which causes it to forego emitting history
2044 events as well as all database access. This flag is used for
2045 cases such as transferring graphs of objects into a :class:`.Session`
2046 from a second level cache, or to transfer just-loaded objects
2047 into the :class:`.Session` owned by a worker thread or process
2048 without re-querying the database.
2049
2050 The ``load=False`` use case adds the caveat that the given
2051 object has to be in a "clean" state, that is, has no pending changes
2052 to be flushed - even if the incoming object is detached from any
2053 :class:`.Session`. This is so that when
2054 the merge operation populates local attributes and
2055 cascades to related objects and
2056 collections, the values can be "stamped" onto the
2057 target object as is, without generating any history or attribute
2058 events, and without the need to reconcile the incoming data with
2059 any existing related objects or collections that might not
2060 be loaded. The resulting objects from ``load=False`` are always
2061 produced as "clean", so it is only appropriate that the given objects
2062 should be "clean" as well, else this suggests a mis-use of the
2063 method.
2064
2065
2066 .. seealso::
2067
2068 :func:`.make_transient_to_detached` - provides for an alternative
2069 means of "merging" a single object into the :class:`.Session`
2070
2071 """
2072
2073 if self._warn_on_events:
2074 self._flush_warning("Session.merge()")
2075
2076 _recursive = {}
2077 _resolve_conflict_map = {}
2078
2079 if load:
2080 # flush current contents if we expect to load data
2081 self._autoflush()
2082
2083 object_mapper(instance) # verify mapped
2084 autoflush = self.autoflush
2085 try:
2086 self.autoflush = False
2087 return self._merge(
2088 attributes.instance_state(instance),
2089 attributes.instance_dict(instance),
2090 load=load,
2091 _recursive=_recursive,
2092 _resolve_conflict_map=_resolve_conflict_map,
2093 )
2094 finally:
2095 self.autoflush = autoflush
2096
2097 def _merge(
2098 self,
2099 state,
2100 state_dict,
2101 load=True,
2102 _recursive=None,
2103 _resolve_conflict_map=None,
2104 ):
2105 mapper = _state_mapper(state)
2106 if state in _recursive:
2107 return _recursive[state]
2108
2109 new_instance = False
2110 key = state.key
2111
2112 if key is None:
2113 if not load:
2114 raise sa_exc.InvalidRequestError(
2115 "merge() with load=False option does not support "
2116 "objects transient (i.e. unpersisted) objects. flush() "
2117 "all changes on mapped instances before merging with "
2118 "load=False."
2119 )
2120 key = mapper._identity_key_from_state(state)
2121 key_is_persistent = attributes.NEVER_SET not in key[1] and (
2122 not _none_set.intersection(key[1])
2123 or (
2124 mapper.allow_partial_pks
2125 and not _none_set.issuperset(key[1])
2126 )
2127 )
2128 else:
2129 key_is_persistent = True
2130
2131 if key in self.identity_map:
2132 try:
2133 merged = self.identity_map[key]
2134 except KeyError:
2135 # object was GC'ed right as we checked for it
2136 merged = None
2137 else:
2138 merged = None
2139
2140 if merged is None:
2141 if key_is_persistent and key in _resolve_conflict_map:
2142 merged = _resolve_conflict_map[key]
2143
2144 elif not load:
2145 if state.modified:
2146 raise sa_exc.InvalidRequestError(
2147 "merge() with load=False option does not support "
2148 "objects marked as 'dirty'. flush() all changes on "
2149 "mapped instances before merging with load=False."
2150 )
2151 merged = mapper.class_manager.new_instance()
2152 merged_state = attributes.instance_state(merged)
2153 merged_state.key = key
2154 self._update_impl(merged_state)
2155 new_instance = True
2156
2157 elif key_is_persistent:
2158 merged = self.query(mapper.class_).get(key[1])
2159
2160 if merged is None:
2161 merged = mapper.class_manager.new_instance()
2162 merged_state = attributes.instance_state(merged)
2163 merged_dict = attributes.instance_dict(merged)
2164 new_instance = True
2165 self._save_or_update_state(merged_state)
2166 else:
2167 merged_state = attributes.instance_state(merged)
2168 merged_dict = attributes.instance_dict(merged)
2169
2170 _recursive[state] = merged
2171 _resolve_conflict_map[key] = merged
2172
2173 # check that we didn't just pull the exact same
2174 # state out.
2175 if state is not merged_state:
2176 # version check if applicable
2177 if mapper.version_id_col is not None:
2178 existing_version = mapper._get_state_attr_by_column(
2179 state,
2180 state_dict,
2181 mapper.version_id_col,
2182 passive=attributes.PASSIVE_NO_INITIALIZE,
2183 )
2184
2185 merged_version = mapper._get_state_attr_by_column(
2186 merged_state,
2187 merged_dict,
2188 mapper.version_id_col,
2189 passive=attributes.PASSIVE_NO_INITIALIZE,
2190 )
2191
2192 if (
2193 existing_version is not attributes.PASSIVE_NO_RESULT
2194 and merged_version is not attributes.PASSIVE_NO_RESULT
2195 and existing_version != merged_version
2196 ):
2197 raise exc.StaleDataError(
2198 "Version id '%s' on merged state %s "
2199 "does not match existing version '%s'. "
2200 "Leave the version attribute unset when "
2201 "merging to update the most recent version."
2202 % (
2203 existing_version,
2204 state_str(merged_state),
2205 merged_version,
2206 )
2207 )
2208
2209 merged_state.load_path = state.load_path
2210 merged_state.load_options = state.load_options
2211
2212 # since we are copying load_options, we need to copy
2213 # the callables_ that would have been generated by those
2214 # load_options.
2215 # assumes that the callables we put in state.callables_
2216 # are not instance-specific (which they should not be)
2217 merged_state._copy_callables(state)
2218
2219 for prop in mapper.iterate_properties:
2220 prop.merge(
2221 self,
2222 state,
2223 state_dict,
2224 merged_state,
2225 merged_dict,
2226 load,
2227 _recursive,
2228 _resolve_conflict_map,
2229 )
2230
2231 if not load:
2232 # remove any history
2233 merged_state._commit_all(merged_dict, self.identity_map)
2234
2235 if new_instance:
2236 merged_state.manager.dispatch.load(merged_state, None)
2237 return merged
2238
2239 def _validate_persistent(self, state):
2240 if not self.identity_map.contains_state(state):
2241 raise sa_exc.InvalidRequestError(
2242 "Instance '%s' is not persistent within this Session"
2243 % state_str(state)
2244 )
2245
2246 def _save_impl(self, state):
2247 if state.key is not None:
2248 raise sa_exc.InvalidRequestError(
2249 "Object '%s' already has an identity - "
2250 "it can't be registered as pending" % state_str(state)
2251 )
2252
2253 obj = state.obj()
2254 to_attach = self._before_attach(state, obj)
2255 if state not in self._new:
2256 self._new[state] = obj
2257 state.insert_order = len(self._new)
2258 if to_attach:
2259 self._after_attach(state, obj)
2260
2261 def _update_impl(self, state, revert_deletion=False):
2262 if state.key is None:
2263 raise sa_exc.InvalidRequestError(
2264 "Instance '%s' is not persisted" % state_str(state)
2265 )
2266
2267 if state._deleted:
2268 if revert_deletion:
2269 if not state._attached:
2270 return
2271 del state._deleted
2272 else:
2273 raise sa_exc.InvalidRequestError(
2274 "Instance '%s' has been deleted. "
2275 "Use the make_transient() "
2276 "function to send this object back "
2277 "to the transient state." % state_str(state)
2278 )
2279
2280 obj = state.obj()
2281
2282 # check for late gc
2283 if obj is None:
2284 return
2285
2286 to_attach = self._before_attach(state, obj)
2287
2288 self._deleted.pop(state, None)
2289 if revert_deletion:
2290 self.identity_map.replace(state)
2291 else:
2292 self.identity_map.add(state)
2293
2294 if to_attach:
2295 self._after_attach(state, obj)
2296 elif revert_deletion:
2297 self.dispatch.deleted_to_persistent(self, obj)
2298
2299 def _save_or_update_impl(self, state):
2300 if state.key is None:
2301 self._save_impl(state)
2302 else:
2303 self._update_impl(state)
2304
2305 def enable_relationship_loading(self, obj):
2306 """Associate an object with this :class:`.Session` for related
2307 object loading.
2308
2309 .. warning::
2310
2311 :meth:`.enable_relationship_loading` exists to serve special
2312 use cases and is not recommended for general use.
2313
2314 Accesses of attributes mapped with :func:`.relationship`
2315 will attempt to load a value from the database using this
2316 :class:`.Session` as the source of connectivity. The values
2317 will be loaded based on foreign key and primary key values
2318 present on this object - if not present, then those relationships
2319 will be unavailable.
2320
2321 The object will be attached to this session, but will
2322 **not** participate in any persistence operations; its state
2323 for almost all purposes will remain either "transient" or
2324 "detached", except for the case of relationship loading.
2325
2326 Also note that backrefs will often not work as expected.
2327 Altering a relationship-bound attribute on the target object
2328 may not fire off a backref event, if the effective value
2329 is what was already loaded from a foreign-key-holding value.
2330
2331 The :meth:`.Session.enable_relationship_loading` method is
2332 similar to the ``load_on_pending`` flag on :func:`.relationship`.
2333 Unlike that flag, :meth:`.Session.enable_relationship_loading` allows
2334 an object to remain transient while still being able to load
2335 related items.
2336
2337 To make a transient object associated with a :class:`.Session`
2338 via :meth:`.Session.enable_relationship_loading` pending, add
2339 it to the :class:`.Session` using :meth:`.Session.add` normally.
2340 If the object instead represents an existing identity in the database,
2341 it should be merged using :meth:`.Session.merge`.
2342
2343 :meth:`.Session.enable_relationship_loading` does not improve
2344 behavior when the ORM is used normally - object references should be
2345 constructed at the object level, not at the foreign key level, so
2346 that they are present in an ordinary way before flush()
2347 proceeds. This method is not intended for general use.
2348
2349 .. seealso::
2350
2351 ``load_on_pending`` at :func:`.relationship` - this flag
2352 allows per-relationship loading of many-to-ones on items that
2353 are pending.
2354
2355 :func:`.make_transient_to_detached` - allows for an object to
2356 be added to a :class:`.Session` without SQL emitted, which then
2357 will unexpire attributes on access.
2358
2359 """
2360 state = attributes.instance_state(obj)
2361 to_attach = self._before_attach(state, obj)
2362 state._load_pending = True
2363 if to_attach:
2364 self._after_attach(state, obj)
2365
2366 def _before_attach(self, state, obj):
2367 if state.session_id == self.hash_key:
2368 return False
2369
2370 if state.session_id and state.session_id in _sessions:
2371 raise sa_exc.InvalidRequestError(
2372 "Object '%s' is already attached to session '%s' "
2373 "(this is '%s')"
2374 % (state_str(state), state.session_id, self.hash_key)
2375 )
2376
2377 self.dispatch.before_attach(self, obj)
2378
2379 return True
2380
2381 def _after_attach(self, state, obj):
2382 state.session_id = self.hash_key
2383 if state.modified and state._strong_obj is None:
2384 state._strong_obj = obj
2385 self.dispatch.after_attach(self, obj)
2386
2387 if state.key:
2388 self.dispatch.detached_to_persistent(self, obj)
2389 else:
2390 self.dispatch.transient_to_pending(self, obj)
2391
2392 def __contains__(self, instance):
2393 """Return True if the instance is associated with this session.
2394
2395 The instance may be pending or persistent within the Session for a
2396 result of True.
2397
2398 """
2399 try:
2400 state = attributes.instance_state(instance)
2401 except exc.NO_STATE:
2402 raise exc.UnmappedInstanceError(instance)
2403 return self._contains_state(state)
2404
2405 def __iter__(self):
2406 """Iterate over all pending or persistent instances within this
2407 Session.
2408
2409 """
2410 return iter(
2411 list(self._new.values()) + list(self.identity_map.values())
2412 )
2413
2414 def _contains_state(self, state):
2415 return state in self._new or self.identity_map.contains_state(state)
2416
2417 def flush(self, objects=None):
2418 """Flush all the object changes to the database.
2419
2420 Writes out all pending object creations, deletions and modifications
2421 to the database as INSERTs, DELETEs, UPDATEs, etc. Operations are
2422 automatically ordered by the Session's unit of work dependency
2423 solver.
2424
2425 Database operations will be issued in the current transactional
2426 context and do not affect the state of the transaction, unless an
2427 error occurs, in which case the entire transaction is rolled back.
2428 You may flush() as often as you like within a transaction to move
2429 changes from Python to the database's transaction buffer.
2430
2431 For ``autocommit`` Sessions with no active manual transaction, flush()
2432 will create a transaction on the fly that surrounds the entire set of
2433 operations into the flush.
2434
2435 :param objects: Optional; restricts the flush operation to operate
2436 only on elements that are in the given collection.
2437
2438 This feature is for an extremely narrow set of use cases where
2439 particular objects may need to be operated upon before the
2440 full flush() occurs. It is not intended for general use.
2441
2442 """
2443
2444 if self._flushing:
2445 raise sa_exc.InvalidRequestError("Session is already flushing")
2446
2447 if self._is_clean():
2448 return
2449 try:
2450 self._flushing = True
2451 self._flush(objects)
2452 finally:
2453 self._flushing = False
2454
2455 def _flush_warning(self, method):
2456 util.warn(
2457 "Usage of the '%s' operation is not currently supported "
2458 "within the execution stage of the flush process. "
2459 "Results may not be consistent. Consider using alternative "
2460 "event listeners or connection-level operations instead." % method
2461 )
2462
2463 def _is_clean(self):
2464 return (
2465 not self.identity_map.check_modified()
2466 and not self._deleted
2467 and not self._new
2468 )
2469
2470 def _flush(self, objects=None):
2471
2472 dirty = self._dirty_states
2473 if not dirty and not self._deleted and not self._new:
2474 self.identity_map._modified.clear()
2475 return
2476
2477 flush_context = UOWTransaction(self)
2478
2479 if self.dispatch.before_flush:
2480 self.dispatch.before_flush(self, flush_context, objects)
2481 # re-establish "dirty states" in case the listeners
2482 # added
2483 dirty = self._dirty_states
2484
2485 deleted = set(self._deleted)
2486 new = set(self._new)
2487
2488 dirty = set(dirty).difference(deleted)
2489
2490 # create the set of all objects we want to operate upon
2491 if objects:
2492 # specific list passed in
2493 objset = set()
2494 for o in objects:
2495 try:
2496 state = attributes.instance_state(o)
2497 except exc.NO_STATE:
2498 raise exc.UnmappedInstanceError(o)
2499 objset.add(state)
2500 else:
2501 objset = None
2502
2503 # store objects whose fate has been decided
2504 processed = set()
2505
2506 # put all saves/updates into the flush context. detect top-level
2507 # orphans and throw them into deleted.
2508 if objset:
2509 proc = new.union(dirty).intersection(objset).difference(deleted)
2510 else:
2511 proc = new.union(dirty).difference(deleted)
2512
2513 for state in proc:
2514 is_orphan = _state_mapper(state)._is_orphan(state)
2515
2516 is_persistent_orphan = is_orphan and state.has_identity
2517
2518 if (
2519 is_orphan
2520 and not is_persistent_orphan
2521 and state._orphaned_outside_of_session
2522 ):
2523 self._expunge_states([state])
2524 else:
2525 _reg = flush_context.register_object(
2526 state, isdelete=is_persistent_orphan
2527 )
2528 assert _reg, "Failed to add object to the flush context!"
2529 processed.add(state)
2530
2531 # put all remaining deletes into the flush context.
2532 if objset:
2533 proc = deleted.intersection(objset).difference(processed)
2534 else:
2535 proc = deleted.difference(processed)
2536 for state in proc:
2537 _reg = flush_context.register_object(state, isdelete=True)
2538 assert _reg, "Failed to add object to the flush context!"
2539
2540 if not flush_context.has_work:
2541 return
2542
2543 flush_context.transaction = transaction = self.begin(
2544 subtransactions=True
2545 )
2546 try:
2547 self._warn_on_events = True
2548 try:
2549 flush_context.execute()
2550 finally:
2551 self._warn_on_events = False
2552
2553 self.dispatch.after_flush(self, flush_context)
2554
2555 flush_context.finalize_flush_changes()
2556
2557 if not objects and self.identity_map._modified:
2558 len_ = len(self.identity_map._modified)
2559
2560 statelib.InstanceState._commit_all_states(
2561 [
2562 (state, state.dict)
2563 for state in self.identity_map._modified
2564 ],
2565 instance_dict=self.identity_map,
2566 )
2567 util.warn(
2568 "Attribute history events accumulated on %d "
2569 "previously clean instances "
2570 "within inner-flush event handlers have been "
2571 "reset, and will not result in database updates. "
2572 "Consider using set_committed_value() within "
2573 "inner-flush event handlers to avoid this warning." % len_
2574 )
2575
2576 # useful assertions:
2577 # if not objects:
2578 # assert not self.identity_map._modified
2579 # else:
2580 # assert self.identity_map._modified == \
2581 # self.identity_map._modified.difference(objects)
2582
2583 self.dispatch.after_flush_postexec(self, flush_context)
2584
2585 transaction.commit()
2586
2587 except:
2588 with util.safe_reraise():
2589 transaction.rollback(_capture_exception=True)
2590
2591 def bulk_save_objects(
2592 self,
2593 objects,
2594 return_defaults=False,
2595 update_changed_only=True,
2596 preserve_order=True,
2597 ):
2598 """Perform a bulk save of the given list of objects.
2599
2600 The bulk save feature allows mapped objects to be used as the
2601 source of simple INSERT and UPDATE operations which can be more easily
2602 grouped together into higher performing "executemany"
2603 operations; the extraction of data from the objects is also performed
2604 using a lower-latency process that ignores whether or not attributes
2605 have actually been modified in the case of UPDATEs, and also ignores
2606 SQL expressions.
2607
2608 The objects as given are not added to the session and no additional
2609 state is established on them, unless the ``return_defaults`` flag
2610 is also set, in which case primary key attributes and server-side
2611 default values will be populated.
2612
2613 .. versionadded:: 1.0.0
2614
2615 .. warning::
2616
2617 The bulk save feature allows for a lower-latency INSERT/UPDATE
2618 of rows at the expense of most other unit-of-work features.
2619 Features such as object management, relationship handling,
2620 and SQL clause support are **silently omitted** in favor of raw
2621 INSERT/UPDATES of records.
2622
2623 **Please read the list of caveats at** :ref:`bulk_operations`
2624 **before using this method, and fully test and confirm the
2625 functionality of all code developed using these systems.**
2626
2627 :param objects: a list of mapped object instances. The mapped
2628 objects are persisted as is, and are **not** associated with the
2629 :class:`.Session` afterwards.
2630
2631 For each object, whether the object is sent as an INSERT or an
2632 UPDATE is dependent on the same rules used by the :class:`.Session`
2633 in traditional operation; if the object has the
2634 :attr:`.InstanceState.key`
2635 attribute set, then the object is assumed to be "detached" and
2636 will result in an UPDATE. Otherwise, an INSERT is used.
2637
2638 In the case of an UPDATE, statements are grouped based on which
2639 attributes have changed, and are thus to be the subject of each
2640 SET clause. If ``update_changed_only`` is False, then all
2641 attributes present within each object are applied to the UPDATE
2642 statement, which may help in allowing the statements to be grouped
2643 together into a larger executemany(), and will also reduce the
2644 overhead of checking history on attributes.
2645
2646 :param return_defaults: when True, rows that are missing values which
2647 generate defaults, namely integer primary key defaults and sequences,
2648 will be inserted **one at a time**, so that the primary key value
2649 is available. In particular this will allow joined-inheritance
2650 and other multi-table mappings to insert correctly without the need
2651 to provide primary key values ahead of time; however,
2652 :paramref:`.Session.bulk_save_objects.return_defaults` **greatly
2653 reduces the performance gains** of the method overall.
2654
2655 :param update_changed_only: when True, UPDATE statements are rendered
2656 based on those attributes in each state that have logged changes.
2657 When False, all attributes present are rendered into the SET clause
2658 with the exception of primary key attributes.
2659
2660 :param preserve_order: when True, the order of inserts and updates
2661 matches exactly the order in which the objects are given. When
2662 False, common types of objects are grouped into inserts
2663 and updates, to allow for more batching opportunities.
2664
2665 .. versionadded:: 1.3
2666
2667 .. seealso::
2668
2669 :ref:`bulk_operations`
2670
2671 :meth:`.Session.bulk_insert_mappings`
2672
2673 :meth:`.Session.bulk_update_mappings`
2674
2675 """
2676
2677 def key(state):
2678 return (state.mapper, state.key is not None)
2679
2680 obj_states = tuple(attributes.instance_state(obj) for obj in objects)
2681 if not preserve_order:
2682 obj_states = sorted(obj_states, key=key)
2683
2684 for (mapper, isupdate), states in itertools.groupby(obj_states, key):
2685 self._bulk_save_mappings(
2686 mapper,
2687 states,
2688 isupdate,
2689 True,
2690 return_defaults,
2691 update_changed_only,
2692 False,
2693 )
2694
2695 def bulk_insert_mappings(
2696 self, mapper, mappings, return_defaults=False, render_nulls=False
2697 ):
2698 """Perform a bulk insert of the given list of mapping dictionaries.
2699
2700 The bulk insert feature allows plain Python dictionaries to be used as
2701 the source of simple INSERT operations which can be more easily
2702 grouped together into higher performing "executemany"
2703 operations. Using dictionaries, there is no "history" or session
2704 state management features in use, reducing latency when inserting
2705 large numbers of simple rows.
2706
2707 The values within the dictionaries as given are typically passed
2708 without modification into Core :meth:`.Insert` constructs, after
2709 organizing the values within them across the tables to which
2710 the given mapper is mapped.
2711
2712 .. versionadded:: 1.0.0
2713
2714 .. warning::
2715
2716 The bulk insert feature allows for a lower-latency INSERT
2717 of rows at the expense of most other unit-of-work features.
2718 Features such as object management, relationship handling,
2719 and SQL clause support are **silently omitted** in favor of raw
2720 INSERT of records.
2721
2722 **Please read the list of caveats at** :ref:`bulk_operations`
2723 **before using this method, and fully test and confirm the
2724 functionality of all code developed using these systems.**
2725
2726 :param mapper: a mapped class, or the actual :class:`.Mapper` object,
2727 representing the single kind of object represented within the mapping
2728 list.
2729
2730 :param mappings: a list of dictionaries, each one containing the state
2731 of the mapped row to be inserted, in terms of the attribute names
2732 on the mapped class. If the mapping refers to multiple tables,
2733 such as a joined-inheritance mapping, each dictionary must contain
2734 all keys to be populated into all tables.
2735
2736 :param return_defaults: when True, rows that are missing values which
2737 generate defaults, namely integer primary key defaults and sequences,
2738 will be inserted **one at a time**, so that the primary key value
2739 is available. In particular this will allow joined-inheritance
2740 and other multi-table mappings to insert correctly without the need
2741 to provide primary
2742 key values ahead of time; however,
2743 :paramref:`.Session.bulk_insert_mappings.return_defaults`
2744 **greatly reduces the performance gains** of the method overall.
2745 If the rows
2746 to be inserted only refer to a single table, then there is no
2747 reason this flag should be set as the returned default information
2748 is not used.
2749
2750 :param render_nulls: When True, a value of ``None`` will result
2751 in a NULL value being included in the INSERT statement, rather
2752 than the column being omitted from the INSERT. This allows all
2753 the rows being INSERTed to have the identical set of columns which
2754 allows the full set of rows to be batched to the DBAPI. Normally,
2755 each column-set that contains a different combination of NULL values
2756 than the previous row must omit a different series of columns from
2757 the rendered INSERT statement, which means it must be emitted as a
2758 separate statement. By passing this flag, the full set of rows
2759 are guaranteed to be batchable into one batch; the cost however is
2760 that server-side defaults which are invoked by an omitted column will
2761 be skipped, so care must be taken to ensure that these are not
2762 necessary.
2763
2764 .. warning::
2765
2766 When this flag is set, **server side default SQL values will
2767 not be invoked** for those columns that are inserted as NULL;
2768 the NULL value will be sent explicitly. Care must be taken
2769 to ensure that no server-side default functions need to be
2770 invoked for the operation as a whole.
2771
2772 .. versionadded:: 1.1
2773
2774 .. seealso::
2775
2776 :ref:`bulk_operations`
2777
2778 :meth:`.Session.bulk_save_objects`
2779
2780 :meth:`.Session.bulk_update_mappings`
2781
2782 """
2783 self._bulk_save_mappings(
2784 mapper,
2785 mappings,
2786 False,
2787 False,
2788 return_defaults,
2789 False,
2790 render_nulls,
2791 )
2792
2793 def bulk_update_mappings(self, mapper, mappings):
2794 """Perform a bulk update of the given list of mapping dictionaries.
2795
2796 The bulk update feature allows plain Python dictionaries to be used as
2797 the source of simple UPDATE operations which can be more easily
2798 grouped together into higher performing "executemany"
2799 operations. Using dictionaries, there is no "history" or session
2800 state management features in use, reducing latency when updating
2801 large numbers of simple rows.
2802
2803 .. versionadded:: 1.0.0
2804
2805 .. warning::
2806
2807 The bulk update feature allows for a lower-latency UPDATE
2808 of rows at the expense of most other unit-of-work features.
2809 Features such as object management, relationship handling,
2810 and SQL clause support are **silently omitted** in favor of raw
2811 UPDATES of records.
2812
2813 **Please read the list of caveats at** :ref:`bulk_operations`
2814 **before using this method, and fully test and confirm the
2815 functionality of all code developed using these systems.**
2816
2817 :param mapper: a mapped class, or the actual :class:`.Mapper` object,
2818 representing the single kind of object represented within the mapping
2819 list.
2820
2821 :param mappings: a list of dictionaries, each one containing the state
2822 of the mapped row to be updated, in terms of the attribute names
2823 on the mapped class. If the mapping refers to multiple tables,
2824 such as a joined-inheritance mapping, each dictionary may contain
2825 keys corresponding to all tables. All those keys which are present
2826 and are not part of the primary key are applied to the SET clause
2827 of the UPDATE statement; the primary key values, which are required,
2828 are applied to the WHERE clause.
2829
2830
2831 .. seealso::
2832
2833 :ref:`bulk_operations`
2834
2835 :meth:`.Session.bulk_insert_mappings`
2836
2837 :meth:`.Session.bulk_save_objects`
2838
2839 """
2840 self._bulk_save_mappings(
2841 mapper, mappings, True, False, False, False, False
2842 )
2843
2844 def _bulk_save_mappings(
2845 self,
2846 mapper,
2847 mappings,
2848 isupdate,
2849 isstates,
2850 return_defaults,
2851 update_changed_only,
2852 render_nulls,
2853 ):
2854 mapper = _class_to_mapper(mapper)
2855 self._flushing = True
2856
2857 transaction = self.begin(subtransactions=True)
2858 try:
2859 if isupdate:
2860 persistence._bulk_update(
2861 mapper,
2862 mappings,
2863 transaction,
2864 isstates,
2865 update_changed_only,
2866 )
2867 else:
2868 persistence._bulk_insert(
2869 mapper,
2870 mappings,
2871 transaction,
2872 isstates,
2873 return_defaults,
2874 render_nulls,
2875 )
2876 transaction.commit()
2877
2878 except:
2879 with util.safe_reraise():
2880 transaction.rollback(_capture_exception=True)
2881 finally:
2882 self._flushing = False
2883
2884 @util.deprecated_params(
2885 passive=(
2886 "0.8",
2887 "The :paramref:`.Session.is_modified.passive` flag is deprecated "
2888 "and will be removed in a future release. The flag is no longer "
2889 "used and is ignored.",
2890 )
2891 )
2892 def is_modified(self, instance, include_collections=True, passive=None):
2893 r"""Return ``True`` if the given instance has locally
2894 modified attributes.
2895
2896 This method retrieves the history for each instrumented
2897 attribute on the instance and performs a comparison of the current
2898 value to its previously committed value, if any.
2899
2900 It is in effect a more expensive and accurate
2901 version of checking for the given instance in the
2902 :attr:`.Session.dirty` collection; a full test for
2903 each attribute's net "dirty" status is performed.
2904
2905 E.g.::
2906
2907 return session.is_modified(someobject)
2908
2909 A few caveats to this method apply:
2910
2911 * Instances present in the :attr:`.Session.dirty` collection may
2912 report ``False`` when tested with this method. This is because
2913 the object may have received change events via attribute mutation,
2914 thus placing it in :attr:`.Session.dirty`, but ultimately the state
2915 is the same as that loaded from the database, resulting in no net
2916 change here.
2917 * Scalar attributes may not have recorded the previously set
2918 value when a new value was applied, if the attribute was not loaded,
2919 or was expired, at the time the new value was received - in these
2920 cases, the attribute is assumed to have a change, even if there is
2921 ultimately no net change against its database value. SQLAlchemy in
2922 most cases does not need the "old" value when a set event occurs, so
2923 it skips the expense of a SQL call if the old value isn't present,
2924 based on the assumption that an UPDATE of the scalar value is
2925 usually needed, and in those few cases where it isn't, is less
2926 expensive on average than issuing a defensive SELECT.
2927
2928 The "old" value is fetched unconditionally upon set only if the
2929 attribute container has the ``active_history`` flag set to ``True``.
2930 This flag is set typically for primary key attributes and scalar
2931 object references that are not a simple many-to-one. To set this
2932 flag for any arbitrary mapped column, use the ``active_history``
2933 argument with :func:`.column_property`.
2934
2935 :param instance: mapped instance to be tested for pending changes.
2936 :param include_collections: Indicates if multivalued collections
2937 should be included in the operation. Setting this to ``False`` is a
2938 way to detect only local-column based properties (i.e. scalar columns
2939 or many-to-one foreign keys) that would result in an UPDATE for this
2940 instance upon flush.
2941 :param passive: not used
2942
2943 """
2944 state = object_state(instance)
2945
2946 if not state.modified:
2947 return False
2948
2949 dict_ = state.dict
2950
2951 for attr in state.manager.attributes:
2952 if (
2953 not include_collections
2954 and hasattr(attr.impl, "get_collection")
2955 ) or not hasattr(attr.impl, "get_history"):
2956 continue
2957
2958 (added, unchanged, deleted) = attr.impl.get_history(
2959 state, dict_, passive=attributes.NO_CHANGE
2960 )
2961
2962 if added or deleted:
2963 return True
2964 else:
2965 return False
2966
2967 @property
2968 def is_active(self):
2969 """True if this :class:`.Session` is in "transaction mode" and
2970 is not in "partial rollback" state.
2971
2972 The :class:`.Session` in its default mode of ``autocommit=False``
2973 is essentially always in "transaction mode", in that a
2974 :class:`.SessionTransaction` is associated with it as soon as
2975 it is instantiated. This :class:`.SessionTransaction` is immediately
2976 replaced with a new one as soon as it is ended, due to a rollback,
2977 commit, or close operation.
2978
2979 "Transaction mode" does *not* indicate whether
2980 or not actual database connection resources are in use; the
2981 :class:`.SessionTransaction` object coordinates among zero or more
2982 actual database transactions, and starts out with none, accumulating
2983 individual DBAPI connections as different data sources are used
2984 within its scope. The best way to track when a particular
2985 :class:`.Session` has actually begun to use DBAPI resources is to
2986 implement a listener using the :meth:`.SessionEvents.after_begin`
2987 method, which will deliver both the :class:`.Session` as well as the
2988 target :class:`.Connection` to a user-defined event listener.
2989
2990 The "partial rollback" state refers to when an "inner" transaction,
2991 typically used during a flush, encounters an error and emits a
2992 rollback of the DBAPI connection. At this point, the
2993 :class:`.Session` is in "partial rollback" and awaits for the user to
2994 call :meth:`.Session.rollback`, in order to close out the
2995 transaction stack. It is in this "partial rollback" period that the
2996 :attr:`.is_active` flag returns False. After the call to
2997 :meth:`.Session.rollback`, the :class:`.SessionTransaction` is
2998 replaced with a new one and :attr:`.is_active` returns ``True`` again.
2999
3000 When a :class:`.Session` is used in ``autocommit=True`` mode, the
3001 :class:`.SessionTransaction` is only instantiated within the scope
3002 of a flush call, or when :meth:`.Session.begin` is called. So
3003 :attr:`.is_active` will always be ``False`` outside of a flush or
3004 :meth:`.Session.begin` block in this mode, and will be ``True``
3005 within the :meth:`.Session.begin` block as long as it doesn't enter
3006 "partial rollback" state.
3007
3008 From all the above, it follows that the only purpose to this flag is
3009 for application frameworks that wish to detect is a "rollback" is
3010 necessary within a generic error handling routine, for
3011 :class:`.Session` objects that would otherwise be in
3012 "partial rollback" mode. In a typical integration case, this is also
3013 not necessary as it is standard practice to emit
3014 :meth:`.Session.rollback` unconditionally within the outermost
3015 exception catch.
3016
3017 To track the transactional state of a :class:`.Session` fully,
3018 use event listeners, primarily the :meth:`.SessionEvents.after_begin`,
3019 :meth:`.SessionEvents.after_commit`,
3020 :meth:`.SessionEvents.after_rollback` and related events.
3021
3022 """
3023 return self.transaction and self.transaction.is_active
3024
3025 identity_map = None
3026 """A mapping of object identities to objects themselves.
3027
3028 Iterating through ``Session.identity_map.values()`` provides
3029 access to the full set of persistent objects (i.e., those
3030 that have row identity) currently in the session.
3031
3032 .. seealso::
3033
3034 :func:`.identity_key` - helper function to produce the keys used
3035 in this dictionary.
3036
3037 """
3038
3039 @property
3040 def _dirty_states(self):
3041 """The set of all persistent states considered dirty.
3042
3043 This method returns all states that were modified including
3044 those that were possibly deleted.
3045
3046 """
3047 return self.identity_map._dirty_states()
3048
3049 @property
3050 def dirty(self):
3051 """The set of all persistent instances considered dirty.
3052
3053 E.g.::
3054
3055 some_mapped_object in session.dirty
3056
3057 Instances are considered dirty when they were modified but not
3058 deleted.
3059
3060 Note that this 'dirty' calculation is 'optimistic'; most
3061 attribute-setting or collection modification operations will
3062 mark an instance as 'dirty' and place it in this set, even if
3063 there is no net change to the attribute's value. At flush
3064 time, the value of each attribute is compared to its
3065 previously saved value, and if there's no net change, no SQL
3066 operation will occur (this is a more expensive operation so
3067 it's only done at flush time).
3068
3069 To check if an instance has actionable net changes to its
3070 attributes, use the :meth:`.Session.is_modified` method.
3071
3072 """
3073 return util.IdentitySet(
3074 [
3075 state.obj()
3076 for state in self._dirty_states
3077 if state not in self._deleted
3078 ]
3079 )
3080
3081 @property
3082 def deleted(self):
3083 "The set of all instances marked as 'deleted' within this ``Session``"
3084
3085 return util.IdentitySet(list(self._deleted.values()))
3086
3087 @property
3088 def new(self):
3089 "The set of all instances marked as 'new' within this ``Session``."
3090
3091 return util.IdentitySet(list(self._new.values()))
3092
3093
3094class sessionmaker(_SessionClassMethods):
3095 """A configurable :class:`.Session` factory.
3096
3097 The :class:`.sessionmaker` factory generates new
3098 :class:`.Session` objects when called, creating them given
3099 the configurational arguments established here.
3100
3101 e.g.::
3102
3103 # global scope
3104 Session = sessionmaker(autoflush=False)
3105
3106 # later, in a local scope, create and use a session:
3107 sess = Session()
3108
3109 Any keyword arguments sent to the constructor itself will override the
3110 "configured" keywords::
3111
3112 Session = sessionmaker()
3113
3114 # bind an individual session to a connection
3115 sess = Session(bind=connection)
3116
3117 The class also includes a method :meth:`.configure`, which can
3118 be used to specify additional keyword arguments to the factory, which
3119 will take effect for subsequent :class:`.Session` objects generated.
3120 This is usually used to associate one or more :class:`.Engine` objects
3121 with an existing :class:`.sessionmaker` factory before it is first
3122 used::
3123
3124 # application starts
3125 Session = sessionmaker()
3126
3127 # ... later
3128 engine = create_engine('sqlite:///foo.db')
3129 Session.configure(bind=engine)
3130
3131 sess = Session()
3132
3133 .. seealso:
3134
3135 :ref:`session_getting` - introductory text on creating
3136 sessions using :class:`.sessionmaker`.
3137
3138 """
3139
3140 def __init__(
3141 self,
3142 bind=None,
3143 class_=Session,
3144 autoflush=True,
3145 autocommit=False,
3146 expire_on_commit=True,
3147 info=None,
3148 **kw
3149 ):
3150 r"""Construct a new :class:`.sessionmaker`.
3151
3152 All arguments here except for ``class_`` correspond to arguments
3153 accepted by :class:`.Session` directly. See the
3154 :meth:`.Session.__init__` docstring for more details on parameters.
3155
3156 :param bind: a :class:`.Engine` or other :class:`.Connectable` with
3157 which newly created :class:`.Session` objects will be associated.
3158 :param class_: class to use in order to create new :class:`.Session`
3159 objects. Defaults to :class:`.Session`.
3160 :param autoflush: The autoflush setting to use with newly created
3161 :class:`.Session` objects.
3162 :param autocommit: The autocommit setting to use with newly created
3163 :class:`.Session` objects.
3164 :param expire_on_commit=True: the expire_on_commit setting to use
3165 with newly created :class:`.Session` objects.
3166 :param info: optional dictionary of information that will be available
3167 via :attr:`.Session.info`. Note this dictionary is *updated*, not
3168 replaced, when the ``info`` parameter is specified to the specific
3169 :class:`.Session` construction operation.
3170
3171 .. versionadded:: 0.9.0
3172
3173 :param \**kw: all other keyword arguments are passed to the
3174 constructor of newly created :class:`.Session` objects.
3175
3176 """
3177 kw["bind"] = bind
3178 kw["autoflush"] = autoflush
3179 kw["autocommit"] = autocommit
3180 kw["expire_on_commit"] = expire_on_commit
3181 if info is not None:
3182 kw["info"] = info
3183 self.kw = kw
3184 # make our own subclass of the given class, so that
3185 # events can be associated with it specifically.
3186 self.class_ = type(class_.__name__, (class_,), {})
3187
3188 def __call__(self, **local_kw):
3189 """Produce a new :class:`.Session` object using the configuration
3190 established in this :class:`.sessionmaker`.
3191
3192 In Python, the ``__call__`` method is invoked on an object when
3193 it is "called" in the same way as a function::
3194
3195 Session = sessionmaker()
3196 session = Session() # invokes sessionmaker.__call__()
3197
3198 """
3199 for k, v in self.kw.items():
3200 if k == "info" and "info" in local_kw:
3201 d = v.copy()
3202 d.update(local_kw["info"])
3203 local_kw["info"] = d
3204 else:
3205 local_kw.setdefault(k, v)
3206 return self.class_(**local_kw)
3207
3208 def configure(self, **new_kw):
3209 """(Re)configure the arguments for this sessionmaker.
3210
3211 e.g.::
3212
3213 Session = sessionmaker()
3214
3215 Session.configure(bind=create_engine('sqlite://'))
3216 """
3217 self.kw.update(new_kw)
3218
3219 def __repr__(self):
3220 return "%s(class_=%r, %s)" % (
3221 self.__class__.__name__,
3222 self.class_.__name__,
3223 ", ".join("%s=%r" % (k, v) for k, v in self.kw.items()),
3224 )
3225
3226
3227def close_all_sessions():
3228 """Close all sessions in memory.
3229
3230 This function consults a global registry of all :class:`.Session` objects
3231 and calls :meth:`.Session.close` on them, which resets them to a clean
3232 state.
3233
3234 This function is not for general use but may be useful for test suites
3235 within the teardown scheme.
3236
3237 .. versionadded:: 1.3
3238
3239 """
3240
3241 for sess in _sessions.values():
3242 sess.close()
3243
3244
3245def make_transient(instance):
3246 """Alter the state of the given instance so that it is :term:`transient`.
3247
3248 .. note::
3249
3250 :func:`.make_transient` is a special-case function for
3251 advanced use cases only.
3252
3253 The given mapped instance is assumed to be in the :term:`persistent` or
3254 :term:`detached` state. The function will remove its association with any
3255 :class:`.Session` as well as its :attr:`.InstanceState.identity`. The
3256 effect is that the object will behave as though it were newly constructed,
3257 except retaining any attribute / collection values that were loaded at the
3258 time of the call. The :attr:`.InstanceState.deleted` flag is also reset
3259 if this object had been deleted as a result of using
3260 :meth:`.Session.delete`.
3261
3262 .. warning::
3263
3264 :func:`.make_transient` does **not** "unexpire" or otherwise eagerly
3265 load ORM-mapped attributes that are not currently loaded at the time
3266 the function is called. This includes attributes which:
3267
3268 * were expired via :meth:`.Session.expire`
3269
3270 * were expired as the natural effect of committing a session
3271 transaction, e.g. :meth:`.Session.commit`
3272
3273 * are normally :term:`lazy loaded` but are not currently loaded
3274
3275 * are "deferred" via :ref:`deferred` and are not yet loaded
3276
3277 * were not present in the query which loaded this object, such as that
3278 which is common in joined table inheritance and other scenarios.
3279
3280 After :func:`.make_transient` is called, unloaded attributes such
3281 as those above will normally resolve to the value ``None`` when
3282 accessed, or an empty collection for a collection-oriented attribute.
3283 As the object is transient and un-associated with any database
3284 identity, it will no longer retrieve these values.
3285
3286 .. seealso::
3287
3288 :func:`.make_transient_to_detached`
3289
3290 """
3291 state = attributes.instance_state(instance)
3292 s = _state_session(state)
3293 if s:
3294 s._expunge_states([state])
3295
3296 # remove expired state
3297 state.expired_attributes.clear()
3298
3299 # remove deferred callables
3300 if state.callables:
3301 del state.callables
3302
3303 if state.key:
3304 del state.key
3305 if state._deleted:
3306 del state._deleted
3307
3308
3309def make_transient_to_detached(instance):
3310 """Make the given transient instance :term:`detached`.
3311
3312 .. note::
3313
3314 :func:`.make_transient_to_detached` is a special-case function for
3315 advanced use cases only.
3316
3317 All attribute history on the given instance
3318 will be reset as though the instance were freshly loaded
3319 from a query. Missing attributes will be marked as expired.
3320 The primary key attributes of the object, which are required, will be made
3321 into the "key" of the instance.
3322
3323 The object can then be added to a session, or merged
3324 possibly with the load=False flag, at which point it will look
3325 as if it were loaded that way, without emitting SQL.
3326
3327 This is a special use case function that differs from a normal
3328 call to :meth:`.Session.merge` in that a given persistent state
3329 can be manufactured without any SQL calls.
3330
3331 .. versionadded:: 0.9.5
3332
3333 .. seealso::
3334
3335 :func:`.make_transient`
3336
3337 :meth:`.Session.enable_relationship_loading`
3338
3339 """
3340 state = attributes.instance_state(instance)
3341 if state.session_id or state.key:
3342 raise sa_exc.InvalidRequestError("Given object must be transient")
3343 state.key = state.mapper._identity_key_from_state(state)
3344 if state._deleted:
3345 del state._deleted
3346 state._commit_all(state.dict)
3347 state._expire_attributes(state.dict, state.unloaded_expirable)
3348
3349
3350def object_session(instance):
3351 """Return the :class:`.Session` to which the given instance belongs.
3352
3353 This is essentially the same as the :attr:`.InstanceState.session`
3354 accessor. See that attribute for details.
3355
3356 """
3357
3358 try:
3359 state = attributes.instance_state(instance)
3360 except exc.NO_STATE:
3361 raise exc.UnmappedInstanceError(instance)
3362 else:
3363 return _state_session(state)
3364
3365
3366_new_sessionid = util.counter()