""" Database connection pool for StarPunk Per ADR-053 and developer Q&A Q2: - Provides connection pooling for improved performance - Integrates with Flask's g object for request-scoped connections - Maintains same interface as get_db() for transparency - Pool statistics available for metrics Note: Migrations use direct connections (not pooled) for isolation """ import sqlite3 from pathlib import Path from threading import Lock from collections import deque from flask import g class ConnectionPool: """ Simple connection pool for SQLite SQLite doesn't benefit from traditional connection pooling like PostgreSQL, but this provides connection reuse and request-scoped connection management. """ def __init__(self, db_path, pool_size=5, timeout=10.0): """ Initialize connection pool Args: db_path: Path to SQLite database file pool_size: Maximum number of connections in pool timeout: Timeout for getting connection (seconds) """ self.db_path = Path(db_path) self.pool_size = pool_size self.timeout = timeout self._pool = deque(maxlen=pool_size) self._lock = Lock() self._stats = { 'connections_created': 0, 'connections_reused': 0, 'connections_closed': 0, 'pool_hits': 0, 'pool_misses': 0, } def _create_connection(self): """Create a new database connection""" conn = sqlite3.connect( self.db_path, timeout=self.timeout, check_same_thread=False # Allow connection reuse across threads ) conn.row_factory = sqlite3.Row # Return rows as dictionaries # Enable WAL mode for better concurrency conn.execute("PRAGMA journal_mode=WAL") self._stats['connections_created'] += 1 return conn def get_connection(self): """ Get a connection from the pool Returns: sqlite3.Connection: Database connection """ with self._lock: if self._pool: # Reuse existing connection conn = self._pool.pop() self._stats['pool_hits'] += 1 self._stats['connections_reused'] += 1 return conn else: # Create new connection self._stats['pool_misses'] += 1 return self._create_connection() def return_connection(self, conn): """ Return a connection to the pool Args: conn: Database connection to return """ if not conn: return with self._lock: if len(self._pool) < self.pool_size: # Return to pool self._pool.append(conn) else: # Pool is full, close connection conn.close() self._stats['connections_closed'] += 1 def close_connection(self, conn): """ Close a connection without returning to pool Args: conn: Database connection to close """ if conn: conn.close() self._stats['connections_closed'] += 1 def get_stats(self): """ Get pool statistics Returns: dict: Pool statistics for monitoring """ with self._lock: return { **self._stats, 'pool_size': len(self._pool), 'max_pool_size': self.pool_size, } def close_all(self): """Close all connections in the pool""" with self._lock: while self._pool: conn = self._pool.pop() conn.close() self._stats['connections_closed'] += 1 # Global pool instance (initialized by app factory) _pool = None def init_pool(app): """ Initialize the connection pool Args: app: Flask application instance """ global _pool db_path = app.config['DATABASE_PATH'] pool_size = app.config.get('DB_POOL_SIZE', 5) timeout = app.config.get('DB_TIMEOUT', 10.0) _pool = ConnectionPool(db_path, pool_size, timeout) app.logger.info(f"Database connection pool initialized (size={pool_size})") # Register teardown handler @app.teardown_appcontext def close_connection(error): """Return connection to pool when request context ends""" conn = g.pop('db', None) if conn: _pool.return_connection(conn) def get_db(app=None): """ Get database connection for current request Uses Flask's g object for request-scoped connection management. Connection is automatically returned to pool at end of request. Args: app: Flask application (optional, for backward compatibility with tests) When provided, this parameter is ignored as we use the pool Returns: sqlite3.Connection: Database connection """ # Note: app parameter is kept for backward compatibility but ignored # The pool is request-scoped via Flask's g object if 'db' not in g: g.db = _pool.get_connection() return g.db def get_pool_stats(): """ Get connection pool statistics Returns: dict: Pool statistics for monitoring """ if _pool: return _pool.get_stats() return {}