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