diff --git a/pytest_django/plugin.py b/pytest_django/plugin.py index e2b6f120..6f76d3d0 100644 --- a/pytest_django/plugin.py +++ b/pytest_django/plugin.py @@ -55,6 +55,7 @@ SETTINGS_MODULE_ENV = "DJANGO_SETTINGS_MODULE" CONFIGURATION_ENV = "DJANGO_CONFIGURATION" +CONFIGURATION_HOOK_ENV = "DJANGO_CONFIGURATION_HOOK" INVALID_TEMPLATE_VARS_ENV = "FAIL_INVALID_TEMPLATE_VARS" _report_header = [] @@ -98,6 +99,14 @@ def pytest_addoption(parser) -> None: default=None, help="Set DJANGO_CONFIGURATION.", ) + group.addoption( + "--dch", + action="store", + type=str, + dest="dch", + default=None, + help="Set DJANGO_CONFIGURATION_HOOK.", + ) group.addoption( "--nomigrations", "--no-migrations", @@ -124,6 +133,9 @@ def pytest_addoption(parser) -> None: parser.addini( SETTINGS_MODULE_ENV, "Django settings module to use by pytest-django." ) + parser.addini( + CONFIGURATION_HOOK_ENV, "Callback Hook to prepare Django settings alternatively." + ) parser.addini( "django_find_project", @@ -329,6 +341,7 @@ def _get_option_with_source( ds, ds_source = _get_option_with_source(options.ds, SETTINGS_MODULE_ENV) dc, dc_source = _get_option_with_source(options.dc, CONFIGURATION_ENV) + dch, dch_source = _get_option_with_source(options.dch, CONFIGURATION_HOOK_ENV) if ds: _report_header.append(f"settings: {ds} (from {ds_source})") @@ -349,6 +362,35 @@ def _get_option_with_source( with _handle_import_error(_django_project_scan_outcome): dj_settings.DATABASES + elif dch: + # Forcefully load Django settings, throws ImportError or + # ImproperlyConfigured if settings cannot be loaded. + from django.conf import settings as dj_settings + + # Call a HOOK that could initialize djangos + # object with a custom configuration + + if "." not in dch: + raise ImportError("Invalid path for configuration hook: {}".format(dch)) + + pkg_parts = dch.split(".") + module_path = ".".join(pkg_parts[:-1]) + function_name = pkg_parts[-1] + + import importlib + + try: + mod = importlib.import_module(module_path) + except (ImportError, AttributeError): + raise ImportError("Unable to import module {}".format(module_path)) + func = getattr(mod, function_name, None) + + if not func: + raise ImportError("No function found with name {} in module {}!" + .format(function_name, module_path)) + + # Call the function + func() _setup_django() diff --git a/tests/test_django_settings_module.py b/tests/test_django_settings_module.py index fb008e12..641a8528 100644 --- a/tests/test_django_settings_module.py +++ b/tests/test_django_settings_module.py @@ -505,3 +505,103 @@ def test_no_django_settings_but_django_imported(testdir, monkeypatch) -> None: testdir.makeconftest("import django") r = testdir.runpytest_subprocess("--help") assert r.ret == 0 + + +def test_dch_ini(testdir, monkeypatch) -> None: + monkeypatch.delenv("DJANGO_SETTINGS_MODULE") + testdir.makeini( + """ + [pytest] + DJANGO_CONFIGURATION_HOOK = tpkg.test.setup + """ + ) + pkg = testdir.mkpydir("tpkg") + pkg.join("test.py").write(""" +# Test +from django.conf import settings + +def setup(): + settings.configure() +""") + testdir.makepyfile( + """ + import os + + def test_ds(): + pass + """ + ) + result = testdir.runpytest_subprocess() + assert result.ret == 0 + + +def test_dch_ini_invalid_path(testdir, monkeypatch) -> None: + monkeypatch.delenv("DJANGO_SETTINGS_MODULE") + testdir.makeini( + """ + [pytest] + DJANGO_CONFIGURATION_HOOK = invalid_path + """ + ) + testdir.makepyfile( + """ + import os + + def test_ds(): + pass + """ + ) + result = testdir.runpytest_subprocess() + result.stderr.fnmatch_lines(["ImportError: Invalid path for configuration hook: invalid_path"]) + assert result.ret == 1 + + +def test_dch_ini_no_module(testdir, monkeypatch) -> None: + monkeypatch.delenv("DJANGO_SETTINGS_MODULE") + testdir.makeini( + """ + [pytest] + DJANGO_CONFIGURATION_HOOK = tpkg.not_existing.setup + """ + ) + testdir.makepyfile( + """ + import os + + def test_ds(): + pass + """ + ) + result = testdir.runpytest_subprocess() + result.stderr.fnmatch_lines(["ImportError: Unable to import module tpkg.not_existing"]) + assert result.ret == 1 + + +def test_dch_ini_module_but_no_func(testdir, monkeypatch) -> None: + monkeypatch.delenv("DJANGO_SETTINGS_MODULE") + testdir.makeini( + """ + [pytest] + DJANGO_CONFIGURATION_HOOK = tpkg.test.not_existing_function + """ + ) + pkg = testdir.mkpydir("tpkg") + pkg.join("test.py").write(""" +# Test +from django.conf import settings + +def setup(): + settings.configure() +""") + testdir.makepyfile( + """ + import os + + def test_ds(): + pass + """ + ) + result = testdir.runpytest_subprocess() + result.stderr.fnmatch_lines(["ImportError: No function found with name " + "not_existing_function in module tpkg.test!"]) + assert result.ret == 1