web/lib/django/db/transaction.py
changeset 0 0d40e90630ef
child 29 cc9b7e14412b
equal deleted inserted replaced
-1:000000000000 0:0d40e90630ef
       
     1 """
       
     2 This module implements a transaction manager that can be used to define
       
     3 transaction handling in a request or view function. It is used by transaction
       
     4 control middleware and decorators.
       
     5 
       
     6 The transaction manager can be in managed or in auto state. Auto state means the
       
     7 system is using a commit-on-save strategy (actually it's more like
       
     8 commit-on-change). As soon as the .save() or .delete() (or related) methods are
       
     9 called, a commit is made.
       
    10 
       
    11 Managed transactions don't do those commits, but will need some kind of manual
       
    12 or implicit commits or rollbacks.
       
    13 """
       
    14 
       
    15 try:
       
    16     import thread
       
    17 except ImportError:
       
    18     import dummy_thread as thread
       
    19 try:
       
    20     from functools import wraps
       
    21 except ImportError:
       
    22     from django.utils.functional import wraps  # Python 2.3, 2.4 fallback.
       
    23 from django.db import connection
       
    24 from django.conf import settings
       
    25 
       
    26 class TransactionManagementError(Exception):
       
    27     """
       
    28     This exception is thrown when something bad happens with transaction
       
    29     management.
       
    30     """
       
    31     pass
       
    32 
       
    33 # The states are dictionaries of lists. The key to the dict is the current
       
    34 # thread and the list is handled as a stack of values.
       
    35 state = {}
       
    36 savepoint_state = {}
       
    37 
       
    38 # The dirty flag is set by *_unless_managed functions to denote that the
       
    39 # code under transaction management has changed things to require a
       
    40 # database commit.
       
    41 dirty = {}
       
    42 
       
    43 def enter_transaction_management(managed=True):
       
    44     """
       
    45     Enters transaction management for a running thread. It must be balanced with
       
    46     the appropriate leave_transaction_management call, since the actual state is
       
    47     managed as a stack.
       
    48 
       
    49     The state and dirty flag are carried over from the surrounding block or
       
    50     from the settings, if there is no surrounding block (dirty is always false
       
    51     when no current block is running).
       
    52     """
       
    53     thread_ident = thread.get_ident()
       
    54     if thread_ident in state and state[thread_ident]:
       
    55         state[thread_ident].append(state[thread_ident][-1])
       
    56     else:
       
    57         state[thread_ident] = []
       
    58         state[thread_ident].append(settings.TRANSACTIONS_MANAGED)
       
    59     if thread_ident not in dirty:
       
    60         dirty[thread_ident] = False
       
    61     connection._enter_transaction_management(managed)
       
    62 
       
    63 def leave_transaction_management():
       
    64     """
       
    65     Leaves transaction management for a running thread. A dirty flag is carried
       
    66     over to the surrounding block, as a commit will commit all changes, even
       
    67     those from outside. (Commits are on connection level.)
       
    68     """
       
    69     connection._leave_transaction_management(is_managed())
       
    70     thread_ident = thread.get_ident()
       
    71     if thread_ident in state and state[thread_ident]:
       
    72         del state[thread_ident][-1]
       
    73     else:
       
    74         raise TransactionManagementError("This code isn't under transaction management")
       
    75     if dirty.get(thread_ident, False):
       
    76         rollback()
       
    77         raise TransactionManagementError("Transaction managed block ended with pending COMMIT/ROLLBACK")
       
    78     dirty[thread_ident] = False
       
    79 
       
    80 def is_dirty():
       
    81     """
       
    82     Returns True if the current transaction requires a commit for changes to
       
    83     happen.
       
    84     """
       
    85     return dirty.get(thread.get_ident(), False)
       
    86 
       
    87 def set_dirty():
       
    88     """
       
    89     Sets a dirty flag for the current thread and code streak. This can be used
       
    90     to decide in a managed block of code to decide whether there are open
       
    91     changes waiting for commit.
       
    92     """
       
    93     thread_ident = thread.get_ident()
       
    94     if thread_ident in dirty:
       
    95         dirty[thread_ident] = True
       
    96     else:
       
    97         raise TransactionManagementError("This code isn't under transaction management")
       
    98 
       
    99 def set_clean():
       
   100     """
       
   101     Resets a dirty flag for the current thread and code streak. This can be used
       
   102     to decide in a managed block of code to decide whether a commit or rollback
       
   103     should happen.
       
   104     """
       
   105     thread_ident = thread.get_ident()
       
   106     if thread_ident in dirty:
       
   107         dirty[thread_ident] = False
       
   108     else:
       
   109         raise TransactionManagementError("This code isn't under transaction management")
       
   110     clean_savepoints()
       
   111 
       
   112 def clean_savepoints():
       
   113     thread_ident = thread.get_ident()
       
   114     if thread_ident in savepoint_state:
       
   115         del savepoint_state[thread_ident]
       
   116 
       
   117 def is_managed():
       
   118     """
       
   119     Checks whether the transaction manager is in manual or in auto state.
       
   120     """
       
   121     thread_ident = thread.get_ident()
       
   122     if thread_ident in state:
       
   123         if state[thread_ident]:
       
   124             return state[thread_ident][-1]
       
   125     return settings.TRANSACTIONS_MANAGED
       
   126 
       
   127 def managed(flag=True):
       
   128     """
       
   129     Puts the transaction manager into a manual state: managed transactions have
       
   130     to be committed explicitly by the user. If you switch off transaction
       
   131     management and there is a pending commit/rollback, the data will be
       
   132     commited.
       
   133     """
       
   134     thread_ident = thread.get_ident()
       
   135     top = state.get(thread_ident, None)
       
   136     if top:
       
   137         top[-1] = flag
       
   138         if not flag and is_dirty():
       
   139             connection._commit()
       
   140             set_clean()
       
   141     else:
       
   142         raise TransactionManagementError("This code isn't under transaction management")
       
   143 
       
   144 def commit_unless_managed():
       
   145     """
       
   146     Commits changes if the system is not in managed transaction mode.
       
   147     """
       
   148     if not is_managed():
       
   149         connection._commit()
       
   150         clean_savepoints()
       
   151     else:
       
   152         set_dirty()
       
   153 
       
   154 def rollback_unless_managed():
       
   155     """
       
   156     Rolls back changes if the system is not in managed transaction mode.
       
   157     """
       
   158     if not is_managed():
       
   159         connection._rollback()
       
   160     else:
       
   161         set_dirty()
       
   162 
       
   163 def commit():
       
   164     """
       
   165     Does the commit itself and resets the dirty flag.
       
   166     """
       
   167     connection._commit()
       
   168     set_clean()
       
   169 
       
   170 def rollback():
       
   171     """
       
   172     This function does the rollback itself and resets the dirty flag.
       
   173     """
       
   174     connection._rollback()
       
   175     set_clean()
       
   176 
       
   177 def savepoint():
       
   178     """
       
   179     Creates a savepoint (if supported and required by the backend) inside the
       
   180     current transaction. Returns an identifier for the savepoint that will be
       
   181     used for the subsequent rollback or commit.
       
   182     """
       
   183     thread_ident = thread.get_ident()
       
   184     if thread_ident in savepoint_state:
       
   185         savepoint_state[thread_ident].append(None)
       
   186     else:
       
   187         savepoint_state[thread_ident] = [None]
       
   188     tid = str(thread_ident).replace('-', '')
       
   189     sid = "s%s_x%d" % (tid, len(savepoint_state[thread_ident]))
       
   190     connection._savepoint(sid)
       
   191     return sid
       
   192 
       
   193 def savepoint_rollback(sid):
       
   194     """
       
   195     Rolls back the most recent savepoint (if one exists). Does nothing if
       
   196     savepoints are not supported.
       
   197     """
       
   198     if thread.get_ident() in savepoint_state:
       
   199         connection._savepoint_rollback(sid)
       
   200 
       
   201 def savepoint_commit(sid):
       
   202     """
       
   203     Commits the most recent savepoint (if one exists). Does nothing if
       
   204     savepoints are not supported.
       
   205     """
       
   206     if thread.get_ident() in savepoint_state:
       
   207         connection._savepoint_commit(sid)
       
   208 
       
   209 ##############
       
   210 # DECORATORS #
       
   211 ##############
       
   212 
       
   213 def autocommit(func):
       
   214     """
       
   215     Decorator that activates commit on save. This is Django's default behavior;
       
   216     this decorator is useful if you globally activated transaction management in
       
   217     your settings file and want the default behavior in some view functions.
       
   218     """
       
   219     def _autocommit(*args, **kw):
       
   220         try:
       
   221             enter_transaction_management(managed=False)
       
   222             managed(False)
       
   223             return func(*args, **kw)
       
   224         finally:
       
   225             leave_transaction_management()
       
   226     return wraps(func)(_autocommit)
       
   227 
       
   228 def commit_on_success(func):
       
   229     """
       
   230     This decorator activates commit on response. This way, if the view function
       
   231     runs successfully, a commit is made; if the viewfunc produces an exception,
       
   232     a rollback is made. This is one of the most common ways to do transaction
       
   233     control in web apps.
       
   234     """
       
   235     def _commit_on_success(*args, **kw):
       
   236         try:
       
   237             enter_transaction_management()
       
   238             managed(True)
       
   239             try:
       
   240                 res = func(*args, **kw)
       
   241             except:
       
   242                 # All exceptions must be handled here (even string ones).
       
   243                 if is_dirty():
       
   244                     rollback()
       
   245                 raise
       
   246             else:
       
   247                 if is_dirty():
       
   248                     commit()
       
   249             return res
       
   250         finally:
       
   251             leave_transaction_management()
       
   252     return wraps(func)(_commit_on_success)
       
   253 
       
   254 def commit_manually(func):
       
   255     """
       
   256     Decorator that activates manual transaction control. It just disables
       
   257     automatic transaction control and doesn't do any commit/rollback of its
       
   258     own -- it's up to the user to call the commit and rollback functions
       
   259     themselves.
       
   260     """
       
   261     def _commit_manually(*args, **kw):
       
   262         try:
       
   263             enter_transaction_management()
       
   264             managed(True)
       
   265             return func(*args, **kw)
       
   266         finally:
       
   267             leave_transaction_management()
       
   268 
       
   269     return wraps(func)(_commit_manually)