B2D-agentic-ai / agentic_core /project_store.py
AMRYB's picture
Upload 91 files
287f3d3 verified
Raw
History Blame Contribute Delete
4.27 kB
"""SQLite-backed persistence for projects.
Projects are stored as JSON blobs in a single SQLite database file
(``data/b2d.db``). The public API matches the previous JSON-file store, so the
rest of the system is unchanged: ``create``, ``save``, ``load``, ``list_ids``.
"""
from __future__ import annotations
import sqlite3
import uuid
from pathlib import Path
from .schemas import ProjectContext
_SCHEMA = """
CREATE TABLE IF NOT EXISTS projects (
project_id TEXT PRIMARY KEY,
data TEXT NOT NULL,
status TEXT,
updated_at TEXT
);
"""
class ProjectStore:
def __init__(self, db_path: Path, legacy_dir: Path | None = None):
self._path = Path(db_path)
self._path.parent.mkdir(parents=True, exist_ok=True)
self._init_db()
if legacy_dir is not None:
self._migrate_legacy(legacy_dir)
def _connect(self) -> sqlite3.Connection:
conn = sqlite3.connect(self._path)
conn.row_factory = sqlite3.Row
return conn
def _init_db(self) -> None:
with self._connect() as conn:
conn.execute(_SCHEMA)
def _migrate_legacy(self, legacy_dir: Path) -> None:
"""One-time import of projects saved as JSON files by the old store."""
legacy_dir = Path(legacy_dir)
if not legacy_dir.is_dir():
return
with self._connect() as conn:
existing = {
row["project_id"]
for row in conn.execute("SELECT project_id FROM projects").fetchall()
}
for path in sorted(legacy_dir.glob("*.json")):
try:
context = ProjectContext.model_validate_json(
path.read_text(encoding="utf-8")
)
except Exception:
continue
if context.project_id in existing:
continue
conn.execute(
"INSERT INTO projects (project_id, data, status, updated_at) "
"VALUES (?, ?, ?, ?)",
(
context.project_id,
context.model_dump_json(indent=2),
context.status,
context.updated_at.isoformat(),
),
)
existing.add(context.project_id)
def create(self, business_idea: str, project_id: str | None = None) -> ProjectContext:
project_id = project_id or f"proj_{uuid.uuid4().hex[:10]}"
context = ProjectContext(project_id=project_id, business_idea=business_idea)
self.save(context)
return context
def save(self, context: ProjectContext) -> None:
with self._connect() as conn:
conn.execute(
"INSERT INTO projects (project_id, data, status, updated_at) "
"VALUES (?, ?, ?, ?) "
"ON CONFLICT(project_id) DO UPDATE SET "
"data = excluded.data, status = excluded.status, "
"updated_at = excluded.updated_at",
(
context.project_id,
context.model_dump_json(indent=2),
context.status,
context.updated_at.isoformat(),
),
)
def load(self, project_id: str) -> ProjectContext | None:
with self._connect() as conn:
row = conn.execute(
"SELECT data FROM projects WHERE project_id = ?", (project_id,)
).fetchone()
if row is None:
return None
return ProjectContext.model_validate_json(row["data"])
def list_ids(self) -> list[str]:
with self._connect() as conn:
rows = conn.execute(
"SELECT project_id FROM projects ORDER BY project_id"
).fetchall()
return [row["project_id"] for row in rows]
def delete(self, project_id: str) -> bool:
with self._connect() as conn:
cursor = conn.execute(
"DELETE FROM projects WHERE project_id = ?", (project_id,)
)
return cursor.rowcount > 0