from __future__ import annotations import json from dataclasses import asdict, is_dataclass from pathlib import Path from typing import Any def write_asset_manifest(records: list[Any], path: str | Path, *, metadata: dict[str, Any] | None = None) -> dict[str, Any]: manifest = { "metadata": metadata or {}, "records": [_to_jsonable(record) for record in records], } path = Path(path) path.parent.mkdir(parents=True, exist_ok=True) path.write_text(json.dumps(manifest, indent=2, sort_keys=True)) return manifest def load_asset_manifest(path: str | Path) -> dict[str, Any]: return json.loads(Path(path).read_text()) def _to_jsonable(value: Any) -> Any: if hasattr(value, "to_dict"): return _to_jsonable(value.to_dict()) if is_dataclass(value): return _to_jsonable(asdict(value)) if isinstance(value, dict): return {str(k): _to_jsonable(v) for k, v in value.items()} if isinstance(value, (list, tuple)): return [_to_jsonable(v) for v in value] if isinstance(value, Path): return str(value) return value