#!/usr/bin/env python from __future__ import annotations import argparse import concurrent.futures import json import os import shutil import signal import subprocess import sys import tempfile import threading import time import typing from dataclasses import dataclass from multiprocessing import current_process from pathlib import Path from typing import Dict, Optional, Tuple, Union from termcolor import colored try: import yaml except ImportError: print("pyyaml not found.\n\nPlease install pyyaml:\n\tpip install pyyaml\n") sys.exit(1) try: import nbclient from nbclient.client import ( CellExecutionError, CellTimeoutError, NotebookClient, ) except ImportError: if current_process().name == "MainProcess": print("nbclient not found.\n\nPlease install nbclient:\n\tpip install nbclient\n") print("test won't work without nbclient") try: import nbformat from nbformat import NotebookNode except ImportError: if current_process().name == "MainProcess": print("nbformat not found.\n\nPlease install nbformat:\n\tpip install nbformat\n") print("test won't work without nbclient") class Result: def __init__(self, returncode: int, stdout: str, stderr: str): self.returncode = returncode self.stdout = stdout self.stderr = stderr def check_quarto_bin(quarto_bin: str = "quarto") -> None: """Check if quarto is installed.""" try: version = subprocess.check_output([quarto_bin, "--version"], text=True).strip() version = tuple(map(int, version.split("."))) if version < (1, 5, 23): print("Quarto version is too old. Please upgrade to 1.5.23 or later.") sys.exit(1) except FileNotFoundError: print("Quarto is not installed. Please install it from https://quarto.org") sys.exit(1) def notebooks_target_dir(website_directory: Path) -> Path: """Return the target directory for notebooks.""" return website_directory / "docs" / "notebooks" def load_metadata(notebook: Path) -> typing.Dict: content = json.load(notebook.open(encoding="utf-8")) return content["metadata"] def skip_reason_or_none_if_ok(notebook: Path) -> typing.Optional[str]: """Return a reason to skip the notebook, or None if it should not be skipped.""" if notebook.suffix != ".ipynb": return "not a notebook" if not notebook.exists(): return "file does not exist" # Extra checks for notebooks in the notebook directory if "notebook" not in notebook.parts: return None with open(notebook, "r", encoding="utf-8") as f: content = f.read() # Load the json and get the first cell json_content = json.loads(content) first_cell = json_content["cells"][0] # must exists on lines on their own if first_cell["cell_type"] == "markdown" and first_cell["source"][0].strip() == "