Learn more  » Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Bower components Debian packages RPM packages NuGet packages

aroundthecode / SQLAlchemy   python

Repository URL to install this package:

Version: 1.2.10 

/ orm / session.py

# orm/session.py
# Copyright (C) 2005-2018 the SQLAlchemy authors and contributors
# <see AUTHORS file>
#
# This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php
"""Provides the Session class and related utilities."""


import weakref
from .. import util, sql, engine, exc as sa_exc
from ..sql import util as sql_util, expression
from . import (
    SessionExtension, attributes, exc, query,
    loading, identity
)
from ..inspection import inspect
from .base import (
    object_mapper, class_mapper,
    _class_to_mapper, _state_mapper, object_state,
    _none_set, state_str, instance_str
)
import itertools
from . import persistence
from .unitofwork import UOWTransaction
from . import state as statelib
import sys

__all__ = ['Session', 'SessionTransaction',
           'SessionExtension', 'sessionmaker']

_sessions = weakref.WeakValueDictionary()
"""Weak-referencing dictionary of :class:`.Session` objects.
"""


def _state_session(state):
    """Given an :class:`.InstanceState`, return the :class:`.Session`
        associated, if any.
    """
    if state.session_id:
        try:
            return _sessions[state.session_id]
        except KeyError:
            pass
    return None


class _SessionClassMethods(object):
    """Class-level methods for :class:`.Session`, :class:`.sessionmaker`."""

    @classmethod
    def close_all(cls):
        """Close *all* sessions in memory."""

        for sess in _sessions.values():
            sess.close()

    @classmethod
    @util.dependencies("sqlalchemy.orm.util")
    def identity_key(cls, orm_util, *args, **kwargs):
        """Return an identity key.

        This is an alias of :func:`.util.identity_key`.

        """
        return orm_util.identity_key(*args, **kwargs)

    @classmethod
    def object_session(cls, instance):
        """Return the :class:`.Session` to which an object belongs.

        This is an alias of :func:`.object_session`.

        """

        return object_session(instance)


ACTIVE = util.symbol('ACTIVE')
PREPARED = util.symbol('PREPARED')
COMMITTED = util.symbol('COMMITTED')
DEACTIVE = util.symbol('DEACTIVE')
CLOSED = util.symbol('CLOSED')


