Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ default_stages: [pre-commit]

repos:
- repo: https://github.com/psf/black-pre-commit-mirror
rev: 25.11.0
rev: 25.12.0
hooks:
- id: black

Expand All @@ -13,10 +13,10 @@ repos:
- id: end-of-file-fixer
- id: debug-statements

- repo: https://github.com/christopher-hacker/enforce-notebook-run-order
rev: 2.1.1
hooks:
- id: enforce-notebook-run-order
# - repo: https://github.com/christopher-hacker/enforce-notebook-run-order
# rev: 2.1.1
# hooks:
# - id: enforce-notebook-run-order

- repo: local
hooks:
Expand All @@ -38,3 +38,11 @@ repos:
args: [--fix-header, --repo-name=devops_tests]
language: python
types: [jupyter]

- id: notebooks-output
name: notebooks output
entry: notebooks_output
additional_dependencies:
- nbformat
language: python
types: [jupyter]
8 changes: 8 additions & 0 deletions .pre-commit-hooks.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,11 @@
language: python
stages: [pre-commit]
types: [jupyter]

- id: notebooks-output
name: notebooks output
description: check if notebooks are executed and without errors and warnings
entry: notebooks_output
language: python
stages: [pre-commit]
types: [jupyter]
44 changes: 15 additions & 29 deletions hooks/check_notebooks.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#!/usr/bin/env python3
# pylint: disable=duplicate-code #TODO #62
"""
Checks notebook execution status for Jupyter notebooks"""
from __future__ import annotations
Expand All @@ -13,26 +14,6 @@ class NotebookTestError(Exception):
"""Raised when a notebook validation test fails."""


def test_cell_contains_output(notebook):
"""checks if all notebook cells have an output present"""
for cell in notebook.cells:
if cell.cell_type == "code" and cell.source != "":
if cell.execution_count is None:
raise ValueError("Cell does not contain output!")


def test_no_errors_or_warnings_in_output(notebook):
"""checks if all example Jupyter notebooks have clear std-err output
(i.e., no errors or warnings) visible; except acceptable
diagnostics from the joblib package"""
for cell in notebook.cells:
if cell.cell_type == "code":
for output in cell.outputs:
if "name" in output and output["name"] == "stderr":
if not output["text"].startswith("[Parallel(n_jobs="):
raise ValueError(output["text"])


def test_show_plot_used_instead_of_matplotlib(notebook):
"""checks if plotting is done with open_atmos_jupyter_utils show_plot()"""
matplot_used = False
Expand Down Expand Up @@ -82,20 +63,13 @@ def test_jetbrains_bug_py_66491(notebook):
)


def main(argv: Sequence[str] | None = None) -> int:
"""test all notebooks"""
def open_and_test_notebooks(argv, test_functions):
"""Create argparser and run notebook tests"""
parser = argparse.ArgumentParser()
parser.add_argument("filenames", nargs="*", help="Filenames to check.")
args = parser.parse_args(argv)

retval = 0
test_functions = [
test_cell_contains_output,
test_no_errors_or_warnings_in_output,
test_jetbrains_bug_py_66491,
test_show_anim_used_instead_of_matplotlib,
test_show_plot_used_instead_of_matplotlib,
]
for filename in args.filenames:
with open(filename, encoding="utf8") as notebook_file:
notebook = nbformat.read(notebook_file, nbformat.NO_CONVERT)
Expand All @@ -108,5 +82,17 @@ def main(argv: Sequence[str] | None = None) -> int:
return retval


def main(argv: Sequence[str] | None = None) -> int:
"""test all notebooks"""
return open_and_test_notebooks(
argv=argv,
test_functions=[
test_jetbrains_bug_py_66491,
test_show_anim_used_instead_of_matplotlib,
test_show_plot_used_instead_of_matplotlib,
],
)


if __name__ == "__main__":
raise SystemExit(main())
75 changes: 75 additions & 0 deletions hooks/notebooks_output.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#!/usr/bin/env python3
# pylint: disable=duplicate-code #TODO #62
"""checks if notebook is executed and do not contain 'stderr"""

from __future__ import annotations

import argparse
from collections.abc import Sequence

import nbformat


class NotebookTestError(Exception):
"""Raised when a notebook validation test fails."""


def test_cell_contains_output(notebook):
"""checks if all notebook cells have an output present"""
for cell_idx, cell in enumerate(notebook.cells):
if cell.cell_type == "code" and cell.source != "":
if cell.execution_count is None:
raise ValueError(f"Cell {cell_idx} does not contain output")


def test_no_errors_or_warnings_in_output(notebook):
"""checks if all example Jupyter notebooks have clear std-err output
(i.e., no errors or warnings) visible; except acceptable
diagnostics from the joblib package"""
for cell_idx, cell in enumerate(notebook.cells):
if cell.cell_type == "code":
for output in cell.outputs:
ot = output.get("output_type")
if ot == "error":
raise ValueError(
f"Cell [{cell_idx}] contain error or warning. \n\n"
f"Cell [{cell_idx}] output:\n{output}\n"
)
if ot == "stream" and output.get("name") == "stderr":
out_text = output.get("text")
if out_text and not out_text.startswith("[Parallel(n_jobs="):
raise ValueError(f" Cell [{cell_idx}]: {out_text}")


def open_and_test_notebooks(argv, test_functions):
"""Create argparser and run notebook tests"""
parser = argparse.ArgumentParser()
parser.add_argument("filenames", nargs="*", help="Filenames to check.")
args = parser.parse_args(argv)

retval = 0
for filename in args.filenames:
with open(filename, encoding="utf8") as notebook_file:
notebook = nbformat.read(notebook_file, nbformat.NO_CONVERT)
for func in test_functions:
try:
func(notebook)
except NotebookTestError as e:
print(f"{filename} : {e}")
retval = 1
return retval


def main(argv: Sequence[str] | None = None) -> int:
"""test all notebooks"""
return open_and_test_notebooks(
argv=argv,
test_functions=[
test_cell_contains_output,
test_no_errors_or_warnings_in_output,
],
)


if __name__ == "__main__":
raise SystemExit(main())
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,4 @@ dynamic = ['version']
[project.scripts]
check_notebooks = "hooks.check_notebooks:main"
check_badges = "hooks.check_badges:main"
notebooks_output = "hooks.notebooks_output:main"
Loading