From a9d4eecd13e16c74de9e587d72eedd31d12ae210 Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Fri, 6 Nov 2026 20:29:16 +0100 Subject: [PATCH] fix: keep main's site commits inside the gates Three site commits pushed to main fail checks CI runs: two prose semicolons, an untyped signature, and GoatCounter answers read as a dict without a test. GoatCounter's stats now go through one reader that refuses an answer that is not an object. --- mypy-baseline.txt | 3 --- scripts/generate_site.py | 6 +++--- scripts/site_insights.py | 16 ++++++++++++---- scripts/sitestarlight.py | 2 +- 4 files changed, 16 insertions(+), 11 deletions(-) diff --git a/mypy-baseline.txt b/mypy-baseline.txt index 7a2310dd3..71fc9d12f 100644 --- a/mypy-baseline.txt +++ b/mypy-baseline.txt @@ -899,9 +899,6 @@ scripts/scraper/targets/rocknix_targets_scraper.py:0: error: Name "github_header scripts/scraper/targets/rocknix_targets_scraper.py:0: error: Name "standalone_emulators" already defined (possibly by an import) [no-redef] scripts/sect233r1.py:0: error: Incompatible types in assignment (expression has type "Optional[tuple[int, int]]", variable has type "tuple[int, int]") [assignment] scripts/site_insights.py:0: error: Item "list[Any]" of "Union[dict[Any, Any], list[Any]]" has no attribute "get" [union-attr] -scripts/site_insights.py:0: error: Item "list[Any]" of "Union[dict[Any, Any], list[Any]]" has no attribute "get" [union-attr] -scripts/site_insights.py:0: error: Item "list[Any]" of "Union[dict[Any, Any], list[Any]]" has no attribute "get" [union-attr] -scripts/site_insights.py:0: error: Item "list[Any]" of "Union[dict[Any, Any], list[Any]]" has no attribute "get" [union-attr] scripts/sitediagrams.py:0: error: "AST" has no attribute "value" [attr-defined] scripts/sitediagrams.py:0: error: Argument 1 to "lstrip" of "bytes" has incompatible type "str"; expected "Optional[Buffer]" [arg-type] scripts/sitediagrams.py:0: error: If x = b'abc' then f"{x}" or "{}".format(x) produces "b'abc'", not "abc". If this is desired behavior, use f"{x!r}" or "{!r}".format(x). Otherwise, decode the bytes [str-bytes-safe] diff --git a/scripts/generate_site.py b/scripts/generate_site.py index 851cefaf6..83b370da7 100644 --- a/scripts/generate_site.py +++ b/scripts/generate_site.py @@ -32,7 +32,7 @@ import urllib.parse import urllib.request import zipfile from collections import Counter -from collections.abc import Callable, Iterator +from collections.abc import Callable, Iterable, Iterator from datetime import datetime, timezone from pathlib import Path from typing import TypeVar @@ -7907,9 +7907,9 @@ def generate_emulators_index(profiles: dict) -> str: return "\n".join(lines) + "\n" -def _snippet_noun(names, firmware: int, fetched: int) -> str: +def _snippet_noun(names: Iterable[str], firmware: int, fetched: int) -> str: """What an emulator's files are called in its snippet. A Switch - emulator reads keys, and "eden keys" is what readers search for; Ship + emulator reads keys, and "eden keys" is what readers search for. Ship of Harkinian reads game ROMs, which are no BIOS.""" names = list(names) keys = sum("key" in name.casefold() for name in names) diff --git a/scripts/site_insights.py b/scripts/site_insights.py index f9edc0bfa..0bfb3701c 100644 --- a/scripts/site_insights.py +++ b/scripts/site_insights.py @@ -123,7 +123,15 @@ def fetch_queries(start: dt.date, end: dt.date) -> list[Query]: return rows -def fetch_goatcounter(start: dt.date, end: dt.date) -> dict[str, list[tuple[str, int]]]: +def _get_object(url: str, headers: dict[str, str] | None = None) -> dict: + """A JSON answer that must be an object: GoatCounter's stats endpoints.""" + answer = _get_json(url, headers) + if not isinstance(answer, dict): + raise ValueError(f"{url} answered {type(answer).__name__}, not an object") + return answer + + +def fetch_goatcounter(start: dt.date, end: dt.date) -> dict[str, list[tuple[str, int]] | int]: """Paths and events with their visit counts, and the referring domains.""" token = (CONFIG / "goatcounter-api-token.txt").read_text(encoding="utf-8").strip() auth = {"Authorization": f"Bearer {token}"} @@ -132,14 +140,14 @@ def fetch_goatcounter(start: dt.date, end: dt.date) -> dict[str, list[tuple[str, exclude: list[str] = [] while True: params = urllib.parse.urlencode({**span, "limit": 100, "exclude_paths": ",".join(exclude)}) - answer = _get_json(f"{GOATCOUNTER}/stats/hits?{params}", auth) + answer = _get_object(f"{GOATCOUNTER}/stats/hits?{params}", auth) for hit in answer.get("hits", []): hits.append((hit["path"] + (" [404]" if hit.get("title") == "404" else ""), int(hit["count"]))) exclude.append(str(hit["path_id"])) if not answer.get("more"): break - refs = _get_json(f"{GOATCOUNTER}/stats/toprefs?{urllib.parse.urlencode({**span, 'limit': 50})}", auth) - total = _get_json(f"{GOATCOUNTER}/stats/total?{urllib.parse.urlencode(span)}", auth) + refs = _get_object(f"{GOATCOUNTER}/stats/toprefs?{urllib.parse.urlencode({**span, 'limit': 50})}", auth) + total = _get_object(f"{GOATCOUNTER}/stats/total?{urllib.parse.urlencode(span)}", auth) # An action carries no referrer, and GoatCounter files it as a direct # visit: 592 of the first day's 1,145 "direct" were clicks. events = int(total.get("total_events", 0)) diff --git a/scripts/sitestarlight.py b/scripts/sitestarlight.py index 4f39e20ce..7bd717e95 100644 --- a/scripts/sitestarlight.py +++ b/scripts/sitestarlight.py @@ -330,7 +330,7 @@ OG_DIR = Path(__file__).resolve().parent.parent / "docs_assets" / "og" # An emulator page's card is that of the maker of the first system it runs, # by page path: "emulators/mgba.mdx" -> "nintendo". One card per emulator -# would be some 900 images in the repository; the maker's names the +# would be some 900 images in the repository. The maker's names the # consoles. Filled by generate_site.record_emulator_page. EMULATOR_CARDS: dict[str, str] = {}