Skip to content

Commit ef8dfa9

Browse files
authored
Do not call django.setup() multiple times (#629)
Fixes #531.
1 parent 4d61618 commit ef8dfa9

File tree

4 files changed

+84
-1
lines changed

4 files changed

+84
-1
lines changed

docs/conf.py

+11
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,15 @@
4444
'python': ('https://docs.python.org/3', None),
4545
'django': ('https://docs.djangoproject.com/en/dev/',
4646
'https://docs.djangoproject.com/en/dev/_objects/'),
47+
'pytest': ('https://docs.pytest.org/en/latest/', None),
4748
}
49+
50+
51+
def setup(app):
52+
# Allow linking to pytest's confvals.
53+
app.add_description_unit(
54+
"confval",
55+
"pytest-confval",
56+
objname="configuration value",
57+
indextemplate="pair: %s; configuration value",
58+
)

docs/configuring_django.rst

+19
Original file line numberDiff line numberDiff line change
@@ -83,3 +83,22 @@ This can be done from your project's ``conftest.py`` file::
8383
def pytest_configure():
8484
settings.configure(DATABASES=...)
8585

86+
Changing your app before Django gets set up
87+
-------------------------------------------
88+
89+
pytest-django calls :py:func:`django.setup` automatically. If you want to do
90+
anything before this, you have to create a pytest plugin and use
91+
the :py:func:`~_pytest.hookspec.pytest_load_initial_conftests` hook, with
92+
``tryfirst=True``, so that it gets run before the hook in pytest-django
93+
itself::
94+
95+
@pytest.hookimpl(tryfirst=True)
96+
def pytest_load_initial_conftests(early_config, parser, args):
97+
import project.app.signals
98+
99+
def noop(*args, **kwargs):
100+
pass
101+
102+
project.app.signals.something = noop
103+
104+
This plugin can then be used e.g. via ``-p`` in :pytest-confval:`addopts`.

pytest_django/plugin.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,10 @@ def _setup_django():
150150
if not django.conf.settings.configured:
151151
return
152152

153-
django.setup()
153+
import django.apps
154+
if not django.apps.apps.ready:
155+
django.setup()
156+
154157
_blocking_manager.block()
155158

156159

tests/test_initialization.py

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
from textwrap import dedent
2+
3+
4+
def test_django_setup_order_and_uniqueness(django_testdir, monkeypatch):
5+
"""
6+
The django.setup() function shall not be called multiple times by
7+
pytest-django, since it resets logging conf each time.
8+
"""
9+
django_testdir.makeconftest('''
10+
import django.apps
11+
assert django.apps.apps.ready
12+
from tpkg.app.models import Item
13+
14+
print("conftest")
15+
def pytest_configure():
16+
import django
17+
print("pytest_configure: conftest")
18+
django.setup = lambda: SHOULD_NOT_GET_CALLED
19+
''')
20+
21+
django_testdir.project_root.join('tpkg', 'plugin.py').write(dedent('''
22+
import pytest
23+
import django.apps
24+
assert not django.apps.apps.ready
25+
26+
print("plugin")
27+
def pytest_configure():
28+
assert django.apps.apps.ready
29+
from tpkg.app.models import Item
30+
print("pytest_configure: plugin")
31+
32+
@pytest.hookimpl(tryfirst=True)
33+
def pytest_load_initial_conftests(early_config, parser, args):
34+
print("pytest_load_initial_conftests")
35+
assert not django.apps.apps.ready
36+
'''))
37+
django_testdir.makepyfile("""
38+
def test_ds():
39+
pass
40+
""")
41+
result = django_testdir.runpytest_subprocess('-s', '-p', 'tpkg.plugin')
42+
result.stdout.fnmatch_lines([
43+
'plugin',
44+
'pytest_load_initial_conftests',
45+
'conftest',
46+
'pytest_configure: conftest',
47+
'pytest_configure: plugin',
48+
'*1 passed*',
49+
])
50+
assert result.ret == 0

0 commit comments

Comments
 (0)