Add meta-framework spec, pydantic schemas, profile/extension YAML, extension registry, ext.compose-ssh backend, SandboxManager with State Hub events, CLI commands, integration docs, capability registry entry, and compose-e2e runbook. Nine unit tests pass. T10 remote smoke test remains for operator.
52 lines
No EOL
1.5 KiB
Python
52 lines
No EOL
1.5 KiB
Python
"""Persistent sandbox status store (JSON file)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
from datetime import UTC, datetime
|
|
from pathlib import Path
|
|
|
|
from sandboxer.models import SandboxStatus
|
|
|
|
|
|
def _default_store_path() -> Path:
|
|
base = Path(os.environ.get("XDG_DATA_HOME", Path.home() / ".local" / "share"))
|
|
return base / "sandboxer" / "sandboxes.json"
|
|
|
|
|
|
class SandboxStore:
|
|
def __init__(self, path: Path | None = None) -> None:
|
|
self.path = path or _default_store_path()
|
|
self.path.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
def _read(self) -> dict[str, dict]:
|
|
if not self.path.exists():
|
|
return {}
|
|
return json.loads(self.path.read_text())
|
|
|
|
def _write(self, data: dict[str, dict]) -> None:
|
|
self.path.write_text(json.dumps(data, indent=2, default=str))
|
|
|
|
def save(self, status: SandboxStatus) -> None:
|
|
data = self._read()
|
|
data[status.sandbox_id] = status.model_dump(mode="json")
|
|
self._write(data)
|
|
|
|
def get(self, sandbox_id: str) -> SandboxStatus | None:
|
|
raw = self._read().get(sandbox_id)
|
|
if not raw:
|
|
return None
|
|
return SandboxStatus.model_validate(raw)
|
|
|
|
def list_all(self) -> list[SandboxStatus]:
|
|
return [SandboxStatus.model_validate(v) for v in self._read().values()]
|
|
|
|
def delete(self, sandbox_id: str) -> None:
|
|
data = self._read()
|
|
data.pop(sandbox_id, None)
|
|
self._write(data)
|
|
|
|
|
|
def utcnow() -> datetime:
|
|
return datetime.now(UTC) |