-
Notifications
You must be signed in to change notification settings - Fork 6
feat(db): raise when entering transaction if testcase has pending on_… #87
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -27,6 +27,39 @@ | |||||||||||||||||||||||||||||||||||||||
| "transaction_required", | ||||||||||||||||||||||||||||||||||||||||
| ] | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| @attrs.frozen | ||||||||||||||||||||||||||||||||||||||||
| class _PendingTestcaseAfterCommitCallbacks(Exception): | ||||||||||||||||||||||||||||||||||||||||
| pending: int | ||||||||||||||||||||||||||||||||||||||||
| database: str | None = None | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| def __str__(self) -> str: | ||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||
| f"{self.pending} pending on_commit() callback(s) already queued on the " | ||||||||||||||||||||||||||||||||||||||||
| f"test-case transaction (db={self.database!r}). " | ||||||||||||||||||||||||||||||||||||||||
| "Register on_commit() inside a real transaction or set " | ||||||||||||||||||||||||||||||||||||||||
| "SUBATOMIC_RAISE_IF_PENDING_TESTCASE_ON_COMMIT_ON_ENTER=False to opt out." | ||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| @contextlib.contextmanager | ||||||||||||||||||||||||||||||||||||||||
| def _error_if_pending_testcase_on_commit_callbacks(*, using: str | None = None): | ||||||||||||||||||||||||||||||||||||||||
| if not getattr( | ||||||||||||||||||||||||||||||||||||||||
| settings, | ||||||||||||||||||||||||||||||||||||||||
| "SUBATOMIC_RAISE_IF_PENDING_TESTCASE_ON_COMMIT_ON_ENTER", | ||||||||||||||||||||||||||||||||||||||||
| True, # strict par défaut | ||||||||||||||||||||||||||||||||||||||||
| ): | ||||||||||||||||||||||||||||||||||||||||
| yield | ||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| if _innermost_atomic_block_wraps_testcase(using=using): | ||||||||||||||||||||||||||||||||||||||||
| connection = django_transaction.get_connection(using=using) | ||||||||||||||||||||||||||||||||||||||||
| pending = getattr(connection, "run_on_commit", None) | ||||||||||||||||||||||||||||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||
| if pending: # non vide => des callbacks ont été enregistrés avant | ||||||||||||||||||||||||||||||||||||||||
| raise _PendingTestcaseAfterCommitCallbacks( | ||||||||||||||||||||||||||||||||||||||||
| pending=len(pending), | ||||||||||||||||||||||||||||||||||||||||
| database=getattr(connection, "alias", None), | ||||||||||||||||||||||||||||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A
Suggested change
|
||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||
| yield | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| @contextlib.contextmanager | ||||||||||||||||||||||||||||||||||||||||
| def transaction(*, using: str | None = None) -> Generator[None]: | ||||||||||||||||||||||||||||||||||||||||
|
|
@@ -43,11 +76,11 @@ def transaction(*, using: str | None = None) -> Generator[None]: | |||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||
| # Note that `savepoint=False` is not required here because | ||||||||||||||||||||||||||||||||||||||||
| # the `savepoint` flag is ignored when `durable` is `True`. | ||||||||||||||||||||||||||||||||||||||||
| with ( | ||||||||||||||||||||||||||||||||||||||||
| _execute_on_commit_callbacks_in_tests(using), | ||||||||||||||||||||||||||||||||||||||||
| django_transaction.atomic(using=using, durable=True), | ||||||||||||||||||||||||||||||||||||||||
| ): | ||||||||||||||||||||||||||||||||||||||||
| yield | ||||||||||||||||||||||||||||||||||||||||
| with _error_if_pending_testcase_on_commit_callbacks(using=using): | ||||||||||||||||||||||||||||||||||||||||
| with _execute_on_commit_callbacks_in_tests(using), django_transaction.atomic( | ||||||||||||||||||||||||||||||||||||||||
| using=using, durable=True | ||||||||||||||||||||||||||||||||||||||||
| ): | ||||||||||||||||||||||||||||||||||||||||
| yield | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+79
to
+83
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We prefer this style for nested context managers:
Suggested change
|
||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| @contextlib.contextmanager | ||||||||||||||||||||||||||||||||||||||||
|
|
@@ -81,13 +114,17 @@ def transaction_if_not_already(*, using: str | None = None) -> Generator[None]: | |||||||||||||||||||||||||||||||||||||||
| # If the innermost atomic block is from a test case, we should create a SAVEPOINT here. | ||||||||||||||||||||||||||||||||||||||||
| # This allows for a rollback when an exception propagates out of this block, and so | ||||||||||||||||||||||||||||||||||||||||
| # better simulates a production transaction behaviour in tests. | ||||||||||||||||||||||||||||||||||||||||
| savepoint = _innermost_atomic_block_wraps_testcase(using=using) | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| with ( | ||||||||||||||||||||||||||||||||||||||||
| _execute_on_commit_callbacks_in_tests(using), | ||||||||||||||||||||||||||||||||||||||||
| django_transaction.atomic(using=using, savepoint=savepoint), | ||||||||||||||||||||||||||||||||||||||||
| ): | ||||||||||||||||||||||||||||||||||||||||
| if in_transaction(using=using): | ||||||||||||||||||||||||||||||||||||||||
| # Already in a transaction; don't open another. | ||||||||||||||||||||||||||||||||||||||||
| yield | ||||||||||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||||||||||
| # If the innermost atomic block comes from the testcase, create a SAVEPOINT. | ||||||||||||||||||||||||||||||||||||||||
| savepoint = _innermost_atomic_block_wraps_testcase(using=using) | ||||||||||||||||||||||||||||||||||||||||
| with _error_if_pending_testcase_on_commit_callbacks(using=using): | ||||||||||||||||||||||||||||||||||||||||
| with _execute_on_commit_callbacks_in_tests(using), django_transaction.atomic( | ||||||||||||||||||||||||||||||||||||||||
| using=using, savepoint=savepoint | ||||||||||||||||||||||||||||||||||||||||
| ): | ||||||||||||||||||||||||||||||||||||||||
| yield | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+117
to
+127
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we want the
Suggested change
|
||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| @_utils.contextmanager | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,39 @@ | ||||||||||||||||||||||
| # tests/test_on_commit_guard.py | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| from django.test import TestCase, override_settings | ||||||||||||||||||||||
| from django.db import transaction as dj_tx | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| from django_subatomic import db | ||||||||||||||||||||||
| from django_subatomic.db import _PendingTestcaseAfterCommitCallbacks | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| class TestOnCommitGuardWithTransaction(TestCase): | ||||||||||||||||||||||
| def test_raises_on_enter_if_testcase_has_pending_callbacks(self): | ||||||||||||||||||||||
| dj_tx.on_commit(lambda: None) | ||||||||||||||||||||||
| with self.assertRaises(_PendingTestcaseAfterCommitCallbacks): | ||||||||||||||||||||||
| with db.transaction(): | ||||||||||||||||||||||
| pass | ||||||||||||||||||||||
|
Comment on lines
+12
to
+15
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it makes sense to
Suggested change
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| @override_settings(SUBATOMIC_RAISE_IF_PENDING_TESTCASE_ON_COMMIT_ON_ENTER=False) | ||||||||||||||||||||||
| def test_opt_out_keeps_previous_behaviour(self): | ||||||||||||||||||||||
| calls = [] | ||||||||||||||||||||||
| dj_tx.on_commit(lambda: calls.append("ok")) | ||||||||||||||||||||||
| with db.transaction(): | ||||||||||||||||||||||
| self.assertEqual(calls, []) | ||||||||||||||||||||||
| self.assertEqual(calls, ["ok"]) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| class TestOnCommitGuardWithTransactionIfNotAlready(TestCase): | ||||||||||||||||||||||
| def test_raises_on_enter_in_transaction_if_not_already(self): | ||||||||||||||||||||||
| dj_tx.on_commit(lambda: None) | ||||||||||||||||||||||
| with self.assertRaises(_PendingTestcaseAfterCommitCallbacks): | ||||||||||||||||||||||
| with db.transaction_if_not_already(): | ||||||||||||||||||||||
| pass | ||||||||||||||||||||||
|
Comment on lines
+28
to
+31
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| @override_settings(SUBATOMIC_RAISE_IF_PENDING_TESTCASE_ON_COMMIT_ON_ENTER=False) | ||||||||||||||||||||||
| def test_opt_out_keeps_previous_behaviour_in_transaction_if_not_already(self): | ||||||||||||||||||||||
| calls = [] | ||||||||||||||||||||||
| dj_tx.on_commit(lambda: calls.append("ok")) | ||||||||||||||||||||||
| with db.transaction_if_not_already(): | ||||||||||||||||||||||
| self.assertEqual(calls, []) | ||||||||||||||||||||||
| self.assertEqual(calls, ["ok"]) | ||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.