import os from dataclasses import dataclass from cnet import HttpError, as_dict, as_float, as_str, fetch_json class GeoError(Exception): ... @dataclass(frozen=True) class Geo: city: str lat: float | None lon: float | None def _parse_float(text: str) -> float | None: try: return float(text) except ValueError: return None def _get_coords(data: dict[str, object]) -> tuple[float, float] | None: loc = data.get("loc") if isinstance(loc, str): parts = loc.split(",", 1) if len(parts) == 2: lat = _parse_float(parts[0]) lon = _parse_float(parts[1]) if lat is not None and lon is not None: return lat, lon lat = as_float(data.get("latitude")) lon = as_float(data.get("longitude")) if lat is not None and lon is not None: return lat, lon return None def get_geo() -> Geo: url = os.getenv("GEO_API_URL", "https://ipinfo.io/json") try: raw = fetch_json(url) data = as_dict(raw) city = as_str(data.get("city")) or as_str(data.get("cityName")) coords = _get_coords(data) if not city or coords is None: raise ValueError("city not found") return Geo(city=city, lat=coords[0], lon=coords[1]) except (HttpError, ValueError) as exc: fallback = os.getenv("GEO_API_FALLBACK_CITY", "").strip() if fallback: print(f"fallback city: {fallback}") return Geo(city=fallback, lat=None, lon=None) raise GeoError(f"No geo: {exc}") from exc