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