|
|
| """Country metadata from the REST Countries API with static fallback."""
|
|
|
| from __future__ import annotations
|
|
|
| from functools import lru_cache
|
| from pathlib import Path
|
| from typing import Any
|
|
|
| import requests
|
| import yaml
|
|
|
| REST_COUNTRIES_URL = (
|
| "https://restcountries.com/v3.1/all"
|
| "?fields=name,cca2,cca3,region,subregion,capital,flags,latlng,area"
|
| )
|
| FALLBACK_PATH = Path(__file__).resolve().parents[1] / "data" / "countries_fallback.yaml"
|
|
|
|
|
| @lru_cache(maxsize=1)
|
| def _fallback_index() -> dict[str, dict[str, Any]]:
|
| with FALLBACK_PATH.open(encoding="utf-8") as handle:
|
| raw = yaml.safe_load(handle) or {}
|
| by_iso2: dict[str, dict[str, Any]] = {}
|
| by_iso3: dict[str, dict[str, Any]] = {}
|
| for iso2, entry in raw.items():
|
| normalized = {
|
| "name": entry["name"],
|
| "lat": float(entry["lat"]),
|
| "lng": float(entry["lng"]),
|
| "cca2": str(entry["cca2"]).upper(),
|
| "cca3": str(entry["cca3"]).upper(),
|
| "region": entry.get("region"),
|
| "subregion": entry.get("subregion"),
|
| "capital": entry.get("capital") or [],
|
| "area": entry.get("area"),
|
| "flag": entry.get("flag"),
|
| "flag_alt": entry.get("flag_alt"),
|
| }
|
| by_iso2[normalized["cca2"]] = normalized
|
| by_iso3[normalized["cca3"]] = normalized
|
| return {"iso2": by_iso2, "iso3": by_iso3}
|
|
|
|
|
| def _entry_from_api_country(country: dict[str, Any]) -> dict[str, Any] | None:
|
| latlng = country.get("latlng")
|
| if not latlng or len(latlng) != 2:
|
| return None
|
|
|
| lat, lng = latlng
|
| if not isinstance(lat, (int, float)) or not isinstance(lng, (int, float)):
|
| return None
|
|
|
| return {
|
| "name": country["name"]["common"],
|
| "lat": float(lat),
|
| "lng": float(lng),
|
| "cca2": country.get("cca2", "").upper(),
|
| "cca3": country.get("cca3", "").upper(),
|
| "region": country.get("region"),
|
| "subregion": country.get("subregion"),
|
| "capital": country.get("capital") or [],
|
| "area": country.get("area"),
|
| "flag": (country.get("flags") or {}).get("png")
|
| or (country.get("flags") or {}).get("svg"),
|
| "flag_alt": (country.get("flags") or {}).get("alt"),
|
| }
|
|
|
|
|
| @lru_cache(maxsize=1)
|
| def _country_index() -> dict[str, dict[str, Any]]:
|
| by_iso2: dict[str, dict[str, Any]] = {}
|
| by_iso3: dict[str, dict[str, Any]] = {}
|
|
|
| try:
|
| resp = requests.get(REST_COUNTRIES_URL, timeout=20)
|
| resp.raise_for_status()
|
| payload = resp.json()
|
| if isinstance(payload, list):
|
| for country in payload:
|
| if not isinstance(country, dict):
|
| continue
|
| entry = _entry_from_api_country(country)
|
| if not entry:
|
| continue
|
| if entry["cca2"]:
|
| by_iso2[entry["cca2"]] = entry
|
| if entry["cca3"]:
|
| by_iso3[entry["cca3"]] = entry
|
| except Exception:
|
| pass
|
|
|
| if by_iso2:
|
| return {"iso2": by_iso2, "iso3": by_iso3}
|
|
|
| return _fallback_index()
|
|
|
|
|
| def lookup_country(code: str) -> dict[str, Any] | None:
|
| normalized = (code or "").strip().upper()
|
| if not normalized:
|
| return None
|
|
|
| index = _country_index()
|
| if len(normalized) == 2:
|
| return index["iso2"].get(normalized)
|
| if len(normalized) == 3:
|
| return index["iso3"].get(normalized)
|
| return index["iso2"].get(normalized) or index["iso3"].get(normalized)
|
|
|