"""Tiny, dependency-free helpers (stdlib only).""" from __future__ import annotations import json import urllib.request import urllib.error from datetime import datetime, timezone class HttpError(Exception): def __init__(self, status: int, url: str, body: str = ""): super().__init__(f"HTTP {status} for {url}") self.status, self.url, self.body = status, url, body def default_get(url: str, headers: dict | None = None, timeout: int = 15): """Return (status, text). The single network primitive — injected in tests.""" req = urllib.request.Request(url, headers=headers or {}) try: with urllib.request.urlopen(req, timeout=timeout) as r: return getattr(r, "status", r.getcode()), r.read().decode("utf-8") except urllib.error.HTTPError as e: return e.code, e.read().decode("utf-8", "replace") def get_json(url: str, headers: dict | None = None, getter=default_get): status, body = getter(url, headers) if status != 200: raise HttpError(status, url, body) return json.loads(body) def dig(data, path: str): """Read a dotted path out of nested dicts: dig(d, 'a.b') -> d['a']['b'] or None.""" cur = data for part in path.split("."): if isinstance(cur, dict): cur = cur.get(part) else: return None return cur def utcnow() -> str: return datetime.now(timezone.utc).isoformat(timespec="seconds") def fmt(v) -> str: if v is None: return "—" # em dash try: f = float(v) return str(int(f)) if f.is_integer() else str(round(f, 2)) except (TypeError, ValueError): return str(v)