| from contextlib import contextmanager |
| from typing import Any, Iterator |
|
|
| try: |
| import psycopg2 |
| from psycopg2.extras import RealDictCursor |
| except Exception: |
| psycopg2 = None |
| RealDictCursor = None |
|
|
| from core.config import settings |
| from core.logger import logger |
|
|
|
|
| class Database: |
| def is_configured(self) -> bool: |
| return bool(settings.DATABASE_URL) |
|
|
| def connect(self): |
| if psycopg2 is None or RealDictCursor is None: |
| raise RuntimeError("psycopg2 is not installed in the active Python environment") |
|
|
| if not self.is_configured(): |
| raise RuntimeError("DATABASE_URL is not configured") |
|
|
| return psycopg2.connect( |
| settings.DATABASE_URL, |
| cursor_factory=RealDictCursor, |
| ) |
|
|
| @contextmanager |
| def cursor(self) -> Iterator[Any]: |
| connection = None |
| cursor = None |
|
|
| try: |
| connection = self.connect() |
| cursor = connection.cursor() |
| yield cursor |
| connection.commit() |
| except Exception: |
| if connection is not None: |
| connection.rollback() |
| raise |
| finally: |
| if cursor is not None: |
| cursor.close() |
| if connection is not None: |
| connection.close() |
|
|
| def fetch_all(self, query: str, params: tuple[Any, ...] | None = None) -> list[dict[str, Any]]: |
| if not self.is_configured(): |
| return [] |
|
|
| try: |
| with self.cursor() as cursor: |
| cursor.execute(query, params or ()) |
| return list(cursor.fetchall()) |
| except Exception as exc: |
| logger.warning(f"Database fetch_all failed: {exc}") |
| return [] |
|
|
| def fetch_one(self, query: str, params: tuple[Any, ...] | None = None) -> dict[str, Any] | None: |
| if not self.is_configured(): |
| return None |
|
|
| try: |
| with self.cursor() as cursor: |
| cursor.execute(query, params or ()) |
| return cursor.fetchone() |
| except Exception as exc: |
| logger.warning(f"Database fetch_one failed: {exc}") |
| return None |
|
|
| def execute(self, query: str, params: tuple[Any, ...] | None = None) -> bool: |
| if not self.is_configured(): |
| return False |
|
|
| try: |
| with self.cursor() as cursor: |
| cursor.execute(query, params or ()) |
| return True |
| except Exception as exc: |
| logger.warning(f"Database execute failed: {exc}") |
| return False |
|
|
|
|
| db = Database() |
|
|