summaryrefslogtreecommitdiff
path: root/lib/python2.7/site-packages/django/db/transaction.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/python2.7/site-packages/django/db/transaction.py')
-rw-r--r--lib/python2.7/site-packages/django/db/transaction.py541
1 files changed, 541 insertions, 0 deletions
diff --git a/lib/python2.7/site-packages/django/db/transaction.py b/lib/python2.7/site-packages/django/db/transaction.py
new file mode 100644
index 0000000..f75137a
--- /dev/null
+++ b/lib/python2.7/site-packages/django/db/transaction.py
@@ -0,0 +1,541 @@
+"""
+This module implements a transaction manager that can be used to define
+transaction handling in a request or view function. It is used by transaction
+control middleware and decorators.
+
+The transaction manager can be in managed or in auto state. Auto state means the
+system is using a commit-on-save strategy (actually it's more like
+commit-on-change). As soon as the .save() or .delete() (or related) methods are
+called, a commit is made.
+
+Managed transactions don't do those commits, but will need some kind of manual
+or implicit commits or rollbacks.
+"""
+
+import warnings
+
+from functools import wraps
+
+from django.db import (
+ connections, DEFAULT_DB_ALIAS,
+ DatabaseError, Error, ProgrammingError)
+from django.utils.decorators import available_attrs
+
+
+class TransactionManagementError(ProgrammingError):
+ """
+ This exception is thrown when transaction management is used improperly.
+ """
+ pass
+
+################
+# Private APIs #
+################
+
+def get_connection(using=None):
+ """
+ Get a database connection by name, or the default database connection
+ if no name is provided.
+ """
+ if using is None:
+ using = DEFAULT_DB_ALIAS
+ return connections[using]
+
+###########################
+# Deprecated private APIs #
+###########################
+
+def abort(using=None):
+ """
+ Roll back any ongoing transactions and clean the transaction management
+ state of the connection.
+
+ This method is to be used only in cases where using balanced
+ leave_transaction_management() calls isn't possible. For example after a
+ request has finished, the transaction state isn't known, yet the connection
+ must be cleaned up for the next request.
+ """
+ get_connection(using).abort()
+
+def enter_transaction_management(managed=True, using=None, forced=False):
+ """
+ Enters transaction management for a running thread. It must be balanced with
+ the appropriate leave_transaction_management call, since the actual state is
+ managed as a stack.
+
+ The state and dirty flag are carried over from the surrounding block or
+ from the settings, if there is no surrounding block (dirty is always false
+ when no current block is running).
+ """
+ get_connection(using).enter_transaction_management(managed, forced)
+
+def leave_transaction_management(using=None):
+ """
+ Leaves transaction management for a running thread. A dirty flag is carried
+ over to the surrounding block, as a commit will commit all changes, even
+ those from outside. (Commits are on connection level.)
+ """
+ get_connection(using).leave_transaction_management()
+
+def is_dirty(using=None):
+ """
+ Returns True if the current transaction requires a commit for changes to
+ happen.
+ """
+ return get_connection(using).is_dirty()
+
+def set_dirty(using=None):
+ """
+ Sets a dirty flag for the current thread and code streak. This can be used
+ to decide in a managed block of code to decide whether there are open
+ changes waiting for commit.
+ """
+ get_connection(using).set_dirty()
+
+def set_clean(using=None):
+ """
+ Resets a dirty flag for the current thread and code streak. This can be used
+ to decide in a managed block of code to decide whether a commit or rollback
+ should happen.
+ """
+ get_connection(using).set_clean()
+
+def is_managed(using=None):
+ warnings.warn("'is_managed' is deprecated.",
+ PendingDeprecationWarning, stacklevel=2)
+
+def managed(flag=True, using=None):
+ warnings.warn("'managed' no longer serves a purpose.",
+ PendingDeprecationWarning, stacklevel=2)
+
+def commit_unless_managed(using=None):
+ warnings.warn("'commit_unless_managed' is now a no-op.",
+ PendingDeprecationWarning, stacklevel=2)
+
+def rollback_unless_managed(using=None):
+ warnings.warn("'rollback_unless_managed' is now a no-op.",
+ PendingDeprecationWarning, stacklevel=2)
+
+###############
+# Public APIs #
+###############
+
+def get_autocommit(using=None):
+ """
+ Get the autocommit status of the connection.
+ """
+ return get_connection(using).get_autocommit()
+
+def set_autocommit(autocommit, using=None):
+ """
+ Set the autocommit status of the connection.
+ """
+ return get_connection(using).set_autocommit(autocommit)
+
+def commit(using=None):
+ """
+ Commits a transaction and resets the dirty flag.
+ """
+ get_connection(using).commit()
+
+def rollback(using=None):
+ """
+ Rolls back a transaction and resets the dirty flag.
+ """
+ get_connection(using).rollback()
+
+def savepoint(using=None):
+ """
+ Creates a savepoint (if supported and required by the backend) inside the
+ current transaction. Returns an identifier for the savepoint that will be
+ used for the subsequent rollback or commit.
+ """
+ return get_connection(using).savepoint()
+
+def savepoint_rollback(sid, using=None):
+ """
+ Rolls back the most recent savepoint (if one exists). Does nothing if
+ savepoints are not supported.
+ """
+ get_connection(using).savepoint_rollback(sid)
+
+def savepoint_commit(sid, using=None):
+ """
+ Commits the most recent savepoint (if one exists). Does nothing if
+ savepoints are not supported.
+ """
+ get_connection(using).savepoint_commit(sid)
+
+def clean_savepoints(using=None):
+ """
+ Resets the counter used to generate unique savepoint ids in this thread.
+ """
+ get_connection(using).clean_savepoints()
+
+def get_rollback(using=None):
+ """
+ Gets the "needs rollback" flag -- for *advanced use* only.
+ """
+ return get_connection(using).get_rollback()
+
+def set_rollback(rollback, using=None):
+ """
+ Sets or unsets the "needs rollback" flag -- for *advanced use* only.
+
+ When `rollback` is `True`, it triggers a rollback when exiting the
+ innermost enclosing atomic block that has `savepoint=True` (that's the
+ default). Use this to force a rollback without raising an exception.
+
+ When `rollback` is `False`, it prevents such a rollback. Use this only
+ after rolling back to a known-good state! Otherwise, you break the atomic
+ block and data corruption may occur.
+ """
+ return get_connection(using).set_rollback(rollback)
+
+#################################
+# Decorators / context managers #
+#################################
+
+class Atomic(object):
+ """
+ This class guarantees the atomic execution of a given block.
+
+ An instance can be used either as a decorator or as a context manager.
+
+ When it's used as a decorator, __call__ wraps the execution of the
+ decorated function in the instance itself, used as a context manager.
+
+ When it's used as a context manager, __enter__ creates a transaction or a
+ savepoint, depending on whether a transaction is already in progress, and
+ __exit__ commits the transaction or releases the savepoint on normal exit,
+ and rolls back the transaction or to the savepoint on exceptions.
+
+ It's possible to disable the creation of savepoints if the goal is to
+ ensure that some code runs within a transaction without creating overhead.
+
+ A stack of savepoints identifiers is maintained as an attribute of the
+ connection. None denotes the absence of a savepoint.
+
+ This allows reentrancy even if the same AtomicWrapper is reused. For
+ example, it's possible to define `oa = @atomic('other')` and use `@oa` or
+ `with oa:` multiple times.
+
+ Since database connections are thread-local, this is thread-safe.
+ """
+
+ def __init__(self, using, savepoint):
+ self.using = using
+ self.savepoint = savepoint
+
+ def __enter__(self):
+ connection = get_connection(self.using)
+
+ if not connection.in_atomic_block:
+ # Reset state when entering an outermost atomic block.
+ connection.commit_on_exit = True
+ connection.needs_rollback = False
+ if not connection.get_autocommit():
+ # Some database adapters (namely sqlite3) don't handle
+ # transactions and savepoints properly when autocommit is off.
+ # Turning autocommit back on isn't an option; it would trigger
+ # a premature commit. Give up if that happens.
+ if connection.features.autocommits_when_autocommit_is_off:
+ raise TransactionManagementError(
+ "Your database backend doesn't behave properly when "
+ "autocommit is off. Turn it on before using 'atomic'.")
+ # When entering an atomic block with autocommit turned off,
+ # Django should only use savepoints and shouldn't commit.
+ # This requires at least a savepoint for the outermost block.
+ if not self.savepoint:
+ raise TransactionManagementError(
+ "The outermost 'atomic' block cannot use "
+ "savepoint = False when autocommit is off.")
+ # Pretend we're already in an atomic block to bypass the code
+ # that disables autocommit to enter a transaction, and make a
+ # note to deal with this case in __exit__.
+ connection.in_atomic_block = True
+ connection.commit_on_exit = False
+
+ if connection.in_atomic_block:
+ # We're already in a transaction; create a savepoint, unless we
+ # were told not to or we're already waiting for a rollback. The
+ # second condition avoids creating useless savepoints and prevents
+ # overwriting needs_rollback until the rollback is performed.
+ if self.savepoint and not connection.needs_rollback:
+ sid = connection.savepoint()
+ connection.savepoint_ids.append(sid)
+ else:
+ connection.savepoint_ids.append(None)
+ else:
+ # We aren't in a transaction yet; create one.
+ # The usual way to start a transaction is to turn autocommit off.
+ # However, some database adapters (namely sqlite3) don't handle
+ # transactions and savepoints properly when autocommit is off.
+ # In such cases, start an explicit transaction instead, which has
+ # the side-effect of disabling autocommit.
+ if connection.features.autocommits_when_autocommit_is_off:
+ connection._start_transaction_under_autocommit()
+ connection.autocommit = False
+ else:
+ connection.set_autocommit(False)
+ connection.in_atomic_block = True
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ connection = get_connection(self.using)
+
+ if connection.savepoint_ids:
+ sid = connection.savepoint_ids.pop()
+ else:
+ # Prematurely unset this flag to allow using commit or rollback.
+ connection.in_atomic_block = False
+
+ try:
+ if connection.closed_in_transaction:
+ # The database will perform a rollback by itself.
+ # Wait until we exit the outermost block.
+ pass
+
+ elif exc_type is None and not connection.needs_rollback:
+ if connection.in_atomic_block:
+ # Release savepoint if there is one
+ if sid is not None:
+ try:
+ connection.savepoint_commit(sid)
+ except DatabaseError:
+ try:
+ connection.savepoint_rollback(sid)
+ except Error:
+ # If rolling back to a savepoint fails, mark for
+ # rollback at a higher level and avoid shadowing
+ # the original exception.
+ connection.needs_rollback = True
+ raise
+ else:
+ # Commit transaction
+ try:
+ connection.commit()
+ except DatabaseError:
+ try:
+ connection.rollback()
+ except Error:
+ # An error during rollback means that something
+ # went wrong with the connection. Drop it.
+ connection.close()
+ raise
+ else:
+ # This flag will be set to True again if there isn't a savepoint
+ # allowing to perform the rollback at this level.
+ connection.needs_rollback = False
+ if connection.in_atomic_block:
+ # Roll back to savepoint if there is one, mark for rollback
+ # otherwise.
+ if sid is None:
+ connection.needs_rollback = True
+ else:
+ try:
+ connection.savepoint_rollback(sid)
+ except Error:
+ # If rolling back to a savepoint fails, mark for
+ # rollback at a higher level and avoid shadowing
+ # the original exception.
+ connection.needs_rollback = True
+ else:
+ # Roll back transaction
+ try:
+ connection.rollback()
+ except Error:
+ # An error during rollback means that something
+ # went wrong with the connection. Drop it.
+ connection.close()
+
+ finally:
+ # Outermost block exit when autocommit was enabled.
+ if not connection.in_atomic_block:
+ if connection.closed_in_transaction:
+ connection.connection = None
+ elif connection.features.autocommits_when_autocommit_is_off:
+ connection.autocommit = True
+ else:
+ connection.set_autocommit(True)
+ # Outermost block exit when autocommit was disabled.
+ elif not connection.savepoint_ids and not connection.commit_on_exit:
+ if connection.closed_in_transaction:
+ connection.connection = None
+ else:
+ connection.in_atomic_block = False
+
+ def __call__(self, func):
+ @wraps(func, assigned=available_attrs(func))
+ def inner(*args, **kwargs):
+ with self:
+ return func(*args, **kwargs)
+ return inner
+
+
+def atomic(using=None, savepoint=True):
+ # Bare decorator: @atomic -- although the first argument is called
+ # `using`, it's actually the function being decorated.
+ if callable(using):
+ return Atomic(DEFAULT_DB_ALIAS, savepoint)(using)
+ # Decorator: @atomic(...) or context manager: with atomic(...): ...
+ else:
+ return Atomic(using, savepoint)
+
+
+def _non_atomic_requests(view, using):
+ try:
+ view._non_atomic_requests.add(using)
+ except AttributeError:
+ view._non_atomic_requests = set([using])
+ return view
+
+
+def non_atomic_requests(using=None):
+ if callable(using):
+ return _non_atomic_requests(using, DEFAULT_DB_ALIAS)
+ else:
+ if using is None:
+ using = DEFAULT_DB_ALIAS
+ return lambda view: _non_atomic_requests(view, using)
+
+
+############################################
+# Deprecated decorators / context managers #
+############################################
+
+class Transaction(object):
+ """
+ Acts as either a decorator, or a context manager. If it's a decorator it
+ takes a function and returns a wrapped function. If it's a contextmanager
+ it's used with the ``with`` statement. In either event entering/exiting
+ are called before and after, respectively, the function/block is executed.
+
+ autocommit, commit_on_success, and commit_manually contain the
+ implementations of entering and exiting.
+ """
+ def __init__(self, entering, exiting, using):
+ self.entering = entering
+ self.exiting = exiting
+ self.using = using
+
+ def __enter__(self):
+ self.entering(self.using)
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ self.exiting(exc_type, self.using)
+
+ def __call__(self, func):
+ @wraps(func)
+ def inner(*args, **kwargs):
+ with self:
+ return func(*args, **kwargs)
+ return inner
+
+def _transaction_func(entering, exiting, using):
+ """
+ Takes 3 things, an entering function (what to do to start this block of
+ transaction management), an exiting function (what to do to end it, on both
+ success and failure, and using which can be: None, indiciating using is
+ DEFAULT_DB_ALIAS, a callable, indicating that using is DEFAULT_DB_ALIAS and
+ to return the function already wrapped.
+
+ Returns either a Transaction objects, which is both a decorator and a
+ context manager, or a wrapped function, if using is a callable.
+ """
+ # Note that although the first argument is *called* `using`, it
+ # may actually be a function; @autocommit and @autocommit('foo')
+ # are both allowed forms.
+ if using is None:
+ using = DEFAULT_DB_ALIAS
+ if callable(using):
+ return Transaction(entering, exiting, DEFAULT_DB_ALIAS)(using)
+ return Transaction(entering, exiting, using)
+
+
+def autocommit(using=None):
+ """
+ Decorator that activates commit on save. This is Django's default behavior;
+ this decorator is useful if you globally activated transaction management in
+ your settings file and want the default behavior in some view functions.
+ """
+ warnings.warn("autocommit is deprecated in favor of set_autocommit.",
+ PendingDeprecationWarning, stacklevel=2)
+
+ def entering(using):
+ enter_transaction_management(managed=False, using=using)
+
+ def exiting(exc_type, using):
+ leave_transaction_management(using=using)
+
+ return _transaction_func(entering, exiting, using)
+
+def commit_on_success(using=None):
+ """
+ This decorator activates commit on response. This way, if the view function
+ runs successfully, a commit is made; if the viewfunc produces an exception,
+ a rollback is made. This is one of the most common ways to do transaction
+ control in Web apps.
+ """
+ warnings.warn("commit_on_success is deprecated in favor of atomic.",
+ PendingDeprecationWarning, stacklevel=2)
+
+ def entering(using):
+ enter_transaction_management(using=using)
+
+ def exiting(exc_type, using):
+ try:
+ if exc_type is not None:
+ if is_dirty(using=using):
+ rollback(using=using)
+ else:
+ if is_dirty(using=using):
+ try:
+ commit(using=using)
+ except:
+ rollback(using=using)
+ raise
+ finally:
+ leave_transaction_management(using=using)
+
+ return _transaction_func(entering, exiting, using)
+
+def commit_manually(using=None):
+ """
+ Decorator that activates manual transaction control. It just disables
+ automatic transaction control and doesn't do any commit/rollback of its
+ own -- it's up to the user to call the commit and rollback functions
+ themselves.
+ """
+ warnings.warn("commit_manually is deprecated in favor of set_autocommit.",
+ PendingDeprecationWarning, stacklevel=2)
+
+ def entering(using):
+ enter_transaction_management(using=using)
+
+ def exiting(exc_type, using):
+ leave_transaction_management(using=using)
+
+ return _transaction_func(entering, exiting, using)
+
+def commit_on_success_unless_managed(using=None, savepoint=False):
+ """
+ Transitory API to preserve backwards-compatibility while refactoring.
+
+ Once the legacy transaction management is fully deprecated, this should
+ simply be replaced by atomic. Until then, it's necessary to guarantee that
+ a commit occurs on exit, which atomic doesn't do when it's nested.
+
+ Unlike atomic, savepoint defaults to False because that's closer to the
+ legacy behavior.
+ """
+ connection = get_connection(using)
+ if connection.get_autocommit() or connection.in_atomic_block:
+ return atomic(using, savepoint)
+ else:
+ def entering(using):
+ pass
+
+ def exiting(exc_type, using):
+ set_dirty(using=using)
+
+ return _transaction_func(entering, exiting, using)