From baf6720d48d2c23eef98713d827f9214f1344ab5 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Tue, 13 Aug 2024 23:06:40 +0200 Subject: [PATCH] Build all docs notebooks in test_notebooks --- docs/_config.yml | 6 +++-- notebooks/test_notebooks.py | 51 ++++++++++++++++++++++++++++++++----- 2 files changed, 48 insertions(+), 9 deletions(-) diff --git a/docs/_config.yml b/docs/_config.yml index 84e3477d..80d7ee5a 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -20,7 +20,9 @@ only_build_toc_files : true ####################################################################################### # Execution settings execute: - execute_notebooks : "auto" # NOTE: Repeat below in `nb_execution_mode`. Whether to execute notebooks at build time. Must be one of ("auto", "force", "cache", "off") + # NOTE: Notebooks are not executed, because test_notebooks.py executes them and stores them with outputs in the docs/ folder + # NOTE: If changed, repeat below in `nb_execution_mode`. + execute_notebooks : "off" # Whether to execute notebooks at build time. Must be one of ("auto", "force", "cache", "off") cache : "" # A path to the jupyter cache that will be used to store execution artifacts. Defaults to `_build/.jupyter_cache/` exclude_patterns : [] # A list of patterns to *skip* in execution (e.g. a notebook that takes a really long time) timeout : 1000 # The maximum time (in seconds) each notebook cell is allowed to run. @@ -139,7 +141,7 @@ sphinx: add_module_names: False github_username: thu-ml github_repository: tianshou - nb_execution_mode: "auto" + nb_execution_mode: "off" nb_merge_streams: True # This is important for cell outputs to appear as single blocks rather than one block per line python_use_unqualified_type_names: True nb_mime_priority_overrides: [ diff --git a/notebooks/test_notebooks.py b/notebooks/test_notebooks.py index b28e4856..c01dde94 100644 --- a/notebooks/test_notebooks.py +++ b/notebooks/test_notebooks.py @@ -2,6 +2,7 @@ import logging import os import pathlib +from glob import glob from typing import Dict, Tuple import nbformat @@ -32,7 +33,7 @@ def notebooksUsedInDocs() -> Tuple[Dict[str, str], str]: for notebook_filename in notebooks_to_copy: if not os.path.exists(NOTEBOOKS_DIR / notebook_filename): raise FileNotFoundError(f"Notebook {notebook_filename} does not exist in notebooks directory") - return notebooks_to_copy, str(path) + return notebooks_to_copy, str(path.absolute()) raise Exception("Could not find notebooks directory in docs") @@ -49,22 +50,58 @@ def preprocess_cell(self, cell, resources, index): return super().preprocess_cell(cell, resources, index) +def execute_notebook(notebook_path): + """ + Execute a jupyter notebook and return the executed notebook + + :param notebook_path: path to the notebook + :return: the notebook with output cells added + """ + log.info(f"Reading jupyter notebook from {notebook_path}") + with open(notebook_path) as f: + nb = nbformat.read(f, as_version=4) + ep = LoggingExecutePreprocessor(notebook_path, timeout=600) + ep.preprocess(nb, resources={"metadata": {"path": str(NOTEBOOKS_DIR)}}) + return nb + + @pytest.mark.parametrize( "notebook", [file for file in os.listdir(NOTEBOOKS_DIR) if file.endswith(".ipynb") and file not in NOTEBOOKS_NOT_TESTED] ) def test_notebook(notebook): + """ + Tests notebooks in the /notebooks folder, optionally copying the executed notebook to the /docs folder + + :param notebook: + :return: + """ notebook_path = NOTEBOOKS_DIR / notebook - log.info(f"Reading jupyter notebook from {notebook_path}") - with open(notebook_path) as f: - nb = nbformat.read(f, as_version=4) - ep = LoggingExecutePreprocessor(notebook, timeout=600) - ep.preprocess(nb, resources={"metadata": {"path": str(NOTEBOOKS_DIR)}}) + nb = execute_notebook(notebook_path) # saving the executed notebook to docs if notebook in NOTEBOOKS_TO_COPY: output_path = os.path.join(DOCS_NOTEBOOKS_DIR, NOTEBOOKS_TO_COPY[notebook]) - log.info(f"Saving executed notebook to {output_path} for documentation purposes") + log.info(f"Saving executed notebook to {output_path}") with open(output_path, "w", encoding="utf-8") as f: nbformat.write(nb, f) else: log.info(f"Notebook {notebook} is not used in docs; not copied") + + +@pytest.mark.parametrize( + "notebook_path", [p for p in [os.path.abspath(g) for g in glob(f"{DOCS_DIR}/**/*.ipynb")] if not p.startswith(DOCS_NOTEBOOKS_DIR)] +) +def test_build_docs_notebook_non_tutorial(notebook_path): + """ + Process all notebooks in the docs/ folder that are purely documentation, i.e. ones that aren't tutorials which + have been copied to DOCS_NOTEBOOKS_DIR by the other test above. + + Runs each notebook and saves it with output cells in-place. + + :param notebook_path: + :return: + """ + nb = execute_notebook(notebook_path) + with open(notebook_path, "w", encoding="utf-8") as f: + log.info(f"Saving executed notebook with outputs to {notebook_path}") + nbformat.write(nb, f)