app/django/db/transaction.py
changeset 54 03e267d67478
child 323 ff1a9aa48cfd
equal deleted inserted replaced
53:57b4279d8c4e 54:03e267d67478
       
     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 state is a dictionary 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 
       
    37 # The dirty flag is set by *_unless_managed functions to denote that the
       
    38 # code under transaction management has changed things to require a
       
    39 # database commit.
       
    40 dirty = {}
       
    41 
       
    42 def enter_transaction_management():
       
    43     """
       
    44     Enters transaction management for a running thread. It must be balanced with
       
    45     the appropriate leave_transaction_management call, since the actual state is
       
    46     managed as a stack.
       
    47 
       
    48     The state and dirty flag are carried over from the surrounding block or
       
    49     from the settings, if there is no surrounding block (dirty is always false
       
    50     when no current block is running).
       
    51     """
       
    52     thread_ident = thread.get_ident()
       
    53     if thread_ident in state and state[thread_ident]:
       
    54         state[thread_ident].append(state[thread_ident][-1])
       
    55     else:
       
    56         state[thread_ident] = []
       
    57         state[thread_ident].append(settings.TRANSACTIONS_MANAGED)
       
    58     if thread_ident not in dirty:
       
    59         dirty[thread_ident] = False
       
    60 
       
    61 def leave_transaction_management():
       
    62     """
       
    63     Leaves transaction management for a running thread. A dirty flag is carried
       
    64     over to the surrounding block, as a commit will commit all changes, even
       
    65     those from outside. (Commits are on connection level.)
       
    66     """
       
    67     thread_ident = thread.get_ident()
       
    68     if thread_ident in state and state[thread_ident]:
       
    69         del state[thread_ident][-1]
       
    70     else:
       
    71         raise TransactionManagementError("This code isn't under transaction management")
       
    72     if dirty.get(thread_ident, False):
       
    73         rollback()
       
    74         raise TransactionManagementError("Transaction managed block ended with pending COMMIT/ROLLBACK")
       
    75     dirty[thread_ident] = False
       
    76 
       
    77 def is_dirty():
       
    78     """
       
    79     Returns True if the current transaction requires a commit for changes to
       
    80     happen.
       
    81     """
       
    82     return dirty.get(thread.get_ident(), False)
       
    83 
       
    84 def set_dirty():
       
    85     """
       
    86     Sets a dirty flag for the current thread and code streak. This can be used
       
    87     to decide in a managed block of code to decide whether there are open
       
    88     changes waiting for commit.
       
    89     """
       
    90     thread_ident = thread.get_ident()
       
    91     if thread_ident in dirty:
       
    92         dirty[thread_ident] = True
       
    93     else:
       
    94         raise TransactionManagementError("This code isn't under transaction management")
       
    95 
       
    96 def set_clean():
       
    97     """
       
    98     Resets a dirty flag for the current thread and code streak. This can be used
       
    99     to decide in a managed block of code to decide whether a commit or rollback
       
   100     should happen.
       
   101     """
       
   102     thread_ident = thread.get_ident()
       
   103     if thread_ident in dirty:
       
   104         dirty[thread_ident] = False
       
   105     else:
       
   106         raise TransactionManagementError("This code isn't under transaction management")
       
   107 
       
   108 def is_managed():
       
   109     """
       
   110     Checks whether the transaction manager is in manual or in auto state.
       
   111     """
       
   112     thread_ident = thread.get_ident()
       
   113     if thread_ident in state:
       
   114         if state[thread_ident]:
       
   115             return state[thread_ident][-1]
       
   116     return settings.TRANSACTIONS_MANAGED
       
   117 
       
   118 def managed(flag=True):
       
   119     """
       
   120     Puts the transaction manager into a manual state: managed transactions have
       
   121     to be committed explicitly by the user. If you switch off transaction
       
   122     management and there is a pending commit/rollback, the data will be
       
   123     commited.
       
   124     """
       
   125     thread_ident = thread.get_ident()
       
   126     top = state.get(thread_ident, None)
       
   127     if top:
       
   128         top[-1] = flag
       
   129         if not flag and is_dirty():
       
   130             connection._commit()
       
   131             set_clean()
       
   132     else:
       
   133         raise TransactionManagementError("This code isn't under transaction management")
       
   134 
       
   135 def commit_unless_managed():
       
   136     """
       
   137     Commits changes if the system is not in managed transaction mode.
       
   138     """
       
   139     if not is_managed():
       
   140         connection._commit()
       
   141     else:
       
   142         set_dirty()
       
   143 
       
   144 def rollback_unless_managed():
       
   145     """
       
   146     Rolls back changes if the system is not in managed transaction mode.
       
   147     """
       
   148     if not is_managed():
       
   149         connection._rollback()
       
   150     else:
       
   151         set_dirty()
       
   152 
       
   153 def commit():
       
   154     """
       
   155     Does the commit itself and resets the dirty flag.
       
   156     """
       
   157     connection._commit()
       
   158     set_clean()
       
   159 
       
   160 def rollback():
       
   161     """
       
   162     This function does the rollback itself and resets the dirty flag.
       
   163     """
       
   164     connection._rollback()
       
   165     set_clean()
       
   166 
       
   167 ##############
       
   168 # DECORATORS #
       
   169 ##############
       
   170 
       
   171 def autocommit(func):
       
   172     """
       
   173     Decorator that activates commit on save. This is Django's default behavior;
       
   174     this decorator is useful if you globally activated transaction management in
       
   175     your settings file and want the default behavior in some view functions.
       
   176     """
       
   177     def _autocommit(*args, **kw):
       
   178         try:
       
   179             enter_transaction_management()
       
   180             managed(False)
       
   181             return func(*args, **kw)
       
   182         finally:
       
   183             leave_transaction_management()
       
   184     return wraps(func)(_autocommit)
       
   185 
       
   186 def commit_on_success(func):
       
   187     """
       
   188     This decorator activates commit on response. This way, if the view function
       
   189     runs successfully, a commit is made; if the viewfunc produces an exception,
       
   190     a rollback is made. This is one of the most common ways to do transaction
       
   191     control in web apps.
       
   192     """
       
   193     def _commit_on_success(*args, **kw):
       
   194         try:
       
   195             enter_transaction_management()
       
   196             managed(True)
       
   197             try:
       
   198                 res = func(*args, **kw)
       
   199             except Exception, e:
       
   200                 if is_dirty():
       
   201                     rollback()
       
   202                 raise
       
   203             else:
       
   204                 if is_dirty():
       
   205                     commit()
       
   206             return res
       
   207         finally:
       
   208             leave_transaction_management()
       
   209     return wraps(func)(_commit_on_success)
       
   210 
       
   211 def commit_manually(func):
       
   212     """
       
   213     Decorator that activates manual transaction control. It just disables
       
   214     automatic transaction control and doesn't do any commit/rollback of its
       
   215     own -- it's up to the user to call the commit and rollback functions
       
   216     themselves.
       
   217     """
       
   218     def _commit_manually(*args, **kw):
       
   219         try:
       
   220             enter_transaction_management()
       
   221             managed(True)
       
   222             return func(*args, **kw)
       
   223         finally:
       
   224             leave_transaction_management()
       
   225 
       
   226     return wraps(func)(_commit_manually)