Django

Code

root/django/trunk/django/db/transaction.py

Revision 8419, 8.4 kB (checked in by mtredinnick, 3 months ago)

Fixed #7241 -- More robust exception catching in the transaction management
code. As pointed out in the ticket, Python still lets you raise all sorts of
odd things as exceptions (e.g. strings), so even though they're bad form, we
should still handle them. We do that cleanly now. Thanks to jim-django@dsdd.org
for the patch.

  • Property svn:eol-style set to native
Line 
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.
5
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.
10
11 Managed transactions don't do those commits, but will need some kind of manual
12 or implicit commits or rollbacks.
13 """
14
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
25
26 class TransactionManagementError(Exception):
27     """
28     This exception is thrown when something bad happens with transaction
29     management.
30     """
31     pass
32
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 = {}
37
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 = {}
42
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.
48
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
61
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
77
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)
84
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")
96
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.
102     """
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()
109
110 def clean_savepoints():
111     thread_ident = thread.get_ident()
112     if thread_ident in savepoint_state:
113         del savepoint_state[thread_ident]
114
115 def is_managed():
116     """
117     Checks whether the transaction manager is in manual or in auto state.
118     """
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
124
125 def managed(flag=True):
126     """
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.
131     """
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")
141
142 def commit_unless_managed():
143     """
144     Commits changes if the system is not in managed transaction mode.
145     """
146     if not is_managed():
147         connection._commit()
148         clean_savepoints()
149     else:
150         set_dirty()
151
152 def rollback_unless_managed():
153     """
154     Rolls back changes if the system is not in managed transaction mode.
155     """
156     if not is_managed():
157         connection._rollback()
158     else:
159         set_dirty()
160
161 def commit():
162     """
163     Does the commit itself and resets the dirty flag.
164     """
165     connection._commit()
166     set_clean()
167
168 def rollback():
169     """
170     This function does the rollback itself and resets the dirty flag.
171     """
172     connection._rollback()
173     set_clean()
174
175 def savepoint():
176     """
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.
180     """
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
190
191 def savepoint_rollback(sid):
192     """
193     Rolls back the most recent savepoint (if one exists). Does nothing if
194     savepoints are not supported.
195     """
196     if thread.get_ident() in savepoint_state:
197         connection._savepoint_rollback(sid)
198
199 def savepoint_commit(sid):
200     """
201     Commits the most recent savepoint (if one exists). Does nothing if
202     savepoints are not supported.
203     """
204     if thread.get_ident() in savepoint_state:
205         connection._savepoint_commit(sid)
206
207 ##############
208 # DECORATORS #
209 ##############
210
211 def autocommit(func):
212     """
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.
216     """
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)
225
226 def commit_on_success(func):
227     """
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.
232     """
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)
251
252 def commit_manually(func):
253     """
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.
258     """
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()
266
267     return wraps(func)(_commit_manually)
Note: See TracBrowser for help on using the browser.