From 569587d1c53d925de0832b392711c07fe06ac16d Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 21 Mar 2022 13:35:05 +0100 Subject: [PATCH 1/5] add DJANGO_CONFIGURATION_HOOK as optional parameter to allow customly configured settings instances --- pytest_django/plugin.py | 41 ++++++++++++++++++++++++++++ tests/test_django_settings_module.py | 29 ++++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/pytest_django/plugin.py b/pytest_django/plugin.py index e2b6f120..e86b2d8f 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,34 @@ 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(f"Invalid path for configuration hook: {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(f"Unable to import module {module_path}") + func = getattr(mod, function_name, None) + + if not func: + raise ImportError(f"No function found with name {function_name} in module {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..b4b49305 100644 --- a/tests/test_django_settings_module.py +++ b/tests/test_django_settings_module.py @@ -505,3 +505,32 @@ 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 From 0b9dad148facf334214b517c8fedf3903f452b1c Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 21 Mar 2022 13:47:05 +0100 Subject: [PATCH 2/5] fixed linting --- pytest_django/plugin.py | 3 ++- tests/test_django_settings_module.py | 5 ++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pytest_django/plugin.py b/pytest_django/plugin.py index e86b2d8f..5c9b64bf 100644 --- a/pytest_django/plugin.py +++ b/pytest_django/plugin.py @@ -386,7 +386,8 @@ def _get_option_with_source( func = getattr(mod, function_name, None) if not func: - raise ImportError(f"No function found with name {function_name} in module {module_path}!") + raise ImportError(f"No function found with name {function_name} in module " + f"{module_path}!") # Call the function func() diff --git a/tests/test_django_settings_module.py b/tests/test_django_settings_module.py index b4b49305..f786ba7b 100644 --- a/tests/test_django_settings_module.py +++ b/tests/test_django_settings_module.py @@ -516,13 +516,12 @@ def test_dch_ini(testdir, monkeypatch) -> None: """ ) pkg = testdir.mkpydir("tpkg") - pkg.join("test.py").write( - """ + pkg.join("test.py").write(""" # Test from django.conf import settings def setup(): - settings.configure() + settings.configure() """) testdir.makepyfile( """ From 5f9fa9916310d726de5e006b0c5fcc5d77563c4e Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 21 Mar 2022 13:56:53 +0100 Subject: [PATCH 3/5] removed f-strings --- pytest_django/plugin.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pytest_django/plugin.py b/pytest_django/plugin.py index 5c9b64bf..6f76d3d0 100644 --- a/pytest_django/plugin.py +++ b/pytest_django/plugin.py @@ -371,7 +371,7 @@ def _get_option_with_source( # object with a custom configuration if "." not in dch: - raise ImportError(f"Invalid path for configuration hook: {dch}") + raise ImportError("Invalid path for configuration hook: {}".format(dch)) pkg_parts = dch.split(".") module_path = ".".join(pkg_parts[:-1]) @@ -382,12 +382,12 @@ def _get_option_with_source( try: mod = importlib.import_module(module_path) except (ImportError, AttributeError): - raise ImportError(f"Unable to import module {module_path}") + raise ImportError("Unable to import module {}".format(module_path)) func = getattr(mod, function_name, None) if not func: - raise ImportError(f"No function found with name {function_name} in module " - f"{module_path}!") + raise ImportError("No function found with name {} in module {}!" + .format(function_name, module_path)) # Call the function func() From 5c851a7bd96c8f04c8756a4d6f03c072cf9477f7 Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 21 Mar 2022 14:18:24 +0100 Subject: [PATCH 4/5] Improved code coverage --- tests/test_django_settings_module.py | 51 ++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/tests/test_django_settings_module.py b/tests/test_django_settings_module.py index f786ba7b..c8b8e2c1 100644 --- a/tests/test_django_settings_module.py +++ b/tests/test_django_settings_module.py @@ -533,3 +533,54 @@ def test_ds(): ) result = testdir.runpytest_subprocess() assert result.ret == 0 + + +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 From 16db732dd2d0e28d271d465b29f7605d85e5f3b8 Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 21 Mar 2022 14:23:35 +0100 Subject: [PATCH 5/5] Improved code coverage --- tests/test_django_settings_module.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/test_django_settings_module.py b/tests/test_django_settings_module.py index c8b8e2c1..641a8528 100644 --- a/tests/test_django_settings_module.py +++ b/tests/test_django_settings_module.py @@ -535,6 +535,27 @@ def test_ds(): 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(