import httpx import logging from pydantic import BaseModel from cachetools import TTLCache from tenacity import retry, stop_after_attempt, wait_exponential from api.config import HTTP_TIMEOUT, HTTP_MAX_CONNECTIONS, HTTP_MAX_KEEPALIVE, CACHE_TTL, CACHE_MAX_SIZE _cache = TTLCache(maxsize=CACHE_MAX_SIZE, ttl=CACHE_TTL) _client = httpx.AsyncClient( timeout=httpx.Timeout(HTTP_TIMEOUT), limits=httpx.Limits(max_connections=HTTP_MAX_CONNECTIONS, max_keepalive_connections=HTTP_MAX_KEEPALIVE), ) logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) logger = logging.getLogger(__name__) def docstring_from_model(model: type[BaseModel]): def decorator(func): if func.__doc__: func.__doc__ = func.__doc__.format( params="\n".join( f"\t\t- {name}: {field.description or 'No description'}" for name, field in model.model_fields.items() ) ) return func return decorator @retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=1, max=5)) async def fetch_api_data(icon: str, url: str, params: dict, remove_keys: list = None) -> dict: try: cache_key = f"{url}:{sorted(params.items())}" if cache_key in _cache: logger.info(f"\n💾 {icon}Cache hit: {cache_key}\n") return _cache[cache_key] logger.info(f"\n🔨 {icon} Input parameters: {params}\n") response = await _client.get(url, params=params) response.raise_for_status() logger.debug(f"\n🖇️ {icon} Request URL: {response.url}\n") data = response.json() if remove_keys and isinstance(data, dict): for key in remove_keys: data.pop(key, None) logger.info(f"\n✅ {icon} Success: {url}") _cache[cache_key] = data return data except httpx.HTTPStatusError as e: logger.error(f"\n{icon}HTTP error: {e.response.status_code} - {e.response.text}\n") return {"error": "http_error", "status_code": e.response.status_code} except httpx.RequestError as e: logger.error(f"\n{icon}Request error: {str(e)}\n") return {"error": "request_error", "status_code": str(e)} except Exception as e: logger.critical(f"\n{icon}Unexpected error: {str(e)}\n", exc_info=True) return {"error": "unexpected_error", "status_code": str(e)}