Ticket #2227: transaction.py.diff

File transaction.py.diff, 6.9 KB (added by weaver@…, 16 years ago)
  • django/db/transaction.py

     
    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
Back to Top