from __future__ import annotations import logging from typing import Any import httpx from app.config import Settings logger = logging.getLogger(__name__) class BaseHTTPClient: def __init__(self, settings: Settings, name: str, base_url: str | None) -> None: self.settings = settings self.name = name self.base_url = str(base_url).rstrip("/") if base_url else None async def _request_json( self, method: str, path: str, *, headers: dict[str, str] | None = None, params: dict[str, Any] | None = None, auth: tuple[str, str] | None = None, ) -> Any | None: response = await self._request( method, path, headers=headers, params=params, auth=auth, ) if response is None: return None try: return response.json() except ValueError: logger.warning("%s returned non-JSON payload for %s", self.name, path) return None async def _request_text( self, method: str, path: str, *, headers: dict[str, str] | None = None, params: dict[str, Any] | None = None, auth: tuple[str, str] | None = None, ) -> str | None: response = await self._request( method, path, headers=headers, params=params, auth=auth, ) if response is None: return None return response.text async def _request( self, method: str, path: str, *, headers: dict[str, str] | None = None, params: dict[str, Any] | None = None, auth: tuple[str, str] | None = None, ) -> httpx.Response | None: if not self.base_url: logger.info("%s client skipped because base URL is not configured", self.name) return None url = f"{self.base_url}/{path.lstrip('/')}" try: async with httpx.AsyncClient( timeout=self.settings.request_timeout_seconds, trust_env=False, ) as client: response = await client.request( method, url, headers=headers, params=params, auth=auth, ) response.raise_for_status() return response except httpx.TimeoutException: logger.warning("%s request timed out: %s %s", self.name, method, url) except httpx.HTTPStatusError as exc: logger.warning( "%s request failed with status %s for %s %s", self.name, exc.response.status_code, method, url, ) except httpx.HTTPError as exc: logger.warning("%s request error for %s %s: %s", self.name, method, url, exc) return None