mirror of
https://github.com/pacnpal/Pac-cogs.git
synced 2025-12-20 02:41:06 -05:00
diabolical
This commit is contained in:
@@ -10,24 +10,29 @@ import threading
|
||||
from queue import Queue, Empty
|
||||
from datetime import datetime
|
||||
|
||||
#try:
|
||||
# Try relative imports first
|
||||
from ..utils.exceptions import DatabaseError, ErrorContext, ErrorSeverity
|
||||
#except ImportError:
|
||||
# Fall back to absolute imports if relative imports fail
|
||||
# from videoarchiver.utils.exceptions import DatabaseError, ErrorContext, ErrorSeverity
|
||||
# try:
|
||||
# Try relative imports first
|
||||
from utils.exceptions import DatabaseError, ErrorContext, ErrorSeverity
|
||||
|
||||
# except ImportError:
|
||||
# Fall back to absolute imports if relative imports fail
|
||||
# from videoarchiver.utils.exceptions import DatabaseError, ErrorContext, ErrorSeverity
|
||||
|
||||
logger = logging.getLogger("DBConnectionManager")
|
||||
|
||||
|
||||
class ConnectionState(Enum):
|
||||
"""Connection states"""
|
||||
|
||||
AVAILABLE = auto()
|
||||
IN_USE = auto()
|
||||
CLOSED = auto()
|
||||
ERROR = auto()
|
||||
|
||||
|
||||
class ConnectionStatus(TypedDict):
|
||||
"""Type definition for connection status"""
|
||||
|
||||
state: str
|
||||
created_at: str
|
||||
last_used: str
|
||||
@@ -36,8 +41,10 @@ class ConnectionStatus(TypedDict):
|
||||
pool_size: int
|
||||
available_connections: int
|
||||
|
||||
|
||||
class ConnectionMetrics(TypedDict):
|
||||
"""Type definition for connection metrics"""
|
||||
|
||||
total_connections: int
|
||||
active_connections: int
|
||||
idle_connections: int
|
||||
@@ -46,6 +53,7 @@ class ConnectionMetrics(TypedDict):
|
||||
failed_transactions: int
|
||||
average_transaction_time: float
|
||||
|
||||
|
||||
class ConnectionInfo:
|
||||
"""Tracks connection information"""
|
||||
|
||||
@@ -73,6 +81,7 @@ class ConnectionInfo:
|
||||
return 0.0
|
||||
return self.total_transaction_time / self.transaction_count
|
||||
|
||||
|
||||
class DatabaseConnectionManager:
|
||||
"""Manages SQLite database connections and connection pooling"""
|
||||
|
||||
@@ -83,11 +92,11 @@ class DatabaseConnectionManager:
|
||||
def __init__(self, db_path: Path, pool_size: int = DEFAULT_POOL_SIZE) -> None:
|
||||
"""
|
||||
Initialize the connection manager.
|
||||
|
||||
|
||||
Args:
|
||||
db_path: Path to the SQLite database file
|
||||
pool_size: Maximum number of connections in the pool
|
||||
|
||||
|
||||
Raises:
|
||||
DatabaseError: If initialization fails
|
||||
"""
|
||||
@@ -97,14 +106,14 @@ class DatabaseConnectionManager:
|
||||
self._connection_info: Dict[int, ConnectionInfo] = {}
|
||||
self._local = threading.local()
|
||||
self._lock = threading.Lock()
|
||||
|
||||
|
||||
# Initialize connection pool
|
||||
self._initialize_pool()
|
||||
|
||||
def _initialize_pool(self) -> None:
|
||||
"""
|
||||
Initialize the connection pool.
|
||||
|
||||
|
||||
Raises:
|
||||
DatabaseError: If pool initialization fails
|
||||
"""
|
||||
@@ -123,17 +132,17 @@ class DatabaseConnectionManager:
|
||||
"ConnectionManager",
|
||||
"initialize_pool",
|
||||
{"pool_size": self.pool_size},
|
||||
ErrorSeverity.CRITICAL
|
||||
)
|
||||
ErrorSeverity.CRITICAL,
|
||||
),
|
||||
)
|
||||
|
||||
def _create_connection(self) -> Optional[sqlite3.Connection]:
|
||||
"""
|
||||
Create a new database connection with proper settings.
|
||||
|
||||
|
||||
Returns:
|
||||
New database connection or None if creation fails
|
||||
|
||||
|
||||
Raises:
|
||||
DatabaseError: If connection creation fails
|
||||
"""
|
||||
@@ -141,23 +150,23 @@ class DatabaseConnectionManager:
|
||||
conn = sqlite3.connect(
|
||||
self.db_path,
|
||||
detect_types=sqlite3.PARSE_DECLTYPES | sqlite3.PARSE_COLNAMES,
|
||||
timeout=self.CONNECTION_TIMEOUT
|
||||
timeout=self.CONNECTION_TIMEOUT,
|
||||
)
|
||||
|
||||
|
||||
# Enable foreign keys
|
||||
conn.execute("PRAGMA foreign_keys = ON")
|
||||
|
||||
|
||||
# Set journal mode to WAL for better concurrency
|
||||
conn.execute("PRAGMA journal_mode = WAL")
|
||||
|
||||
|
||||
# Set synchronous mode to NORMAL for better performance
|
||||
conn.execute("PRAGMA synchronous = NORMAL")
|
||||
|
||||
|
||||
# Enable extended result codes for better error handling
|
||||
conn.execute("PRAGMA extended_result_codes = ON")
|
||||
|
||||
|
||||
return conn
|
||||
|
||||
|
||||
except sqlite3.Error as e:
|
||||
error = f"Failed to create database connection: {str(e)}"
|
||||
logger.error(error, exc_info=True)
|
||||
@@ -167,18 +176,18 @@ class DatabaseConnectionManager:
|
||||
"ConnectionManager",
|
||||
"create_connection",
|
||||
{"path": str(self.db_path)},
|
||||
ErrorSeverity.HIGH
|
||||
)
|
||||
ErrorSeverity.HIGH,
|
||||
),
|
||||
)
|
||||
|
||||
@contextmanager
|
||||
def get_connection(self) -> Generator[sqlite3.Connection, None, None]:
|
||||
"""
|
||||
Get a database connection from the pool.
|
||||
|
||||
|
||||
Yields:
|
||||
Database connection
|
||||
|
||||
|
||||
Raises:
|
||||
DatabaseError: If unable to get a connection
|
||||
"""
|
||||
@@ -186,7 +195,7 @@ class DatabaseConnectionManager:
|
||||
start_time = datetime.utcnow()
|
||||
try:
|
||||
# Check if we have a transaction-bound connection
|
||||
conn = getattr(self._local, 'transaction_connection', None)
|
||||
conn = getattr(self._local, "transaction_connection", None)
|
||||
if conn is not None:
|
||||
yield conn
|
||||
return
|
||||
@@ -204,8 +213,8 @@ class DatabaseConnectionManager:
|
||||
"ConnectionManager",
|
||||
"get_connection",
|
||||
None,
|
||||
ErrorSeverity.HIGH
|
||||
)
|
||||
ErrorSeverity.HIGH,
|
||||
),
|
||||
)
|
||||
|
||||
# Update connection info
|
||||
@@ -229,32 +238,31 @@ class DatabaseConnectionManager:
|
||||
raise DatabaseError(
|
||||
error,
|
||||
context=ErrorContext(
|
||||
"ConnectionManager",
|
||||
"get_connection",
|
||||
None,
|
||||
ErrorSeverity.HIGH
|
||||
)
|
||||
"ConnectionManager", "get_connection", None, ErrorSeverity.HIGH
|
||||
),
|
||||
)
|
||||
|
||||
finally:
|
||||
if conn and not hasattr(self._local, 'transaction_connection'):
|
||||
if conn and not hasattr(self._local, "transaction_connection"):
|
||||
try:
|
||||
conn.rollback() # Reset connection state
|
||||
self._connection_pool.put(conn)
|
||||
|
||||
|
||||
# Update connection info
|
||||
if id(conn) in self._connection_info:
|
||||
conn_info = self._connection_info[id(conn)]
|
||||
conn_info.state = ConnectionState.AVAILABLE
|
||||
duration = (datetime.utcnow() - start_time).total_seconds()
|
||||
conn_info.total_transaction_time += duration
|
||||
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error returning connection to pool: {e}")
|
||||
try:
|
||||
conn.close()
|
||||
if id(conn) in self._connection_info:
|
||||
self._connection_info[id(conn)].state = ConnectionState.CLOSED
|
||||
self._connection_info[id(conn)].state = (
|
||||
ConnectionState.CLOSED
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@@ -262,22 +270,19 @@ class DatabaseConnectionManager:
|
||||
def transaction(self) -> Generator[sqlite3.Connection, None, None]:
|
||||
"""
|
||||
Start a database transaction.
|
||||
|
||||
|
||||
Yields:
|
||||
Database connection for the transaction
|
||||
|
||||
|
||||
Raises:
|
||||
DatabaseError: If unable to start transaction
|
||||
"""
|
||||
if hasattr(self._local, 'transaction_connection'):
|
||||
if hasattr(self._local, "transaction_connection"):
|
||||
raise DatabaseError(
|
||||
"Nested transactions are not supported",
|
||||
context=ErrorContext(
|
||||
"ConnectionManager",
|
||||
"transaction",
|
||||
None,
|
||||
ErrorSeverity.HIGH
|
||||
)
|
||||
"ConnectionManager", "transaction", None, ErrorSeverity.HIGH
|
||||
),
|
||||
)
|
||||
|
||||
conn = None
|
||||
@@ -293,11 +298,8 @@ class DatabaseConnectionManager:
|
||||
raise DatabaseError(
|
||||
"Failed to create database connection",
|
||||
context=ErrorContext(
|
||||
"ConnectionManager",
|
||||
"transaction",
|
||||
None,
|
||||
ErrorSeverity.HIGH
|
||||
)
|
||||
"ConnectionManager", "transaction", None, ErrorSeverity.HIGH
|
||||
),
|
||||
)
|
||||
|
||||
# Update connection info
|
||||
@@ -330,42 +332,41 @@ class DatabaseConnectionManager:
|
||||
raise DatabaseError(
|
||||
error,
|
||||
context=ErrorContext(
|
||||
"ConnectionManager",
|
||||
"transaction",
|
||||
None,
|
||||
ErrorSeverity.HIGH
|
||||
)
|
||||
"ConnectionManager", "transaction", None, ErrorSeverity.HIGH
|
||||
),
|
||||
)
|
||||
|
||||
finally:
|
||||
if conn:
|
||||
try:
|
||||
# Remove thread-local binding
|
||||
delattr(self._local, 'transaction_connection')
|
||||
|
||||
delattr(self._local, "transaction_connection")
|
||||
|
||||
# Return connection to pool
|
||||
self._connection_pool.put(conn)
|
||||
|
||||
|
||||
# Update connection info
|
||||
if id(conn) in self._connection_info:
|
||||
conn_info = self._connection_info[id(conn)]
|
||||
conn_info.state = ConnectionState.AVAILABLE
|
||||
duration = (datetime.utcnow() - start_time).total_seconds()
|
||||
conn_info.total_transaction_time += duration
|
||||
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error cleaning up transaction: {e}")
|
||||
try:
|
||||
conn.close()
|
||||
if id(conn) in self._connection_info:
|
||||
self._connection_info[id(conn)].state = ConnectionState.CLOSED
|
||||
self._connection_info[id(conn)].state = (
|
||||
ConnectionState.CLOSED
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def close_all(self) -> None:
|
||||
"""
|
||||
Close all connections in the pool.
|
||||
|
||||
|
||||
Raises:
|
||||
DatabaseError: If cleanup fails
|
||||
"""
|
||||
@@ -377,7 +378,9 @@ class DatabaseConnectionManager:
|
||||
try:
|
||||
conn.close()
|
||||
if id(conn) in self._connection_info:
|
||||
self._connection_info[id(conn)].state = ConnectionState.CLOSED
|
||||
self._connection_info[id(conn)].state = (
|
||||
ConnectionState.CLOSED
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Error closing connection: {e}")
|
||||
except Empty:
|
||||
@@ -388,79 +391,74 @@ class DatabaseConnectionManager:
|
||||
raise DatabaseError(
|
||||
error,
|
||||
context=ErrorContext(
|
||||
"ConnectionManager",
|
||||
"close_all",
|
||||
None,
|
||||
ErrorSeverity.HIGH
|
||||
)
|
||||
"ConnectionManager", "close_all", None, ErrorSeverity.HIGH
|
||||
),
|
||||
)
|
||||
|
||||
def get_status(self) -> ConnectionStatus:
|
||||
"""
|
||||
Get current connection manager status.
|
||||
|
||||
|
||||
Returns:
|
||||
Connection status information
|
||||
"""
|
||||
active_connections = sum(
|
||||
1 for info in self._connection_info.values()
|
||||
1
|
||||
for info in self._connection_info.values()
|
||||
if info.state == ConnectionState.IN_USE
|
||||
)
|
||||
|
||||
|
||||
return ConnectionStatus(
|
||||
state="healthy" if active_connections < self.pool_size else "exhausted",
|
||||
created_at=min(
|
||||
info.created_at.isoformat()
|
||||
for info in self._connection_info.values()
|
||||
info.created_at.isoformat() for info in self._connection_info.values()
|
||||
),
|
||||
last_used=max(
|
||||
info.last_used.isoformat()
|
||||
for info in self._connection_info.values()
|
||||
info.last_used.isoformat() for info in self._connection_info.values()
|
||||
),
|
||||
error=None,
|
||||
transaction_count=sum(
|
||||
info.transaction_count
|
||||
for info in self._connection_info.values()
|
||||
info.transaction_count for info in self._connection_info.values()
|
||||
),
|
||||
pool_size=self.pool_size,
|
||||
available_connections=self.pool_size - active_connections
|
||||
available_connections=self.pool_size - active_connections,
|
||||
)
|
||||
|
||||
def get_metrics(self) -> ConnectionMetrics:
|
||||
"""
|
||||
Get connection metrics.
|
||||
|
||||
|
||||
Returns:
|
||||
Connection metrics information
|
||||
"""
|
||||
total_transactions = sum(
|
||||
info.transaction_count
|
||||
for info in self._connection_info.values()
|
||||
)
|
||||
total_errors = sum(
|
||||
info.error_count
|
||||
for info in self._connection_info.values()
|
||||
info.transaction_count for info in self._connection_info.values()
|
||||
)
|
||||
total_errors = sum(info.error_count for info in self._connection_info.values())
|
||||
total_time = sum(
|
||||
info.total_transaction_time
|
||||
for info in self._connection_info.values()
|
||||
info.total_transaction_time for info in self._connection_info.values()
|
||||
)
|
||||
|
||||
|
||||
return ConnectionMetrics(
|
||||
total_connections=len(self._connection_info),
|
||||
active_connections=sum(
|
||||
1 for info in self._connection_info.values()
|
||||
1
|
||||
for info in self._connection_info.values()
|
||||
if info.state == ConnectionState.IN_USE
|
||||
),
|
||||
idle_connections=sum(
|
||||
1 for info in self._connection_info.values()
|
||||
1
|
||||
for info in self._connection_info.values()
|
||||
if info.state == ConnectionState.AVAILABLE
|
||||
),
|
||||
failed_connections=sum(
|
||||
1 for info in self._connection_info.values()
|
||||
1
|
||||
for info in self._connection_info.values()
|
||||
if info.state == ConnectionState.ERROR
|
||||
),
|
||||
total_transactions=total_transactions,
|
||||
failed_transactions=total_errors,
|
||||
average_transaction_time=total_time / total_transactions if total_transactions > 0 else 0.0
|
||||
average_transaction_time=(
|
||||
total_time / total_transactions if total_transactions > 0 else 0.0
|
||||
),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user