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 | from django.db import connection
|
---|
20 | from django.conf import settings
|
---|
21 |
|
---|
22 | class TransactionManagementError(Exception):
|
---|
23 | """
|
---|
24 | This exception is thrown when something bad happens with transaction
|
---|
25 | management.
|
---|
26 | """
|
---|
27 | pass
|
---|
28 |
|
---|
29 | # The state is a dictionary of lists. The key to the dict is the current
|
---|
30 | # thread and the list is handled as a stack of values.
|
---|
31 | state = {}
|
---|
32 |
|
---|
33 | # The dirty flag is set by *_unless_managed functions to denote that the
|
---|
34 | # code under transaction management has changed things to require a
|
---|
35 | # database commit.
|
---|
36 | dirty = {}
|
---|
37 |
|
---|
38 | def enter_transaction_management():
|
---|
39 | """
|
---|
40 | Enters transaction management for a running thread. It must be balanced with
|
---|
41 | the appropriate leave_transaction_management call, since the actual state is
|
---|
42 | managed as a stack.
|
---|
43 |
|
---|
44 | The state and dirty flag are carried over from the surrounding block or
|
---|
45 | from the settings, if there is no surrounding block (dirty is always false
|
---|
46 | when no current block is running).
|
---|
47 | """
|
---|
48 | thread_ident = thread.get_ident()
|
---|
49 | if state.has_key(thread_ident) and state[thread_ident]:
|
---|
50 | state[thread_ident].append(state[thread_ident][-1])
|
---|
51 | else:
|
---|
52 | state[thread_ident] = []
|
---|
53 | state[thread_ident].append(settings.TRANSACTIONS_MANAGED)
|
---|
54 | if not dirty.has_key(thread_ident):
|
---|
55 | dirty[thread_ident] = False
|
---|
56 |
|
---|
57 | def leave_transaction_management():
|
---|
58 | """
|
---|
59 | Leaves transaction management for a running thread. A dirty flag is carried
|
---|
60 | over to the surrounding block, as a commit will commit all changes, even
|
---|
61 | those from outside. (Commits are on connection level.)
|
---|
62 | """
|
---|
63 | thread_ident = thread.get_ident()
|
---|
64 | if state.has_key(thread_ident) and state[thread_ident]:
|
---|
65 | del state[thread_ident][-1]
|
---|
66 | else:
|
---|
67 | raise TransactionManagementError("This code isn't under transaction management")
|
---|
68 | if dirty.get(thread_ident, False):
|
---|
69 | rollback()
|
---|
70 | raise TransactionManagementError("Transaction managed block ended with pending COMMIT/ROLLBACK")
|
---|
71 | dirty[thread_ident] = False
|
---|
72 |
|
---|
73 | def is_dirty():
|
---|
74 | """
|
---|
75 | Returns True if the current transaction requires a commit for changes to
|
---|
76 | happen.
|
---|
77 | """
|
---|
78 | return dirty.get(thread.get_ident(), False)
|
---|
79 |
|
---|
80 | def set_dirty():
|
---|
81 | """
|
---|
82 | Sets a dirty flag for the current thread and code streak. This can be used
|
---|
83 | to decide in a managed block of code to decide whether there are open
|
---|
84 | changes waiting for commit.
|
---|
85 | ...
|
---|
86 | Patch: DISABLE_TRANSACTION_MANAGEMENT puts the developer in control
|
---|
87 | of the Unit of Work. This will ignore the framework of the changes and allow
|
---|
88 | the developer to make the determination of when to commit and rollback as well
|
---|
89 | as assume the responsibility. This code was not working previous to this change
|
---|
90 | """
|
---|
91 | if (settings.DISABLE_TRANSACTION_MANAGEMENT):
|
---|
92 | return
|
---|
93 |
|
---|
94 | # End of changes
|
---|
95 |
|
---|
96 | thread_ident = thread.get_ident()
|
---|
97 | if dirty.has_key(thread_ident):
|
---|
98 | dirty[thread_ident] = True
|
---|
99 | else:
|
---|
100 | raise TransactionManagementError("This code isn't under transaction management")
|
---|
101 |
|
---|
102 | def set_clean():
|
---|
103 | """
|
---|
104 | Resets a dirty flag for the current thread and code streak. This can be used
|
---|
105 | to decide in a managed block of code to decide whether a commit or rollback
|
---|
106 | should happen.
|
---|
107 | """
|
---|
108 | thread_ident = thread.get_ident()
|
---|
109 | if dirty.has_key(thread_ident):
|
---|
110 | dirty[thread_ident] = False
|
---|
111 | else:
|
---|
112 | raise TransactionManagementError("This code isn't under transaction management")
|
---|
113 |
|
---|
114 | def is_managed():
|
---|
115 | """
|
---|
116 | Checks whether the transaction manager is in manual or in auto state.
|
---|
117 | """
|
---|
118 | thread_ident = thread.get_ident()
|
---|
119 | if state.has_key(thread_ident):
|
---|
120 | if state[thread_ident]:
|
---|
121 | return state[thread_ident][-1]
|
---|
122 | return settings.TRANSACTIONS_MANAGED
|
---|
123 |
|
---|
124 | def managed(flag=True):
|
---|
125 | """
|
---|
126 | Puts the transaction manager into a manual state: managed transactions have
|
---|
127 | to be committed explicitly by the user. If you switch off transaction
|
---|
128 | management and there is a pending commit/rollback, the data will be
|
---|
129 | commited.
|
---|
130 | """
|
---|
131 | thread_ident = thread.get_ident()
|
---|
132 | top = state.get(thread_ident, None)
|
---|
133 | if top:
|
---|
134 | top[-1] = flag
|
---|
135 | if not flag and is_dirty():
|
---|
136 | connection._commit()
|
---|
137 | set_clean()
|
---|
138 | else:
|
---|
139 | raise TransactionManagementError("This code isn't under transaction management")
|
---|
140 |
|
---|
141 | def commit_unless_managed():
|
---|
142 | """
|
---|
143 | Commits changes if the system is not in managed transaction mode.
|
---|
144 | """
|
---|
145 | if not is_managed():
|
---|
146 | connection._commit()
|
---|
147 | else:
|
---|
148 | set_dirty()
|
---|
149 |
|
---|
150 | def rollback_unless_managed():
|
---|
151 | """
|
---|
152 | Rolls back changes if the system is not in managed transaction mode.
|
---|
153 | """
|
---|
154 | if not is_managed():
|
---|
155 | connection._rollback()
|
---|
156 | else:
|
---|
157 | set_dirty()
|
---|
158 |
|
---|
159 | def commit():
|
---|
160 | """
|
---|
161 | Does the commit itself and resets the dirty flag.
|
---|
162 | """
|
---|
163 | connection._commit()
|
---|
164 | set_clean()
|
---|
165 |
|
---|
166 | def rollback():
|
---|
167 | """
|
---|
168 | This function does the rollback itself and resets the dirty flag.
|
---|
169 | """
|
---|
170 | connection._rollback()
|
---|
171 | set_clean()
|
---|
172 |
|
---|
173 | ##############
|
---|
174 | # DECORATORS #
|
---|
175 | ##############
|
---|
176 |
|
---|
177 | def autocommit(func):
|
---|
178 | """
|
---|
179 | Decorator that activates commit on save. This is Django's default behavior;
|
---|
180 | this decorator is useful if you globally activated transaction management in
|
---|
181 | your settings file and want the default behavior in some view functions.
|
---|
182 | """
|
---|
183 | def _autocommit(*args, **kw):
|
---|
184 | try:
|
---|
185 | enter_transaction_management()
|
---|
186 | managed(False)
|
---|
187 | return func(*args, **kw)
|
---|
188 | finally:
|
---|
189 | leave_transaction_management()
|
---|
190 | return _autocommit
|
---|
191 |
|
---|
192 | def commit_on_success(func):
|
---|
193 | """
|
---|
194 | This decorator activates commit on response. This way, if the view function
|
---|
195 | runs successfully, a commit is made; if the viewfunc produces an exception,
|
---|
196 | a rollback is made. This is one of the most common ways to do transaction
|
---|
197 | control in web apps.
|
---|
198 | """
|
---|
199 | def _commit_on_success(*args, **kw):
|
---|
200 | try:
|
---|
201 | enter_transaction_management()
|
---|
202 | managed(True)
|
---|
203 | try:
|
---|
204 | res = func(*args, **kw)
|
---|
205 | except Exception, e:
|
---|
206 | if is_dirty():
|
---|
207 | rollback()
|
---|
208 | raise
|
---|
209 | else:
|
---|
210 | if is_dirty():
|
---|
211 | commit()
|
---|
212 | return res
|
---|
213 | finally:
|
---|
214 | leave_transaction_management()
|
---|
215 | return _commit_on_success
|
---|
216 |
|
---|
217 | def commit_manually(func):
|
---|
218 | """
|
---|
219 | Decorator that activates manual transaction control. It just disables
|
---|
220 | automatic transaction control and doesn't do any commit/rollback of its
|
---|
221 | own -- it's up to the user to call the commit and rollback functions
|
---|
222 | themselves.
|
---|
223 | """
|
---|
224 | def _commit_manually(*args, **kw):
|
---|
225 | try:
|
---|
226 | enter_transaction_management()
|
---|
227 | managed(True)
|
---|
228 | return func(*args, **kw)
|
---|
229 | finally:
|
---|
230 | leave_transaction_management()
|
---|
231 |
|
---|
232 | return _commit_manually
|
---|