Added mentor-marker.png to content\images.
[Melange.git] / app / django / db / transaction.py
blob506074f1b310631f993f2ad06bd0f553f0d483f3
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.
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.
11 Managed transactions don't do those commits, but will need some kind of manual
12 or implicit commits or rollbacks.
13 """
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.3, 2.4 fallback.
23 from django.db import connection
24 from django.conf import settings
26 class TransactionManagementError(Exception):
27 """
28 This exception is thrown when something bad happens with transaction
29 management.
30 """
31 pass
33 # The states are dictionaries of lists. The key to the dict is the current
34 # thread and the list is handled as a stack of values.
35 state = {}
36 savepoint_state = {}
38 # 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 # database commit.
41 dirty = {}
43 def enter_transaction_management():
44 """
45 Enters transaction management for a running thread. It must be balanced with
46 the appropriate leave_transaction_management call, since the actual state is
47 managed as a stack.
49 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
51 when no current block is running).
52 """
53 thread_ident = thread.get_ident()
54 if thread_ident in state and state[thread_ident]:
55 state[thread_ident].append(state[thread_ident][-1])
56 else:
57 state[thread_ident] = []
58 state[thread_ident].append(settings.TRANSACTIONS_MANAGED)
59 if thread_ident not in dirty:
60 dirty[thread_ident] = False
62 def leave_transaction_management():
63 """
64 Leaves transaction management for a running thread. A dirty flag is carried
65 over to the surrounding block, as a commit will commit all changes, even
66 those from outside. (Commits are on connection level.)
67 """
68 thread_ident = thread.get_ident()
69 if thread_ident in state and state[thread_ident]:
70 del state[thread_ident][-1]
71 else:
72 raise TransactionManagementError("This code isn't under transaction management")
73 if dirty.get(thread_ident, False):
74 rollback()
75 raise TransactionManagementError("Transaction managed block ended with pending COMMIT/ROLLBACK")
76 dirty[thread_ident] = False
78 def is_dirty():
79 """
80 Returns True if the current transaction requires a commit for changes to
81 happen.
82 """
83 return dirty.get(thread.get_ident(), False)
85 def set_dirty():
86 """
87 Sets a dirty flag for the current thread and code streak. This can be used
88 to decide in a managed block of code to decide whether there are open
89 changes waiting for commit.
90 """
91 thread_ident = thread.get_ident()
92 if thread_ident in dirty:
93 dirty[thread_ident] = True
94 else:
95 raise TransactionManagementError("This code isn't under transaction management")
97 def set_clean():
98 """
99 Resets a dirty flag for the current thread and code streak. This can be used
100 to decide in a managed block of code to decide whether a commit or rollback
101 should happen.
103 thread_ident = thread.get_ident()
104 if thread_ident in dirty:
105 dirty[thread_ident] = False
106 else:
107 raise TransactionManagementError("This code isn't under transaction management")
108 clean_savepoints()
110 def clean_savepoints():
111 thread_ident = thread.get_ident()
112 if thread_ident in savepoint_state:
113 del savepoint_state[thread_ident]
115 def is_managed():
117 Checks whether the transaction manager is in manual or in auto state.
119 thread_ident = thread.get_ident()
120 if thread_ident in state:
121 if state[thread_ident]:
122 return state[thread_ident][-1]
123 return settings.TRANSACTIONS_MANAGED
125 def managed(flag=True):
127 Puts the transaction manager into a manual state: managed transactions have
128 to be committed explicitly by the user. If you switch off transaction
129 management and there is a pending commit/rollback, the data will be
130 commited.
132 thread_ident = thread.get_ident()
133 top = state.get(thread_ident, None)
134 if top:
135 top[-1] = flag
136 if not flag and is_dirty():
137 connection._commit()
138 set_clean()
139 else:
140 raise TransactionManagementError("This code isn't under transaction management")
142 def commit_unless_managed():
144 Commits changes if the system is not in managed transaction mode.
146 if not is_managed():
147 connection._commit()
148 clean_savepoints()
149 else:
150 set_dirty()
152 def rollback_unless_managed():
154 Rolls back changes if the system is not in managed transaction mode.
156 if not is_managed():
157 connection._rollback()
158 else:
159 set_dirty()
161 def commit():
163 Does the commit itself and resets the dirty flag.
165 connection._commit()
166 set_clean()
168 def rollback():
170 This function does the rollback itself and resets the dirty flag.
172 connection._rollback()
173 set_clean()
175 def savepoint():
177 Creates a savepoint (if supported and required by the backend) inside the
178 current transaction. Returns an identifier for the savepoint that will be
179 used for the subsequent rollback or commit.
181 thread_ident = thread.get_ident()
182 if thread_ident in savepoint_state:
183 savepoint_state[thread_ident].append(None)
184 else:
185 savepoint_state[thread_ident] = [None]
186 tid = str(thread_ident).replace('-', '')
187 sid = "s%s_x%d" % (tid, len(savepoint_state[thread_ident]))
188 connection._savepoint(sid)
189 return sid
191 def savepoint_rollback(sid):
193 Rolls back the most recent savepoint (if one exists). Does nothing if
194 savepoints are not supported.
196 if thread.get_ident() in savepoint_state:
197 connection._savepoint_rollback(sid)
199 def savepoint_commit(sid):
201 Commits the most recent savepoint (if one exists). Does nothing if
202 savepoints are not supported.
204 if thread.get_ident() in savepoint_state:
205 connection._savepoint_commit(sid)
207 ##############
208 # DECORATORS #
209 ##############
211 def autocommit(func):
213 Decorator that activates commit on save. This is Django's default behavior;
214 this decorator is useful if you globally activated transaction management in
215 your settings file and want the default behavior in some view functions.
217 def _autocommit(*args, **kw):
218 try:
219 enter_transaction_management()
220 managed(False)
221 return func(*args, **kw)
222 finally:
223 leave_transaction_management()
224 return wraps(func)(_autocommit)
226 def commit_on_success(func):
228 This decorator activates commit on response. This way, if the view function
229 runs successfully, a commit is made; if the viewfunc produces an exception,
230 a rollback is made. This is one of the most common ways to do transaction
231 control in web apps.
233 def _commit_on_success(*args, **kw):
234 try:
235 enter_transaction_management()
236 managed(True)
237 try:
238 res = func(*args, **kw)
239 except:
240 # All exceptions must be handled here (even string ones).
241 if is_dirty():
242 rollback()
243 raise
244 else:
245 if is_dirty():
246 commit()
247 return res
248 finally:
249 leave_transaction_management()
250 return wraps(func)(_commit_on_success)
252 def commit_manually(func):
254 Decorator that activates manual transaction control. It just disables
255 automatic transaction control and doesn't do any commit/rollback of its
256 own -- it's up to the user to call the commit and rollback functions
257 themselves.
259 def _commit_manually(*args, **kw):
260 try:
261 enter_transaction_management()
262 managed(True)
263 return func(*args, **kw)
264 finally:
265 leave_transaction_management()
267 return wraps(func)(_commit_manually)