Skip to content

Commit

Permalink
feat: Allow setting django_settings_module from env (#2021)
Browse files Browse the repository at this point in the history
* feat: allow setting django_settings_module from env

* tests: add tests for setting django settings from env

* docs: remove mentions of django settings module being required

* lint

* refactor: change default definition

---------

Co-authored-by: Armanc Keser <[email protected]>
  • Loading branch information
armanckeser and Armanc Keser authored Mar 21, 2024
1 parent cb7e368 commit 93b6cf1
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 14 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ django_settings_module = "myproject.settings"
Two things happening here:

1. We need to explicitly list our plugin to be loaded by `mypy`
2. Our plugin also requires `django` settings module (what you put into `DJANGO_SETTINGS_MODULE` variable) to be specified
2. You can either specify `django_settings_module` as seen above, or let `django_stubs` use the `DJANGO_SETTINGS_MODULE` variable from your environment.

This fully working [typed boilerplate](https://github.com/wemake-services/wemake-django-template) can serve you as an example.

Expand Down Expand Up @@ -96,7 +96,7 @@ django-stubs has a few settings, which you can list in:

The supported settings are:

- `django_settings_module`, a string.
- `django_settings_module`, a string, default to `os.getenv(DJANGO_SETTINGS_MODULE)`.

Specify the import path of your settings module, the same as Django’s [`DJANGO_SETTINGS_MODULE` environment variable](https://docs.djangoproject.com/en/stable/topics/settings/#designating-the-settings).

Expand Down
24 changes: 17 additions & 7 deletions mypy_django_plugin/config.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import configparser
import os
import sys
import textwrap
from functools import partial
Expand All @@ -14,22 +15,24 @@
(config)
...
[mypy.plugins.django-stubs]
django_settings_module = str (required)
django_settings_module = str (default: `os.getenv("DJANGO_SETTINGS_MODULE")`)
strict_settings = bool (default: true)
...
"""
TOML_USAGE = """
(config)
...
[tool.django-stubs]
django_settings_module = str (required)
django_settings_module = str (default: `os.getenv("DJANGO_SETTINGS_MODULE")`)
strict_settings = bool (default: true)
...
"""
INVALID_FILE = "mypy config file is not specified or found"
COULD_NOT_LOAD_FILE = "could not load configuration file"
MISSING_SECTION = "no section [{section}] found"
MISSING_DJANGO_SETTINGS = "missing required 'django_settings_module' config"
DJANGO_SETTINGS_ENV_VAR = "DJANGO_SETTINGS_MODULE"
MISSING_DJANGO_SETTINGS = f"missing required 'django_settings_module' config.\
Either specify this config or set your `{DJANGO_SETTINGS_ENV_VAR}` env var"
INVALID_BOOL_SETTING = "invalid {key!r}: the setting must be a boolean"


Expand Down Expand Up @@ -80,10 +83,12 @@ def parse_toml_file(self, filepath: Path) -> None:
except KeyError:
toml_exit(MISSING_SECTION.format(section="tool.django-stubs"))

if "django_settings_module" not in config:
django_settings_module = config.get("django_settings_module") or os.getenv(DJANGO_SETTINGS_ENV_VAR)
if not django_settings_module:
toml_exit(MISSING_DJANGO_SETTINGS)

self.django_settings_module = config["django_settings_module"]
self.django_settings_module = django_settings_module

if not isinstance(self.django_settings_module, str):
toml_exit("invalid 'django_settings_module': the setting must be a string")

Expand All @@ -103,10 +108,15 @@ def parse_ini_file(self, filepath: Path) -> None:
if not parser.has_section(section):
exit_with_error(MISSING_SECTION.format(section=section))

if not parser.has_option(section, "django_settings_module"):
if parser.has_option(section, "django_settings_module"):
django_settings_module = parser.get(section, "django_settings_module").strip("'\"")
else:
django_settings_module = os.getenv(DJANGO_SETTINGS_ENV_VAR, "")

if not django_settings_module:
exit_with_error(MISSING_DJANGO_SETTINGS)

self.django_settings_module = parser.get(section, "django_settings_module").strip("'\"")
self.django_settings_module = django_settings_module

try:
self.strict_settings = parser.getboolean(section, "strict_settings", fallback=True)
Expand Down
52 changes: 47 additions & 5 deletions tests/test_error_handling.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import os
import tempfile
import uuid
from contextlib import contextmanager
from typing import Any, Generator, List, Optional
from unittest import mock

import pytest

Expand All @@ -11,7 +13,7 @@
(config)
...
[mypy.plugins.django-stubs]
django_settings_module = str (required)
django_settings_module = str (default: `os.getenv("DJANGO_SETTINGS_MODULE")`)
strict_settings = bool (default: true)
...
(django-stubs) mypy: error: {}
Expand All @@ -21,7 +23,7 @@
(config)
...
[tool.django-stubs]
django_settings_module = str (required)
django_settings_module = str (default: `os.getenv("DJANGO_SETTINGS_MODULE")`)
strict_settings = bool (default: true)
...
(django-stubs) mypy: error: {}
Expand All @@ -46,12 +48,14 @@ def write_to_file(file_contents: str, suffix: Optional[str] = None) -> Generator
),
pytest.param(
["[mypy.plugins.django-stubs]", "\tnot_django_not_settings_module = badbadmodule"],
"missing required 'django_settings_module' config",
"missing required 'django_settings_module' config.\
Either specify this config or set your `DJANGO_SETTINGS_MODULE` env var",
id="missing-settings-module",
),
pytest.param(
["[mypy.plugins.django-stubs]"],
"missing required 'django_settings_module' config",
"missing required 'django_settings_module' config.\
Either specify this config or set your `DJANGO_SETTINGS_MODULE` env var",
id="no-settings-given",
),
pytest.param(
Expand Down Expand Up @@ -112,7 +116,8 @@ def test_handles_filename(capsys: Any, filename: str) -> None:
[tool.django-stubs]
not_django_not_settings_module = "badbadmodule"
""",
"missing required 'django_settings_module' config",
"missing required 'django_settings_module' config.\
Either specify this config or set your `DJANGO_SETTINGS_MODULE` env var",
id="missing django_settings_module",
),
pytest.param(
Expand Down Expand Up @@ -172,3 +177,40 @@ def test_correct_configuration(boolean_value) -> None:

assert config.django_settings_module == "my.module"
assert config.strict_settings is (boolean_value.lower() == "true")


@pytest.mark.parametrize("boolean_value", ["true", "false"])
def test_correct_toml_configuration_with_django_setting_from_env(boolean_value: str) -> None:
config_file_contents = f"""
[tool.django-stubs]
some_other_setting = "setting"
strict_settings = {boolean_value}
"""
django_settings_env_value = "my.module"

with write_to_file(config_file_contents, suffix=".toml") as filename:
with mock.patch.dict(os.environ, {"DJANGO_SETTINGS_MODULE": django_settings_env_value}):
config = DjangoPluginConfig(filename)

assert config.django_settings_module == django_settings_env_value
assert config.strict_settings is (boolean_value == "true")


@pytest.mark.parametrize("boolean_value", ["true", "True", "false", "False"])
def test_correct_configuration_with_django_setting_from_env(boolean_value) -> None:
"""Django settings module gets extracted given valid configuration."""
config_file_contents = "\n".join(
[
"[mypy.plugins.django-stubs]",
"some_other_setting = setting",
f"strict_settings = {boolean_value}",
]
)
django_settings_env_value = "my.module"

with write_to_file(config_file_contents) as filename:
with mock.patch.dict(os.environ, {"DJANGO_SETTINGS_MODULE": django_settings_env_value}):
config = DjangoPluginConfig(filename)

assert config.django_settings_module == django_settings_env_value
assert config.strict_settings is (boolean_value.lower() == "true")

0 comments on commit 93b6cf1

Please sign in to comment.