exoskeleton/code/experiments/schema.py

59 lines
1.8 KiB
Python
Raw Normal View History

"""Versioned contracts shared by plans, trial artifacts, and analysis."""
from __future__ import annotations
from collections.abc import Mapping, Sequence
from typing import Any
SCHEMA_VERSION = "1.0.0"
SCHEMA_MAJOR = 1
STORAGE_FORMATS = {
"plan": "json",
"batch_manifest": "json",
"trial_manifest": "json",
"samples": "numpy-npz",
"events": "json-lines",
"trial_metrics": "json-lines",
"paper_source_data": "csv",
}
class ContractError(ValueError):
"""Raised when an evidence artifact violates its declared contract."""
def require_schema(document: Mapping[str, Any], expected_kind: str) -> None:
if not isinstance(document, Mapping):
raise ContractError("document must be a mapping")
if document.get("kind") != expected_kind:
raise ContractError(
f"expected kind {expected_kind!r}, got {document.get('kind')!r}"
)
version = document.get("schema_version")
if not isinstance(version, str):
raise ContractError("schema_version must be a string")
try:
major = int(version.split(".", 1)[0])
except (TypeError, ValueError) as exc:
raise ContractError(f"invalid schema_version {version!r}") from exc
if major != SCHEMA_MAJOR:
raise ContractError(
f"unsupported schema major {major}; expected {SCHEMA_MAJOR}"
)
def require_nonempty_string(value: Any, name: str) -> str:
if not isinstance(value, str) or not value.strip():
raise ContractError(f"{name} must be a non-empty string")
return value
def require_sequence(value: Any, name: str) -> Sequence[Any]:
if isinstance(value, (str, bytes)) or not isinstance(value, Sequence):
raise ContractError(f"{name} must be a sequence")
if not value:
raise ContractError(f"{name} must not be empty")
return value