"""Connection pool for backend services.""" import asyncio from typing import Any, Protocol class ConnectionProtocol(Protocol): """Protocol for poolable connections.""" async def connect(self) -> None: ... async def close(self) -> None: ... def is_alive(self) -> bool: ... class ConnectionPool: """Async connection pool with health checks and automatic recycling. Generic pool for database connections, HTTP clients, or any poolable resource. """ def __init__( self, factory: Any, min_size: int = 2, max_size: int = 10, max_idle_seconds: float = 300.0, ) -> None: self._factory = factory self._min_size = min_size self._max_size = max_size self._max_idle = max_idle_seconds self._available: asyncio.Queue[Any] = asyncio.Queue(maxsize=max_size) self._in_use: int = 0 self._total_created: int = 0 self._initialized = False async def initialize(self) -> None: """Pre-populate pool with min_size connections.""" if self._initialized: return for _ in range(self._min_size): conn = await self._create_connection() await self._available.put(conn) self._initialized = True async def _create_connection(self) -> Any: """Create a new connection via the factory.""" conn = self._factory() if hasattr(conn, 'connect'): await conn.connect() self._total_created += 1 return conn async def acquire(self) -> Any: """Acquire a connection from the pool.""" if not self._initialized: await self.initialize() try: conn = self._available.get_nowait() if hasattr(conn, 'is_alive') and not conn.is_alive(): conn = await self._create_connection() except asyncio.QueueEmpty: if self._in_use + self._available.qsize() < self._max_size: conn = await self._create_connection() else: conn = await self._available.get() self._in_use += 1 return conn async def release(self, conn: Any) -> None: """Return a connection to the pool.""" self._in_use -= 1 try: self._available.put_nowait(conn) except asyncio.QueueFull: if hasattr(conn, 'close'): await conn.close() async def close_all(self) -> None: """Close all connections in the pool.""" while not self._available.empty(): conn = self._available.get_nowait() if hasattr(conn, 'close'): await conn.close() self._initialized = False self._in_use = 0 def stats(self) -> dict[str, int]: """Return pool statistics.""" return { "available": self._available.qsize(), "in_use": self._in_use, "total_created": self._total_created, "max_size": self._max_size, }