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).
38 lines
1.3 KiB
Python
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())
|