58 lines
1.4 KiB
Python
58 lines
1.4 KiB
Python
|
|
"""Manifest loading and validation — Issue #2."""
|
||
|
|
import yaml
|
||
|
|
from typing import Optional
|
||
|
|
|
||
|
|
|
||
|
|
def validate_profile(profile: dict) -> dict:
|
||
|
|
"""Validate and normalize a single manifest profile entry.
|
||
|
|
|
||
|
|
Required fields: id, name, model_path.
|
||
|
|
Optional field: flags (defaults to {}).
|
||
|
|
"""
|
||
|
|
for field in ("id", "name", "model_path"):
|
||
|
|
if field not in profile:
|
||
|
|
raise ValueError(f"Missing required field: {field}")
|
||
|
|
|
||
|
|
return {
|
||
|
|
"id": profile["id"],
|
||
|
|
"name": profile["name"],
|
||
|
|
"model_path": profile["model_path"],
|
||
|
|
"flags": profile.get("flags", {}),
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
def load_manifest(path: str) -> Optional[list]:
|
||
|
|
"""Load and validate profiles from a YAML manifest file.
|
||
|
|
|
||
|
|
Returns a list of validated profile dicts, or None on any error.
|
||
|
|
"""
|
||
|
|
try:
|
||
|
|
with open(path, "r") as f:
|
||
|
|
content = f.read()
|
||
|
|
except (FileNotFoundError, OSError):
|
||
|
|
return None
|
||
|
|
|
||
|
|
if not content.strip():
|
||
|
|
return []
|
||
|
|
|
||
|
|
try:
|
||
|
|
data = yaml.safe_load(content)
|
||
|
|
except yaml.YAMLError:
|
||
|
|
return None
|
||
|
|
|
||
|
|
if data is None or data == []:
|
||
|
|
return []
|
||
|
|
|
||
|
|
if not isinstance(data, list):
|
||
|
|
return None
|
||
|
|
|
||
|
|
profiles = []
|
||
|
|
for item in data:
|
||
|
|
try:
|
||
|
|
profiles.append(validate_profile(item))
|
||
|
|
except ValueError:
|
||
|
|
# Skip invalid profiles rather than failing the whole manifest
|
||
|
|
continue
|
||
|
|
|
||
|
|
return profiles
|