"""Shared pooled httpx.AsyncClient. Hot paths (scrapers, Telegram send/poll, price feeds, X poster) used to build a fresh AsyncClient per request, paying a TCP+TLS handshake every time. This module owns one process-wide client with keep-alive pooling; callers override the timeout per request (`client.get(url, timeout=10)`). Lifecycle: lazily created on first use; main.py's lifespan closes it on shutdown. Low-frequency daily jobs (KOL/macro fetchers) may keep their own ad-hoc clients — pooling only matters on the per-second paths. """ import logging from typing import Optional import httpx logger = logging.getLogger(__name__) _client: Optional[httpx.AsyncClient] = None def get_client() -> httpx.AsyncClient: global _client if _client is None or _client.is_closed: _client = httpx.AsyncClient( timeout=httpx.Timeout(20.0), follow_redirects=True, limits=httpx.Limits(max_connections=50, max_keepalive_connections=20), ) return _client async def aclose() -> None: global _client if _client is not None and not _client.is_closed: await _client.aclose() _client = None