Initial Commit
This commit is contained in:
commit
4dbdc7d793
18 changed files with 2384 additions and 0 deletions
171
pyqt6_scaffold/core/database.py
Normal file
171
pyqt6_scaffold/core/database.py
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
from .logging import setup_logger
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
log = setup_logger(__name__)
|
||||
|
||||
class CursorContext:
|
||||
"""
|
||||
Proxy wrapper around a database cursor.
|
||||
Manages cursor lifecycle as a context manager and delegates
|
||||
all attribute access to the underlying cursor object.
|
||||
"""
|
||||
def __init__(self, cursor):
|
||||
self._cursor = cursor
|
||||
|
||||
def __enter__(self):
|
||||
"""
|
||||
Return the underlying cursor.
|
||||
"""
|
||||
return self._cursor
|
||||
|
||||
def __exit__(self, *args):
|
||||
"""
|
||||
Close the cursor on exit.
|
||||
"""
|
||||
self._cursor.close()
|
||||
return False
|
||||
|
||||
def __getattr__(self, item):
|
||||
return getattr(self._cursor, item)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._cursor)
|
||||
|
||||
def __next__(self):
|
||||
return next(self._cursor)
|
||||
|
||||
class AbstractDatabase(ABC):
|
||||
"""
|
||||
Abstract base class for database connections.
|
||||
|
||||
Provides a unified interface for connecting, executing queries,
|
||||
and managing transactions. Subclasses must implement _connect()
|
||||
and placeholder.
|
||||
|
||||
Args:
|
||||
strict: If True, raises RuntimeError when connect() is called
|
||||
on an already open connection. If False, closes the
|
||||
existing connection silently.
|
||||
"""
|
||||
def __init__(self, strict: bool = True):
|
||||
self._conn = None
|
||||
self._strict = strict
|
||||
|
||||
@abstractmethod
|
||||
def _connect(self):
|
||||
"""
|
||||
Establish and return a database connection.
|
||||
|
||||
Must be implemented by subclasses. Read configuration from
|
||||
environment variables or any other source, then return a
|
||||
DB-API 2.0 compatible connection object.
|
||||
|
||||
Returns:
|
||||
A database connection object.
|
||||
"""
|
||||
pass
|
||||
|
||||
def connect(self, *args, **kwargs):
|
||||
"""
|
||||
Open the database connection.
|
||||
|
||||
Calls _connect() internally. Should not be overridden.
|
||||
|
||||
Raises:
|
||||
RuntimeError: If connection is already open and strict=True.
|
||||
RuntimeError: If _connect() returns None.
|
||||
"""
|
||||
if self._conn is not None:
|
||||
if self._strict:
|
||||
raise RuntimeError("Database connection already open")
|
||||
else:
|
||||
try:
|
||||
self._conn.close()
|
||||
except Exception as e:
|
||||
log.error(f"Query execution failed: {e}")
|
||||
return None
|
||||
|
||||
conn = self._connect(*args, **kwargs)
|
||||
|
||||
if conn is None:
|
||||
raise RuntimeError("_connect() must return a connection object")
|
||||
|
||||
self._conn = conn
|
||||
return conn
|
||||
|
||||
def disconnect(self):
|
||||
"""
|
||||
Close the database connection and release resources.
|
||||
"""
|
||||
if self._conn is not None:
|
||||
try:
|
||||
self._conn.close()
|
||||
finally:
|
||||
self._conn = None
|
||||
|
||||
@property
|
||||
def connection(self):
|
||||
"""
|
||||
Return the active connection object.
|
||||
|
||||
Raises:
|
||||
RuntimeError: If the database is not connected.
|
||||
"""
|
||||
if self._conn is None:
|
||||
raise RuntimeError("Database not connected")
|
||||
return self._conn
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def placeholder(self) -> str:
|
||||
"""
|
||||
SQL parameter placeholder for the target dialect.
|
||||
Must be implemented by subclasses.
|
||||
"""
|
||||
pass
|
||||
|
||||
def execute(self, sql: str, params: tuple = (), autocommit: bool = False) -> CursorContext | None:
|
||||
"""
|
||||
Execute a SQL query and return a CursorContext.
|
||||
|
||||
Args:
|
||||
sql: SQL query string with placeholders.
|
||||
params: Query parameters as a tuple.
|
||||
autocommit: If True, commits the transaction after execution.
|
||||
|
||||
Returns:
|
||||
CursorContext wrapping the cursor, or raises on failure.
|
||||
|
||||
Raises:
|
||||
Exception: Re-raises any database error after rollback.
|
||||
"""
|
||||
cursor = self.connection.cursor()
|
||||
|
||||
try:
|
||||
cursor.execute(sql, params)
|
||||
|
||||
if autocommit:
|
||||
self.connection.commit()
|
||||
|
||||
return CursorContext(cursor)
|
||||
|
||||
except Exception:
|
||||
self.connection.rollback()
|
||||
log.exception("Query execution failed")
|
||||
raise
|
||||
|
||||
def __enter__(self):
|
||||
"""
|
||||
Support use as a context manager for connection lifecycle.
|
||||
Calls disconnect() on exit.
|
||||
"""
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
"""
|
||||
Support use as a context manager for connection lifecycle.
|
||||
Calls disconnect() on exit.
|
||||
"""
|
||||
self.disconnect()
|
||||
Loading…
Add table
Add a link
Reference in a new issue