@@ -63,6 +63,9 @@ def _transaction(*, using: str | None) -> Generator[None]:
6363 # the `savepoint` flag is ignored when `durable` is `True`.
6464 django_transaction .atomic (using = using , durable = True ),
6565 ):
66+ connection = django_transaction .get_connection (using = using )
67+ atomic_block = connection .atomic_blocks [- 1 ]
68+ atomic_block ._from_subatomic = True # noqa: SLF001
6669 yield
6770
6871 decorator = _transaction (using = using )
@@ -325,6 +328,37 @@ class _MissingRequiredTransaction(Exception):
325328 database : str
326329
327330
331+ @attrs .frozen
332+ class _AmbiguousAfterCommitTestBehaviour (Exception ):
333+ """
334+ Raised in tests when it's unclear if after-commit callbacks should be run.
335+
336+ You are calling `run_after_commit` inside an `atomic` block in a test.
337+ This `atomic` is inside the test suite's transaction,
338+ so after-commit callbacks will not be run
339+ (because the test suite will roll back the transaction).
340+
341+ Subatomic doesn't know if you intended for these callbacks to be run or not,
342+ so raises this error to avoid silently doing the wrong thing.
343+
344+ In production code, or tests where after-commit callbacks should be run,
345+ replace (or wrap) the `atomic` block with `subatomic.db.transaction`.
346+
347+ In tests where after-commit callbacks should not be run,
348+ use `subatomic.test.part_of_a_transaction` instead.
349+
350+ To help your project progressively adopt this check,
351+ you can disable this requirement for after-commit callbacks by setting
352+ `settings.SUBATOMIC_AFTER_COMMIT_AMBIGUITY_ERROR_IN_TESTS` to `False`.
353+
354+ See Note [_MissingRequiredTransaction in tests]
355+
356+ This exception should not be caught, as it indicates a programming error.
357+ """
358+
359+ database : str
360+
361+
328362@attrs .frozen
329363class _UnexpectedOpenTransaction (Exception ):
330364 """
@@ -446,6 +480,12 @@ def _ensure_transaction_is_open(*, using: str) -> None:
446480 `SUBATOMIC_AFTER_COMMIT_NEEDS_TRANSACTION`.
447481
448482 See Note [After-commit callbacks require a transaction]
483+
484+ When transactions are managed by the test suite,
485+ this also ensures after-commit emulation is accounted for by Subatomic.
486+ When they are not, `_AmbiguousAfterCommitTestBehaviour` is raised.
487+ This can be silenced with the Django setting
488+ `SUBATOMIC_AFTER_COMMIT_AMBIGUITY_ERROR_IN_TESTS`.
449489 """
450490 needs_transaction = getattr (
451491 settings , "SUBATOMIC_AFTER_COMMIT_NEEDS_TRANSACTION" , True
@@ -458,6 +498,26 @@ def _ensure_transaction_is_open(*, using: str) -> None:
458498 if not in_transaction (using = using ):
459499 raise _MissingRequiredTransaction (database = using )
460500
501+ ambiguity_error_in_tests = getattr (
502+ settings , "SUBATOMIC_AFTER_COMMIT_AMBIGUITY_ERROR_IN_TESTS" , True
503+ )
504+ if not ambiguity_error_in_tests :
505+ return
506+
507+ connection = django_transaction .get_connection (using = using )
508+
509+ # We expect after-commit callbacks to be handled by Django
510+ # if we're not in a test-managed transaction.
511+ if not connection .atomic_blocks [0 ]._from_testcase : # noqa: SLF001
512+ return
513+
514+ # `_from_testcase` told us that we're in a test-managed transaction.
515+ # `in_transaction` told us that we're in a further atomic context.
516+ # If Subatomic didn't open that context then we don't know if the
517+ # test expects after-commit callbacks to be emulated or not.
518+ if not hasattr (connection .atomic_blocks [1 ], "_from_subatomic" ):
519+ raise _AmbiguousAfterCommitTestBehaviour (database = using )
520+
461521
462522def _innermost_atomic_block_wraps_testcase (* , using : str | None = None ) -> bool :
463523 """
0 commit comments