Ticket #2227: transaction.py.diff
File transaction.py.diff, 6.9 KB (added by , 16 years ago) |
---|
-
django/db/transaction.py
19 19 try: 20 20 from functools import wraps 21 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.3, 2.4 fallback. 23 import threading 23 24 from django.db import connection 24 25 from django.conf import settings 25 26 … … 30 31 """ 31 32 pass 32 33 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 = {} 34 class TransactionState(threading.local): 35 __slots__ = ('managed', 'dirty') 36 37 def __init__(self): 38 self.managed = [] 39 self.dirty = False 36 40 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 41 43 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 54 def 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 62 state = TransactionState() 63 42 64 def enter_transaction_management(): 43 65 """ 44 66 Enters transaction management for a running thread. It must be balanced with … … 49 71 from the settings, if there is no surrounding block (dirty is always false 50 72 when no current block is running). 51 73 """ 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())) 60 77 78 @assert_managed 61 79 def leave_transaction_management(): 62 80 """ 63 81 Leaves transaction management for a running thread. A dirty flag is carried 64 82 over to the surrounding block, as a commit will commit all changes, even 65 83 those from outside. (Commits are on connection level.) 66 84 """ 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(): 73 87 rollback() 74 88 raise TransactionManagementError("Transaction managed block ended with pending COMMIT/ROLLBACK") 75 dirty[thread_ident] = False76 89 77 90 def is_dirty(): 78 91 """ 79 92 Returns True if the current transaction requires a commit for changes to 80 93 happen. 81 94 """ 82 return dirty.get(thread.get_ident(), False)95 return state.dirty 83 96 84 97 def set_dirty(): 85 98 """ … … 87 100 to decide in a managed block of code to decide whether there are open 88 101 changes waiting for commit. 89 102 """ 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 95 104 96 105 def set_clean(): 97 106 """ … … 99 108 to decide in a managed block of code to decide whether a commit or rollback 100 109 should happen. 101 110 """ 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 107 112 108 113 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 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 117 122 123 @assert_managed 118 124 def managed(flag=True): 119 125 """ 120 126 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. 124 128 """ 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) 132 134 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") 134 136 135 137 def commit_unless_managed(): 136 138 """ 137 139 Commits changes if the system is not in managed transaction mode. 138 140 """ 139 141 if not is_managed(): 140 co nnection._commit()142 commit() 141 143 else: 142 144 set_dirty() 143 145 … … 146 148 Rolls back changes if the system is not in managed transaction mode. 147 149 """ 148 150 if not is_managed(): 149 connection._rollback()151 rollback() 150 152 else: 151 153 set_dirty() 152 154 153 155 def commit(): 154 156 """ 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. 156 159 """ 157 connection._commit() 158 set_clean() 160 if state.really_transact(): 161 connection._commit() 162 set_clean() 159 163 160 164 def rollback(): 161 165 """ 162 166 This function does the rollback itself and resets the dirty flag. 167 This can only be called at the top-level. 163 168 """ 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.") 166 174 167 175 ############## 168 176 # DECORATORS # … … 196 204 managed(True) 197 205 try: 198 206 res = func(*args, **kw) 199 except Exception, e:207 except: 200 208 if is_dirty(): 201 209 rollback() 202 210 raise