Research-Stack/4-Infrastructure/NoDupeLabs/nodupe/tools/databases/repository_interface.py

186 lines
6.3 KiB
Python

"""Database Repository Interface.
Generic repository pattern for database operations.
Key Features:
- Generic CRUD operations for any table
- Type-safe parameter handling
- Error handling and validation
- Connection management
- Standard library only (no external dependencies)
Dependencies:
- sqlite3 (standard library)
- typing (standard library)
"""
import sqlite3
from typing import Dict, Any, Optional, List
class RepositoryError(Exception):
"""Repository operation error"""
class DatabaseRepository:
"""Generic database repository pattern for CRUD operations.
Provides a consistent interface for database operations across tables.
"""
def __init__(self, connection: sqlite3.Connection):
"""Initialize repository with database connection.
Args:
connection: SQLite database connection
"""
self.connection = connection
def create(self, table: str, data: Dict[str, Any]) -> int:
"""Create a new record in the specified table."""
try:
cursor = self.connection.cursor()
keys = ", ".join(data.keys())
placeholders = ", ".join(["?"] * len(data))
values = list(data.values())
cursor.execute(f"INSERT INTO {table} ({keys}) VALUES ({placeholders})", values)
self.connection.commit()
return cursor.lastrowid
except sqlite3.Error as e:
raise RepositoryError(f"Failed to create record in {table}: {e}") from e
def read(self, table: str, record_id: int) -> Optional[Dict[str, Any]]:
"""Read a record by ID from the specified table."""
try:
cursor = self.connection.cursor()
cursor.execute(f"SELECT * FROM {table} WHERE id = ?", (record_id,))
row = cursor.fetchone()
if not row:
return None
columns = [description[0] for description in cursor.description]
return dict(zip(columns, row))
except sqlite3.Error as e:
raise RepositoryError(f"Failed to read record from {table}: {e}") from e
def read_all(self, table: str, where: Optional[Dict[str, Any]] = None) -> List[Dict[str, Any]]:
"""Read all records from the specified table.
Args:
table: Name of the table
where: Optional dictionary of column names to values for filtering
Returns:
List of dictionaries of column names to values
Raises:
RepositoryError: If read operation fails
"""
try:
cursor = self.connection.cursor()
if where:
# Build WHERE clause
where_clause = ' AND '.join([f"{k} = ?" for k in where.keys()])
query = f"SELECT * FROM {table} WHERE {where_clause}"
cursor.execute(query, list(where.values()))
else:
cursor.execute(f"SELECT * FROM {table}")
rows = cursor.fetchall()
if not rows:
return []
# Get column names
columns = [description[0] for description in cursor.description]
# Build list of dictionaries
return [dict(zip(columns, row)) for row in rows]
except sqlite3.Error as e:
raise RepositoryError(f"Failed to read records from {table}: {e}") from e
def update(self, table: str, record_id: int, data: Dict[str, Any]) -> bool:
"""Update a record in the specified table."""
try:
cursor = self.connection.cursor()
set_clause = ', '.join([f"{k} = ?" for k in data.keys()])
values = list(data.values()) + [record_id]
cursor.execute(f"UPDATE {table} SET {set_clause} WHERE id = ?", values)
self.connection.commit()
return cursor.rowcount > 0
except sqlite3.Error as e:
raise RepositoryError(f"Failed to update record in {table}: {e}") from e
def delete(self, table: str, record_id: int) -> bool:
"""Delete a record from the specified table."""
try:
cursor = self.connection.cursor()
cursor.execute(f"DELETE FROM {table} WHERE id = ?", (record_id,))
self.connection.commit()
return cursor.rowcount > 0
except sqlite3.Error as e:
raise RepositoryError(f"Failed to delete record from {table}: {e}") from e
def count(self, table: str, where: Optional[Dict[str, Any]] = None) -> int:
"""Count records in the specified table.
Args:
table: Name of the table
where: Optional dictionary of column names to values for filtering
Returns:
Number of records
Raises:
RepositoryError: If count operation fails
"""
try:
cursor = self.connection.cursor()
if where:
# Build WHERE clause
where_clause = ' AND '.join([f"{k} = ?" for k in where.keys()])
query = f"SELECT COUNT(*) FROM {table} WHERE {where_clause}"
cursor.execute(query, list(where.values()))
else:
cursor.execute(f"SELECT COUNT(*) FROM {table}")
result = cursor.fetchone()
return result[0] if result else 0
except sqlite3.Error as e:
raise RepositoryError(f"Failed to count records in {table}: {e}") from e
def exists(self, table: str, record_id: int) -> bool:
"""Check if a record exists in the specified table.
Args:
table: Name of the table
record_id: ID of the record to check
Returns:
True if record exists, False otherwise
Raises:
RepositoryError: If check fails
"""
try:
cursor = self.connection.cursor()
cursor.execute(f"SELECT 1 FROM {table} WHERE id = ? LIMIT 1", (record_id,))
return cursor.fetchone() is not None
except sqlite3.Error as e:
raise RepositoryError(f"Failed to check existence in {table}: {e}") from e
def create_repository(connection: sqlite3.Connection) -> DatabaseRepository:
"""Create a database repository instance.
Args:
connection: SQLite database connection
Returns:
DatabaseRepository instance
"""
return DatabaseRepository(connection)