Fix types

This commit is contained in:
Joakim Hellsén 2026-03-16 18:40:04 +01:00
commit c092d3089f
Signed by: Joakim Hellsén
SSH key fingerprint: SHA256:/9h/CsExpFp+PRhsfA0xznFx2CGfTT5R/kpuFfUgEQk
13 changed files with 48 additions and 18 deletions

View file

@ -12,7 +12,6 @@ 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
@ -28,7 +27,7 @@ def reload_settings_module() -> Generator[Callable[..., ModuleType]]:
original_env: dict[str, str] = os.environ.copy()
@contextmanager
def temporary_env(env: dict[str, str]) -> Iterator[None]:
def temporary_env(env: dict[str, str]) -> Generator[None]:
previous_env: dict[str, str] = os.environ.copy()
os.environ.clear()
os.environ.update(env)

View file

@ -53,9 +53,9 @@ if TYPE_CHECKING:
from os import stat_result
from pathlib import Path
from debug_toolbar.utils import QueryDict
from django.db.models import QuerySet
from django.http import HttpRequest
from django.http.request import QueryDict
logger: logging.Logger = logging.getLogger("ttvdrops.views")
@ -309,7 +309,7 @@ def docs_rss_view(request: HttpRequest) -> HttpResponse:
# Add limit=1 to GET parameters
get_data: QueryDict = request.GET.copy()
get_data["limit"] = "1"
limited_request.GET = get_data # pyright: ignore[reportAttributeAccessIssue]
limited_request.GET = get_data
response: HttpResponse = feed_view(limited_request, *args)
return _pretty_example(response.content.decode("utf-8"))

View file

@ -289,7 +289,7 @@ class KickDropCampaign(auto_prefetch.Model):
"""Return the image URL for the campaign."""
# Image from first drop
if self.rewards.exists(): # pyright: ignore[reportAttributeAccessIssue]
first_reward: KickReward = self.rewards.first() # pyright: ignore[reportAttributeAccessIssue]
first_reward: KickReward | None = self.rewards.first() # pyright: ignore[reportAttributeAccessIssue]
if first_reward and first_reward.image_url:
return first_reward.full_image_url

View file

@ -442,6 +442,8 @@ class ImportKickDropsCommandTest(TestCase):
campaign: KickDropCampaign = KickDropCampaign.objects.get()
assert campaign.name == "PUBG 9th Anniversary"
assert campaign.status == "active"
assert campaign.organization is not None
assert campaign.category is not None
assert campaign.organization.name == "KRAFTON"
assert campaign.category.name == "PUBG: Battlegrounds"

View file

@ -177,7 +177,9 @@ class TTVDropsAtomBaseFeed(TTVDropsBaseFeed):
feed_type = BrowserFriendlyAtom1Feed
def _with_campaign_related(queryset: QuerySet[DropCampaign]) -> QuerySet[DropCampaign]:
def _with_campaign_related(
queryset: QuerySet[DropCampaign, DropCampaign],
) -> QuerySet[DropCampaign, DropCampaign]:
"""Apply related-selects/prefetches needed by feed rendering to avoid N+1 queries.
Returns:

View file

@ -631,6 +631,9 @@ class Command(BaseCommand):
)
return
if game_obj.box_art_file is None:
return
game_obj.box_art_file.save(file_name, ContentFile(response.content), save=True)
def _get_or_create_channel(self, channel_info: ChannelInfoSchema) -> Channel:

View file

@ -11,8 +11,8 @@ from twitch.models import Game
from twitch.models import Organization
if TYPE_CHECKING:
from debug_toolbar.panels.templates.panel import QuerySet
from django.core.management.base import CommandParser
from django.db.models import QuerySet
class Command(BaseCommand):

View file

@ -38,7 +38,7 @@ class Command(BaseCommand):
help="Re-download even if a local box art file already exists.",
)
def handle(self, *_args: object, **options: object) -> None:
def handle(self, *_args: object, **options: object) -> None: # noqa: PLR0914
"""Download Twitch box art images for all games."""
limit_value: object | None = options.get("limit")
limit: int | None = limit_value if isinstance(limit_value, int) else None
@ -92,6 +92,10 @@ class Command(BaseCommand):
skipped += 1
continue
if game.box_art_file is None:
failed += 1
continue
game.box_art_file.save(
file_name,
ContentFile(response.content),
@ -99,7 +103,9 @@ class Command(BaseCommand):
)
# Auto-convert to WebP and AVIF
self._convert_to_modern_formats(game.box_art_file.path)
box_art_path: str | None = getattr(game.box_art_file, "path", None)
if box_art_path:
self._convert_to_modern_formats(box_art_path)
downloaded += 1

View file

@ -264,7 +264,7 @@ class Command(BaseCommand):
client: httpx.Client,
image_url: str,
twitch_id: str,
file_field: FieldFile,
file_field: FieldFile | None,
) -> str:
"""Download a single image and save it to the file field.
@ -281,6 +281,9 @@ class Command(BaseCommand):
suffix: str = Path(parsed_url.path).suffix or ".jpg"
file_name: str = f"{twitch_id}{suffix}"
if file_field is None:
return "failed"
try:
response: httpx.Response = client.get(image_url)
response.raise_for_status()
@ -299,7 +302,9 @@ class Command(BaseCommand):
file_field.save(file_name, ContentFile(response.content), save=True)
# Auto-convert to WebP and AVIF
self._convert_to_modern_formats(file_field.path)
image_path: str | None = getattr(file_field, "path", None)
if image_path:
self._convert_to_modern_formats(image_path)
return "downloaded"

