- platform/interface.py: PlatformClient Protocol + Pydantic models (User, MessageResponse, UserSettings) — no explicit session management, Master handles container lifecycle - platform/mock.py: MockPlatformClient with simulated latency, [MOCK] responses, is_new correctly True only on first creation - core/protocol.py: unified dataclasses for all events and responses (IncomingMessage/Command/Callback, OutgoingMessage/UI/Notification, AuthFlow, ChatContext, SettingsAction, etc.) - core/store.py: StateStore Protocol + InMemoryStore (tests) + SQLiteStore (prod) with JSON serialization - core/chat.py: ChatManager — chat metadata (C1/C2/C3), not container lifecycle (that's the platform's job) - core/auth.py: AuthManager — start_flow / confirm / is_authenticated - core/settings.py: SettingsManager — get/apply with store cache - core/handler.py: EventDispatcher — registry-based routing with keys (command name, action name, attachment type, "*" catch-all) - core/handlers/: register_all() + start/new/message/callback/settings handlers; voice slot falls back to stub text until voice_handler added - conftest.py: sys.path fix so local platform/ shadows stdlib platform - docs/api-contract.md: rewritten for Lambda Lab 3.0 container model 46 tests passing, 0 warnings.
73 lines
2.2 KiB
Python
73 lines
2.2 KiB
Python
# core/store.py
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import sqlite3
|
|
from typing import Protocol
|
|
|
|
|
|
class StateStore(Protocol):
|
|
async def get(self, key: str) -> dict | None: ...
|
|
async def set(self, key: str, value: dict) -> None: ...
|
|
async def delete(self, key: str) -> None: ...
|
|
async def keys(self, prefix: str) -> list[str]: ...
|
|
|
|
|
|
class InMemoryStore:
|
|
def __init__(self) -> None:
|
|
self._data: dict[str, dict] = {}
|
|
|
|
async def get(self, key: str) -> dict | None:
|
|
return self._data.get(key)
|
|
|
|
async def set(self, key: str, value: dict) -> None:
|
|
self._data[key] = value
|
|
|
|
async def delete(self, key: str) -> None:
|
|
self._data.pop(key, None)
|
|
|
|
async def keys(self, prefix: str) -> list[str]:
|
|
return [k for k in self._data if k.startswith(prefix)]
|
|
|
|
|
|
class SQLiteStore:
|
|
def __init__(self, db_path: str) -> None:
|
|
self._db_path = db_path
|
|
self._init_db()
|
|
|
|
def _init_db(self) -> None:
|
|
conn = sqlite3.connect(self._db_path)
|
|
conn.execute(
|
|
"CREATE TABLE IF NOT EXISTS kv (key TEXT PRIMARY KEY, value TEXT NOT NULL)"
|
|
)
|
|
conn.commit()
|
|
conn.close()
|
|
|
|
async def get(self, key: str) -> dict | None:
|
|
conn = sqlite3.connect(self._db_path)
|
|
row = conn.execute("SELECT value FROM kv WHERE key = ?", (key,)).fetchone()
|
|
conn.close()
|
|
return json.loads(row[0]) if row else None
|
|
|
|
async def set(self, key: str, value: dict) -> None:
|
|
conn = sqlite3.connect(self._db_path)
|
|
conn.execute(
|
|
"INSERT OR REPLACE INTO kv (key, value) VALUES (?, ?)",
|
|
(key, json.dumps(value, default=str)),
|
|
)
|
|
conn.commit()
|
|
conn.close()
|
|
|
|
async def delete(self, key: str) -> None:
|
|
conn = sqlite3.connect(self._db_path)
|
|
conn.execute("DELETE FROM kv WHERE key = ?", (key,))
|
|
conn.commit()
|
|
conn.close()
|
|
|
|
async def keys(self, prefix: str) -> list[str]:
|
|
conn = sqlite3.connect(self._db_path)
|
|
rows = conn.execute(
|
|
"SELECT key FROM kv WHERE key LIKE ?", (prefix + "%",)
|
|
).fetchall()
|
|
conn.close()
|
|
return [row[0] for row in rows]
|