Initial commit

This commit is contained in:
2026-06-26 16:08:31 +05:30
commit f49193ee73
23 changed files with 6056 additions and 0 deletions

72
core/http_client.py Normal file
View File

@@ -0,0 +1,72 @@
"""Shared aiohttp session with connection pooling and exponential-backoff retry."""
import asyncio
from typing import Optional, Dict, Any
import aiohttp
from core.logger import logger
_session: Optional[aiohttp.ClientSession] = None
def _new_session() -> aiohttp.ClientSession:
connector = aiohttp.TCPConnector(limit=100, limit_per_host=20, keepalive_timeout=30)
return aiohttp.ClientSession(
connector=connector,
timeout=aiohttp.ClientTimeout(total=30),
)
async def get_session() -> aiohttp.ClientSession:
global _session
if _session is None or _session.closed:
_session = _new_session()
return _session
async def close_session():
global _session
if _session and not _session.closed:
await _session.close()
_session = None
async def api_get(url: str, **kwargs) -> Optional[Dict[str, Any]]:
return await _request("get", url, **kwargs)
async def api_post(url: str, **kwargs) -> Optional[Dict[str, Any]]:
return await _request("post", url, **kwargs)
async def api_patch(url: str, **kwargs) -> Optional[Dict[str, Any]]:
return await _request("patch", url, **kwargs)
async def _request(method: str, url: str, **kwargs) -> Optional[Dict[str, Any]]:
"""Execute an HTTP request with up to 3 attempts and exponential backoff."""
max_retries = 3
backoff = 1.0
session = await get_session()
for attempt in range(max_retries):
try:
async with getattr(session, method)(url, **kwargs) as resp:
if resp.content_type == "application/json":
return await resp.json()
return {"status_code": resp.status}
except aiohttp.ClientError as exc:
if attempt < max_retries - 1:
logger.warning(
f"{method.upper()} {url} attempt {attempt + 1} failed: {exc} "
f"— retrying in {backoff:.0f}s"
)
await asyncio.sleep(backoff)
backoff *= 2
else:
logger.error(f"{method.upper()} {url} failed after {max_retries} attempts: {exc}")
except Exception as exc:
logger.error(f"{method.upper()} {url} unexpected error: {exc}")
break
return None