gitea-saas-core/gitea_saas/db.py
pbwraith-worker 6d764d46f6 gitea-saas-core#1: billing ledger core (clients/ledger/holds, idempotent, fail-closed)
SQLite schema + ledger operations in gitea_saas/billing_core.py (+ db.py,
schema.sql). balance = credits + refunds - debits - active holds, never
negative; every mutation idempotent via UNIQUE idempotency_key; holds
expire (default 30 min) and auto-release on the next read/query. All ops
return (ok, details) with structured errors; never raise. Thread-safe via
single lock + WAL. 26 hermetic pytest tests (no network/Stripe/Gitea).
2026-08-25 04:16:35 -05:00

38 lines
1.3 KiB
Python

"""SQLite connection + schema bootstrap for gitea-saas-core.
Pure stdlib (sqlite3, pathlib). No HTTP, no Stripe, no Gitea.
"""
from __future__ import annotations
import sqlite3
from pathlib import Path
SCHEMA_PATH = Path(__file__).resolve().parent / "schema.sql"
def connect(db_path: Path) -> sqlite3.Connection:
"""Open a SQLite connection tuned for the billing core.
- WAL journal: readers never block writers (writes are additionally
serialized by BillingCore's single lock).
- Foreign keys enforced.
- ``check_same_thread=False``: BillingCore serializes all access with
one lock, so a single long-lived connection is safe across threads.
"""
path = Path(db_path)
path.parent.mkdir(parents=True, exist_ok=True)
conn = sqlite3.connect(str(path), check_same_thread=False)
conn.row_factory = sqlite3.Row
conn.execute("PRAGMA journal_mode=WAL")
conn.execute("PRAGMA foreign_keys=ON")
conn.execute("PRAGMA busy_timeout=5000")
return conn
def create_tables(conn: sqlite3.Connection, schema_path: Path | None = None) -> None:
"""Apply schema.sql idempotently (safe to call any number of times)."""
path = Path(schema_path) if schema_path is not None else SCHEMA_PATH
with open(path, encoding="utf-8") as f:
conn.executescript(f.read())