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