| 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
|
|---|