Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ Compatibility

* Official Django 6.1 support.

Bugfixes
^^^^^^^^

* Fixed ``db`` / ``transactional_db`` (and related database fixtures) requested
via ``request.getfixturevalue()``, including indirect parametrization, always
using ``django.test.TestCase`` without transaction support
(`#1157 <https://github.com/pytest-dev/pytest-django/issues/1157>`__).

v4.14.0 (2026-08-10)
--------------------

Expand Down
33 changes: 28 additions & 5 deletions pytest_django/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ def django_db_setup( # noqa: PLR0917
django_db_modify_db_settings: None, # noqa: ARG001
) -> Generator[None]:
"""Top level fixture to ensure test databases are available"""
from django.db import DEFAULT_DB_ALIAS
from django.test.utils import setup_databases, teardown_databases

setup_databases_args = {}
Expand All @@ -181,6 +182,11 @@ def django_db_setup( # noqa: PLR0917
setup_databases_args["keepdb"] = True

aliases, serialized_aliases = _get_databases_for_setup(request.session.items)
if not aliases:
# This fixture is only requested when a test needs the database. Tests
# that obtain db fixtures via getfixturevalue() are invisible at
# collection time, so fall back to the default alias.
aliases = {DEFAULT_DB_ALIAS}

with django_db_blocker.unblock():
db_cfg = setup_databases(
Expand All @@ -203,6 +209,24 @@ def django_db_setup( # noqa: PLR0917
)


def _requested_fixture_names(request: pytest.FixtureRequest) -> set[str]:
"""Return fixture names for this test, including those currently being set up.

``request.fixturenames`` is the collected fixture closure plus fixtures that
have already finished. A fixture requested via ``request.getfixturevalue()``
is only recorded after it finishes, so ``_django_db_helper`` (a dependency
of ``db`` / ``transactional_db``) would otherwise miss it.
"""
names = set(request.fixturenames)
current: pytest.FixtureRequest | None = request
while current is not None:
fixturename = current.fixturename
if fixturename is not None:
names.add(fixturename)
current = getattr(current, "_parent_request", None)
return names


@pytest.fixture
def _django_db_helper(
request: pytest.FixtureRequest,
Expand Down Expand Up @@ -233,14 +257,13 @@ def _django_db_helper(
available_apps,
) = False, False, None, False, None

requested = _requested_fixture_names(request)
reset_sequences = reset_sequences or ("django_db_reset_sequences" in requested)
serialized_rollback = serialized_rollback or ("django_db_serialized_rollback" in requested)
transactional = (
transactional
or reset_sequences
or ("transactional_db" in request.fixturenames or "live_server" in request.fixturenames)
)
reset_sequences = reset_sequences or ("django_db_reset_sequences" in request.fixturenames)
serialized_rollback = serialized_rollback or (
"django_db_serialized_rollback" in request.fixturenames
or ("transactional_db" in requested or "live_server" in requested)
)

with django_db_blocker.unblock():
Expand Down
34 changes: 34 additions & 0 deletions tests/test_database.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,20 @@ def test_transactions_enabled_via_reset_seq(

assert not connection.in_atomic_block

def test_transactions_match_dynamic_db_fixture(
self,
all_dbs: None, # noqa: ARG002
request: pytest.FixtureRequest,
) -> None:
if not connection.features.supports_transactions:
pytest.skip("transactions required for this test")

fixture_name = request.node.callspec.params["all_dbs"]
if fixture_name in {"transactional_db", "django_db_reset_sequences"}:
assert not connection.in_atomic_block
else:
assert connection.in_atomic_block

def test_django_db_reset_sequences_fixture(
self,
db: None, # noqa: ARG002
Expand Down Expand Up @@ -213,6 +227,26 @@ def test_durable_transactions(self, all_dbs: None) -> None: # noqa: ARG002
assert Item.objects.get() == item


@pytest.mark.parametrize("db_access", ["db", "transactional_db"], indirect=True)
class TestIndirectDbParametrization:
"""Indirect parametrization of db vs transactional_db (issue #1157)."""

@pytest.fixture
def db_access(self, request: pytest.FixtureRequest) -> str:
fixture_name: str = request.param
request.getfixturevalue(fixture_name)
return fixture_name

def test_atomic_block_matches_fixture(self, db_access: str) -> None:
if not connection.features.supports_transactions:
pytest.skip("transactions required for this test")

if db_access == "transactional_db":
assert not connection.in_atomic_block
else:
assert connection.in_atomic_block


class TestDatabaseFixturesAllOrder:
@pytest.fixture
def fixture_with_db(self, db: None) -> None: # noqa: ARG002
Expand Down