This commit is contained in:
2026-09-28 17:10:20 +03:00
commit 59db0dee99
13 changed files with 1029 additions and 0 deletions
+56
View File
@@ -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