71 lines
2.5 KiB
Python
71 lines
2.5 KiB
Python
import sqlite3
|
|
import json
|
|
from typing import Any, Dict, List, Optional
|
|
from datetime import datetime
|
|
from .models import NarrativeBlock
|
|
|
|
|
|
class EventLog:
|
|
"""Simple append-only event log stored in SQLite. Events are JSON with a timestamp."""
|
|
|
|
def __init__(self, path: str = ":memory:"):
|
|
self.path = path
|
|
self._conn = sqlite3.connect(self.path, check_same_thread=False)
|
|
self._init_db()
|
|
|
|
def _init_db(self):
|
|
cur = self._conn.cursor()
|
|
cur.execute("""
|
|
CREATE TABLE IF NOT EXISTS events (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
ts TEXT NOT NULL,
|
|
type TEXT NOT NULL,
|
|
payload TEXT NOT NULL
|
|
)
|
|
""")
|
|
cur.execute("""
|
|
CREATE TABLE IF NOT EXISTS blocks (
|
|
id TEXT PRIMARY KEY,
|
|
ts TEXT NOT NULL,
|
|
block_json TEXT NOT NULL,
|
|
ledger_anchor TEXT
|
|
)
|
|
""")
|
|
self._conn.commit()
|
|
|
|
def append_event(self, event_type: str, payload: Dict[str, Any], ts: Optional[datetime] = None):
|
|
ts = ts or datetime.utcnow()
|
|
cur = self._conn.cursor()
|
|
cur.execute(
|
|
"INSERT INTO events (ts, type, payload) VALUES (?, ?, ?)",
|
|
(ts.isoformat(), event_type, json.dumps(payload, sort_keys=True)),
|
|
)
|
|
self._conn.commit()
|
|
|
|
def read_events(self) -> List[Dict[str, Any]]:
|
|
cur = self._conn.cursor()
|
|
cur.execute("SELECT ts, type, payload FROM events ORDER BY ts, id")
|
|
rows = cur.fetchall()
|
|
out = []
|
|
for ts, type_, payload in rows:
|
|
out.append({"ts": ts, "type": type_, "payload": json.loads(payload)})
|
|
return out
|
|
|
|
def save_block(self, block: NarrativeBlock, ledger_anchor: Optional[str] = None):
|
|
cur = self._conn.cursor()
|
|
# model_dump -> dict, then dump with deterministic keys for ledger
|
|
block_json = json.dumps(block.model_dump(), sort_keys=True, default=str)
|
|
cur.execute(
|
|
"INSERT OR REPLACE INTO blocks (id, ts, block_json, ledger_anchor) VALUES (?, ?, ?, ?)",
|
|
(block.id, block.timestamp.isoformat(), block_json, ledger_anchor),
|
|
)
|
|
self._conn.commit()
|
|
|
|
def get_block(self, block_id: str) -> Optional[NarrativeBlock]:
|
|
cur = self._conn.cursor()
|
|
cur.execute("SELECT block_json FROM blocks WHERE id = ?", (block_id,))
|
|
row = cur.fetchone()
|
|
if not row:
|
|
return None
|
|
return NarrativeBlock.parse_raw(row[0])
|