AX-Meeting-Studio / tools /verify_assets.py
Nnow2024's picture
Upload 8 files
2c57606 verified
Raw
History Blame Contribute Delete
3.17 kB
#!/usr/bin/env python3
"""Validate external model or third-party asset manifests without loading them."""
from __future__ import annotations
import argparse
import hashlib
import json
import sys
from pathlib import Path
from typing import Any, Iterable
def _digest(path: Path) -> str:
hasher = hashlib.sha256()
with path.open("rb") as source:
for chunk in iter(lambda: source.read(1024 * 1024), b""):
hasher.update(chunk)
return hasher.hexdigest()
def _model_entries(manifest: dict[str, Any], backend: str | None) -> Iterable[dict[str, Any]]:
yield from manifest.get("common", [])
if backend:
try:
yield from manifest["backends"][backend]
except KeyError as error:
available = ", ".join(sorted(manifest.get("backends", {})))
raise ValueError(f"Unknown backend {backend!r}; expected one of: {available}") from error
else:
for entries in manifest.get("backends", {}).values():
yield from entries
def _third_party_entries(manifest: dict[str, Any]) -> Iterable[dict[str, Any]]:
for entry in manifest.get("assets", []):
yield entry
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--manifest", required=True, type=Path)
parser.add_argument("--root", required=True, type=Path)
parser.add_argument("--backend", help="validate one model backend instead of every model asset")
parser.add_argument(
"--verify-sha256",
action="store_true",
help="also calculate and compare SHA-256 values (optional, potentially slow)",
)
parser.add_argument("--verify-size", action="store_true", help="also compare recorded byte sizes")
args = parser.parse_args()
manifest = json.loads(args.manifest.read_text(encoding="utf-8"))
if "backends" in manifest:
entries = list(_model_entries(manifest, args.backend))
else:
if args.backend:
parser.error("--backend is only valid for a model manifest")
entries = list(_third_party_entries(manifest))
errors: list[str] = []
for entry in entries:
path = args.root / entry["path" if "path" in entry else "target"]
label = entry.get("role", entry.get("id", path.name))
if not path.is_file():
errors.append(f"missing {label}: {path}")
continue
expected_size = entry.get("size_bytes")
if args.verify_size and expected_size is not None and path.stat().st_size != expected_size:
errors.append(f"wrong size for {label}: {path} (expected {expected_size}, got {path.stat().st_size})")
continue
if args.verify_sha256 and entry.get("sha256") and _digest(path) != entry["sha256"]:
errors.append(f"SHA-256 mismatch for {label}: {path}")
if errors:
print("Asset verification failed:", file=sys.stderr)
print("\n".join(f"- {error}" for error in errors), file=sys.stderr)
return 1
print(f"Verified {len(entries)} asset(s) from {args.manifest} against {args.root}")
return 0
if __name__ == "__main__":
raise SystemExit(main())