import json from pathlib import Path from typing import Optional import anyio from .models import AppConfig class ConfigStore: def __init__(self, file_path: Path): self._file_path = file_path self._lock = anyio.Lock() def _write_unlocked(self, cfg: AppConfig) -> None: self._file_path.parent.mkdir(parents=True, exist_ok=True) self._file_path.write_text( json.dumps(cfg.model_dump(mode="json"), indent=2, ensure_ascii=False), encoding="utf-8", ) async def load(self) -> AppConfig: async with self._lock: if not self._file_path.exists(): cfg = AppConfig(cameras=[]) self._write_unlocked(cfg) return cfg raw = self._file_path.read_text(encoding="utf-8") data = json.loads(raw) if raw.strip() else {} cfg = AppConfig.model_validate(data) # Auto-fill newly introduced config keys. normalized = cfg.model_dump(mode="json") if normalized != data: self._write_unlocked(cfg) return cfg async def save(self, cfg: AppConfig) -> None: async with self._lock: self._write_unlocked(cfg) def default_store(project_root: Optional[Path] = None) -> ConfigStore: root = project_root or Path(__file__).resolve().parents[2] return ConfigStore(root / "api" / "data" / "config.json")