import json from typing import Any import httpx from pydantic import AliasChoices, BaseModel, Field from app.core.config import settings class TiandituGeocodeRequest(BaseModel): keyword: str = Field( ..., min_length=1, validation_alias=AliasChoices("keyword", "keyWord"), description="地理编码地址关键字", ) class TiandituGeocodingConfigError(RuntimeError): pass class TiandituGeocodingAPIError(RuntimeError): def __init__(self, status_code: int, detail: Any): super().__init__("Tianditu Geocoding API request failed") self.status_code = status_code self.detail = detail async def geocode_tianditu( request: TiandituGeocodeRequest, *, client: httpx.AsyncClient | None = None, ) -> dict[str, Any]: if not settings.TIANDITU_GEOCODER_TOKEN: raise TiandituGeocodingConfigError("TIANDITU_GEOCODER_TOKEN is not configured") params = { "ds": json.dumps({"keyWord": request.keyword}, ensure_ascii=False), "tk": settings.TIANDITU_GEOCODER_TOKEN, } if client is not None: response = await client.get(settings.TIANDITU_GEOCODER_URL, params=params) return _parse_response(response) async with httpx.AsyncClient( timeout=settings.TIANDITU_GEOCODER_TIMEOUT_SECONDS ) as managed_client: response = await managed_client.get( settings.TIANDITU_GEOCODER_URL, params=params, ) return _parse_response(response) def _parse_response(response: httpx.Response) -> dict[str, Any]: try: response.raise_for_status() except httpx.HTTPStatusError as exc: raise TiandituGeocodingAPIError( exc.response.status_code, _response_detail(exc.response), ) from exc data = response.json() if str(data.get("status")) != "0": raise TiandituGeocodingAPIError(502, data) return data def _response_detail(response: httpx.Response) -> Any: try: return response.json() except ValueError: return response.text