Skip to content

Commit 8000db0

Browse files
committed
Only setup and serialize databases as needed by tests
Fix #1184
1 parent 6f7fc26 commit 8000db0

File tree

2 files changed

+67
-0
lines changed

2 files changed

+67
-0
lines changed

docs/changelog.rst

+11
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,17 @@ Compatibility
1010
* Added official support for Django 5.2 (`PR #1179 <https://github.com/pytest-dev/pytest-django/pull/1179>`__).
1111
* Dropped testing on MySQL’s MyISAM storage engine (`PR #1180 <https://github.com/pytest-dev/pytest-django/pull/1180>`__).
1212

13+
Bugfixes
14+
^^^^^^^^
15+
16+
* Stopped setting up and serializing databases on test session setup when not needed (the database is not requested / ``serialized_rollback`` is not used).
17+
On test databases with large amounts of pre-seeded data, this may remove a delay of a few seconds when running ``pytest --reuse-db``.
18+
19+
The determination of which databases to setup is done by static inspection of the test suite.
20+
Using pytest's dynamic features to request db access, such as :meth:`request.getfixturevalue("db") <pytest.FixtureRequest.getfixturevalue>`, may throw off this analysis.
21+
If you start seeing ``DatabaseOperationForbidden`` or "unable to open database" errors, this is likely the cause.
22+
To fix this, decorate at least one test with the :func:`django_db <pytest.mark.django_db>` marker with appropriate ``databases`` and ``serialized_rollback`` settings.
23+
1324
v4.10.0 (2025-02-10)
1425
--------------------
1526

pytest_django/fixtures.py

+56
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from functools import partial
88
from typing import (
99
TYPE_CHECKING,
10+
AbstractSet,
1011
Any,
1112
Callable,
1213
ContextManager,
@@ -16,6 +17,7 @@
1617
Literal,
1718
Optional,
1819
Protocol,
20+
Sequence,
1921
Tuple,
2022
Union,
2123
)
@@ -119,6 +121,56 @@ def django_db_createdb(request: pytest.FixtureRequest) -> bool:
119121
return create_db
120122

121123

124+
def _get_databases_for_test(test: pytest.Item) -> tuple[Iterable[str], bool]:
125+
"""Get the database aliases that need to be setup for a test, and whether
126+
they need to be serialized."""
127+
from django.db import DEFAULT_DB_ALIAS, connections
128+
from django.test import TransactionTestCase
129+
130+
test_cls = getattr(test, "cls", None)
131+
if test_cls and issubclass(test_cls, TransactionTestCase):
132+
serialized_rollback = getattr(test, "serialized_rollback", False)
133+
databases = getattr(test, "databases", None)
134+
else:
135+
fixtures = getattr(test, "fixturenames", ())
136+
marker_db = test.get_closest_marker("django_db")
137+
if marker_db:
138+
(
139+
transaction,
140+
reset_sequences,
141+
databases,
142+
serialized_rollback,
143+
available_apps,
144+
) = validate_django_db(marker_db)
145+
elif "db" in fixtures or "transactional_db" in fixtures or "live_server" in fixtures:
146+
serialized_rollback = "django_db_serialized_rollback" in fixtures
147+
databases = None
148+
else:
149+
return (), False
150+
if databases is None:
151+
return (DEFAULT_DB_ALIAS,), serialized_rollback
152+
elif databases == "__all__":
153+
return connections, serialized_rollback
154+
else:
155+
return databases, serialized_rollback
156+
157+
158+
def _get_databases_for_setup(
159+
items: Sequence[pytest.Item],
160+
) -> tuple[AbstractSet[str], AbstractSet[str]]:
161+
"""Get the database aliases that need to be setup, and the subset that needs
162+
to be serialized."""
163+
# Code derived from django.test.utils.DiscoverRunner.get_databases().
164+
aliases: set[str] = set()
165+
serialized_aliases: set[str] = set()
166+
for test in items:
167+
databases, serialized_rollback = _get_databases_for_test(test)
168+
aliases.update(databases)
169+
if serialized_rollback:
170+
serialized_aliases.update(databases)
171+
return aliases, serialized_aliases
172+
173+
122174
@pytest.fixture(scope="session")
123175
def django_db_setup(
124176
request: pytest.FixtureRequest,
@@ -140,10 +192,14 @@ def django_db_setup(
140192
if django_db_keepdb and not django_db_createdb:
141193
setup_databases_args["keepdb"] = True
142194

195+
aliases, serialized_aliases = _get_databases_for_setup(request.session.items)
196+
143197
with django_db_blocker.unblock():
144198
db_cfg = setup_databases(
145199
verbosity=request.config.option.verbose,
146200
interactive=False,
201+
aliases=aliases,
202+
serialized_aliases=serialized_aliases,
147203
**setup_databases_args,
148204
)
149205

0 commit comments

Comments
 (0)