feat(backend): consolidate queue logic for scheduling

This commit is contained in:
2025-08-21 17:17:42 -04:00
parent 8f0dc0d658
commit d0dbba21fb
19 changed files with 1256 additions and 146 deletions

View File

@@ -1,24 +1,170 @@
# backend/database/connection.py
"""
Thin convenience layer over the builtin ``sqlite3`` module.
Why we need a wrapper
---------------------
* The repository (`repository.py`) expects the following public API:
- ``execute`` run an INSERT/UPDATE/DELETE.
- ``fetchone`` / ``fetchall`` run a SELECT and get the result(s).
- ``lastrowid`` primarykey of the most recent INSERT.
- ``close`` close the DB connection.
* A wrapper lets us:
• Set a sensible ``row_factory`` (``sqlite3.Row``) so column names are
accessible as ``row["ColumnName"]``.
• Centralise ``commit``/``rollback`` handling.
• Provide type hints and a contextmanager interface
(``with DatabaseConnection(...):``) which is handy for tests.
* No external dependencies everything stays purePython/SQLite.
The implementation below is deliberately tiny: it only does what the
application needs while remaining easy to extend later (e.g. add
connectionpooling or logging).
"""
from __future__ import annotations
import sqlite3
from typing import List, Tuple
from pathlib import Path
from typing import Any, Iterable, Tuple, List, Optional, Union
class DatabaseConnection:
def __init__(self, db_name: str):
self.conn = sqlite3.connect(db_name)
self.cursor = self.conn.cursor()
"""
Simple wrapper around a SQLite connection.
def close_connection(self):
self.conn.close()
Core behaviour
---------------
* ``row_factory`` is set to :class:`sqlite3.Row` callers can use
``row["col_name"]`` or treat the row like a mapping.
* All ``execute`` calls are automatically committed.
If an exception bubbles out, the transaction is rolled back.
* ``execute`` returns the cursor so callers can chain
``cursor.lastrowid`` if they need the autogenerated PK.
* Implements the contextmanager protocol (``with ... as db:``).
def execute_query(self, query: str, params: Tuple = None):
if params:
self.cursor.execute(query, params)
The public API matches what ``Repository`` expects:
- execute(sql, params=None) → None
- fetchone(sql, params=None) → Optional[sqlite3.Row]
- fetchall(sql, params=None) → List[sqlite3.Row]
- lastrowid → int
- close()
"""
# -----------------------------------------------------------------
# Construction / contextmanager protocol
# -----------------------------------------------------------------
def __init__(
self,
db_path: Union[str, Path],
*,
timeout: float = 5.0,
detect_types: int = sqlite3.PARSE_DECLTYPES | sqlite3.PARSE_COLNAMES,
) -> None:
"""
Parameters
----------
db_path
Path to the SQLite file. ``":memory:"`` works for tests.
timeout
Seconds to wait for a lock before raising ``sqlite3.OperationalError``.
detect_types
Enable type conversion (so DATE/DATETIME are returned as ``datetime``).
"""
self._conn: sqlite3.Connection = sqlite3.connect(
str(db_path), timeout=timeout, detect_types=detect_types
)
# ``Row`` makes column access dictionarylike and preserves order.
self._conn.row_factory = sqlite3.Row
self._cursor: sqlite3.Cursor = self._conn.cursor()
def __enter__(self) -> "DatabaseConnection":
"""Allow ``with DatabaseConnection(...) as db:`` usage."""
return self
def __exit__(self, exc_type, exc_val, exc_tb) -> Optional[bool]:
"""
On normal exit commit the transaction, otherwise roll back.
Returning ``False`` propagates any exception.
"""
if exc_type is None:
try:
self._conn.commit()
finally:
self.close()
else:
self.cursor.execute(query)
self.conn.commit()
# Something went wrong roll back to keep the DB clean.
self._conn.rollback()
self.close()
# ``None`` means “dont suppress exceptions”
return None
def execute_query_with_return(self, query: str, params: Tuple = None):
if params:
self.cursor.execute(query, params)
# -----------------------------------------------------------------
# Lowlevel helpers used by the repository
# -----------------------------------------------------------------
@property
def cursor(self) -> sqlite3.Cursor:
"""Expose the underlying cursor rarely needed outside the repo."""
return self._cursor
@property
def lastrowid(self) -> int:
"""PK of the most recent ``INSERT`` executed on this connection."""
return self._cursor.lastrowid
# -----------------------------------------------------------------
# Public API the four methods used throughout the code base
# -----------------------------------------------------------------
def execute(self, sql: str, params: Optional[Tuple[Any, ...]] = None) -> None:
"""
Run an INSERT/UPDATE/DELETE statement and commit immediately.
``params`` may be ``None`` (no placeholders) or a tuple of values.
"""
try:
if params is None:
self._cursor.execute(sql)
else:
self._cursor.execute(sql, params)
self._conn.commit()
except Exception:
# Ensure we dont leave the connection in a halfcommitted state.
self._conn.rollback()
raise
def fetchone(
self, sql: str, params: Optional[Tuple[Any, ...]] = None
) -> Optional[sqlite3.Row]:
"""
Execute a SELECT that returns at most one row.
Returns ``None`` when the result set is empty.
"""
if params is None:
self._cursor.execute(sql)
else:
self.cursor.execute(query)
return self.cursor.fetchall()
self._cursor.execute(sql, params)
return self._cursor.fetchone()
def fetchall(
self, sql: str, params: Optional[Tuple[Any, ...]] = None
) -> List[sqlite3.Row]:
"""
Execute a SELECT and return **all** rows as a list.
The rows are ``sqlite3.Row`` instances, which behave like dicts.
"""
if params is None:
self._cursor.execute(sql)
else:
self._cursor.execute(sql, params)
return self._cursor.fetchall()
def close(self) -> None:
"""Close the underlying SQLite connection."""
# ``cursor`` is automatically closed when the connection closes,
# but we explicitly close it for clarity.
try:
self._cursor.close()
finally:
self._conn.close()