ci: mypy ratchet -- keep type-clean modules clean (71 modules, 536 -> 442 errors) (#661)

* ci: mypy ratchet -- keep type-clean modules clean

mypy-clean.txt lists the 71 modules under src/ that type-check clean;
scripts/check_types.py runs mypy (--follow-imports=silent) on exactly
those files and fails on any error or a missing/unsorted/duplicate entry.
A new "Type check (mypy ratchet)" CI job runs it with mypy 1.20.2 and
pinned stubs; the manual pre-commit mypy hook now runs the same script
(a local hook, so mypy sees the installed requirements like CI does).

35 modules were made clean with annotation-only fixes: hints, typing.cast,
TYPE_CHECKING imports, implicit-Optional defaults made explicit, and
annotations widened (never guards removed) where mypy called a defensive
isinstance check unreachable. No runtime behaviour change.

mypy.ini: numpy and orjson are treated as Any (follow_imports=skip, also
for stubs). numpy 2.3+ stubs use 3.12 `type` statements that mypy won't
parse at python_version 3.10, and orjson is optional, so seeing its stubs
made the result depend on whether it was installed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore: annotate check_types.py's list-form mypy subprocess

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Chuck
2026-09-28 15:01:35 -04:00
committed by GitHub
co-authored by Claude Opus 5.5
parent e6e0a16140
commit b8c01c69fb
42 changed files with 372 additions and 104 deletions
+11 -7
View File
@@ -11,12 +11,16 @@ import time
from datetime import datetime
from types import MappingProxyType
from src.common.espn_dates import ESPN_MAX_LIMIT
from typing import Any, Dict, Mapping, Optional
from typing import TYPE_CHECKING, Any, Dict, Mapping, Optional, cast
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
if TYPE_CHECKING:
# What Session() puts in .headers; the stubs only promise a MutableMapping.
from requests.structures import CaseInsensitiveDict
#: The User-Agent core sends to ESPN and other data APIs. It names the client
#: and links to it: around 2026-08-04 ESPN began 403ing bare custom tokens
@@ -84,7 +88,7 @@ class APIHelper:
self.session.headers.update({**DEFAULT_HTTP_HEADERS, 'Connection': 'keep-alive'})
# Rate limiting
self._last_request_time = 0 # wall clock, reported by get_request_stats()
self._last_request_time: float = 0 # wall clock, reported by get_request_stats()
# The interval is measured on time.monotonic(): a wall-clock step
# back (NTP correcting a Pi with no RTC) made time_since_last
# negative and the "remaining interval" sleep as long as the step.
@@ -112,14 +116,14 @@ class APIHelper:
cached = self._get_from_cache(cache_key, cache_ttl)
if cached is not None:
self.logger.debug(f"Using cached response for {cache_key}")
return cached
return cast(Dict[Any, Any], cached)
# Rate limiting
self._enforce_rate_limit()
try:
# Prepare request
request_headers = self.session.headers.copy()
request_headers = cast('CaseInsensitiveDict[Any]', self.session.headers).copy()
if headers:
request_headers.update(headers)
@@ -133,7 +137,7 @@ class APIHelper:
response.raise_for_status()
# Parse JSON response
data = response.json()
data: Dict[Any, Any] = response.json()
# Cache response if cache key provided
if cache_key and self.cache_manager:
@@ -247,7 +251,7 @@ class APIHelper:
self._enforce_rate_limit()
try:
request_headers = self.session.headers.copy()
request_headers = cast('CaseInsensitiveDict[Any]', self.session.headers).copy()
if headers:
request_headers.update(headers)
@@ -260,7 +264,7 @@ class APIHelper:
)
response.raise_for_status()
return response.json()
return cast(Optional[Dict[Any, Any]], response.json())
except requests.exceptions.RequestException as e:
self.logger.error(f"POST request failed for {url}: {e}")