View file

@ -279,6 +279,7 @@ class RSSFeedTestCase(TestCase):
def test_campaign_and_game_feeds_use_absolute_media_enclosure_urls(self) -> None:
"""Campaign/game RSS+Atom enclosures should use absolute URLs for local media files."""
self.game.box_art = ""
assert self.game.box_art_file is not None
self.game.box_art_file.save(
"box.png",
ContentFile(b"game-image-bytes"),
@ -289,6 +290,7 @@ class RSSFeedTestCase(TestCase):
self.game.save()
self.campaign.image_url = ""
assert self.campaign.image_file is not None
self.campaign.image_file.save(
"campaign.png",
ContentFile(b"campaign-image-bytes"),
@ -712,6 +714,7 @@ class RSSFeedTestCase(TestCase):
name="File Game",
display_name="File Game",
)
assert game2.box_art_file is not None
game2.box_art_file.save("sample.png", ContentFile(b"hello"))
game2.save()
@ -723,6 +726,7 @@ class RSSFeedTestCase(TestCase):
end_at=timezone.now() + timedelta(days=1),
operation_names=["DropCampaignDetails"],
)
assert campaign2.image_file is not None
campaign2.image_file.save("camp.jpg", ContentFile(b"world"))
campaign2.save()

View file

@ -1067,9 +1067,18 @@ class TestSEOHelperFunctions:
def test_build_seo_context_with_all_parameters(self) -> None:
"""Test _build_seo_context with all parameters."""
now: datetime.datetime = timezone.now()
breadcrumb: list[dict[str, int | str]] = [
{"position": 1, "name": "Home", "url": "/"},
]
breadcrumb: dict[str, Any] = {
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{
"@type": "ListItem",
"position": 1,
"name": "Home",
"item": "/",
},
],
}
context: dict[str, Any] = _build_seo_context(
page_title="Test",
@ -1077,7 +1086,7 @@ class TestSEOHelperFunctions:
page_image="https://example.com/img.jpg",
og_type="article",
schema_data={},
breadcrumb_schema=breadcrumb, # pyright: ignore[reportArgumentType]
breadcrumb_schema=breadcrumb,
pagination_info=[{"rel": "next", "url": "/page/2/"}],
published_date=now.isoformat(),
modified_date=now.isoformat(),

View file

@ -53,7 +53,7 @@ def normalize_twitch_box_art_url(url: str) -> str:
return url
normalized_path: str = TWITCH_BOX_ART_SIZE_PATTERN.sub("", parsed.path)
return urlunparse(parsed._replace(path=normalized_path))
return str(urlunparse(parsed._replace(path=normalized_path)))
@lru_cache(maxsize=40 * 40 * 1024)

View file

@ -1396,7 +1396,7 @@ def reward_campaign_detail_view(request: HttpRequest, twitch_id: str) -> HttpRes
class GamesListView(GamesGridView):
"""List view for games in simple list format."""
template_name: str = "twitch/games_list.html"
template_name: str | None = "twitch/games_list.html"
# MARK: /channels/
@ -1748,7 +1748,7 @@ def badge_set_detail_view(request: HttpRequest, set_id: str) -> HttpResponse:
)
return ChatBadge.objects.filter(pk__in=badge_ids).order_by(preserved_order)
badges = get_sorted_badges(badge_set)
badges: QuerySet[ChatBadge, ChatBadge] = get_sorted_badges(badge_set)
# Attach award_campaigns attribute to each badge for template use
for badge in badges: