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.
This commit is contained in:
Abdessamad Derraz committed 2026-11-06 20:29:16 +01:00
1 parent ba348b6ca8
commit a9d4eecd13
4 files changed
+16 -11

No files matched your search

-3
View File
@@ -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]
+3 -3
View File
@@ -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)
+12 -4
View File
@@ -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))
+1 -1
View File
@@ -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] = {}