app/django/db/transaction.py
changeset 54 03e267d67478
child 323 ff1a9aa48cfd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/app/django/db/transaction.py	Fri Jul 18 18:22:23 2008 +0000
@@ -0,0 +1,226 @@
+"""
+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.
+"""
+
+try:
+    import thread
+except ImportError:
+    import dummy_thread as thread
+try:
+    from functools import wraps
+except ImportError:
+    from django.utils.functional import wraps  # Python 2.3, 2.4 fallback. 
+from django.db import connection
+from django.conf import settings
+
+class TransactionManagementError(Exception):
+    """
+    This exception is thrown when something bad happens with transaction
+    management.
+    """
+    pass
+
+# The state is a dictionary of lists. The key to the dict is the current
+# thread and the list is handled as a stack of values.
+state = {}
+
+# The dirty flag is set by *_unless_managed functions to denote that the
+# code under transaction management has changed things to require a
+# database commit.
+dirty = {}
+
+def enter_transaction_management():
+    """
+    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).
+    """
+    thread_ident = thread.get_ident()
+    if thread_ident in state and state[thread_ident]:
+        state[thread_ident].append(state[thread_ident][-1])
+    else:
+        state[thread_ident] = []
+        state[thread_ident].append(settings.TRANSACTIONS_MANAGED)
+    if thread_ident not in dirty:
+        dirty[thread_ident] = False
+
+def leave_transaction_management():
+    """
+    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.)
+    """
+    thread_ident = thread.get_ident()
+    if thread_ident in state and state[thread_ident]:
+        del state[thread_ident][-1]
+    else:
+        raise TransactionManagementError("This code isn't under transaction management")
+    if dirty.get(thread_ident, False):
+        rollback()
+        raise TransactionManagementError("Transaction managed block ended with pending COMMIT/ROLLBACK")
+    dirty[thread_ident] = False
+
+def is_dirty():
+    """
+    Returns True if the current transaction requires a commit for changes to
+    happen.
+    """
+    return dirty.get(thread.get_ident(), False)
+
+def set_dirty():
+    """
+    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.
+    """
+    thread_ident = thread.get_ident()
+    if thread_ident in dirty:
+        dirty[thread_ident] = True
+    else:
+        raise TransactionManagementError("This code isn't under transaction management")
+
+def set_clean():
+    """
+    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.
+    """
+    thread_ident = thread.get_ident()
+    if thread_ident in dirty:
+        dirty[thread_ident] = False
+    else:
+        raise TransactionManagementError("This code isn't under transaction management")
+
+def is_managed():
+    """
+    Checks whether the transaction manager is in manual or in auto state.
+    """
+    thread_ident = thread.get_ident()
+    if thread_ident in state:
+        if state[thread_ident]:
+            return state[thread_ident][-1]
+    return settings.TRANSACTIONS_MANAGED
+
+def managed(flag=True):
+    """
+    Puts the transaction manager into a manual state: managed transactions have
+    to be committed explicitly by the user. If you switch off transaction
+    management and there is a pending commit/rollback, the data will be
+    commited.
+    """
+    thread_ident = thread.get_ident()
+    top = state.get(thread_ident, None)
+    if top:
+        top[-1] = flag
+        if not flag and is_dirty():
+            connection._commit()
+            set_clean()
+    else:
+        raise TransactionManagementError("This code isn't under transaction management")
+
+def commit_unless_managed():
+    """
+    Commits changes if the system is not in managed transaction mode.
+    """
+    if not is_managed():
+        connection._commit()
+    else:
+        set_dirty()
+
+def rollback_unless_managed():
+    """
+    Rolls back changes if the system is not in managed transaction mode.
+    """
+    if not is_managed():
+        connection._rollback()
+    else:
+        set_dirty()
+
+def commit():
+    """
+    Does the commit itself and resets the dirty flag.
+    """
+    connection._commit()
+    set_clean()
+
+def rollback():
+    """
+    This function does the rollback itself and resets the dirty flag.
+    """
+    connection._rollback()
+    set_clean()
+
+##############
+# DECORATORS #
+##############
+
+def autocommit(func):
+    """
+    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.
+    """
+    def _autocommit(*args, **kw):
+        try:
+            enter_transaction_management()
+            managed(False)
+            return func(*args, **kw)
+        finally:
+            leave_transaction_management()
+    return wraps(func)(_autocommit)
+
+def commit_on_success(func):
+    """
+    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.
+    """
+    def _commit_on_success(*args, **kw):
+        try:
+            enter_transaction_management()
+            managed(True)
+            try:
+                res = func(*args, **kw)
+            except Exception, e:
+                if is_dirty():
+                    rollback()
+                raise
+            else:
+                if is_dirty():
+                    commit()
+            return res
+        finally:
+            leave_transaction_management()
+    return wraps(func)(_commit_on_success)
+
+def commit_manually(func):
+    """
+    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.
+    """
+    def _commit_manually(*args, **kw):
+        try:
+            enter_transaction_management()
+            managed(True)
+            return func(*args, **kw)
+        finally:
+            leave_transaction_management()
+
+    return wraps(func)(_commit_manually)