~ubuntu-branches/ubuntu/saucy/python-django/saucy-updates

« back to all changes in this revision

Viewing changes to django/db/transaction.py

  • Committer: Bazaar Package Importer
  • Author(s): Chris Lamb
  • Date: 2010-05-21 07:52:55 UTC
  • mfrom: (1.3.6 upstream)
  • mto: This revision was merged to the branch mainline in revision 28.
  • Revision ID: james.westby@ubuntu.com-20100521075255-ii78v1dyfmyu3uzx
Tags: upstream-1.2
ImportĀ upstreamĀ versionĀ 1.2

Show diffs side-by-side

added added

removed removed

Lines of Context:
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.
23
 
from django.db import connection
 
22
    from django.utils.functional import wraps  # Python 2.4 fallback.
 
23
from django.db import connections, DEFAULT_DB_ALIAS
24
24
from django.conf import settings
25
25
 
26
26
class TransactionManagementError(Exception):
30
30
    """
31
31
    pass
32
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.
 
33
# The states are dictionaries of dictionaries of lists. The key to the outer
 
34
# dict is the current thread, and the key to the inner dictionary is the
 
35
# connection alias and the list is handled as a stack of values.
35
36
state = {}
36
37
savepoint_state = {}
37
38
 
38
39
# The dirty flag is set by *_unless_managed functions to denote that the
39
40
# code under transaction management has changed things to require a
40
41
# database commit.
 
42
# This is a dictionary mapping thread to a dictionary mapping connection
 
43
# alias to a boolean.
41
44
dirty = {}
42
45
 
43
 
def enter_transaction_management(managed=True):
 
46
def enter_transaction_management(managed=True, using=None):
44
47
    """
45
48
    Enters transaction management for a running thread. It must be balanced with
46
49
    the appropriate leave_transaction_management call, since the actual state is
50
53
    from the settings, if there is no surrounding block (dirty is always false
51
54
    when no current block is running).
52
55
    """
 
56
    if using is None:
 
57
        using = DEFAULT_DB_ALIAS
 
58
    connection = connections[using]
53
59
    thread_ident = thread.get_ident()
54
 
    if thread_ident in state and state[thread_ident]:
55
 
        state[thread_ident].append(state[thread_ident][-1])
 
60
    if thread_ident in state and state[thread_ident].get(using):
 
61
        state[thread_ident][using].append(state[thread_ident][using][-1])
56
62
    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
 
63
        state.setdefault(thread_ident, {})
 
64
        state[thread_ident][using] = [settings.TRANSACTIONS_MANAGED]
 
65
    if thread_ident not in dirty or using not in dirty[thread_ident]:
 
66
        dirty.setdefault(thread_ident, {})
 
67
        dirty[thread_ident][using] = False
61
68
    connection._enter_transaction_management(managed)
62
69
 
63
 
def leave_transaction_management():
 
70
def leave_transaction_management(using=None):
64
71
    """
65
72
    Leaves transaction management for a running thread. A dirty flag is carried
66
73
    over to the surrounding block, as a commit will commit all changes, even
67
74
    those from outside. (Commits are on connection level.)
68
75
    """
69
 
    connection._leave_transaction_management(is_managed())
 
76
    if using is None:
 
77
        using = DEFAULT_DB_ALIAS
 
78
    connection = connections[using]
 
79
    connection._leave_transaction_management(is_managed(using=using))
70
80
    thread_ident = thread.get_ident()
71
 
    if thread_ident in state and state[thread_ident]:
72
 
        del state[thread_ident][-1]
 
81
    if thread_ident in state and state[thread_ident].get(using):
 
82
        del state[thread_ident][using][-1]
73
83
    else:
74
84
        raise TransactionManagementError("This code isn't under transaction management")
75
 
    if dirty.get(thread_ident, False):
76
 
        rollback()
 
85
    if dirty.get(thread_ident, {}).get(using, False):
 
86
        rollback(using=using)
77
87
        raise TransactionManagementError("Transaction managed block ended with pending COMMIT/ROLLBACK")
78
 
    dirty[thread_ident] = False
 
88
    dirty[thread_ident][using] = False
79
89
 
80
 
def is_dirty():
 
90
def is_dirty(using=None):
81
91
    """
82
92
    Returns True if the current transaction requires a commit for changes to
83
93
    happen.
84
94
    """
85
 
    return dirty.get(thread.get_ident(), False)
 
95
    if using is None:
 
96
        using = DEFAULT_DB_ALIAS
 
97
    return dirty.get(thread.get_ident(), {}).get(using, False)
86
98
 
87
 
def set_dirty():
 
99
def set_dirty(using=None):
88
100
    """
89
101
    Sets a dirty flag for the current thread and code streak. This can be used
90
102
    to decide in a managed block of code to decide whether there are open
91
103
    changes waiting for commit.
92
104
    """
 
105
    if using is None:
 
106
        using = DEFAULT_DB_ALIAS
93
107
    thread_ident = thread.get_ident()
94
 
    if thread_ident in dirty:
95
 
        dirty[thread_ident] = True
 
108
    if thread_ident in dirty and using in dirty[thread_ident]:
 
109
        dirty[thread_ident][using] = True
96
110
    else:
97
111
        raise TransactionManagementError("This code isn't under transaction management")
98
112
 
99
 
def set_clean():
 
113
def set_clean(using=None):
100
114
    """
101
115
    Resets a dirty flag for the current thread and code streak. This can be used
102
116
    to decide in a managed block of code to decide whether a commit or rollback
103
117
    should happen.
104
118
    """
 
119
    if using is None:
 
120
        using = DEFAULT_DB_ALIAS
105
121
    thread_ident = thread.get_ident()
106
 
    if thread_ident in dirty:
107
 
        dirty[thread_ident] = False
 
122
    if thread_ident in dirty and using in dirty[thread_ident]:
 
123
        dirty[thread_ident][using] = False
108
124
    else:
109
125
        raise TransactionManagementError("This code isn't under transaction management")
110
 
    clean_savepoints()
 
126
    clean_savepoints(using=using)
111
127
 
112
 
def clean_savepoints():
 
128
def clean_savepoints(using=None):
 
129
    if using is None:
 
130
        using = DEFAULT_DB_ALIAS
113
131
    thread_ident = thread.get_ident()
114
 
    if thread_ident in savepoint_state:
115
 
        del savepoint_state[thread_ident]
 
132
    if thread_ident in savepoint_state and using in savepoint_state[thread_ident]:
 
133
        del savepoint_state[thread_ident][using]
116
134
 
117
 
def is_managed():
 
135
def is_managed(using=None):
118
136
    """
119
137
    Checks whether the transaction manager is in manual or in auto state.
120
138
    """
 
139
    if using is None:
 
140
        using = DEFAULT_DB_ALIAS
121
141
    thread_ident = thread.get_ident()
122
 
    if thread_ident in state:
123
 
        if state[thread_ident]:
124
 
            return state[thread_ident][-1]
 
142
    if thread_ident in state and using in state[thread_ident]:
 
143
        if state[thread_ident][using]:
 
144
            return state[thread_ident][using][-1]
125
145
    return settings.TRANSACTIONS_MANAGED
126
146
 
127
 
def managed(flag=True):
 
147
def managed(flag=True, using=None):
128
148
    """
129
149
    Puts the transaction manager into a manual state: managed transactions have
130
150
    to be committed explicitly by the user. If you switch off transaction
131
151
    management and there is a pending commit/rollback, the data will be
132
152
    commited.
133
153
    """
 
154
    if using is None:
 
155
        using = DEFAULT_DB_ALIAS
 
156
    connection = connections[using]
134
157
    thread_ident = thread.get_ident()
135
 
    top = state.get(thread_ident, None)
 
158
    top = state.get(thread_ident, {}).get(using, None)
136
159
    if top:
137
160
        top[-1] = flag
138
 
        if not flag and is_dirty():
 
161
        if not flag and is_dirty(using=using):
139
162
            connection._commit()
140
 
            set_clean()
 
163
            set_clean(using=using)
141
164
    else:
142
165
        raise TransactionManagementError("This code isn't under transaction management")
143
166
 
144
 
def commit_unless_managed():
 
167
def commit_unless_managed(using=None):
145
168
    """
146
169
    Commits changes if the system is not in managed transaction mode.
147
170
    """
148
 
    if not is_managed():
 
171
    if using is None:
 
172
        using = DEFAULT_DB_ALIAS
 
173
    connection = connections[using]
 
174
    if not is_managed(using=using):
149
175
        connection._commit()
150
 
        clean_savepoints()
 
176
        clean_savepoints(using=using)
151
177
    else:
152
 
        set_dirty()
 
178
        set_dirty(using=using)
153
179
 
154
 
def rollback_unless_managed():
 
180
def rollback_unless_managed(using=None):
155
181
    """
156
182
    Rolls back changes if the system is not in managed transaction mode.
157
183
    """
158
 
    if not is_managed():
 
184
    if using is None:
 
185
        using = DEFAULT_DB_ALIAS
 
186
    connection = connections[using]
 
187
    if not is_managed(using=using):
159
188
        connection._rollback()
160
189
    else:
161
 
        set_dirty()
 
190
        set_dirty(using=using)
162
191
 
163
 
def commit():
 
192
def commit(using=None):
164
193
    """
165
194
    Does the commit itself and resets the dirty flag.
166
195
    """
 
196
    if using is None:
 
197
        using = DEFAULT_DB_ALIAS
 
198
    connection = connections[using]
167
199
    connection._commit()
168
 
    set_clean()
 
200
    set_clean(using=using)
169
201
 
170
 
def rollback():
 
202
def rollback(using=None):
171
203
    """
172
204
    This function does the rollback itself and resets the dirty flag.
173
205
    """
 
206
    if using is None:
 
207
        using = DEFAULT_DB_ALIAS
 
208
    connection = connections[using]
174
209
    connection._rollback()
175
 
    set_clean()
 
210
    set_clean(using=using)
176
211
 
177
 
def savepoint():
 
212
def savepoint(using=None):
178
213
    """
179
214
    Creates a savepoint (if supported and required by the backend) inside the
180
215
    current transaction. Returns an identifier for the savepoint that will be
181
216
    used for the subsequent rollback or commit.
182
217
    """
 
218
    if using is None:
 
219
        using = DEFAULT_DB_ALIAS
 
220
    connection = connections[using]
183
221
    thread_ident = thread.get_ident()
184
 
    if thread_ident in savepoint_state:
185
 
        savepoint_state[thread_ident].append(None)
 
222
    if thread_ident in savepoint_state and using in savepoint_state[thread_ident]:
 
223
        savepoint_state[thread_ident][using].append(None)
186
224
    else:
187
 
        savepoint_state[thread_ident] = [None]
 
225
        savepoint_state.setdefault(thread_ident, {})
 
226
        savepoint_state[thread_ident][using] = [None]
188
227
    tid = str(thread_ident).replace('-', '')
189
 
    sid = "s%s_x%d" % (tid, len(savepoint_state[thread_ident]))
 
228
    sid = "s%s_x%d" % (tid, len(savepoint_state[thread_ident][using]))
190
229
    connection._savepoint(sid)
191
230
    return sid
192
231
 
193
 
def savepoint_rollback(sid):
 
232
def savepoint_rollback(sid, using=None):
194
233
    """
195
234
    Rolls back the most recent savepoint (if one exists). Does nothing if
196
235
    savepoints are not supported.
197
236
    """
198
 
    if thread.get_ident() in savepoint_state:
 
237
    if using is None:
 
238
        using = DEFAULT_DB_ALIAS
 
239
    connection = connections[using]
 
240
    thread_ident = thread.get_ident()
 
241
    if thread_ident in savepoint_state and using in savepoint_state[thread_ident]:
199
242
        connection._savepoint_rollback(sid)
200
243
 
201
 
def savepoint_commit(sid):
 
244
def savepoint_commit(sid, using=None):
202
245
    """
203
246
    Commits the most recent savepoint (if one exists). Does nothing if
204
247
    savepoints are not supported.
205
248
    """
206
 
    if thread.get_ident() in savepoint_state:
 
249
    if using is None:
 
250
        using = DEFAULT_DB_ALIAS
 
251
    connection = connections[using]
 
252
    thread_ident = thread.get_ident()
 
253
    if thread_ident in savepoint_state and using in savepoint_state[thread_ident]:
207
254
        connection._savepoint_commit(sid)
208
255
 
209
256
##############
210
257
# DECORATORS #
211
258
##############
212
259
 
213
 
def autocommit(func):
 
260
def autocommit(using=None):
214
261
    """
215
262
    Decorator that activates commit on save. This is Django's default behavior;
216
263
    this decorator is useful if you globally activated transaction management in
217
264
    your settings file and want the default behavior in some view functions.