class SessionTransaction(object):
    """A :class:`.Session`-level transaction.

    :class:`.SessionTransaction` is a mostly behind-the-scenes object
    not normally referenced directly by application code.   It coordinates
    among multiple :class:`.Connection` objects, maintaining a database
    transaction for each one individually, committing or rolling them
    back all at once.   It also provides optional two-phase commit behavior
    which can augment this coordination operation.

    The :attr:`.Session.transaction` attribute of :class:`.Session`
    refers to the current :class:`.SessionTransaction` object in use, if any.
    The :attr:`.SessionTransaction.parent` attribute refers to the parent
    :class:`.SessionTransaction` in the stack of :class:`.SessionTransaction`
    objects.  If this attribute is ``None``, then this is the top of the stack.
    If non-``None``, then this :class:`.SessionTransaction` refers either
    to a so-called "subtransaction" or a "nested" transaction.  A
    "subtransaction" is a scoping concept that demarcates an inner portion
    of the outermost "real" transaction.  A nested transaction, which
    is indicated when the :attr:`.SessionTransaction.nested`
    attribute is also True, indicates that this :class:`.SessionTransaction`
    corresponds to a SAVEPOINT.

    **Life Cycle**

    A :class:`.SessionTransaction` is associated with a :class:`.Session`
    in its default mode of ``autocommit=False`` immediately, associated
    with no database connections.  As the :class:`.Session` is called upon
    to emit SQL on behalf of various :class:`.Engine` or :class:`.Connection`
    objects, a corresponding :class:`.Connection` and associated
    :class:`.Transaction` is added to a collection within the
    :class:`.SessionTransaction` object, becoming one of the
    connection/transaction pairs maintained by the
    :class:`.SessionTransaction`.  The start of a :class:`.SessionTransaction`
    can be tracked using the :meth:`.SessionEvents.after_transaction_create`
    event.

    The lifespan of the :class:`.SessionTransaction` ends when the
    :meth:`.Session.commit`, :meth:`.Session.rollback` or
    :meth:`.Session.close` methods are called.  At this point, the
    :class:`.SessionTransaction` removes its association with its parent
    :class:`.Session`.   A :class:`.Session` that is in ``autocommit=False``
    mode will create a new :class:`.SessionTransaction` to replace it
    immediately, whereas a :class:`.Session` that's in ``autocommit=True``
    mode will remain without a :class:`.SessionTransaction` until the
    :meth:`.Session.begin` method is called.  The end of a
    :class:`.SessionTransaction` can be tracked using the
    :meth:`.SessionEvents.after_transaction_end` event.

    **Nesting and Subtransactions**

    Another detail of :class:`.SessionTransaction` behavior is that it is
    capable of "nesting".  This means that the :meth:`.Session.begin` method
    can be called while an existing :class:`.SessionTransaction` is already
    present, producing a new :class:`.SessionTransaction` that temporarily
    replaces the parent :class:`.SessionTransaction`.   When a
    :class:`.SessionTransaction` is produced as nested, it assigns itself to
    the :attr:`.Session.transaction` attribute, and it additionally will assign
    the previous :class:`.SessionTransaction` to its :attr:`.Session.parent`
    attribute.  The behavior is effectively a
    stack, where :attr:`.Session.transaction` refers to the current head of
    the stack, and the :attr:`.SessionTransaction.parent` attribute allows
    traversal up the stack until :attr:`.SessionTransaction.parent` is
    ``None``, indicating the top of the stack.

    When the scope of :class:`.SessionTransaction` is ended via
    :meth:`.Session.commit` or :meth:`.Session.rollback`, it restores its
    parent :class:`.SessionTransaction` back onto the
    :attr:`.Session.transaction` attribute.

    The purpose of this stack is to allow nesting of
    :meth:`.Session.rollback` or :meth:`.Session.commit` calls in context
    with various flavors of :meth:`.Session.begin`. This nesting behavior
    applies to when :meth:`.Session.begin_nested` is used to emit a
    SAVEPOINT transaction, and is also used to produce a so-called
    "subtransaction" which allows a block of code to use a
    begin/rollback/commit sequence regardless of whether or not its enclosing
    code block has begun a transaction.  The :meth:`.flush` method, whether
    called explicitly or via autoflush, is the primary consumer of the
    "subtransaction" feature, in that it wishes to guarantee that it works
    within in a transaction block regardless of whether or not the
    :class:`.Session` is in transactional mode when the method is called.

    Note that the flush process that occurs within the "autoflush" feature
    as well as when the :meth:`.Session.flush` method is used **always**
    creates a :class:`.SessionTransaction` object.   This object is normally
    a subtransaction, unless the :class:`.Session` is in autocommit mode
    and no transaction exists at all, in which case it's the outermost
    transaction.   Any event-handling logic or other inspection logic
    needs to take into account whether a :class:`.SessionTransaction`
    is the outermost transaction, a subtransaction, or a "nested" / SAVEPOINT
    transaction.

    .. seealso::

        :meth:`.Session.rollback`

        :meth:`.Session.commit`

        :meth:`.Session.begin`

        :meth:`.Session.begin_nested`

        :attr:`.Session.is_active`

        :meth:`.SessionEvents.after_transaction_create`

        :meth:`.SessionEvents.after_transaction_end`

        :meth:`.SessionEvents.after_commit`

        :meth:`.SessionEvents.after_rollback`

        :meth:`.SessionEvents.after_soft_rollback`

    """

    _rollback_exception = None

    def __init__(self, session, parent=None, nested=False):
        self.session = session
        self._connections = {}
        self._parent = parent
        self.nested = nested
        self._state = ACTIVE
        if not parent and nested:
            raise sa_exc.InvalidRequestError(
                "Can't start a SAVEPOINT transaction when no existing "
                "transaction is in progress")

        if self.session._enable_transaction_accounting:
            self._take_snapshot()

        self.session.dispatch.after_transaction_create(self.session, self)

    @property
    def parent(self):
        """The parent :class:`.SessionTransaction` of this
        :class:`.SessionTransaction`.

        If this attribute is ``None``, indicates this
        :class:`.SessionTransaction` is at the top of the stack, and
        corresponds to a real "COMMIT"/"ROLLBACK"
        block.  If non-``None``, then this is either a "subtransaction"
        or a "nested" / SAVEPOINT transaction.  If the
        :attr:`.SessionTransaction.nested` attribute is ``True``, then
        this is a SAVEPOINT, and if ``False``, indicates this a subtransaction.

        .. versionadded:: 1.0.16 - use ._parent for previous versions

        """
        return self._parent

    nested = False
    """Indicates if this is a nested, or SAVEPOINT, transaction.

    When :attr:`.SessionTransaction.nested` is True, it is expected
    that :attr:`.SessionTransaction.parent` will be True as well.

    """

    @property
    def is_active(self):
        return self.session is not None and self._state is ACTIVE

    def _assert_active(self, prepared_ok=False,
                       rollback_ok=False,
                       deactive_ok=False,
                       closed_msg="This transaction is closed"):
        if self._state is COMMITTED:
            raise sa_exc.InvalidRequestError(
                "This session is in 'committed' state; no further "
                "SQL can be emitted within this transaction."
            )
        elif self._state is PREPARED:
            if not prepared_ok:
                raise sa_exc.InvalidRequestError(
                    "This session is in 'prepared' state; no further "
                    "SQL can be emitted within this transaction."
                )
        elif self._state is DEACTIVE:
            if not deactive_ok and not rollback_ok:
                if self._rollback_exception:
                    raise sa_exc.InvalidRequestError(
                        "This Session's transaction has been rolled back "
                        "due to a previous exception during flush."
                        " To begin a new transaction with this Session, "
                        "first issue Session.rollback()."
                        " Original exception was: %s"
                        % self._rollback_exception
                    )
                elif not deactive_ok:
                    raise sa_exc.InvalidRequestError(
                        "This session is in 'inactive' state, due to the "
                        "SQL transaction being rolled back; no further "
                        "SQL can be emitted within this transaction."
                    )
        elif self._state is CLOSED:
            raise sa_exc.ResourceClosedError(closed_msg)

    @property
    def _is_transaction_boundary(self):
        return self.nested or not self._parent

    def connection(self, bindkey, execution_options=None, **kwargs):
        self._assert_active()
        bind = self.session.get_bind(bindkey, **kwargs)
        return self._connection_for_bind(bind, execution_options)

    def _begin(self, nested=False):
        self._assert_active()
        return SessionTransaction(
            self.session, self, nested=nested)

    def _iterate_self_and_parents(self, upto=None):

        current = self
        result = ()
        while current:
            result += (current, )
            if current._parent is upto:
                break
            elif current._parent is None:
                raise sa_exc.InvalidRequestError(
                    "Transaction %s is not on the active transaction list" % (
                        upto))
            else:
                current = current._parent

        return result

    def _take_snapshot(self):
        if not self._is_transaction_boundary:
            self._new = self._parent._new
            self._deleted = self._parent._deleted
            self._dirty = self._parent._dirty
            self._key_switches = self._parent._key_switches
            return

        if not self.session._flushing:
            self.session.flush()

        self._new = weakref.WeakKeyDictionary()
        self._deleted = weakref.WeakKeyDictionary()
        self._dirty = weakref.WeakKeyDictionary()
        self._key_switches = weakref.WeakKeyDictionary()

    def _restore_snapshot(self, dirty_only=False):
        """Restore the restoration state taken before a transaction began.

        Corresponds to a rollback.

        """
        assert self._is_transaction_boundary

        to_expunge = set(self._new).union(self.session._new)
        self.session._expunge_states(to_expunge, to_transient=True)

        for s, (oldkey, newkey) in self._key_switches.items():
Loading ...