diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs deleted file mode 100644 index a569984..0000000 --- a/.git-blame-ignore-revs +++ /dev/null @@ -1,2 +0,0 @@ -# Changed line-length back to default -1118c03c1b21e217bb66ee2811c423fe3624d546 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5d7ad77..14671bd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -21,7 +21,7 @@ repos: - id: trailing-whitespace - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.15.5 + rev: v0.15.0 hooks: - id: ruff-check args: ["--fix", "--exit-non-zero-on-fix"] @@ -34,6 +34,6 @@ repos: args: ["--py311-plus"] - repo: https://github.com/rhysd/actionlint - rev: v1.7.11 + rev: v1.7.10 hooks: - id: actionlint diff --git a/config/settings.py b/config/settings.py index b112427..6976d78 100644 --- a/config/settings.py +++ b/config/settings.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import logging import os import sys @@ -37,11 +39,7 @@ 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: @@ -120,11 +118,28 @@ 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", @@ -164,7 +179,12 @@ 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": { @@ -176,13 +196,19 @@ 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 298dd35..e1ed291 100644 --- a/config/tests/test_settings.py +++ b/config/tests/test_settings.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import importlib import os import sys @@ -40,10 +42,7 @@ 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: @@ -96,10 +95,7 @@ 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" @@ -116,9 +112,7 @@ def test_get_data_dir_uses_platformdirs( 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") @@ -126,9 +120,7 @@ def test_allowed_hosts_when_debug_false( 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") @@ -136,9 +128,7 @@ def test_allowed_hosts_when_debug_true( 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) @@ -182,9 +172,7 @@ 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") @@ -224,9 +212,7 @@ 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 83738d1..dca6fc9 100644 --- a/config/tests/test_urls.py +++ b/config/tests/test_urls.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import importlib from typing import TYPE_CHECKING diff --git a/config/urls.py b/config/urls.py index 2c031ea..49724ee 100644 --- a/config/urls.py +++ b/config/urls.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import TYPE_CHECKING from django.conf import settings @@ -19,7 +21,10 @@ 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 e61f3f2..05ced01 100644 --- a/config/wsgi.py +++ b/config/wsgi.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os from typing import TYPE_CHECKING diff --git a/manage.py b/manage.py index 1077d0a..c709739 100755 --- a/manage.py +++ b/manage.py @@ -1,6 +1,8 @@ #!/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 5a17154..87d0b4d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,22 +45,15 @@ 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. @@ -70,7 +63,6 @@ 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. @@ -95,6 +87,10 @@ 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 33fadea..a1cf1aa 100644 --- a/twitch/apps.py +++ b/twitch/apps.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from django.apps import AppConfig diff --git a/twitch/feeds.py b/twitch/feeds.py index 4828521..783bdeb 100644 --- a/twitch/feeds.py +++ b/twitch/feeds.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import logging import re from typing import TYPE_CHECKING @@ -12,10 +14,12 @@ 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 @@ -29,9 +33,6 @@ 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") @@ -70,20 +71,12 @@ 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("") ) @@ -137,10 +130,7 @@ 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', @@ -152,11 +142,9 @@ def _build_channels_html( Returns: SafeText: HTML