218
265
    """
219
 
    def _autocommit(*args, **kw):
220
 
        try:
221
 
            enter_transaction_management(managed=False)
222
 
            managed(False)
223
 
            return func(*args, **kw)
224
 
        finally:
225
 
            leave_transaction_management()
226
 
    return wraps(func)(_autocommit)
227
 
 
228
 
def commit_on_success(func):
 
266
    def inner_autocommit(func, db=None):
 
267
        def _autocommit(*args, **kw):
 
268
            try:
 
269
                enter_transaction_management(managed=False, using=db)
 
270
                managed(False, using=db)
 
271
                return func(*args, **kw)
 
272
            finally:
 
273
                leave_transaction_management(using=db)
 
274
        return wraps(func)(_autocommit)
 
275
 
 
276
    # Note that although the first argument is *called* `using`, it
 
277
    # may actually be a function; @autocommit and @autocommit('foo')
 
278
    # are both allowed forms.
 
279
    if using is None:
 
280
        using = DEFAULT_DB_ALIAS
 
281
    if callable(using):
 
282
        return inner_autocommit(using, DEFAULT_DB_ALIAS)
 
283
    return lambda func: inner_autocommit(func,  using)
 
284
 
 
285
 
 
286
def commit_on_success(using=None):
229
287
    """
230
288
    This decorator activates commit on response. This way, if the view function
231
289
    runs successfully, a commit is made; if the viewfunc produces an exception,
232
290
    a rollback is made. This is one of the most common ways to do transaction
233
291
    control in web apps.
234
292
    """
235
 
    def _commit_on_success(*args, **kw):
236
 
        try:
237
 
            enter_transaction_management()
238
 
            managed(True)
 
293
    def inner_commit_on_success(func, db=None):
 
294
        def _commit_on_success(*args, **kw):
239
295
            try:
240
 
                res = func(*args, **kw)
241
 
            except:
242
 
                # All exceptions must be handled here (even string ones).
243
 
                if is_dirty():
244
 
                    rollback()
245
 
                raise
246
 
            else:
247
 
                if is_dirty():
248
 
                    commit()
249
 
            return res
250
 
        finally:
251
 
            leave_transaction_management()
252
 
    return wraps(func)(_commit_on_success)
253
 
 
254
 
def commit_manually(func):
 
296
                enter_transaction_management(using=db)
 
297
                managed(True, using=db)
 
298
                try:
 
299
                    res = func(*args, **kw)
 
300
                except:
 
301
                    # All exceptions must be handled here (even string ones).
 
302
                    if is_dirty(using=db):
 
303
                        rollback(using=db)
 
304
                    raise
 
305
                else:
 
306
                    if is_dirty(using=db):
 
307
                        try:
 
308
                            commit(using=db)
 
309
                        except:
 
310
                            rollback(using=db)
 
311
                            raise
 
312
                return res
 
313
            finally:
 
314
                leave_transaction_management(using=db)
 
315
        return wraps(func)(_commit_on_success)
 
316
 
 
317
    # Note that although the first argument is *called* `using`, it
 
318
    # may actually be a function; @autocommit and @autocommit('foo')
 
319
    # are both allowed forms.
 
320
    if using is None:
 
321
        using = DEFAULT_DB_ALIAS
 
322
    if callable(using):
 
323
        return inner_commit_on_success(using, DEFAULT_DB_ALIAS)
 
324
    return lambda func: inner_commit_on_success(func, using)
 
325
 
 
326
def commit_manually(using=None):
255
327
    """
256
328
    Decorator that activates manual transaction control. It just disables
257
329
    automatic transaction control and doesn't do any commit/rollback of its
258
330
    own -- it's up to the user to call the commit and rollback functions
259
331
    themselves.
260
332
    """
261
 
    def _commit_manually(*args, **kw):
262
 
        try:
263
 
            enter_transaction_management()
264
 
            managed(True)
265
 
            return func(*args, **kw)
266
 
        finally:
267
 
            leave_transaction_management()
268
 
 
269
 
    return wraps(func)(_commit_manually)
 
333
    def inner_commit_manually(func, db=None):
 
334
        def _commit_manually(*args, **kw):
 
335
            try:
 
336
                enter_transaction_management(using=db)
 
337
                managed(True, using=db)
 
338
                return func(*args, **kw)
 
339
            finally:
 
340
                leave_transaction_management(using=db)
 
341
 
 
342
        return wraps(func)(_commit_manually)
 
343
 
 
344
    # Note that although the first argument is *called* `using`, it
 
345
    # may actually be a function; @autocommit and @autocommit('foo')
 
346
    # are both allowed forms.
 
347
    if using is None:
 
348
        using = DEFAULT_DB_ALIAS
 
349
    if callable(using):
 
350
        return inner_commit_manually(using, DEFAULT_DB_ALIAS)
 
351
    return lambda func: inner_commit_manually(func, using)