Files
jarca-ledger-mcp/jarca_ledger_mcp/infrastructure/sqlite/connection.py
T

70 lines
2.0 KiB
Python

from __future__ import annotations
import os
import sqlite3
from contextlib import contextmanager
from pathlib import Path
from typing import Iterator
from urllib.parse import quote
DEFAULT_DB_PATH = Path.home() / ".local" / "share" / "jarca" / "ledger.sqlite3"
class DatabaseNotInitializedError(RuntimeError):
"""Raised when a read is requested before the ledger exists."""
def resolve_db_path(db_path: str | None = None) -> Path:
raw = db_path or os.getenv("JARCA_LEDGER_DB_PATH") or str(DEFAULT_DB_PATH)
return Path(raw).expanduser().resolve()
def connect(db_path: str | None = None) -> sqlite3.Connection:
path = resolve_db_path(db_path)
path.parent.mkdir(parents=True, exist_ok=True)
conn = sqlite3.connect(path)
conn.row_factory = sqlite3.Row
conn.execute("PRAGMA foreign_keys=ON")
return conn
def connect_readonly(db_path: str | None = None) -> sqlite3.Connection:
"""Open an existing ledger in SQLite URI read-only mode."""
path = resolve_db_path(db_path)
if not path.is_file():
raise DatabaseNotInitializedError("ledger database is not initialized")
uri = f"file:{quote(str(path), safe='/')}?mode=ro"
try:
conn = sqlite3.connect(uri, uri=True)
except sqlite3.Error as exc:
raise DatabaseNotInitializedError("ledger database is not initialized") from exc
conn.row_factory = sqlite3.Row
conn.execute("PRAGMA foreign_keys=ON")
return conn
@contextmanager
def read_db(db_path: str | None = None) -> Iterator[sqlite3.Connection]:
"""Open an existing ledger without filesystem or transaction writes."""
conn = connect_readonly(db_path)
try:
yield conn
finally:
conn.close()
@contextmanager
def db(db_path: str | None = None) -> Iterator[sqlite3.Connection]:
from .migrations import init_db
init_db(db_path)
conn = connect(db_path)
try:
yield conn
conn.commit()
except Exception:
conn.rollback()
raise
finally:
conn.close()