Python · 1625 bytes Raw Blame History
1 """Load + validate a ``sway.yaml`` into a :class:`SwaySpec`.
2
3 Separated from :mod:`spec` so the data models stay trivially
4 importable (no YAML dependency at import time for callers that
5 construct specs programmatically).
6 """
7
8 from __future__ import annotations
9
10 from pathlib import Path
11 from typing import Any
12
13 import yaml
14 from pydantic import ValidationError
15
16 from dlm_sway.core.errors import SpecValidationError
17 from dlm_sway.suite.spec import SwaySpec
18
19
20 def load_spec(path: Path | str) -> SwaySpec:
21 """Parse ``path`` and return a validated :class:`SwaySpec`."""
22 resolved = Path(path).expanduser().resolve()
23 try:
24 raw_text = resolved.read_text(encoding="utf-8")
25 except FileNotFoundError as exc:
26 raise SpecValidationError(f"spec file not found: {resolved}", source=str(path)) from exc
27
28 try:
29 data = yaml.safe_load(raw_text)
30 except yaml.YAMLError as exc:
31 raise SpecValidationError(f"invalid YAML: {exc}", source=str(path)) from exc
32
33 if not isinstance(data, dict):
34 raise SpecValidationError("top-level document must be a mapping", source=str(path))
35 return from_dict(data, source=str(path))
36
37
38 def from_dict(data: dict[str, Any], *, source: str | None = None) -> SwaySpec:
39 """Validate a dict (already parsed from YAML or JSON) as a SwaySpec."""
40 try:
41 spec = SwaySpec.model_validate(data)
42 except ValidationError as exc:
43 raise SpecValidationError(str(exc), source=source) from exc
44 try:
45 spec.check_version()
46 except ValueError as exc:
47 raise SpecValidationError(str(exc), source=source) from exc
48 return spec