Page Menu
Home
Software Heritage
Search
Configure Global Search
Log In
Files
F7147910
D2846.id.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
3 KB
Subscribers
None
D2846.id.diff
View Options
diff --git a/swh/core/db/common.py b/swh/core/db/common.py
--- a/swh/core/db/common.py
+++ b/swh/core/db/common.py
@@ -3,8 +3,9 @@
# License: GNU General Public License version 3, or any later version
# See top-level LICENSE file for more information
-import inspect
import functools
+import inspect
+import sys
def remove_kwargs(names):
@@ -33,6 +34,21 @@
return old_options
+def check_no_transaction(f_name):
+ """Checks there is no open DB transaction in the stack
+ (db + cur variables).
+ While it is not necessarily broken code, it is very likely
+ a mistake."""
+ frame = sys._getframe().f_back.f_back
+ while frame:
+ if "db" in frame.f_locals and "cur" in frame.f_locals:
+ raise AssertionError(
+ f'Calling function {f_name} without "db" and "cur" arguments '
+ "from a function ({frame.f_code.co_name}) with these variables."
+ )
+ frame = frame.f_back
+
+
def db_transaction(**client_options):
"""decorator to execute Backend methods within DB transactions
@@ -55,6 +71,7 @@
apply_options(cur, old_options)
return ret
else:
+ check_no_transaction(meth.__name__)
db = self.get_db()
try:
with db.transaction() as cur:
@@ -90,6 +107,7 @@
yield from meth(self, *args, **kwargs)
apply_options(cur, old_options)
else:
+ check_no_transaction(meth.__name__)
db = self.get_db()
try:
with db.transaction() as cur:
diff --git a/swh/core/db/tests/test_db.py b/swh/core/db/tests/test_db.py
--- a/swh/core/db/tests/test_db.py
+++ b/swh/core/db/tests/test_db.py
@@ -170,6 +170,47 @@
assert actual_sig == expected_sig
+def test_db_transaction_nested(mocker):
+ expected_cur = object()
+
+ called = False
+
+ class Storage:
+ @db_transaction()
+ def endpoint_bad_nesting(self, cur=None, db=None):
+ self.inner_endpoint()
+
+ @db_transaction()
+ def endpoint_good_nesting(self, cur=None, db=None):
+ self.inner_endpoint(cur=cur, db=db)
+
+ @db_transaction()
+ def inner_endpoint(self, cur=None, db=None):
+ nonlocal called
+ called = True
+
+ storage = Storage()
+
+ # 'with storage.get_db().transaction() as cur:' should cause
+ # 'cur' to be 'expected_cur'
+ db_mock = Mock()
+ db_mock.transaction.return_value = MagicMock()
+ db_mock.transaction.return_value.__enter__.return_value = expected_cur
+ mocker.patch.object(storage, "get_db", return_value=db_mock, create=True)
+
+ put_db_mock = mocker.patch.object(storage, "put_db", create=True)
+
+ with pytest.raises(AssertionError, match="Calling function inner_endpoint"):
+ storage.endpoint_bad_nesting()
+ assert not called
+ put_db_mock.assert_called_once_with(db_mock)
+ put_db_mock.reset_mock()
+
+ storage.endpoint_good_nesting()
+ assert called
+ put_db_mock.assert_called_once_with(db_mock)
+
+
def test_db_transaction_generator(mocker):
expected_cur = object()
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Thu, Jan 23, 2:54 AM (1 w, 2 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3223840
Attached To
D2846: Detect missing db/cur arguments in 'nested' calls and raise an error.
Event Timeline
Log In to Comment