from dataclasses import dataclass, field from pathlib import Path import yaml @dataclass(slots=True) class OverlayRef: path: str alias: str | None = None @dataclass(slots=True) class InstanceSpec: port: int overlays: list[OverlayRef] = field(default_factory=list) arguments: list[str] = field(default_factory=list) config: list[str] = field(default_factory=list) def _parse_overlay(item) -> OverlayRef: if isinstance(item, str): return OverlayRef(path=item) if isinstance(item, dict): path = item.get("path") if not isinstance(path, str) or not path: raise ValueError(f"overlay entry missing 'path': {item!r}") raw_alias = item.get("alias") alias = str(raw_alias) if raw_alias not in (None, "") else None return OverlayRef(path=path, alias=alias) raise ValueError(f"unsupported overlay entry type: {type(item).__name__}") def load_spec(path: Path) -> InstanceSpec: raw = yaml.safe_load(path.read_text()) or {} return InstanceSpec( port=int(raw["port"]), overlays=[_parse_overlay(item) for item in raw.get("overlays", [])], arguments=[str(item) for item in raw.get("arguments", [])], config=[str(item) for item in raw.get("config", [])], )