Add full type annotations and black formatting across all modules

Annotated all functions with mypy --strict-compatible types (-> None, dict[str,
Any], Generator types, etc.), added # type: ignore for untyped third-party libs
(lxml), and reformatted with black. All 18 source files now pass mypy --strict
with zero errors.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Edward Betts 2026-05-25 21:48:53 +01:00
parent 453d6244ec
commit 13c4341f3a
14 changed files with 1802 additions and 974 deletions

View file

@ -6,6 +6,8 @@ Returns per-train cheapest standard-class fare with restrictions already applied
Cache for 30 days fares rarely change.
"""
from typing import Any, Generator
import httpx
_API_URL = "https://api.gwr.com/api/shopping/journeysearch"
@ -16,7 +18,7 @@ _WALKON_CODES = {"SSS", "SVS", "SDS", "CDS"}
_MAX_PAGES = 20
def _headers() -> dict:
def _headers() -> dict[str, str]:
return {
"user-agent": (
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 "
@ -37,7 +39,7 @@ def _request_body(
travel_date: str,
conversation_token: str | None,
later: bool,
) -> dict:
) -> dict[str, Any]:
return {
"IsNextOutward": False,
"IsPreviousOutward": False,
@ -83,7 +85,7 @@ def _run_pages(
travel_date: str,
first_class: bool = False,
direction: str = "to_paddington",
):
) -> Generator[tuple[str, list[Any]], None, None]:
"""
Iterate all pages of GWR journey search results.
@ -96,7 +98,9 @@ def _run_pages(
later = False
from_code, to_code = _od_codes(station_crs, direction)
for _ in range(_MAX_PAGES):
body = _request_body(from_code, to_code, travel_date, conversation_token, later)
body = _request_body(
from_code, to_code, travel_date, conversation_token, later
)
if first_class:
body["firstclass"] = True
body["standardclass"] = False
@ -121,7 +125,7 @@ def _run_pages_batched(
travel_date: str,
first_class: bool = False,
direction: str = "to_paddington",
):
) -> Generator[list[tuple[str, list[Any]]], None, None]:
"""
Like _run_pages but yields one list of (dep_time, fares_list) per API page call,
allowing callers to stream results a page at a time.
@ -132,7 +136,9 @@ def _run_pages_batched(
later = False
from_code, to_code = _od_codes(station_crs, direction)
for _ in range(_MAX_PAGES):
body = _request_body(from_code, to_code, travel_date, conversation_token, later)
body = _request_body(
from_code, to_code, travel_date, conversation_token, later
)
if first_class:
body["firstclass"] = True
body["standardclass"] = False
@ -157,7 +163,7 @@ def _run_pages_batched(
def fetch(
station_crs: str, travel_date: str, direction: str = "to_paddington"
) -> dict[str, dict]:
) -> dict[str, dict[str, Any]]:
"""
Fetch GWR walk-on single fares for the selected Paddington direction.
@ -165,7 +171,7 @@ def fetch(
where price is in £ and only the cheapest available standard-class walk-on
ticket per departure (with restrictions already applied by GWR) is kept.
"""
result: dict[str, dict] = {}
result: dict[str, dict[str, Any]] = {}
for dep_time, fares in _run_pages(station_crs, travel_date, direction=direction):
cheapest = None
for fare in fares:
@ -193,7 +199,7 @@ def fetch(
def fetch_advance(
station_crs: str, travel_date: str, direction: str = "to_paddington"
) -> dict[str, dict]:
) -> dict[str, dict[str, Any]]:
"""
Fetch advance fares: cheapest standard advance and first-class advance per departure.
@ -201,7 +207,7 @@ def fetch_advance(
Returns {departure_time: {'advance_std': dict or None, 'advance_1st': dict or None}}
where each sub-dict has keys 'ticket', 'price', 'code'.
"""
std_advance: dict[str, dict] = {}
std_advance: dict[str, dict[str, Any]] = {}
for dep_time, fares in _run_pages(
station_crs, travel_date, first_class=False, direction=direction
):
@ -227,7 +233,7 @@ def fetch_advance(
"code": cheapest["code"],
}
first_advance: dict[str, dict] = {}
first_advance: dict[str, dict[str, Any]] = {}
for dep_time, fares in _run_pages(
station_crs, travel_date, first_class=True, direction=direction
):
@ -260,7 +266,7 @@ def fetch_advance(
def fetch_advance_streaming(
station_crs: str, travel_date: str, direction: str = "to_paddington"
):
) -> Generator[dict[str, dict[str, Any]], None, None]:
"""
Generator yielding partial advance fare dicts one GWR API page at a time.
@ -272,7 +278,7 @@ def fetch_advance_streaming(
for batch in _run_pages_batched(
station_crs, travel_date, first_class=False, direction=direction
):
page: dict[str, dict] = {}
page: dict[str, dict[str, Any]] = {}
for dep_time, fares in batch:
cheapest = None
for fare in fares: