| """
|
| Async HTTP Client with Retry Logic
|
| """
|
|
|
| import aiohttp
|
| import asyncio
|
| from typing import Dict, Optional, Any
|
| from datetime import datetime
|
| import logging
|
|
|
| logger = logging.getLogger(__name__)
|
|
|
|
|
| class APIClient:
|
| def __init__(self, timeout: int = 10, max_retries: int = 3):
|
| self.timeout = aiohttp.ClientTimeout(total=timeout)
|
| self.max_retries = max_retries
|
| self.session: Optional[aiohttp.ClientSession] = None
|
|
|
| async def __aenter__(self):
|
| self.session = aiohttp.ClientSession(timeout=self.timeout)
|
| return self
|
|
|
| async def __aexit__(self, exc_type, exc_val, exc_tb):
|
| if self.session:
|
| await self.session.close()
|
|
|
| async def get(
|
| self,
|
| url: str,
|
| headers: Optional[Dict] = None,
|
| params: Optional[Dict] = None,
|
| retry_count: int = 0
|
| ) -> Dict[str, Any]:
|
| """Make GET request with retry logic"""
|
| start_time = datetime.utcnow()
|
|
|
| try:
|
| async with self.session.get(url, headers=headers, params=params) as response:
|
| elapsed_ms = int((datetime.utcnow() - start_time).total_seconds() * 1000)
|
|
|
|
|
| try:
|
| data = await response.json()
|
| except:
|
| data = await response.text()
|
|
|
| return {
|
| "success": response.status == 200,
|
| "status_code": response.status,
|
| "data": data,
|
| "response_time_ms": elapsed_ms,
|
| "error": None if response.status == 200 else {
|
| "type": "http_error",
|
| "message": f"HTTP {response.status}"
|
| }
|
| }
|
|
|
| except asyncio.TimeoutError:
|
| elapsed_ms = int((datetime.utcnow() - start_time).total_seconds() * 1000)
|
|
|
| if retry_count < self.max_retries:
|
| logger.warning(f"Timeout for {url}, retrying ({retry_count + 1}/{self.max_retries})")
|
| await asyncio.sleep(2 ** retry_count)
|
| return await self.get(url, headers, params, retry_count + 1)
|
|
|
| return {
|
| "success": False,
|
| "status_code": 0,
|
| "data": None,
|
| "response_time_ms": elapsed_ms,
|
| "error": {"type": "timeout", "message": "Request timeout"}
|
| }
|
|
|
| except aiohttp.ClientError as e:
|
| elapsed_ms = int((datetime.utcnow() - start_time).total_seconds() * 1000)
|
|
|
| return {
|
| "success": False,
|
| "status_code": 0,
|
| "data": None,
|
| "response_time_ms": elapsed_ms,
|
| "error": {"type": "client_error", "message": str(e)}
|
| }
|
|
|
| except Exception as e:
|
| elapsed_ms = int((datetime.utcnow() - start_time).total_seconds() * 1000)
|
|
|
| logger.error(f"Unexpected error for {url}: {e}")
|
|
|
| return {
|
| "success": False,
|
| "status_code": 0,
|
| "data": None,
|
| "response_time_ms": elapsed_ms,
|
| "error": {"type": "unknown", "message": str(e)}
|
| }
|
|
|