From ef3eea42748a7be8a0df7ba1a2863ebd6b2be30b Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Tue, 10 Jul 2018 21:50:35 +0200 Subject: [PATCH] Do not call django.setup() multiple times Fixes https://github.com/pytest-dev/pytest-django/issues/531. --- docs/conf.py | 11 ++++++++ docs/configuring_django.rst | 19 ++++++++++++++ pytest_django/plugin.py | 5 +++- tests/test_initialization.py | 50 ++++++++++++++++++++++++++++++++++++ 4 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 tests/test_initialization.py diff --git a/docs/conf.py b/docs/conf.py index b15bf1d0e..949e2311f 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -44,4 +44,15 @@ 'python': ('https://docs.python.org/3', None), 'django': ('https://docs.djangoproject.com/en/dev/', 'https://docs.djangoproject.com/en/dev/_objects/'), + 'pytest': ('https://docs.pytest.org/en/latest/', None), } + + +def setup(app): + # Allow linking to pytest's confvals. + app.add_description_unit( + "confval", + "pytest-confval", + objname="configuration value", + indextemplate="pair: %s; configuration value", + ) diff --git a/docs/configuring_django.rst b/docs/configuring_django.rst index e43dd8688..3bc8a7521 100644 --- a/docs/configuring_django.rst +++ b/docs/configuring_django.rst @@ -83,3 +83,22 @@ This can be done from your project's ``conftest.py`` file:: def pytest_configure(): settings.configure(DATABASES=...) +Changing your app before Django gets set up +------------------------------------------- + +pytest-django calls :py:func:`django.setup` automatically. If you want to do +anything before this, you have to create a pytest plugin and use +the :py:func:`~_pytest.hookspec.pytest_load_initial_conftests` hook, with +``tryfirst=True``, so that it gets run before the hook in pytest-django +itself:: + + @pytest.hookimpl(tryfirst=True) + def pytest_load_initial_conftests(early_config, parser, args): + import project.app.signals + + def noop(*args, **kwargs): + pass + + project.app.signals.something = noop + +This plugin can then be used e.g. via ``-p`` in :pytest-confval:`addopts`. diff --git a/pytest_django/plugin.py b/pytest_django/plugin.py index 1eef028bb..0f729ba31 100644 --- a/pytest_django/plugin.py +++ b/pytest_django/plugin.py @@ -149,7 +149,10 @@ def _setup_django(): if not django.conf.settings.configured: return - django.setup() + import django.apps + if not django.apps.apps.ready: + django.setup() + _blocking_manager.block() diff --git a/tests/test_initialization.py b/tests/test_initialization.py new file mode 100644 index 000000000..51cfac5f1 --- /dev/null +++ b/tests/test_initialization.py @@ -0,0 +1,50 @@ +from textwrap import dedent + + +def test_django_setup_order_and_uniqueness(django_testdir, monkeypatch): + """ + The django.setup() function shall not be called multiple times by + pytest-django, since it resets logging conf each time. + """ + django_testdir.makeconftest(''' + import django.apps + assert django.apps.apps.ready + from tpkg.app.models import Item + + print("conftest") + def pytest_configure(): + import django + print("pytest_configure: conftest") + django.setup = lambda: SHOULD_NOT_GET_CALLED + ''') + + django_testdir.project_root.join('tpkg', 'plugin.py').write(dedent(''' + import pytest + import django.apps + assert not django.apps.apps.ready + + print("plugin") + def pytest_configure(): + assert django.apps.apps.ready + from tpkg.app.models import Item + print("pytest_configure: plugin") + + @pytest.hookimpl(tryfirst=True) + def pytest_load_initial_conftests(early_config, parser, args): + print("pytest_load_initial_conftests") + assert not django.apps.apps.ready + ''')) + django_testdir.makepyfile(""" + def test_ds(): + pass + """) + result = django_testdir.runpytest_subprocess('-s', '-p', 'tpkg.plugin') + result.stdout.fnmatch_lines([ + 'plugin', + 'pytest_load_initial_conftests', + 'conftest', + 'pytest_configure: conftest', + 'pytest_configure: plugin', + '*1 passed*', + ]) + assert result.ret == 0