|
|
""" |
|
|
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)} |
|
|
} |
|
|
|