from abc import ABC, abstractmethod from typing import Optional, List, Dict, Any import psycopg2 from psycopg2.extras import RealDictCursor from contextlib import contextmanager import logging logger = logging.getLogger(__name__) class BaseRepository(ABC): """Base repository with common database operations""" def __init__(self, db_url: str): self.db_url = db_url @contextmanager def get_db_connection(self): """Get database connection with automatic cleanup""" conn = None try: conn = psycopg2.connect(self.db_url) yield conn except Exception as e: if conn: conn.rollback() logger.error(f"Database error: {e}") raise finally: if conn: conn.close() @contextmanager def get_db_cursor(self, conn): """Get database cursor with dict results""" cursor = None try: cursor = conn.cursor(cursor_factory=RealDictCursor) yield cursor finally: if cursor: cursor.close() def execute_query(self, query: str, params: tuple = None) -> List[Dict[str, Any]]: """Execute SELECT query and return results""" with self.get_db_connection() as conn: with self.get_db_cursor(conn) as cursor: cursor.execute(query, params) return cursor.fetchall() def execute_one(self, query: str, params: tuple = None) -> Optional[Dict[str, Any]]: """Execute query and return single result""" with self.get_db_connection() as conn: with self.get_db_cursor(conn) as cursor: cursor.execute(query, params) return cursor.fetchone() def execute_insert(self, query: str, params: tuple = None) -> Optional[str]: """Execute INSERT query and return ID""" with self.get_db_connection() as conn: with self.get_db_cursor(conn) as cursor: cursor.execute(query + " RETURNING id", params) result = cursor.fetchone() conn.commit() return result['id'] if result else None def execute_update(self, query: str, params: tuple = None) -> int: """Execute UPDATE query and return affected rows""" with self.get_db_connection() as conn: with self.get_db_cursor(conn) as cursor: cursor.execute(query, params) affected = cursor.rowcount conn.commit() return affected def execute_delete(self, query: str, params: tuple = None) -> int: """Execute DELETE query and return affected rows""" with self.get_db_connection() as conn: with self.get_db_cursor(conn) as cursor: cursor.execute(query, params) affected = cursor.rowcount conn.commit() return affected def execute_batch(self, queries: List[tuple]) -> None: """Execute multiple queries in a transaction""" with self.get_db_connection() as conn: with self.get_db_cursor(conn) as cursor: try: for query, params in queries: cursor.execute(query, params) conn.commit() except Exception as e: conn.rollback() raise