Skip to content

Commit 243879f

Browse files
committed
progress
1 parent b33c34e commit 243879f

File tree

3 files changed

+51
-126
lines changed

3 files changed

+51
-126
lines changed

django_mongodb_backend/base.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from django.db import DEFAULT_DB_ALIAS
66
from django.db.backends.base.base import BaseDatabaseWrapper
77
from django.db.backends.utils import debug_transaction
8+
from django.db.transaction import TransactionManagementError
89
from django.utils.asyncio import async_unsafe
910
from django.utils.functional import cached_property
1011
from pymongo.collection import Collection
@@ -159,6 +160,26 @@ def __init__(self, settings_dict, alias=DEFAULT_DB_ALIAS):
159160
super().__init__(settings_dict, alias=alias)
160161
self.session = None
161162

163+
# Transaction related attributes.
164+
# Tracks if the connection is in autocommit mode. Per PEP 249, by
165+
# default, it isn't.
166+
self.autocommit_mongo = False
167+
# Tracks if the connection is in a transaction managed by 'atomic'.
168+
self.in_atomic_block_mongo = False
169+
# Increment to generate unique savepoint ids.
170+
self.savepoint_state = 0
171+
# List of savepoints created by 'atomic'.
172+
self.savepoint_ids = []
173+
# Stack of active 'atomic' blocks.
174+
self.atomic_blocks_mongo = []
175+
# Tracks if the outermost 'atomic' block should commit on exit,
176+
# ie. if autocommit was active on entry.
177+
self.commit_on_exit_mongo = True
178+
# Tracks if the transaction should be rolled back to the next
179+
# available savepoint because of an exception in an inner block.
180+
self.needs_rollback_mongo = False
181+
self.rollback_exc_mongo = None
182+
162183
def get_collection(self, name, **kwargs):
163184
collection = Collection(self.database, name, **kwargs)
164185
if self.queries_logged:
@@ -250,7 +271,11 @@ def cursor(self):
250271

251272
@requires_transaction_support
252273
def validate_no_broken_transaction(self):
253-
super().validate_no_broken_transaction()
274+
if self.needs_rollback_mongo:
275+
raise TransactionManagementError(
276+
"An error occurred in the current transaction. You can't "
277+
"execute queries until the end of the 'atomic' block."
278+
) from self.rollback_exc
254279

255280
def get_database_version(self):
256281
"""Return a tuple of the database's version."""

django_mongodb_backend/transaction.py

Lines changed: 25 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -1,80 +1,11 @@
1-
from contextlib import ContextDecorator, contextmanager
1+
from contextlib import ContextDecorator
22

33
from django.db import (
44
DEFAULT_DB_ALIAS,
55
DatabaseError,
66
Error,
7-
ProgrammingError,
8-
connections,
97
)
10-
11-
12-
class TransactionManagementError(ProgrammingError):
13-
"""Transaction management is used improperly."""
14-
15-
16-
def get_connection(using=None):
17-
"""
18-
Get a database connection by name, or the default database connection
19-
if no name is provided. This is a private API.
20-
"""
21-
if using is None:
22-
using = DEFAULT_DB_ALIAS
23-
return connections[using]
24-
25-
26-
def commit(using=None):
27-
"""Commit a transaction."""
28-
get_connection(using).commit()
29-
30-
31-
def rollback(using=None):
32-
"""Roll back a transaction."""
33-
get_connection(using).rollback()
34-
35-
36-
def set_rollback(rollback, using=None):
37-
"""
38-
Set or unset the "needs rollback" flag -- for *advanced use* only.
39-
40-
When `rollback` is `True`, trigger a rollback when exiting the innermost
41-
enclosing atomic block that has `savepoint=True` (that's the default). Use
42-
this to force a rollback without raising an exception.
43-
44-
When `rollback` is `False`, prevent such a rollback. Use this only after
45-
rolling back to a known-good state! Otherwise, you break the atomic block
46-
and data corruption may occur.
47-
"""
48-
return get_connection(using).set_rollback(rollback)
49-
50-
51-
@contextmanager
52-
def mark_for_rollback_on_error(using=None):
53-
"""
54-
Internal low-level utility to mark a transaction as "needs rollback" when
55-
an exception is raised while not enforcing the enclosed block to be in a
56-
transaction. This is needed by Model.save() and friends to avoid starting a
57-
transaction when in autocommit mode and a single query is executed.
58-
59-
It's equivalent to:
60-
61-
connection = get_connection(using)
62-
if connection.get_autocommit():
63-
yield
64-
else:
65-
with transaction.atomic(using=using, savepoint=False):
66-
yield
67-
68-
but it uses low-level utilities to avoid performance overhead.
69-
"""
70-
try:
71-
yield
72-
except Exception as exc:
73-
connection = get_connection(using)
74-
if connection.in_atomic_block:
75-
connection.needs_rollback = True
76-
connection.rollback_exc = exc
77-
raise
8+
from django.db.transaction import get_connection
789

7910

8011
def on_commit(func, using=None, robust=False):
@@ -125,42 +56,38 @@ def __init__(self, using, durable):
12556
def __enter__(self):
12657
connection = get_connection(self.using)
12758

