Django

Code

Ticket #2227: transaction.py.diff

File transaction.py.diff, 6.9 kB (added by weaver@coptix.com, 2 years ago)
  • django/db/transaction.py

    old new  
    1919try: 
    2020    from functools import wraps 
    2121except ImportError: 
    22     from django.utils.functional import wraps  # Python 2.3, 2.4 fallback.  
     22    from django.utils.functional import wraps  # Python 2.3, 2.4 fallback. 
     23import threading 
    2324from django.db import connection 
    2425from django.conf import settings 
    2526 
     
    3031    """ 
    3132    pass 
    3233 
    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 = {} 
     34class TransactionState(threading.local): 
     35    __slots__ = ('managed', 'dirty') 
     36     
     37    def __init__(self): 
     38        self.managed = [] 
     39        self.dirty = False 
    3640 
    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    def no_context(self): 
     42        return not self.managed 
    4143 
     44    def top_level(self): 
     45        return len(self.managed) == 1 
     46 
     47    def assert_managed(self): 
     48        if self.no_context(): 
     49            raise TransactionManagementError("This code isn't under transaction management") 
     50 
     51    def really_transact(self): 
     52        return self.top_level() or self.no_context() 
     53 
     54def assert_managed(proc): 
     55 
     56    def internal(*args, **kwargs): 
     57        state.assert_managed() 
     58        return proc(*args, **kwargs) 
     59 
     60    return wraps(proc)(internal) 
     61 
     62state = TransactionState() 
     63 
    4264def enter_transaction_management(): 
    4365    """ 
    4466    Enters transaction management for a running thread. It must be balanced with 
     
    4971    from the settings, if there is no surrounding block (dirty is always false 
    5072    when no current block is running). 
    5173    """ 
    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 
     74    if not is_managed(): 
     75        commit() 
     76    state.managed.append((False, is_managed())) 
    6077 
     78@assert_managed 
    6179def leave_transaction_management(): 
    6280    """ 
    6381    Leaves transaction management for a running thread. A dirty flag is carried 
    6482    over to the surrounding block, as a commit will commit all changes, even 
    6583    those from outside. (Commits are on connection level.) 
    6684    """ 
    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): 
     85    state.managed.pop() 
     86    if state.dirty and state.no_context(): 
    7387        rollback() 
    7488        raise TransactionManagementError("Transaction managed block ended with pending COMMIT/ROLLBACK") 
    75     dirty[thread_ident] = False 
    7689 
    7790def is_dirty(): 
    7891    """ 
    7992    Returns True if the current transaction requires a commit for changes to 
    8093    happen. 
    8194    """ 
    82     return dirty.get(thread.get_ident(), False) 
     95    return state.dirty 
    8396 
    8497def set_dirty(): 
    8598    """ 
     
    87100    to decide in a managed block of code to decide whether there are open 
    88101    changes waiting for commit. 
    89102    """ 
    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") 
     103    state.dirty = True 
    95104 
    96105def set_clean(): 
    97106    """ 
     
    99108    to decide in a managed block of code to decide whether a commit or rollback 
    100109    should happen. 
    101110    """ 
    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") 
     111    state.dirty = False 
    107112 
    108113def 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 
     114     """ 
     115     Checks whether the transaction manager is in manual or in auto state. 
     116     """ 
     117     try: 
     118         (initialized, managed) = state.managed[-1] 
     119         return managed 
     120     except IndexError: 
     121        return settings.TRANSACTIONS_MANAGED 
    117122 
     123@assert_managed 
    118124def managed(flag=True): 
    119125    """ 
    120126    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. 
     127    to be committed explicitly by the user. 
    124128    """ 
    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() 
     129    (initialized, managed) = state.managed[-1] 
     130    if not initialized: 
     131        # Once a transaction is manually managed, subsequent nested 
     132        # transactions cannot be automatically managed. 
     133        state.managed[-1] = (True, managed or flag) 
    132134    else: 
    133         raise TransactionManagementError("This code isn't under transaction management") 
     135       raise TransactionManagementError("`managed' cannot be called twice within the same transaction management block") 
    134136 
    135137def commit_unless_managed(): 
    136138    """ 
    137139    Commits changes if the system is not in managed transaction mode. 
    138140    """ 
    139141    if not is_managed(): 
    140         connection._commit() 
     142        commit() 
    141143    else: 
    142144        set_dirty() 
    143145 
     
    146148    Rolls back changes if the system is not in managed transaction mode. 
    147149    """ 
    148150    if not is_managed(): 
    149         connection._rollback() 
     151        rollback() 
    150152    else: 
    151153        set_dirty() 
    152154 
    153155def commit(): 
    154156    """ 
    155     Does the commit itself and resets the dirty flag. 
     157    Does the commit itself and resets the dirty flag.  This is a no-op 
     158    unless the transaction is top-level. 
    156159    """ 
    157     connection._commit() 
    158     set_clean() 
     160    if state.really_transact(): 
     161        connection._commit() 
     162        set_clean() 
    159163 
    160164def rollback(): 
    161165    """ 
    162166    This function does the rollback itself and resets the dirty flag. 
     167    This can only be called at the top-level. 
    163168    """ 
    164     connection._rollback() 
    165     set_clean() 
     169    if state.really_transact(): 
     170        connection._rollback() 
     171        set_clean() 
     172    else: 
     173        raise TransactionManagementError("`rollback' cannot reliably be called in a nested transaction.")     
    166174 
    167175############## 
    168176# DECORATORS # 
     
    196204            managed(True) 
    197205            try: 
    198206                res = func(*args, **kw) 
    199             except Exception, e
     207            except
    200208                if is_dirty(): 
    201209                    rollback() 
    202210                raise