init
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
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
|
||||
Reference in New Issue
Block a user