79 lines
2.6 KiB
Python
79 lines
2.6 KiB
Python
import httpx
|
|
import logging
|
|
import json
|
|
from cachetools import TTLCache
|
|
from typing import Callable
|
|
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
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
logger.handlers.clear()
|
|
logger.setLevel(logging.INFO)
|
|
|
|
handler = logging.StreamHandler()
|
|
handler.setFormatter(logging.Formatter(
|
|
fmt='%(asctime)s - %(levelname)s - %(message)s',
|
|
datefmt='%H:%M:%S'
|
|
))
|
|
logger.addHandler(handler)
|
|
logger.propagate = False
|
|
|
|
httpx_logger = logging.getLogger("httpx")
|
|
httpx_logger.handlers.clear()
|
|
httpx_logger.setLevel(logging.INFO)
|
|
httpx_logger.addHandler(handler)
|
|
httpx_logger.propagate = False
|
|
|
|
_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),
|
|
)
|
|
|
|
_log_callback: Callable[[str], None] | None = None
|
|
|
|
def set_log_callback(cb: Callable[[str], None] | None):
|
|
global _log_callback
|
|
_log_callback = cb
|
|
|
|
def _log(msg: str):
|
|
logger.info(msg)
|
|
if _log_callback is not None:
|
|
_log_callback(msg)
|
|
|
|
@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:
|
|
_log(f"💾 {icon} Cache hit")
|
|
return _cache[cache_key]
|
|
|
|
_log(f"🔨 {icon} Input parameters:\n{json.dumps(params, ensure_ascii=False, indent=2)}")
|
|
response = await _client.get(url, params=params)
|
|
response.raise_for_status()
|
|
_log(f"🖇️ {icon} Request URL: {response.url}")
|
|
|
|
data = response.json()
|
|
if remove_keys and isinstance(data, dict):
|
|
for key in remove_keys:
|
|
data.pop(key, None)
|
|
|
|
_cache[cache_key] = data
|
|
_log(f"✅ {icon} Success: {url}")
|
|
return data
|
|
|
|
except httpx.HTTPStatusError as e:
|
|
_log(f"❌ {icon}HTTP error: {e.response.status_code} - {e.response.text}")
|
|
return {"error": "http_error", "status_code": e.response.status_code}
|
|
except httpx.RequestError as e:
|
|
_log(f"❌ {icon}Request error: {str(e)}")
|
|
return {"error": "request_error", "status_code": str(e)}
|
|
except Exception as e:
|
|
_log(f"❌ {icon}Unexpected error: {str(e)}")
|
|
return {"error": "unexpected_error", "status_code": str(e)}
|