57 lines
1.5 KiB
Python
57 lines
1.5 KiB
Python
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
|