Execute ONE notebook directly (no cache, no pool) and show its error immediately. The debug loop: run one → read the traceback → fix the .md → run again. On success, add it to the cache so a later build reuses it.
(page)
| 409 | |
| 410 | |
| 411 | def exec_single(page): |
| 412 | """Execute ONE notebook directly (no cache, no pool) and show its error |
| 413 | immediately. The debug loop: run one → read the traceback → fix the .md → |
| 414 | run again. On success, add it to the cache so a later build reuses it.""" |
| 415 | name = page.removesuffix(".ipynb").removesuffix(".md") |
| 416 | md = f"{name}.md" if name.startswith("source/") else f"source/{name}.md" |
| 417 | if not Path(md).exists(): |
| 418 | print(f"❌ source not found: {md}") |
| 419 | sys.exit(2) |
| 420 | nb = Path(md).with_suffix(".ipynb") |
| 421 | |
| 422 | # Compile + normalize the kernel so execution doesn't trip on NoSuchKernel. |
| 423 | run_command(["python", "-m", "jupytext", "--to", "notebook", md]) |
| 424 | import nbformat |
| 425 | n = nbformat.read(nb, 4) |
| 426 | n.metadata["kernelspec"] = {"name": "python3", "display_name": "Python 3"} |
| 427 | nbformat.write(n, nb) |
| 428 | |
| 429 | # Execute directly — a cell error prints its FULL traceback to the console. |
| 430 | print(f"\n⚡ Executing {nb} (timeout {EXEC_TIMEOUT}s/cell)...\n") |
| 431 | result = run_command([ |
| 432 | "python", "-m", "jupyter", "nbconvert", "--to", "notebook", "--execute", |
| 433 | "--inplace", f"--ExecutePreprocessor.timeout={EXEC_TIMEOUT}", str(nb), |
| 434 | ], check=False) |
| 435 | |
| 436 | if result.returncode == 0: |
| 437 | print(f"\n✅ {nb} executed cleanly") |
| 438 | jcache(["cache", "add", str(nb)], check=False) # feed the build cache |
| 439 | else: |
| 440 | print(f"\n❌ {nb} failed — full traceback above. Fix {md} and re-run.") |
| 441 | sys.exit(result.returncode) |
| 442 | |
| 443 | |
| 444 | def _summarize_render(warnfile: Path, renderlog: Path, returncode: int) -> int: |
no test coverage detected
searching dependent graphs…