surfaces/core/store.py
Mikhail Putilovskij 36730ae716 feat: implement core/ and platform/ with full test coverage
- 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.
2026-03-29 21:42:02 +03:00

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]