Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/django/db/transaction.py: 62%
127 statements
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
1from contextlib import ContextDecorator, contextmanager
3from django.db import (
4 DEFAULT_DB_ALIAS,
5 DatabaseError,
6 Error,
7 ProgrammingError,
8 connections,
9)
12class TransactionManagementError(ProgrammingError):
13 """Transaction management is used improperly."""
15 pass
18def get_connection(using=None):
19 """
20 Get a database connection by name, or the default database connection
21 if no name is provided. This is a private API.
22 """
23 if using is None:
24 using = DEFAULT_DB_ALIAS
25 return connections[using]
28def get_autocommit(using=None):
29 """Get the autocommit status of the connection."""
30 return get_connection(using).get_autocommit()
33def set_autocommit(autocommit, using=None):
34 """Set the autocommit status of the connection."""
35 return get_connection(using).set_autocommit(autocommit)
38def commit(using=None):
39 """Commit a transaction."""
40 get_connection(using).commit()
43def rollback(using=None):
44 """Roll back a transaction."""
45 get_connection(using).rollback()
48def savepoint(using=None):
49 """
50 Create a savepoint (if supported and required by the backend) inside the
51 current transaction. Return an identifier for the savepoint that will be
52 used for the subsequent rollback or commit.
53 """
54 return get_connection(using).savepoint()
57def savepoint_rollback(sid, using=None):
58 """
59 Roll back the most recent savepoint (if one exists). Do nothing if
60 savepoints are not supported.
61 """
62 get_connection(using).savepoint_rollback(sid)
65def savepoint_commit(sid, using=None):
66 """
67 Commit the most recent savepoint (if one exists). Do nothing if
68 savepoints are not supported.
69 """
70 get_connection(using).savepoint_commit(sid)
73def clean_savepoints(using=None):
74 """
75 Reset the counter used to generate unique savepoint ids in this thread.
76 """
77 get_connection(using).clean_savepoints()
80def get_rollback(using=None):
81 """Get the "needs rollback" flag -- for *advanced use* only."""
82 return get_connection(using).get_rollback()
85def set_rollback(rollback, using=None):
86 """
87 Set or unset the "needs rollback" flag -- for *advanced use* only.
89 When `rollback` is `True`, trigger a rollback when exiting the innermost
90 enclosing atomic block that has `savepoint=True` (that's the default). Use
91 this to force a rollback without raising an exception.
93 When `rollback` is `False`, prevent such a rollback. Use this only after
94 rolling back to a known-good state! Otherwise, you break the atomic block
95 and data corruption may occur.
96 """
97 return get_connection(using).set_rollback(rollback)
100@contextmanager
101def mark_for_rollback_on_error(using=None):
102 """
103 Internal low-level utility to mark a transaction as "needs rollback" when
104 an exception is raised while not enforcing the enclosed block to be in a
105 transaction. This is needed by Model.save() and friends to avoid starting a
106 transaction when in autocommit mode and a single query is executed.
108 It's equivalent to:
110 connection = get_connection(using)
111 if connection.get_autocommit():
112 yield
113 else:
114 with transaction.atomic(using=using, savepoint=False):
115 yield
117 but it uses low-level utilities to avoid performance overhead.
118 """
119 try:
120 yield
121 except Exception:
122 connection = get_connection(using)
123 if connection.in_atomic_block:
124 connection.needs_rollback = True
125 raise
128def on_commit(func, using=None):
129 """
130 Register `func` to be called when the current transaction is committed.
131 If the current transaction is rolled back, `func` will not be called.
132 """
133 get_connection(using).on_commit(func)
136#################################
137# Decorators / context managers #
138#################################
141class Atomic(ContextDecorator):
142 """
143 Guarantee the atomic execution of a given block.
145 An instance can be used either as a decorator or as a context manager.
147 When it's used as a decorator, __call__ wraps the execution of the
148 decorated function in the instance itself, used as a context manager.
150 When it's used as a context manager, __enter__ creates a transaction or a
151 savepoint, depending on whether a transaction is already in progress, and
152 __exit__ commits the transaction or releases the savepoint on normal exit,
153 and rolls back the transaction or to the savepoint on exceptions.
155 It's possible to disable the creation of savepoints if the goal is to
156 ensure that some code runs within a transaction without creating overhead.
158 A stack of savepoints identifiers is maintained as an attribute of the
159 connection. None denotes the absence of a savepoint.
161 This allows reentrancy even if the same AtomicWrapper is reused. For
162 example, it's possible to define `oa = atomic('other')` and use `@oa` or
163 `with oa:` multiple times.
165 Since database connections are thread-local, this is thread-safe.
167 An atomic block can be tagged as durable. In this case, raise a
168 RuntimeError if it's nested within another atomic block. This guarantees
169 that database changes in a durable block are committed to the database when
170 the block exists without error.
172 This is a private API.
173 """
175 # This private flag is provided only to disable the durability checks in
176 # TestCase.
177 _ensure_durability = True
179 def __init__(self, using, savepoint, durable):
180 self.using = using
181 self.savepoint = savepoint
182 self.durable = durable
184 def __enter__(self):
185 connection = get_connection(self.using)
187 if self.durable and self._ensure_durability and connection.in_atomic_block: 187 ↛ 188line 187 didn't jump to line 188, because the condition on line 187 was never true
188 raise RuntimeError(
189 "A durable atomic block cannot be nested within another "
190 "atomic block."
191 )
192 if not connection.in_atomic_block:
193 # Reset state when entering an outermost atomic block.
194 connection.commit_on_exit = True
195 connection.needs_rollback = False
196 if not connection.get_autocommit(): 196 ↛ 200line 196 didn't jump to line 200, because the condition on line 196 was never true
197 # Pretend we're already in an atomic block to bypass the code
198 # that disables autocommit to enter a transaction, and make a
199 # note to deal with this case in __exit__.
200 connection.in_atomic_block = True
201 connection.commit_on_exit = False
203 if connection.in_atomic_block:
204 # We're already in a transaction; create a savepoint, unless we
205 # were told not to or we're already waiting for a rollback. The
206 # second condition avoids creating useless savepoints and prevents
207 # overwriting needs_rollback until the rollback is performed.
208 if self.savepoint and not connection.needs_rollback:
209 sid = connection.savepoint()
210 connection.savepoint_ids.append(sid)
211 else:
212 connection.savepoint_ids.append(None)
213 else:
214 connection.set_autocommit(
215 False, force_begin_transaction_with_broken_autocommit=True
216 )
217 connection.in_atomic_block = True
219 def __exit__(self, exc_type, exc_value, traceback):
220 connection = get_connection(self.using)
222 if connection.savepoint_ids:
223 sid = connection.savepoint_ids.pop()
224 else:
225 # Prematurely unset this flag to allow using commit or rollback.
226 connection.in_atomic_block = False
228 try:
229 if connection.closed_in_transaction: 229 ↛ 232line 229 didn't jump to line 232, because the condition on line 229 was never true
230 # The database will perform a rollback by itself.
231 # Wait until we exit the outermost block.
232 pass
234 elif exc_type is None and not connection.needs_rollback:
235 if connection.in_atomic_block:
236 # Release savepoint if there is one
237 if sid is not None:
238 try:
239 connection.savepoint_commit(sid)
240 except DatabaseError:
241 try:
242 connection.savepoint_rollback(sid)
243 # The savepoint won't be reused. Release it to
244 # minimize overhead for the database server.
245 connection.savepoint_commit(sid)
246 except Error:
247 # If rolling back to a savepoint fails, mark for
248 # rollback at a higher level and avoid shadowing
249 # the original exception.
250 connection.needs_rollback = True
251 raise
252 else:
253 # Commit transaction
254 try:
255 connection.commit()
256 except DatabaseError:
257 try:
258 connection.rollback()
259 except Error:
260 # An error during rollback means that something
261 # went wrong with the connection. Drop it.
262 connection.close()
263 raise
264 else:
265 # This flag will be set to True again if there isn't a savepoint
266 # allowing to perform the rollback at this level.
267 connection.needs_rollback = False
268 if connection.in_atomic_block:
269 # Roll back to savepoint if there is one, mark for rollback
270 # otherwise.
271 if sid is None: 271 ↛ 272line 271 didn't jump to line 272, because the condition on line 271 was never true
272 connection.needs_rollback = True
273 else:
274 try:
275 connection.savepoint_rollback(sid)
276 # The savepoint won't be reused. Release it to
277 # minimize overhead for the database server.
278 connection.savepoint_commit(sid)
279 except Error:
280 # If rolling back to a savepoint fails, mark for
281 # rollback at a higher level and avoid shadowing
282 # the original exception.
283 connection.needs_rollback = True
284 else:
285 # Roll back transaction
286 try:
287 connection.rollback()
288 except Error:
289 # An error during rollback means that something
290 # went wrong with the connection. Drop it.
291 connection.close()
293 finally:
294 # Outermost block exit when autocommit was enabled.
295 if not connection.in_atomic_block:
296 if connection.closed_in_transaction: 296 ↛ 297line 296 didn't jump to line 297, because the condition on line 296 was never true
297 connection.connection = None
298 else:
299 connection.set_autocommit(True)
300 # Outermost block exit when autocommit was disabled.
301 elif not connection.savepoint_ids and not connection.commit_on_exit: 301 ↛ 302line 301 didn't jump to line 302, because the condition on line 301 was never true
302 if connection.closed_in_transaction:
303 connection.connection = None
304 else:
305 connection.in_atomic_block = False
308def atomic(using=None, savepoint=True, durable=False):
309 # Bare decorator: @atomic -- although the first argument is called
310 # `using`, it's actually the function being decorated.
311 if callable(using):
312 return Atomic(DEFAULT_DB_ALIAS, savepoint, durable)(using)
313 # Decorator: @atomic(...) or context manager: with atomic(...): ...
314 else:
315 return Atomic(using, savepoint, durable)
318def _non_atomic_requests(view, using):
319 try:
320 view._non_atomic_requests.add(using)
321 except AttributeError:
322 view._non_atomic_requests = {using}
323 return view
326def non_atomic_requests(using=None):
327 if callable(using):
328 return _non_atomic_requests(using, DEFAULT_DB_ALIAS)
329 else:
330 if using is None:
331 using = DEFAULT_DB_ALIAS
332 return lambda view: _non_atomic_requests(view, using)