diff --git a/config/settings.py b/config/settings.py index 6976d78..b112427 100644 --- a/config/settings.py +++ b/config/settings.py @@ -1,5 +1,3 @@ -from __future__ import annotations - import logging import os import sys @@ -39,7 +37,11 @@ def env_int(key: str, default: int) -> int: DEBUG: bool = env_bool(key="DEBUG", default=True) -TESTING: bool = env_bool(key="TESTING", default=False) or "test" in sys.argv or "PYTEST_VERSION" in os.environ +TESTING: bool = ( + env_bool(key="TESTING", default=False) + or "test" in sys.argv + or "PYTEST_VERSION" in os.environ +) def get_data_dir() -> Path: @@ -118,28 +120,11 @@ if not DEBUG: LOGGING: dict[str, Any] = { "version": 1, "disable_existing_loggers": False, - "handlers": { - "console": { - "level": "DEBUG", - "class": "logging.StreamHandler", - }, - }, + "handlers": {"console": {"level": "DEBUG", "class": "logging.StreamHandler"}}, "loggers": { - "": { - "handlers": ["console"], - "level": "INFO", - "propagate": True, - }, - "ttvdrops": { - "handlers": ["console"], - "level": "DEBUG", - "propagate": False, - }, - "django": { - "handlers": ["console"], - "level": "INFO", - "propagate": False, - }, + "": {"handlers": ["console"], "level": "INFO", "propagate": True}, + "ttvdrops": {"handlers": ["console"], "level": "DEBUG", "propagate": False}, + "django": {"handlers": ["console"], "level": "INFO", "propagate": False}, "django.utils.autoreload": { "handlers": ["console"], "level": "INFO", @@ -179,12 +164,7 @@ TEMPLATES: list[dict[str, Any]] = [ ] DATABASES: dict[str, dict[str, Any]] = ( - { - "default": { - "ENGINE": "django.db.backends.sqlite3", - "NAME": ":memory:", - }, - } + {"default": {"ENGINE": "django.db.backends.sqlite3", "NAME": ":memory:"}} if TESTING else { "default": { @@ -196,19 +176,13 @@ DATABASES: dict[str, dict[str, Any]] = ( "PORT": env_int("POSTGRES_PORT", 5432), "CONN_MAX_AGE": env_int("CONN_MAX_AGE", 60), "CONN_HEALTH_CHECKS": env_bool("CONN_HEALTH_CHECKS", default=True), - "OPTIONS": { - "connect_timeout": env_int("DB_CONNECT_TIMEOUT", 10), - }, + "OPTIONS": {"connect_timeout": env_int("DB_CONNECT_TIMEOUT", 10)}, }, } ) if not TESTING: - INSTALLED_APPS = [ - *INSTALLED_APPS, - "debug_toolbar", - "silk", - ] + INSTALLED_APPS = [*INSTALLED_APPS, "debug_toolbar", "silk"] MIDDLEWARE = [ "debug_toolbar.middleware.DebugToolbarMiddleware", "silk.middleware.SilkyMiddleware", diff --git a/config/tests/test_settings.py b/config/tests/test_settings.py index e1ed291..298dd35 100644 --- a/config/tests/test_settings.py +++ b/config/tests/test_settings.py @@ -1,5 +1,3 @@ -from __future__ import annotations - import importlib import os import sys @@ -42,7 +40,10 @@ def reload_settings_module() -> Generator[Callable[..., ModuleType]]: 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")) + 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: @@ -95,7 +96,10 @@ def test_env_int_returns_default(monkeypatch: pytest.MonkeyPatch) -> None: assert settings.env_int("MAX_COUNT", 3) == 3 -def test_get_data_dir_uses_platformdirs(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: +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" @@ -112,7 +116,9 @@ def test_get_data_dir_uses_platformdirs(monkeypatch: pytest.MonkeyPatch, tmp_pat assert path.is_dir() is True -def test_allowed_hosts_when_debug_false(reload_settings_module: Callable[..., ModuleType]) -> None: +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") @@ -120,7 +126,9 @@ def test_allowed_hosts_when_debug_false(reload_settings_module: Callable[..., Mo assert reloaded.ALLOWED_HOSTS == ["ttvdrops.lovinator.space"] -def test_allowed_hosts_when_debug_true(reload_settings_module: Callable[..., ModuleType]) -> None: +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") @@ -128,7 +136,9 @@ def test_allowed_hosts_when_debug_true(reload_settings_module: Callable[..., Mod assert reloaded.ALLOWED_HOSTS == [".localhost", "127.0.0.1", "[::1]", "testserver"] -def test_debug_defaults_true_when_missing(reload_settings_module: Callable[..., ModuleType]) -> None: +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) @@ -172,7 +182,9 @@ def test_testing_true_when_sys_argv_contains_test( assert reloaded.TESTING is True -def test_testing_true_when_pytest_version_set(reload_settings_module: Callable[..., ModuleType]) -> None: +def test_testing_true_when_pytest_version_set( + reload_settings_module: Callable[..., ModuleType], +) -> None: """TESTING should be true when PYTEST_VERSION is set in the env.""" reloaded: ModuleType = reload_settings_module(PYTEST_VERSION="7.0.0") @@ -212,7 +224,9 @@ def test_missing_secret_key_causes_system_exit(monkeypatch: pytest.MonkeyPatch) __import__("config.settings") -def test_email_settings_from_env(reload_settings_module: Callable[..., ModuleType]) -> None: +def test_email_settings_from_env( + reload_settings_module: Callable[..., ModuleType], +) -> None: """EMAIL_* values should be read from the environment and cast correctly.""" reloaded: ModuleType = reload_settings_module( EMAIL_HOST="smtp.example.com", diff --git a/config/tests/test_urls.py b/config/tests/test_urls.py index dca6fc9..83738d1 100644 --- a/config/tests/test_urls.py +++ b/config/tests/test_urls.py @@ -1,5 +1,3 @@ -from __future__ import annotations - import importlib from typing import TYPE_CHECKING diff --git a/config/urls.py b/config/urls.py index 49724ee..2c031ea 100644 --- a/config/urls.py +++ b/config/urls.py @@ -1,5 +1,3 @@ -from __future__ import annotations - from typing import TYPE_CHECKING from django.conf import settings @@ -21,10 +19,7 @@ urlpatterns: list[URLPattern | URLResolver] = [ # 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) if not settings.TESTING: from debug_toolbar.toolbar import debug_toolbar_urls diff --git a/config/wsgi.py b/config/wsgi.py index 05ced01..e61f3f2 100644 --- a/config/wsgi.py +++ b/config/wsgi.py @@ -1,5 +1,3 @@ -from __future__ import annotations - import os from typing import TYPE_CHECKING diff --git a/manage.py b/manage.py index c709739..1077d0a 100755 --- a/manage.py +++ b/manage.py @@ -1,8 +1,6 @@ #!/usr/bin/env python """Django's command-line utility for administrative tasks.""" -from __future__ import annotations - import os import sys diff --git a/pyproject.toml b/pyproject.toml index 87d0b4d..5a17154 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,15 +45,22 @@ filterwarnings = [ ] [tool.ruff] +fix = true +preview = true +unsafe-fixes = true + +format.docstring-code-format = true +format.preview = true + +lint.future-annotations = true +lint.isort.force-single-line = true +lint.pycodestyle.ignore-overlong-task-comments = true +lint.pydocstyle.convention = "google" lint.select = ["ALL"] # Don't automatically remove unused variables lint.unfixable = ["F841"] -lint.pydocstyle.convention = "google" -lint.isort.required-imports = ["from __future__ import annotations"] -lint.isort.force-single-line = true - lint.ignore = [ "ANN002", # Checks that function *args arguments have type annotations. "ANN003", # Checks that function **kwargs arguments have type annotations. @@ -63,6 +70,7 @@ lint.ignore = [ "D104", # Checks for undocumented public package definitions. "D105", # Checks for undocumented magic method definitions. "D106", # Checks for undocumented public class definitions, for nested classes. + "E501", # Checks for lines that exceed the specified maximum character length. "ERA001", # Checks for commented-out Python code. "FIX002", # Checks for "TODO" comments. "PLR0911", # Checks for functions or methods with too many return statements. @@ -87,10 +95,6 @@ lint.ignore = [ "Q003", # Checks for strings that include escaped quotes, and suggests changing the quote style to avoid the need to escape them. "W191", # Checks for indentation that uses tabs. ] -preview = true -unsafe-fixes = true -fix = true -line-length = 120 [tool.ruff.lint.per-file-ignores] "**/tests/**" = [ diff --git a/twitch/apps.py b/twitch/apps.py index a1cf1aa..33fadea 100644 --- a/twitch/apps.py +++ b/twitch/apps.py @@ -1,5 +1,3 @@ -from __future__ import annotations - from django.apps import AppConfig diff --git a/twitch/feeds.py b/twitch/feeds.py index 783bdeb..4828521 100644 --- a/twitch/feeds.py +++ b/twitch/feeds.py @@ -1,5 +1,3 @@ -from __future__ import annotations - import logging import re from typing import TYPE_CHECKING @@ -14,12 +12,10 @@ from django.utils import feedgenerator from django.utils import timezone from django.utils.html import format_html from django.utils.html import format_html_join -from django.utils.safestring import SafeString from django.utils.safestring import SafeText from twitch.models import Channel from twitch.models import ChatBadge -from twitch.models import DropBenefit from twitch.models import DropCampaign from twitch.models import Game from twitch.models import Organization @@ -33,6 +29,9 @@ if TYPE_CHECKING: from django.db.models import QuerySet from django.http import HttpRequest from django.http import HttpResponse + from django.utils.safestring import SafeString + + from twitch.models import DropBenefit logger: logging.Logger = logging.getLogger("ttvdrops") @@ -71,12 +70,20 @@ def insert_date_info(item: Model, parts: list[SafeText]) -> None: if start_at or end_at: start_part: SafeString = ( - format_html("Starts: {} ({})", start_at.strftime("%Y-%m-%d %H:%M %Z"), naturaltime(start_at)) + format_html( + "Starts: {} ({})", + start_at.strftime("%Y-%m-%d %H:%M %Z"), + naturaltime(start_at), + ) if start_at else SafeText("") ) end_part: SafeString = ( - format_html("Ends: {} ({})", end_at.strftime("%Y-%m-%d %H:%M %Z"), naturaltime(end_at)) + format_html( + "Ends: {} ({})", + end_at.strftime("%Y-%m-%d %H:%M %Z"), + naturaltime(end_at), + ) if end_at else SafeText("") ) @@ -130,7 +137,10 @@ def _build_drops_data(drops_qs: QuerySet[TimeBasedDrop]) -> list[dict]: return drops_data -def _build_channels_html(channels: list[Channel] | QuerySet[Channel], game: Game | None) -> SafeText: +def _build_channels_html( + channels: list[Channel] | QuerySet[Channel], + game: Game | None, +) -> SafeText: """Render up to max_links channel links as
  • , then a count of additional channels, or fallback to game category link. If only one channel and drop_requirements is '1 subscriptions required', @@ -142,9 +152,11 @@ def _build_channels_html(channels: list[Channel] | QuerySet[Channel], game: Game Returns: SafeText: HTML