web/lib/django/db/transaction.py
changeset 38 77b6da96e6f1
equal deleted inserted replaced
37:8d941af65caf 38:77b6da96e6f1
       
     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.4 fallback.
       
    23 from django.db import connections, DEFAULT_DB_ALIAS
       
    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 dictionaries of lists. The key to the outer
       
    34 # dict is the current thread, and the key to the inner dictionary is the
       
    35 # connection alias and the list is handled as a stack of values.
       
    36 state = {}
       
    37 savepoint_state = {}
       
    38 
       
    39 # The dirty flag is set by *_unless_managed functions to denote that the
       
    40 # code under transaction management has changed things to require a
       
    41 # database commit.
       
    42 # This is a dictionary mapping thread to a dictionary mapping connection
       
    43 # alias to a boolean.
       
    44 dirty = {}
       
    45 
       
    46 def enter_transaction_management(managed=True, using=None):
       
    47     """
       
    48     Enters transaction management for a running thread. It must be balanced with
       
    49     the appropriate leave_transaction_management call, since the actual state is
       
    50     managed as a stack.
       
    51 
       
    52     The state and dirty flag are carried over from the surrounding block or
       
    53     from the settings, if there is no surrounding block (dirty is always false
       
    54     when no current block is running).
       
    55     """
       
    56     if using is None:
       
    57         using = DEFAULT_DB_ALIAS
       
    58     connection = connections[using]
       
    59     thread_ident = thread.get_ident()
       
    60     if thread_ident in state and state[thread_ident].get(using):
       
    61         state[thread_ident][using].append(state[thread_ident][using][-1])
       
    62     else:
       
    63         state.setdefault(thread_ident, {})
       
    64         state[thread_ident][using] = [settings.TRANSACTIONS_MANAGED]
       
    65     if thread_ident not in dirty or using not in dirty[thread_ident]:
       
    66         dirty.setdefault(thread_ident, {})
       
    67         dirty[thread_ident][using] = False
       
    68     connection._enter_transaction_management(managed)
       
    69 
       
    70 def leave_transaction_management(using=None):
       
    71     """
       
    72     Leaves transaction management for a running thread. A dirty flag is carried
       
    73     over to the surrounding block, as a commit will commit all changes, even
       
    74     those from outside. (Commits are on connection level.)
       
    75     """
       
    76     if using is None:
       
    77         using = DEFAULT_DB_ALIAS
       
    78     connection = connections[using]
       
    79     connection._leave_transaction_management(is_managed(using=using))
       
    80     thread_ident = thread.get_ident()
       
    81     if thread_ident in state and state[thread_ident].get(using):
       
    82         del state[thread_ident][using][-1]
       
    83     else:
       
    84         raise TransactionManagementError("This code isn't under transaction management")
       
    85     if dirty.get(thread_ident, {}).get(using, False):
       
    86         rollback(using=using)
       
    87         raise TransactionManagementError("Transaction managed block ended with pending COMMIT/ROLLBACK")
       
    88     dirty[thread_ident][using] = False
       
    89 
       
    90 def is_dirty(using=None):
       
    91     """
       
    92     Returns True if the current transaction requires a commit for changes to
       
    93     happen.
       
    94     """
       
    95     if using is None:
       
    96         using = DEFAULT_DB_ALIAS
       
    97     return dirty.get(thread.get_ident(), {}).get(using, False)
       
    98 
       
    99 def set_dirty(using=None):
       
   100     """
       
   101     Sets 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 there are open
       
   103     changes waiting for commit.
       
   104     """
       
   105     if using is None:
       
   106         using = DEFAULT_DB_ALIAS
       
   107     thread_ident = thread.get_ident()
       
   108     if thread_ident in dirty and using in dirty[thread_ident]:
       
   109         dirty[thread_ident][using] = True
       
   110     else:
       
   111         raise TransactionManagementError("This code isn't under transaction management")
       
   112 
       
   113 def set_clean(using=None):
       
   114     """
       
   115     Resets a dirty flag for the current thread and code streak. This can be used
       
   116     to decide in a managed block of code to decide whether a commit or rollback
       
   117     should happen.
       
   118     """
       
   119     if using is None:
       
   120         using = DEFAULT_DB_ALIAS
       
   121     thread_ident = thread.get_ident()
       
   122     if thread_ident in dirty and using in dirty[thread_ident]:
       
   123         dirty[thread_ident][using] = False
       
   124     else:
       
   125         raise TransactionManagementError("This code isn't under transaction management")
       
   126     clean_savepoints(using=using)
       
   127 
       
   128 def clean_savepoints(using=None):
       
   129     if using is None:
       
   130         using = DEFAULT_DB_ALIAS
       
   131     thread_ident = thread.get_ident()
       
   132     if thread_ident in savepoint_state and using in savepoint_state[thread_ident]:
       
   133         del savepoint_state[thread_ident][using]
       
   134 
       
   135 def is_managed(using=None):
       
   136     """
       
   137     Checks whether the transaction manager is in manual or in auto state.
       
   138     """
       
   139     if using is None:
       
   140         using = DEFAULT_DB_ALIAS
       
   141     thread_ident = thread.get_ident()
       
   142     if thread_ident in state and using in state[thread_ident]:
       
   143         if state[thread_ident][using]:
       
   144             return state[thread_ident][using][-1]
       
   145     return settings.TRANSACTIONS_MANAGED
       
   146 
       
   147 def managed(flag=True, using=None):
       
   148     """
       
   149     Puts the transaction manager into a manual state: managed transactions have
       
   150     to be committed explicitly by the user. If you switch off transaction
       
   151     management and there is a pending commit/rollback, the data will be
       
   152     commited.
       
   153     """
       
   154     if using is None:
       
   155         using = DEFAULT_DB_ALIAS
       
   156     connection = connections[using]
       
   157     thread_ident = thread.get_ident()
       
   158     top = state.get(thread_ident, {}).get(using, None)
       
   159     if top:
       
   160         top[-1] = flag
       
   161         if not flag and is_dirty(using=using):
       
   162             connection._commit()
       
   163             set_clean(using=using)
       
   164     else:
       
   165         raise TransactionManagementError("This code isn't under transaction management")
       
   166 
       
   167 def commit_unless_managed(using=None):
       
   168     """
       
   169     Commits changes if the system is not in managed transaction mode.
       
   170     """
       
   171     if using is None:
       
   172         using = DEFAULT_DB_ALIAS
       
   173     connection = connections[using]
       
   174     if not is_managed(using=using):
       
   175         connection._commit()
       
   176         clean_savepoints(using=using)
       
   177     else:
       
   178         set_dirty(using=using)
       
   179 
       
   180 def rollback_unless_managed(using=None):
       
   181     """
       
   182     Rolls back changes if the system is not in managed transaction mode.
       
   183     """
       
   184     if using is None:
       
   185         using = DEFAULT_DB_ALIAS
       
   186     connection = connections[using]
       
   187     if not is_managed(using=using):
       
   188         connection._rollback()
       
   189     else:
       
   190         set_dirty(using=using)
       
   191 
       
   192 def commit(using=None):
       
   193     """
       
   194     Does the commit itself and resets the dirty flag.
       
   195     """
       
   196     if using is None:
       
   197         using = DEFAULT_DB_ALIAS
       
   198     connection = connections[using]
       
   199     connection._commit()
       
   200     set_clean(using=using)
       
   201 
       
   202 def rollback(using=None):
       
   203     """
       
   204     This function does the rollback itself and resets the dirty flag.
       
   205     """
       
   206     if using is None:
       
   207         using = DEFAULT_DB_ALIAS
       
   208     connection = connections[using]
       
   209     connection._rollback()
       
   210     set_clean(using=using)
       
   211 
       
   212 def savepoint(using=None):
       
   213     """
       
   214     Creates a savepoint (if supported and required by the backend) inside the
       
   215     current transaction. Returns an identifier for the savepoint that will be
       
   216     used for the subsequent rollback or commit.
       
   217     """
       
   218     if using is None:
       
   219         using = DEFAULT_DB_ALIAS
       
   220     connection = connections[using]
       
   221     thread_ident = thread.get_ident()
       
   222     if thread_ident in savepoint_state and using in savepoint_state[thread_ident]:
       
   223         savepoint_state[thread_ident][using].append(None)
       
   224     else:
       
   225         savepoint_state.setdefault(thread_ident, {})
       
   226         savepoint_state[thread_ident][using] = [None]
       
   227     tid = str(thread_ident).replace('-', '')
       
   228     sid = "s%s_x%d" % (tid, len(savepoint_state[thread_ident][using]))
       
   229     connection._savepoint(sid)
       
   230     return sid
       
   231 
       
   232 def savepoint_rollback(sid, using=None):
       
   233     """
       
   234     Rolls back the most recent savepoint (if one exists). Does nothing if
       
   235     savepoints are not supported.
       
   236     """
       
   237     if using is None:
       
   238         using = DEFAULT_DB_ALIAS
       
   239     connection = connections[using]
       
   240     thread_ident = thread.get_ident()
       
   241     if thread_ident in savepoint_state and using in savepoint_state[thread_ident]:
       
   242         connection._savepoint_rollback(sid)
       
   243 
       
   244 def savepoint_commit(sid, using=None):
       
   245     """
       
   246     Commits the most recent savepoint (if one exists). Does nothing if
       
   247     savepoints are not supported.
       
   248     """
       
   249     if using is None:
       
   250         using = DEFAULT_DB_ALIAS
       
   251     connection = connections[using]
       
   252     thread_ident = thread.get_ident()
       
   253     if thread_ident in savepoint_state and using in savepoint_state[thread_ident]:
       
   254         connection._savepoint_commit(sid)
       
   255 
       
   256 ##############
       
   257 # DECORATORS #
       
   258 ##############
       
   259 
       
   260 def autocommit(using=None):
       
   261     """
       
   262     Decorator that activates commit on save. This is Django's default behavior;
       
   263     this decorator is useful if you globally activated transaction management in
       
   264     your settings file and want the default behavior in some view functions.
       
   265     """
       
   266     def inner_autocommit(func, db=None):
       
   267         def _autocommit(*args, **kw):
       
   268             try:
       
   269                 enter_transaction_management(managed=False, using=db)
       
   270                 managed(False, using=db)
       
   271                 return func(*args, **kw)
       
   272             finally:
       
   273                 leave_transaction_management(using=db)
       
   274         return wraps(func)(_autocommit)
       
   275 
       
   276     # Note that although the first argument is *called* `using`, it
       
   277     # may actually be a function; @autocommit and @autocommit('foo')
       
   278     # are both allowed forms.
       
   279     if using is None:
       
   280         using = DEFAULT_DB_ALIAS
       
   281     if callable(using):
       
   282         return inner_autocommit(using, DEFAULT_DB_ALIAS)
       
   283     return lambda func: inner_autocommit(func,  using)
       
   284 
       
   285 
       
   286 def commit_on_success(using=None):
       
   287     """
       
   288     This decorator activates commit on response. This way, if the view function
       
   289     runs successfully, a commit is made; if the viewfunc produces an exception,
       
   290     a rollback is made. This is one of the most common ways to do transaction
       
   291     control in web apps.
       
   292     """
       
   293     def inner_commit_on_success(func, db=None):
       
   294         def _commit_on_success(*args, **kw):
       
   295             try:
       
   296                 enter_transaction_management(using=db)
       
   297                 managed(True, using=db)
       
   298                 try:
       
   299                     res = func(*args, **kw)
       
   300                 except:
       
   301                     # All exceptions must be handled here (even string ones).
       
   302                     if is_dirty(using=db):
       
   303                         rollback(using=db)
       
   304                     raise
       
   305                 else:
       
   306                     if is_dirty(using=db):
       
   307                         try:
       
   308                             commit(using=db)
       
   309                         except:
       
   310                             rollback(using=db)
       
   311                             raise
       
   312                 return res
       
   313             finally:
       
   314                 leave_transaction_management(using=db)
       
   315         return wraps(func)(_commit_on_success)
       
   316 
       
   317     # Note that although the first argument is *called* `using`, it
       
   318     # may actually be a function; @autocommit and @autocommit('foo')
       
   319     # are both allowed forms.
       
   320     if using is None:
       
   321         using = DEFAULT_DB_ALIAS
       
   322     if callable(using):
       
   323         return inner_commit_on_success(using, DEFAULT_DB_ALIAS)
       
   324     return lambda func: inner_commit_on_success(func, using)
       
   325 
       
   326 def commit_manually(using=None):
       
   327     """
       
   328     Decorator that activates manual transaction control. It just disables
       
   329     automatic transaction control and doesn't do any commit/rollback of its
       
   330     own -- it's up to the user to call the commit and rollback functions
       
   331     themselves.
       
   332     """
       
   333     def inner_commit_manually(func, db=None):
       
   334         def _commit_manually(*args, **kw):
       
   335             try:
       
   336                 enter_transaction_management(using=db)
       
   337                 managed(True, using=db)
       
   338                 return func(*args, **kw)
       
   339             finally:
       
   340                 leave_transaction_management(using=db)
       
   341 
       
   342         return wraps(func)(_commit_manually)
       
   343 
       
   344     # Note that although the first argument is *called* `using`, it
       
   345     # may actually be a function; @autocommit and @autocommit('foo')
       
   346     # are both allowed forms.
       
   347     if using is None:
       
   348         using = DEFAULT_DB_ALIAS
       
   349     if callable(using):
       
   350         return inner_commit_manually(using, DEFAULT_DB_ALIAS)
       
   351     return lambda func: inner_commit_manually(func, using)