Enhance type hinting
All checks were successful
Deploy to Server / deploy (push) Successful in 22s

This commit is contained in:
Joakim Hellsén 2026-04-05 03:57:18 +02:00
commit 06c0af7009
Signed by: Joakim Hellsén
SSH key fingerprint: SHA256:/9h/CsExpFp+PRhsfA0xznFx2CGfTT5R/kpuFfUgEQk
6 changed files with 100 additions and 49 deletions

View file

@ -7,6 +7,8 @@ from urllib.parse import urlsplit
from django.conf import settings
if TYPE_CHECKING:
from urllib.parse import SplitResult
from django.http import HttpRequest
@ -16,7 +18,7 @@ def _get_base_url() -> str:
Returns:
str: The configured BASE_URL without trailing slash.
"""
base_url = getattr(settings, "BASE_URL", "")
base_url: str = getattr(settings, "BASE_URL", "")
return base_url.rstrip("/") if base_url else ""
@ -33,7 +35,7 @@ def build_absolute_uri(
Returns:
str: Fully resolved absolute URL.
"""
base_url = _get_base_url()
base_url: str = _get_base_url()
if location is None:
if request is not None:
@ -41,7 +43,7 @@ def build_absolute_uri(
else:
return f"{base_url}/" if base_url else "/"
parsed = urlsplit(location)
parsed: SplitResult = urlsplit(location)
if parsed.scheme and parsed.netloc:
return location
@ -58,7 +60,7 @@ def build_absolute_uri(
def is_secure() -> bool:
"""Return whether the configured BASE_URL uses HTTPS."""
base_url = _get_base_url()
base_url: str = _get_base_url()
return base_url.startswith("https://") if base_url else False
@ -69,9 +71,9 @@ class _TTVDropsSite:
def get_current_site(request: object) -> _TTVDropsSite:
"""Return a site-like object with domain derived from BASE_URL."""
base_url = _get_base_url()
parts = urlsplit(base_url)
domain = parts.netloc or parts.path
base_url: str = _get_base_url()
parts: SplitResult = urlsplit(base_url)
domain: str = parts.netloc or parts.path
return _TTVDropsSite(domain=domain)

View file

@ -12,6 +12,4 @@ def base_url(request: HttpRequest) -> dict[str, str]:
Returns:
dict[str, str]: A dictionary containing the BASE_URL.
"""
return {
"BASE_URL": getattr(settings, "BASE_URL", ""),
}
return {"BASE_URL": getattr(settings, "BASE_URL", "")}

View file

@ -1,3 +1,5 @@
from typing import TYPE_CHECKING
from django.test import RequestFactory
from django.test import TestCase
from django.test.utils import override_settings
@ -5,6 +7,9 @@ from django.urls import reverse
from core.views import _build_base_url
if TYPE_CHECKING:
from django.test.client import _MonkeyPatchedWSGIResponse
@override_settings(ALLOWED_HOSTS=["example.com"])
class TestBuildBaseUrl(TestCase):
@ -16,7 +21,7 @@ class TestBuildBaseUrl(TestCase):
def test_valid_base_url(self) -> None:
"""Test that the base URL is built correctly."""
base_url = _build_base_url()
base_url: str = _build_base_url()
assert base_url == "https://ttvdrops.lovinator.space"
@ -25,12 +30,16 @@ class TestSitemapViews(TestCase):
def test_sitemap_twitch_channels_view(self) -> None:
"""Test that the Twitch channels sitemap view returns a valid XML response."""
response = self.client.get(reverse("sitemap-twitch-channels"))
response: _MonkeyPatchedWSGIResponse = self.client.get(
reverse("sitemap-twitch-channels"),
)
assert response.status_code == 200
assert "<urlset" in response.content.decode()
def test_sitemap_twitch_drops_view(self) -> None:
"""Test that the Twitch drops sitemap view returns a valid XML response."""
response = self.client.get(reverse("sitemap-twitch-drops"))
response: _MonkeyPatchedWSGIResponse = self.client.get(
reverse("sitemap-twitch-drops"),
)
assert response.status_code == 200
assert "<urlset" in response.content.decode()

View file

@ -33,82 +33,122 @@ app_name = "core"
urlpatterns: list[URLPattern | URLResolver] = [
# /
path("", dashboard, name="dashboard"),
path(
route="",
view=dashboard,
name="dashboard",
),
# /search/
path("search/", search_view, name="search"),
path(
route="search/",
view=search_view,
name="search",
),
# /debug/
path("debug/", debug_view, name="debug"),
path(
route="debug/",
view=debug_view,
name="debug",
),
# /datasets/
path("datasets/", dataset_backups_view, name="dataset_backups"),
path(
route="datasets/",
view=dataset_backups_view,
name="dataset_backups",
),
# /datasets/download/<relative_path>/
path(
"datasets/download/<path:relative_path>/",
dataset_backup_download_view,
route="datasets/download/<path:relative_path>/",
view=dataset_backup_download_view,
name="dataset_backup_download",
),
# /docs/rss/
path("docs/rss/", docs_rss_view, name="docs_rss"),
path(
route="docs/rss/",
view=docs_rss_view,
name="docs_rss",
),
# RSS feeds
# /rss/campaigns/ - all active campaigns
path("rss/campaigns/", DropCampaignFeed(), name="campaign_feed"),
path(
route="rss/campaigns/",
view=DropCampaignFeed(),
name="campaign_feed",
),
# /rss/games/ - newly added games
path("rss/games/", GameFeed(), name="game_feed"),
path(
route="rss/games/",
view=GameFeed(),
name="game_feed",
),
# /rss/games/<twitch_id>/campaigns/ - active campaigns for a specific game
path(
"rss/games/<str:twitch_id>/campaigns/",
GameCampaignFeed(),
route="rss/games/<str:twitch_id>/campaigns/",
view=GameCampaignFeed(),
name="game_campaign_feed",
),
# /rss/organizations/ - newly added organizations
path(
"rss/organizations/",
OrganizationRSSFeed(),
route="rss/organizations/",
view=OrganizationRSSFeed(),
name="organization_feed",
),
# /rss/reward-campaigns/ - all active reward campaigns
path(
"rss/reward-campaigns/",
RewardCampaignFeed(),
route="rss/reward-campaigns/",
view=RewardCampaignFeed(),
name="reward_campaign_feed",
),
# Atom feeds (added alongside RSS to preserve backward compatibility)
path("atom/campaigns/", DropCampaignAtomFeed(), name="campaign_feed_atom"),
path("atom/games/", GameAtomFeed(), name="game_feed_atom"),
path(
"atom/games/<str:twitch_id>/campaigns/",
route="atom/campaigns/",
view=DropCampaignAtomFeed(),
name="campaign_feed_atom",
),
path(
route="atom/games/",
view=GameAtomFeed(),
name="game_feed_atom",
),
path(
route="atom/games/<str:twitch_id>/campaigns/",
view=GameCampaignAtomFeed(),
name="game_campaign_feed_atom",
),
path(
"atom/organizations/",
OrganizationAtomFeed(),
route="atom/organizations/",
view=OrganizationAtomFeed(),
name="organization_feed_atom",
),
path(
"atom/reward-campaigns/",
RewardCampaignAtomFeed(),
route="atom/reward-campaigns/",
view=RewardCampaignAtomFeed(),
name="reward_campaign_feed_atom",
),
# Discord feeds (Atom feeds with Discord relative timestamps)
path(
"discord/campaigns/",
DropCampaignDiscordFeed(),
route="discord/campaigns/",
view=DropCampaignDiscordFeed(),
name="campaign_feed_discord",
),
path("discord/games/", GameDiscordFeed(), name="game_feed_discord"),
path(
"discord/games/<str:twitch_id>/campaigns/",
GameCampaignDiscordFeed(),
route="discord/games/",
view=GameDiscordFeed(),
name="game_feed_discord",
),
path(
route="discord/games/<str:twitch_id>/campaigns/",
view=GameCampaignDiscordFeed(),
name="game_campaign_feed_discord",
),
path(
"discord/organizations/",
OrganizationDiscordFeed(),
route="discord/organizations/",
view=OrganizationDiscordFeed(),
name="organization_feed_discord",
),
path(
"discord/reward-campaigns/",
RewardCampaignDiscordFeed(),
route="discord/reward-campaigns/",
view=RewardCampaignDiscordFeed(),
name="reward_campaign_feed_discord",
),
]

View file

@ -24,14 +24,14 @@ def _render_urlset_xml(sitemap_urls: list[dict[str, Any]]) -> str:
Returns:
str: Serialized XML for a <urlset> containing the provided URLs.
"""
urlset = Element("urlset")
urlset: Element[str] = Element("urlset")
urlset.set("xmlns", "http://www.sitemaps.org/schemas/sitemap/0.9")
for url_entry in sitemap_urls:
url = SubElement(urlset, "url")
loc = url_entry.get("loc")
url: Element[str] = SubElement(urlset, "url")
loc: str | None = url_entry.get("loc")
if loc:
child = SubElement(url, "loc")
child: Element[str] = SubElement(url, "loc")
child.text = loc
return tostring(urlset, encoding="unicode")

View file

@ -141,7 +141,9 @@ def _render_urlset_xml(
xml += '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n'
for url_entry in url_entries:
xml += " <url>\n"
loc = url_entry.get("loc") or url_entry.get("url") # Handle both keys
loc: str | None = url_entry.get("loc") or url_entry.get(
"url",
) # Handle both keys
if loc:
xml += f" <loc>{loc}</loc>\n"
if "lastmod" in url_entry: