|
2 | 2 | import logging |
3 | 3 | from itertools import chain |
4 | 4 | from typing import Union, Sequence |
5 | | -from sqlalchemy.engine import URL |
| 5 | +from sqlalchemy.engine import Engine, URL |
6 | 6 | from sqlmodel import create_engine, Session, SQLModel, select, text |
7 | 7 | from utils.core.models import ( |
8 | 8 | Account, |
|
28 | 28 | # --- Database connection functions --- |
29 | 29 |
|
30 | 30 |
|
| 31 | +_engine_cache: dict[str, Engine] = {} |
| 32 | + |
| 33 | + |
| 34 | +def get_engine() -> Engine: |
| 35 | + """Returns a process-wide cached engine for the current connection URL. |
| 36 | +
|
| 37 | + Calling create_engine() per request/call leaks pooled connections until |
| 38 | + Python's GC reclaims them (engines aren't freed by simple refcounting), |
| 39 | + which can exhaust Postgres's max_connections under sustained load. Cache |
| 40 | + by URL rather than as a single global so tests that swap DB_NAME within |
| 41 | + the same process (e.g. browser vs. browser-csrf DBs) still get one |
| 42 | + engine per database instead of sharing a mismatched pool. |
| 43 | + """ |
| 44 | + url = get_connection_url() |
| 45 | + key = str(url) |
| 46 | + engine = _engine_cache.get(key) |
| 47 | + if engine is None: |
| 48 | + engine = create_engine(url) |
| 49 | + _engine_cache[key] = engine |
| 50 | + return engine |
| 51 | + |
| 52 | + |
31 | 53 | def ensure_database_exists(url: URL) -> None: |
32 | 54 | dbname = url.database |
33 | 55 | server_url = url.set(database="postgres") |
34 | 56 | engine = create_engine(server_url, isolation_level="AUTOCOMMIT") |
35 | | - with engine.connect() as conn: |
36 | | - exists = conn.execute( |
37 | | - text("SELECT 1 FROM pg_database WHERE datname = :n"), |
38 | | - {"n": dbname}, |
39 | | - ).scalar() |
40 | | - if not exists: |
41 | | - conn.execute(text(f'CREATE DATABASE "{dbname}"')) |
| 57 | + try: |
| 58 | + with engine.connect() as conn: |
| 59 | + exists = conn.execute( |
| 60 | + text("SELECT 1 FROM pg_database WHERE datname = :n"), |
| 61 | + {"n": dbname}, |
| 62 | + ).scalar() |
| 63 | + if not exists: |
| 64 | + conn.execute(text(f'CREATE DATABASE "{dbname}"')) |
| 65 | + finally: |
| 66 | + engine.dispose() |
42 | 67 |
|
43 | 68 |
|
44 | 69 | def get_connection_url() -> URL: |
@@ -259,28 +284,32 @@ def set_up_db(drop: bool = False) -> None: |
259 | 284 | drop (bool): If True, drops all existing tables before creating new ones. |
260 | 285 | """ |
261 | 286 | engine = create_engine(get_connection_url()) |
262 | | - if drop: |
263 | | - SQLModel.metadata.drop_all(engine) |
264 | | - # Ensure the private schema exists before creating tables |
265 | | - with engine.connect() as conn: |
266 | | - conn.execute(text("CREATE SCHEMA IF NOT EXISTS private")) |
267 | | - conn.commit() |
268 | | - SQLModel.metadata.create_all(engine) |
269 | | - # Create default permissions and seed account emails |
270 | | - with Session(engine) as session: |
271 | | - create_permissions(session) |
272 | | - session.commit() |
273 | | - seed_account_emails(session) |
274 | | - engine.dispose() |
| 287 | + try: |
| 288 | + if drop: |
| 289 | + SQLModel.metadata.drop_all(engine) |
| 290 | + # Ensure the private schema exists before creating tables |
| 291 | + with engine.connect() as conn: |
| 292 | + conn.execute(text("CREATE SCHEMA IF NOT EXISTS private")) |
| 293 | + conn.commit() |
| 294 | + SQLModel.metadata.create_all(engine) |
| 295 | + # Create default permissions and seed account emails |
| 296 | + with Session(engine) as session: |
| 297 | + create_permissions(session) |
| 298 | + session.commit() |
| 299 | + seed_account_emails(session) |
| 300 | + finally: |
| 301 | + engine.dispose() |
275 | 302 |
|
276 | 303 |
|
277 | 304 | def tear_down_db() -> None: |
278 | 305 | """ |
279 | 306 | Tears down the database by dropping all tables and the private schema. |
280 | 307 | """ |
281 | 308 | engine = create_engine(get_connection_url()) |
282 | | - SQLModel.metadata.drop_all(engine) |
283 | | - with engine.connect() as conn: |
284 | | - conn.execute(text("DROP SCHEMA IF EXISTS private CASCADE")) |
285 | | - conn.commit() |
286 | | - engine.dispose() |
| 309 | + try: |
| 310 | + SQLModel.metadata.drop_all(engine) |
| 311 | + with engine.connect() as conn: |
| 312 | + conn.execute(text("DROP SCHEMA IF EXISTS private CASCADE")) |
| 313 | + conn.commit() |
| 314 | + finally: |
| 315 | + engine.dispose() |
0 commit comments