|
1 | 1 | import json |
2 | 2 | import logging |
3 | 3 | import psycopg2 |
| 4 | +from psycopg2 import pool |
4 | 5 | from datetime import datetime, timezone |
5 | 6 | from typing import Dict, List, Optional |
6 | 7 |
|
7 | 8 | logger = logging.getLogger(__name__) |
8 | 9 |
|
| 10 | +# Connection pool defaults |
| 11 | +DEFAULT_POOL_MIN_CONN = 2 |
| 12 | +DEFAULT_POOL_MAX_CONN = 10 |
| 13 | + |
9 | 14 |
|
10 | 15 | class Database: |
11 | | - """PostgreSQL database manager for Hell Divers 2 API data""" |
| 16 | + """PostgreSQL database manager for Hell Divers 2 API data with connection pooling""" |
12 | 17 |
|
13 | 18 | def __init__(self, database_url: Optional[str] = None): |
14 | 19 | """ |
15 | | - Initialize database connection |
16 | | - |
| 20 | + Initialize database connection pool |
| 21 | +
|
17 | 22 | Args: |
18 | 23 | database_url: PostgreSQL connection string (postgresql://user:pass@host:port/db) |
19 | 24 | If None, will try to get from DATABASE_URL env var |
20 | 25 | """ |
21 | 26 | if database_url is None: |
22 | 27 | import os |
23 | 28 | database_url = os.getenv("DATABASE_URL", "") |
24 | | - |
| 29 | + |
25 | 30 | if not database_url: |
26 | 31 | raise ValueError("DATABASE_URL must be provided") |
27 | | - |
| 32 | + |
28 | 33 | self.database_url = database_url |
29 | 34 | self._initialized = False |
30 | | - # Lazy initialization - only connect when first needed |
| 35 | + self._pool: Optional[pool.ThreadedConnectionPool] = None |
| 36 | + |
| 37 | + def _get_pool(self) -> pool.ThreadedConnectionPool: |
| 38 | + """Get or create the connection pool (lazy init)""" |
| 39 | + if self._pool is None: |
| 40 | + self._pool = pool.ThreadedConnectionPool( |
| 41 | + minconn=DEFAULT_POOL_MIN_CONN, |
| 42 | + maxconn=DEFAULT_POOL_MAX_CONN, |
| 43 | + dsn=self.database_url, |
| 44 | + ) |
| 45 | + logger.info("Database connection pool initialized") |
| 46 | + return self._pool |
31 | 47 |
|
32 | 48 | def _get_connection(self): |
33 | | - """Get a database connection""" |
34 | | - return psycopg2.connect(self.database_url) |
| 49 | + """Get a database connection from the pool (returns to pool on close).""" |
| 50 | + conn = self._get_pool().getconn() |
| 51 | + # Wrap so conn.close() returns to pool instead of closing the underlying connection |
| 52 | + def _close(): |
| 53 | + self._get_pool().putconn(conn) |
| 54 | + |
| 55 | + conn.close = _close |
| 56 | + return conn |
| 57 | + |
| 58 | + def close_pool(self): |
| 59 | + """Close the connection pool. Call on application shutdown.""" |
| 60 | + if self._pool is not None: |
| 61 | + self._pool.closeall() |
| 62 | + self._pool = None |
| 63 | + logger.info("Database connection pool closed") |
35 | 64 |
|
36 | 65 | @staticmethod |
37 | 66 | def _parse_expiration_time(expiration_time: str) -> Optional[datetime]: |
|
0 commit comments