idea70-narrativeweave-real-.../idea70_narrativeweave_real_.../storage.py

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])