legal-ai-assistant/backend/tools/api/http_request_handler.py

84 lines
2.6 KiB
Python

import httpx
from cachetools import TTLCache
from tenacity import retry, stop_after_attempt, wait_exponential
import urllib.parse
from backend.logger import setup_logger
from configs import (
JUSTICE_API_BASE,
HTTP_TIMEOUT,
HTTP_MAX_CONNECTIONS,
HTTP_MAX_KEEPALIVE,
CACHE_TTL,
CACHE_MAX_SIZE,
RETRY_ATTEMPTS,
RETRY_MAX_WAIT,
RETRY_MIN_WAIT,
RETRY_MULTIPLIER,
)
WAIT_EXPONENTIAL = wait_exponential(
multiplier=RETRY_MULTIPLIER,
min=RETRY_MIN_WAIT,
max=RETRY_MAX_WAIT
)
STOP_AFTER_ATTEMPT = stop_after_attempt(RETRY_ATTEMPTS)
CACHE = TTLCache(maxsize=CACHE_MAX_SIZE, ttl=CACHE_TTL)
TIMEOUT = httpx.Timeout(HTTP_TIMEOUT)
LIMITS = httpx.Limits(
max_connections=HTTP_MAX_CONNECTIONS,
max_keepalive_connections=HTTP_MAX_KEEPALIVE
)
logger = setup_logger(__name__)
def get_client() -> httpx.AsyncClient:
return httpx.AsyncClient(timeout=TIMEOUT, limits=LIMITS)
@retry(stop=STOP_AFTER_ATTEMPT, wait=WAIT_EXPONENTIAL)
async def http_request(route: str, params: dict | None, remove_keys: list[str] | None = None) -> dict:
"""Sends an HTTP GET request and returns the parsed JSON response."""
url = f"{JUSTICE_API_BASE}{route}"
params_tuple = tuple(sorted(params.items())) if params else ()
cache_key = f"{url}:{params_tuple}"
if cache_key in CACHE:
return CACHE[cache_key]
returned_url = url
try:
logger.info(f"[HTTP REQUEST] | {url} | params={params}")
async with get_client() as client:
if params:
query_string = urllib.parse.urlencode(
params,
doseq=True,
quote_via=urllib.parse.quote
)
final_url = f"{url}?{query_string}"
response = await client.get(final_url)
else:
response = await client.get(url)
response.raise_for_status()
logger.info(f"[HTTP SUCCESS] | {url} | status={response.status_code}")
returned_url = str(response.url)
data = response.json()
if remove_keys and isinstance(data, dict):
for key in remove_keys:
data.pop(key, None)
result = {"url": returned_url, "data": data}
CACHE[cache_key] = result
return result
except httpx.HTTPError as e:
logger.error(f"[HTTP ERROR] | {returned_url} | {str(e)}")
return {"error": "http_error", "detail": str(e)}
except Exception as e:
logger.error(f"[UNEXPECTED ERROR] | {returned_url} | {str(e)}")
return {"error": "unexpected_error", "detail": str(e)}