modelbeast/server/db.py
MODELBEAST 605b1ae347 Phase 1 + framework: settings/secrets, queue lanes, job mgmt, inbox, 8 new operators
Framework:
- server/settings.py: key/value settings + secrets, env-injected into operator
  subprocesses, secret values masked in API and redacted from job logs
- runner: gpu/cpu/net concurrency lanes, job cancel/retry/delete, multi-input,
  graceful 'not installed' error when a tool venv is missing
- db: settings table, asset_ids column (migrated), MODELBEAST_DATA test override
- main: settings + job-action endpoints, inbox watch folder auto-ingest
- store: operators can tag output asset kind (splat, colmap_dataset)

Operators (11 total):
- fal_trellis/trellis2/hunyuan3d/rodin via shared _lib/fal_common.py (verified
  params + endpoint ids; recursive result-URL extractor handles per-endpoint keys)
- sf3d, trellis_mac: local MPS image-to-3D, installed with Metal kernels built,
  gated on owner HuggingFace auth
- colmap_poses (COLMAP 4.x + GLOMAP global mapper), brush_train (native Metal 3DGS)
- Scan pipeline validated end-to-end through the UI: frames -> colmap (48/48
  registered, 0.6px) -> brush -> splat.ply -> in-app SplatViewer

Frontend:
- Settings modal, operator gating (lock + disabled run when requires_env unmet),
  job cancel/retry/delete, Compare grid (multi-select side-by-side viewers),
  SplatViewer (gaussian-splats-3d, Ply format forced for extensionless URLs)

Tooling: scripts/install_{colmap,brush,sf3d,trellis_mac}.sh; vendor/ + venvs/
gitignored; tests/smoke.sh (12 checks passing); BENCHMARKS.md

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 21:42:27 +10:00

88 lines
2.3 KiB
Python

import json
import os
import sqlite3
import time
import uuid
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
# Tests / alternate roots can override the data dir.
DATA = Path(os.environ.get("MODELBEAST_DATA", str(ROOT / "data")))
DB_PATH = DATA / "modelbeast.db"
SCHEMA = """
CREATE TABLE IF NOT EXISTS assets (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
kind TEXT NOT NULL,
path TEXT NOT NULL,
size INTEGER NOT NULL DEFAULT 0,
meta TEXT NOT NULL DEFAULT '{}',
parent_job TEXT,
created_at REAL NOT NULL
);
CREATE TABLE IF NOT EXISTS jobs (
id TEXT PRIMARY KEY,
operator TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'queued',
asset_id TEXT,
asset_ids TEXT NOT NULL DEFAULT '[]',
params TEXT NOT NULL DEFAULT '{}',
outdir TEXT,
log TEXT NOT NULL DEFAULT '',
error TEXT,
created_at REAL NOT NULL,
started_at REAL,
finished_at REAL
);
CREATE TABLE IF NOT EXISTS settings (
key TEXT PRIMARY KEY,
value TEXT NOT NULL DEFAULT ''
);
"""
# Columns added after the original Phase 0 schema; applied idempotently so an
# existing data/modelbeast.db upgrades in place.
MIGRATIONS = [
("jobs", "asset_ids", "TEXT NOT NULL DEFAULT '[]'"),
]
def connect() -> sqlite3.Connection:
DATA.mkdir(parents=True, exist_ok=True)
# FastAPI sync endpoints run in a threadpool; python sqlite3 is built in
# serialized threading mode, so sharing one connection across threads is safe.
con = sqlite3.connect(DB_PATH, check_same_thread=False)
con.row_factory = sqlite3.Row
con.execute("PRAGMA journal_mode=WAL")
con.executescript(SCHEMA)
_migrate(con)
return con
def _migrate(con: sqlite3.Connection) -> None:
for table, column, decl in MIGRATIONS:
cols = {r["name"] for r in con.execute(f"PRAGMA table_info({table})")}
if column not in cols:
con.execute(f"ALTER TABLE {table} ADD COLUMN {column} {decl}")
con.commit()
def new_id() -> str:
return uuid.uuid4().hex[:12]
def now() -> float:
return time.time()
def row_to_dict(row: sqlite3.Row) -> dict:
d = dict(row)
for key in ("meta", "params", "asset_ids"):
if key in d and isinstance(d[key], str):
try:
d[key] = json.loads(d[key])
except ValueError:
pass
return d