Improve import command
This commit is contained in:
parent
b11cfa03ea
commit
1d6c52325c
30 changed files with 2628 additions and 554 deletions
|
|
@ -6,14 +6,41 @@ import sys
|
|||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import django_stubs_ext
|
||||
from dotenv import load_dotenv
|
||||
from platformdirs import user_data_dir
|
||||
|
||||
logger: logging.Logger = logging.getLogger("ttvdrops.settings")
|
||||
django_stubs_ext.monkeypatch()
|
||||
|
||||
load_dotenv(verbose=True)
|
||||
|
||||
DEBUG: bool = os.getenv(key="DEBUG", default="True").lower() == "true"
|
||||
TRUE_VALUES: set[str] = {"1", "true", "yes", "y", "on"}
|
||||
|
||||
|
||||
def env_bool(key: str, *, default: bool = False) -> bool:
|
||||
"""Read a boolean from the environment, accepting common truthy values.
|
||||
|
||||
Returns:
|
||||
bool: Parsed boolean value or the provided default when unset.
|
||||
"""
|
||||
value: str | None = os.getenv(key)
|
||||
if value is None:
|
||||
return default
|
||||
return value.strip().lower() in TRUE_VALUES
|
||||
|
||||
|
||||
def env_int(key: str, default: int) -> int:
|
||||
"""Read an integer from the environment with a fallback default.
|
||||
|
||||
Returns:
|
||||
int: Parsed integer value or the provided default when unset.
|
||||
"""
|
||||
value: str | None = os.getenv(key)
|
||||
return int(value) if value is not None else default
|
||||
|
||||
|
||||
DEBUG: bool = env_bool(key="DEBUG", default=True)
|
||||
|
||||
|
||||
def get_data_dir() -> Path:
|
||||
|
|
@ -53,12 +80,12 @@ DEFAULT_FROM_EMAIL: str | None = os.getenv(key="EMAIL_HOST_USER", default=None)
|
|||
EMAIL_HOST: str = os.getenv(key="EMAIL_HOST", default="smtp.gmail.com")
|
||||
EMAIL_HOST_PASSWORD: str | None = os.getenv(key="EMAIL_HOST_PASSWORD", default=None)
|
||||
EMAIL_HOST_USER: str | None = os.getenv(key="EMAIL_HOST_USER", default=None)
|
||||
EMAIL_PORT: int = int(os.getenv(key="EMAIL_PORT", default="587"))
|
||||
EMAIL_PORT: int = env_int(key="EMAIL_PORT", default=587)
|
||||
EMAIL_SUBJECT_PREFIX = "[TTVDrops] "
|
||||
EMAIL_TIMEOUT: int = int(os.getenv(key="EMAIL_TIMEOUT", default="10"))
|
||||
EMAIL_TIMEOUT: int = env_int(key="EMAIL_TIMEOUT", default=10)
|
||||
EMAIL_USE_LOCALTIME = True
|
||||
EMAIL_USE_TLS: bool = os.getenv(key="EMAIL_USE_TLS", default="True").lower() == "true"
|
||||
EMAIL_USE_SSL: bool = os.getenv(key="EMAIL_USE_SSL", default="False").lower() == "true"
|
||||
EMAIL_USE_TLS: bool = env_bool(key="EMAIL_USE_TLS", default=True)
|
||||
EMAIL_USE_SSL: bool = env_bool(key="EMAIL_USE_SSL", default=False)
|
||||
SERVER_EMAIL: str | None = os.getenv(key="EMAIL_HOST_USER", default=None)
|
||||
|
||||
LOGIN_REDIRECT_URL = "/"
|
||||
|
|
@ -81,11 +108,13 @@ STATICFILES_DIRS: list[Path] = [BASE_DIR / "static"]
|
|||
TIME_ZONE = "UTC"
|
||||
WSGI_APPLICATION = "config.wsgi.application"
|
||||
|
||||
INTERNAL_IPS: list[str] = []
|
||||
if DEBUG:
|
||||
INTERNAL_IPS: list[str] = ["127.0.0.1", "localhost"]
|
||||
INTERNAL_IPS = ["127.0.0.1", "localhost"] # pyright: ignore[reportConstantRedefinition]
|
||||
|
||||
ALLOWED_HOSTS: list[str] = [".localhost", "127.0.0.1", "[::1]"]
|
||||
if not DEBUG:
|
||||
ALLOWED_HOSTS: list[str] = ["ttvdrops.lovinator.space"]
|
||||
ALLOWED_HOSTS = ["ttvdrops.lovinator.space"] # pyright: ignore[reportConstantRedefinition]
|
||||
|
||||
LOGGING: dict[str, Any] = {
|
||||
"version": 1,
|
||||
|
|
@ -124,7 +153,7 @@ MIDDLEWARE: list[str] = [
|
|||
]
|
||||
|
||||
|
||||
TEMPLATES: list[dict[str, str | list[Path] | bool | dict[str, list[str] | list[tuple[str, list[str]]]]]] = [
|
||||
TEMPLATES: list[dict[str, Any]] = [
|
||||
{
|
||||
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||
"DIRS": [BASE_DIR / "templates"],
|
||||
|
|
@ -145,7 +174,9 @@ DATABASES: dict[str, dict[str, str | Path | dict[str, str]]] = {
|
|||
"ENGINE": "django.db.backends.sqlite3",
|
||||
"NAME": DATA_DIR / "ttvdrops.sqlite3",
|
||||
"OPTIONS": {
|
||||
"init_command": "PRAGMA foreign_keys = ON; PRAGMA journal_mode=WAL; PRAGMA synchronous=NORMAL; PRAGMA mmap_size = 134217728; PRAGMA journal_size_limit = 27103364; PRAGMA cache_size=2000;", # noqa: E501
|
||||
"init_command": (
|
||||
"PRAGMA foreign_keys = ON; PRAGMA journal_mode=WAL; PRAGMA synchronous=NORMAL; PRAGMA mmap_size = 134217728; PRAGMA journal_size_limit = 27103364; PRAGMA cache_size=2000;" # noqa: E501
|
||||
),
|
||||
"transaction_mode": "IMMEDIATE",
|
||||
},
|
||||
},
|
||||
|
|
@ -154,7 +185,9 @@ DATABASES: dict[str, dict[str, str | Path | dict[str, str]]] = {
|
|||
TESTING: bool = "test" in sys.argv or "PYTEST_VERSION" in os.environ
|
||||
|
||||
if not TESTING:
|
||||
DEBUG_TOOLBAR_CONFIG: dict[str, str] = {"ROOT_TAG_EXTRA_ATTRS": "hx-preserve"}
|
||||
DEBUG_TOOLBAR_CONFIG: dict[str, str] = {
|
||||
"ROOT_TAG_EXTRA_ATTRS": "hx-preserve",
|
||||
}
|
||||
INSTALLED_APPS = [ # pyright: ignore[reportConstantRedefinition]
|
||||
*INSTALLED_APPS,
|
||||
"debug_toolbar",
|
||||
|
|
|
|||
0
config/tests/__init__.py
Normal file
0
config/tests/__init__.py
Normal file
124
config/tests/test_settings.py
Normal file
124
config/tests/test_settings.py
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import importlib
|
||||
import os
|
||||
from contextlib import contextmanager
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import pytest
|
||||
|
||||
from config import settings
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable
|
||||
from collections.abc import Generator
|
||||
from collections.abc import Iterator
|
||||
from pathlib import Path
|
||||
from types import ModuleType
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def reload_settings_module() -> Generator[Callable[..., ModuleType]]:
|
||||
"""Reload ``config.settings`` with temporary environment overrides.
|
||||
|
||||
Yields:
|
||||
Callable[..., settings]: Function that reloads the settings module using
|
||||
provided environment overrides.
|
||||
"""
|
||||
original_env: dict[str, str] = os.environ.copy()
|
||||
|
||||
@contextmanager
|
||||
def temporary_env(env: dict[str, str]) -> Iterator[None]:
|
||||
previous_env: dict[str, str] = os.environ.copy()
|
||||
os.environ.clear()
|
||||
os.environ.update(env)
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
os.environ.clear()
|
||||
os.environ.update(previous_env)
|
||||
|
||||
def _reload(**env_overrides: str | None) -> ModuleType:
|
||||
env: dict[str, str] = os.environ.copy()
|
||||
env.setdefault("DJANGO_SECRET_KEY", original_env.get("DJANGO_SECRET_KEY", "test-secret-key"))
|
||||
|
||||
for key, value in env_overrides.items():
|
||||
if value is None:
|
||||
env.pop(key, None)
|
||||
else:
|
||||
env[key] = value
|
||||
|
||||
with temporary_env(env):
|
||||
return importlib.reload(settings)
|
||||
|
||||
yield _reload
|
||||
|
||||
with temporary_env(original_env):
|
||||
importlib.reload(settings)
|
||||
|
||||
|
||||
def test_env_bool_truthy_values(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""env_bool should treat common truthy strings as True."""
|
||||
truthy_values: list[str] = ["1", "true", "yes", "y", "on", "TrUe", " YES "]
|
||||
for value in truthy_values:
|
||||
monkeypatch.setenv("FEATURE_FLAG", value)
|
||||
assert settings.env_bool("FEATURE_FLAG") is True
|
||||
|
||||
|
||||
def test_env_bool_default_when_missing(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""env_bool should fall back to the provided default when unset."""
|
||||
monkeypatch.delenv("MISSING_FLAG", raising=False)
|
||||
assert settings.env_bool("MISSING_FLAG", default=False) is False
|
||||
assert settings.env_bool("MISSING_FLAG", default=True) is True
|
||||
|
||||
|
||||
def test_env_int_parses_value(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""env_int should parse integers from the environment."""
|
||||
monkeypatch.setenv("MAX_COUNT", "5")
|
||||
assert settings.env_int("MAX_COUNT", 1) == 5
|
||||
|
||||
|
||||
def test_env_int_returns_default(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""env_int should return the fallback when unset."""
|
||||
monkeypatch.delenv("MAX_COUNT", raising=False)
|
||||
assert settings.env_int("MAX_COUNT", 3) == 3
|
||||
|
||||
|
||||
def test_get_data_dir_uses_platformdirs(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
|
||||
"""get_data_dir should use platformdirs and create the directory."""
|
||||
fake_dir: Path = tmp_path / "data_dir"
|
||||
|
||||
def fake_user_data_dir(**_: str) -> str:
|
||||
fake_dir.mkdir(parents=True, exist_ok=True)
|
||||
return str(fake_dir)
|
||||
|
||||
monkeypatch.setattr(settings, "user_data_dir", fake_user_data_dir)
|
||||
|
||||
path: Path = settings.get_data_dir()
|
||||
|
||||
assert path == fake_dir
|
||||
assert path.exists() is True
|
||||
assert path.is_dir() is True
|
||||
|
||||
|
||||
def test_allowed_hosts_when_debug_false(reload_settings_module: Callable[..., ModuleType]) -> None:
|
||||
"""When DEBUG is false, ALLOWED_HOSTS should use the production host."""
|
||||
reloaded: ModuleType = reload_settings_module(DEBUG="false")
|
||||
|
||||
assert reloaded.DEBUG is False
|
||||
assert reloaded.ALLOWED_HOSTS == ["ttvdrops.lovinator.space"]
|
||||
|
||||
|
||||
def test_allowed_hosts_when_debug_true(reload_settings_module: Callable[..., ModuleType]) -> None:
|
||||
"""When DEBUG is true, development hostnames should be allowed."""
|
||||
reloaded: ModuleType = reload_settings_module(DEBUG="1")
|
||||
|
||||
assert reloaded.DEBUG is True
|
||||
assert reloaded.ALLOWED_HOSTS == [".localhost", "127.0.0.1", "[::1]"]
|
||||
|
||||
|
||||
def test_debug_defaults_true_when_missing(reload_settings_module: Callable[..., ModuleType]) -> None:
|
||||
"""DEBUG should default to True when the environment variable is missing."""
|
||||
reloaded: ModuleType = reload_settings_module(DEBUG=None)
|
||||
|
||||
assert reloaded.DEBUG is True
|
||||
|
|
@ -17,7 +17,7 @@ urlpatterns: list[URLResolver] | list[URLPattern | URLResolver] = [ # type: ign
|
|||
|
||||
if not settings.TESTING:
|
||||
# Import debug_toolbar lazily to avoid ImportError when not installed in testing environments
|
||||
from debug_toolbar.toolbar import debug_toolbar_urls # type: ignore[import-untyped] # pyright: ignore[reportMissingTypeStubs]
|
||||
from debug_toolbar.toolbar import debug_toolbar_urls # pyright: ignore[reportMissingTypeStubs]
|
||||
|
||||
urlpatterns = [
|
||||
*urlpatterns,
|
||||
|
|
@ -26,4 +26,7 @@ if not settings.TESTING:
|
|||
|
||||
# Serve media in development
|
||||
if settings.DEBUG:
|
||||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
urlpatterns += static(
|
||||
settings.MEDIA_URL,
|
||||
document_root=settings.MEDIA_ROOT,
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue