from __future__ import annotations
import pathlib
import sys
try:
import nbformat
except ImportError:
sys.stderr.write(
"error: Python package `nbformat` is required.\ninstall with: pip install nbformat\n"
)
sys.exit(1)
def _has_binary_outputs(nb) -> bool:
for cell in nb.get("cells", []):
for output in cell.get("outputs", []):
data = output.get("data") or {}
for mime in data:
if mime.startswith(("image/", "audio/", "video/")) and mime != "image/svg+xml":
return True
if mime.startswith("application/") and mime not in (
"application/json",
"application/javascript",
):
return True
return False
def main() -> int:
here = pathlib.Path(__file__).resolve().parent
fixtures_dir = here / "notebooks"
expected_dir = fixtures_dir / "expected"
expected_dir.mkdir(exist_ok=True)
written: list[str] = []
skipped: list[tuple[str, str]] = []
for path in sorted(fixtures_dir.glob("*.ipynb")):
try:
nb = nbformat.read(str(path), as_version=nbformat.NO_CONVERT)
if nb.get("nbformat") != 4 or nb.get("nbformat_minor", 0) < 5:
skipped.append(
(
path.name,
f"not v4.5 (nbformat={nb.get('nbformat')}.{nb.get('nbformat_minor')})",
)
)
continue
nbformat.validate(nb)
except Exception as exc:
skipped.append((path.name, f"{type(exc).__name__}: {exc}"))
continue
if path.name.startswith("invalid"):
skipped.append((path.name, "intentionally invalid (Rust crate rejects)"))
continue
if _has_binary_outputs(nb):
skipped.append((path.name, "binary MIME outputs (unrelated serialize divergence)"))
continue
target = expected_dir / path.name
with target.open("w", encoding="utf-8") as fh:
nbformat.write(nb, fh)
written.append(path.name)
print(f"Wrote {len(written)} expected files to {expected_dir.relative_to(here.parent)}:")
for name in written:
print(f" + {name}")
if skipped:
print(f"\nSkipped {len(skipped)} fixtures:")
for name, reason in skipped:
print(f" - {name}: {reason}")
return 0
if __name__ == "__main__":
sys.exit(main())