128-
if (
129-
self.durable
130-
and connection.atomic_blocks
131-
and not connection.atomic_blocks[-1]._from_testcase
132-
):
59+
if self.durable and connection.atomic_blocks_mongo:
13360
raise RuntimeError(
13461
"A durable atomic block cannot be nested within another atomic block."
13562
)
136-
if not connection.in_atomic_block:
63+
if not connection.in_atomic_block_mongo:
13764
# Reset state when entering an outermost atomic block.
138-
connection.commit_on_exit = True
139-
connection.needs_rollback = False
140-
if not connection.get_autocommit():
141-
# Pretend we're already in an atomic block to bypass the code
142-
# that disables autocommit to enter a transaction, and make a
143-
# note to deal with this case in __exit__.
144-
connection.in_atomic_block = True
145-
connection.commit_on_exit = False
146-
147-
if connection.in_atomic_block:
65+
connection.commit_on_exit_mongo = True
66+
connection.needs_rollback_mongo = False
67+
# if not connection.get_autocommit():
68+
# Pretend we're already in an atomic block to bypass the code
69+
# that disables autocommit to enter a transaction, and make a
70+
# note to deal with this case in __exit__.
71+
connection.in_atomic_block_mongo = True
72+
connection.commit_on_exit = False
73+
74+
if connection.in_atomic_block_mongo:
14875
# We're already in a transaction
14976
pass
15077
else:
15178
connection._start_transaction(
15279
False, force_begin_transaction_with_broken_autocommit=True
15380
)
154-
connection.in_atomic_block = True
81+
connection.in_atomic_block_mongo = True
15582

156-
if connection.in_atomic_block:
157-
connection.atomic_blocks.append(self)
83+
if connection.in_atomic_block_mongo:
84+
connection.atomic_blocks_mongo.append(self)
15885

15986
def __exit__(self, exc_type, exc_value, traceback):
16087
connection = get_connection(self.using)
16188

162-
if connection.in_atomic_block:
163-
connection.atomic_blocks.pop()
89+
if connection.in_atomic_block_mongo:
90+
connection.atomic_blocks_mongo.pop()
16491

16592
# Prematurely unset this flag to allow using commit or rollback.
16693
connection._in_atomic_block = False
@@ -170,7 +97,7 @@ def __exit__(self, exc_type, exc_value, traceback):
17097
# Wait until we exit the outermost block.
17198
pass
17299

173-
elif exc_type is None and not connection.needs_rollback:
100+
elif exc_type is None and not connection.needs_rollback_mongo:
174101
if connection._in_atomic_block:
175102
# Release savepoint if there is one
176103
pass
@@ -189,10 +116,10 @@ def __exit__(self, exc_type, exc_value, traceback):
189116
else:
190117
# This flag will be set to True again if there isn't a savepoint
191118
# allowing to perform the rollback at this level.
192-
connection.needs_rollback = False
193-
if connection.in_atomic_block:
119+
connection.needs_rollback_mongo = False
120+
if connection.in_atomic_block_mongo:
194121
# Mark for rollback
195-
connection.needs_rollback = True
122+
connection.needs_rollback_mongo = True
196123
else:
197124
# Roll back transaction
198125
try:
@@ -203,7 +130,7 @@ def __exit__(self, exc_type, exc_value, traceback):
203130
connection.close()
204131
finally:
205132
# Outermost block exit when autocommit was enabled.
206-
if not connection.in_atomic_block:
133+
if not connection.in_atomic_block_mongo:
207134
if connection.closed_in_transaction:
208135
connection.connection = None
209136
# else:
@@ -213,7 +140,7 @@ def __exit__(self, exc_type, exc_value, traceback):
213140
if connection.closed_in_transaction:
214141
connection.connection = None
215142
else:
216-
connection.in_atomic_block = False
143+
connection.in_atomic_block_mongo = False
217144

218145

219146
def atomic(using=None, durable=False):
@@ -223,19 +150,3 @@ def atomic(using=None, durable=False):
223150
return Atomic(DEFAULT_DB_ALIAS, durable)(using)
224151
# Decorator: @atomic(...) or context manager: with atomic(...): ...
225152
return Atomic(using, durable)
226-
227-
228-
def _non_atomic_requests(view, using):
229-
try:
230-
view._non_atomic_requests.add(using)
231-
except AttributeError:
232-
view._non_atomic_requests = {using}
233-
return view
234-
235-
236-
def non_atomic_requests(using=None):
237-
if callable(using):
238-
return _non_atomic_requests(using, DEFAULT_DB_ALIAS)
239-
if using is None:
240-
using = DEFAULT_DB_ALIAS
241-
return lambda view: _non_atomic_requests(view, using)

tests/transactions_/tests.py

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -160,17 +160,6 @@ def tearDown(self):
160160
self.atomic.__exit__(*sys.exc_info())
161161

162162

163-
class AtomicWithoutAutocommitTests(AtomicTests):
164-
"""All basic tests for atomic should also pass when autocommit is turned off."""
165-
166-
def setUp(self):
167-
transaction.set_autocommit(False)
168-
self.addCleanup(transaction.set_autocommit, True)
169-
# The tests access the database after exercising 'atomic', initiating
170-
# a transaction ; a rollback is required before restoring autocommit.
171-
self.addCleanup(transaction.rollback)
172-
173-
174163
@skipUnlessDBFeature("uses_savepoints")
175164
class AtomicErrorsTests(TransactionTestCase):
176165
available_apps = ["transactions"]

0 commit comments

Comments
 (0)