From 82acf12f3e70695b4552c90f2cc1559d663ed988 Mon Sep 17 00:00:00 2001 From: Remco de Boer <29308176+redeboer@users.noreply.github.com> Date: Sun, 8 Feb 2026 20:10:39 +0100 Subject: [PATCH 1/9] BEHAIOR: apply Prettier formatting to `autolink-concat` --- src/compwa_policy/set_nb_cells.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/compwa_policy/set_nb_cells.py b/src/compwa_policy/set_nb_cells.py index 8699f6ab..53442ac8 100644 --- a/src/compwa_policy/set_nb_cells.py +++ b/src/compwa_policy/set_nb_cells.py @@ -153,11 +153,11 @@ def _insert_autolink_concat(filename: str) -> None: if _skip_notebook(filename, ignore_statement=""): return notebook = load_notebook(filename) - expected_cell_content = """ + expected_cell_content = dedent(""" ```{autolink-concat} + ``` - """ - expected_cell_content = dedent(expected_cell_content).strip() + """).strip() for cell_id, cell in enumerate(notebook["cells"]): if cell["cell_type"] != "markdown": continue From cbf8a8557825723af16e1dece499bc2f12c92b52 Mon Sep 17 00:00:00 2001 From: Remco de Boer <29308176+redeboer@users.noreply.github.com> Date: Sun, 8 Feb 2026 20:10:39 +0100 Subject: [PATCH 2/9] FEAT: check number of `autolink-concat cells --- src/compwa_policy/set_nb_cells.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/compwa_policy/set_nb_cells.py b/src/compwa_policy/set_nb_cells.py index 53442ac8..351bcd60 100644 --- a/src/compwa_policy/set_nb_cells.py +++ b/src/compwa_policy/set_nb_cells.py @@ -28,6 +28,7 @@ import nbformat +from compwa_policy.errors import PrecommitError from compwa_policy.utilities.notebook import load_notebook from compwa_policy.utilities.pyproject import Pyproject @@ -113,6 +114,7 @@ def main(argv: Sequence[str] | None = None) -> int: ) if args.autolink_concat: _insert_autolink_concat(filename) + _test_multiple_autolink_concat(filename) return 0 @@ -172,6 +174,26 @@ def _insert_autolink_concat(filename: str) -> None: return +def _test_multiple_autolink_concat(filename: str) -> None: + if _skip_notebook(filename, ignore_statement=""): + return + notebook = load_notebook(filename) + search_terms = [ + "```{autolink-concat}", + ":::{autolink-concat}", + ] + count = 0 + for cell in notebook["cells"]: + if cell["cell_type"] != "markdown": + continue + cell_content: str = cell["source"] + if any(term in cell_content for term in search_terms): + count += 1 + if count > 1: + msg = f"Multiple cells with autolink-concat found in {filename}" + raise PrecommitError(msg) + + def _skip_notebook( filename: str, ignore_statement: str = "" ) -> bool: From e212cebdd51e4d501701749a07023c926fba56c6 Mon Sep 17 00:00:00 2001 From: Remco de Boer <29308176+redeboer@users.noreply.github.com> Date: Sun, 8 Feb 2026 20:10:40 +0100 Subject: [PATCH 3/9] ENH: load notebook only once --- src/compwa_policy/set_nb_cells.py | 56 ++++++++++++++----------------- 1 file changed, 26 insertions(+), 30 deletions(-) diff --git a/src/compwa_policy/set_nb_cells.py b/src/compwa_policy/set_nb_cells.py index 351bcd60..2b0e9a47 100644 --- a/src/compwa_policy/set_nb_cells.py +++ b/src/compwa_policy/set_nb_cells.py @@ -27,6 +27,7 @@ from typing import TYPE_CHECKING import nbformat +from nbformat import NotebookNode from compwa_policy.errors import PrecommitError from compwa_policy.utilities.notebook import load_notebook @@ -89,6 +90,8 @@ def main(argv: Sequence[str] | None = None) -> int: for filename in args.filenames: cell_id = 0 + updated = False + notebook = load_notebook(filename) if args.add_install_cell: cell_content = __get_install_cell().strip("\n") if args.extras_require: @@ -97,8 +100,8 @@ def main(argv: Sequence[str] | None = None) -> int: if args.additional_packages: packages = [s.strip() for s in args.additional_packages.split(",")] cell_content += " " + " ".join(packages) - _update_cell( - filename, + updated |= _update_cell( + notebook, new_content=cell_content, new_metadata=__INSTALL_CELL_METADATA, cell_id=cell_id, @@ -106,15 +109,20 @@ def main(argv: Sequence[str] | None = None) -> int: cell_id += 1 if args.config_cell: config_cell_content = __CONFIG_CELL_CONTENT - _update_cell( + updated |= _update_cell( filename, new_content=config_cell_content.strip("\n"), new_metadata=__CONFIG_CELL_METADATA, cell_id=cell_id, ) - if args.autolink_concat: - _insert_autolink_concat(filename) - _test_multiple_autolink_concat(filename) + if args.autolink_concat and not _skip_notebook( + notebook, ignore_comment="" + ): + updated |= _insert_autolink_concat(notebook) + _test_multiple_autolink_concat(notebook, filename) + if updated: + nbformat.validate(notebook) + nbformat.write(notebook, filename) return 0 @@ -129,14 +137,13 @@ def __get_install_cell() -> str: def _update_cell( - filename: str, + notebook: NotebookNode, new_content: str, new_metadata: dict, cell_id: int, -) -> None: - if _skip_notebook(filename): - return - notebook = load_notebook(filename) +) -> bool: + if _skip_notebook(notebook, ignore_comment=""): + return False exiting_cell = notebook["cells"][cell_id] new_cell = nbformat.v4.new_code_cell( new_content, @@ -147,14 +154,10 @@ def _update_cell( notebook["cells"][cell_id] = new_cell else: notebook["cells"].insert(cell_id, new_cell) - nbformat.validate(notebook) - nbformat.write(notebook, filename) + return True -def _insert_autolink_concat(filename: str) -> None: - if _skip_notebook(filename, ignore_statement=""): - return - notebook = load_notebook(filename) +def _insert_autolink_concat(notebook: NotebookNode) -> bool: expected_cell_content = dedent(""" ```{autolink-concat} @@ -165,19 +168,15 @@ def _insert_autolink_concat(filename: str) -> None: continue cell_content: str = cell["source"] if expected_cell_content in cell_content: - return + return False new_cell = nbformat.v4.new_markdown_cell(expected_cell_content) del new_cell["id"] # following nbformat_minor = 4 notebook["cells"].insert(cell_id, new_cell) - nbformat.validate(notebook) - nbformat.write(notebook, filename) - return + return True + return False -def _test_multiple_autolink_concat(filename: str) -> None: - if _skip_notebook(filename, ignore_statement=""): - return - notebook = load_notebook(filename) +def _test_multiple_autolink_concat(notebook: NotebookNode, filename: str) -> None: search_terms = [ "```{autolink-concat}", ":::{autolink-concat}", @@ -194,15 +193,12 @@ def _test_multiple_autolink_concat(filename: str) -> None: raise PrecommitError(msg) -def _skip_notebook( - filename: str, ignore_statement: str = "" -) -> bool: - notebook = load_notebook(filename) +def _skip_notebook(notebook: NotebookNode, ignore_comment: str) -> bool: for cell in notebook["cells"]: if cell["cell_type"] != "markdown": continue cell_content: str = cell["source"] - if ignore_statement in cell_content.lower(): + if ignore_comment in cell_content.lower(): return True return False From 8ecb232f82e2c682dac891c78c23865c560237f6 Mon Sep 17 00:00:00 2001 From: Remco de Boer <29308176+redeboer@users.noreply.github.com> Date: Sun, 8 Feb 2026 20:10:41 +0100 Subject: [PATCH 4/9] FIX: print error --- src/compwa_policy/set_nb_cells.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/compwa_policy/set_nb_cells.py b/src/compwa_policy/set_nb_cells.py index 2b0e9a47..a202d9df 100644 --- a/src/compwa_policy/set_nb_cells.py +++ b/src/compwa_policy/set_nb_cells.py @@ -29,7 +29,6 @@ import nbformat from nbformat import NotebookNode -from compwa_policy.errors import PrecommitError from compwa_policy.utilities.notebook import load_notebook from compwa_policy.utilities.pyproject import Pyproject @@ -88,6 +87,7 @@ def main(argv: Sequence[str] | None = None) -> int: ) args = parser.parse_args(argv) + failed = False for filename in args.filenames: cell_id = 0 updated = False @@ -119,10 +119,19 @@ def main(argv: Sequence[str] | None = None) -> int: notebook, ignore_comment="" ): updated |= _insert_autolink_concat(notebook) - _test_multiple_autolink_concat(notebook, filename) + if n_autolink := _count_autolink_concat(notebook): + failed |= True + print( # noqa: T201 + f"Found {n_autolink} autolink-concat cells in {filename}, should be" + " only one. Please remove duplicates.", + file=sys.stderr, + ) if updated: nbformat.validate(notebook) nbformat.write(notebook, filename) + failed |= True + if failed: + return 1 return 0 @@ -176,7 +185,7 @@ def _insert_autolink_concat(notebook: NotebookNode) -> bool: return False -def _test_multiple_autolink_concat(notebook: NotebookNode, filename: str) -> None: +def _count_autolink_concat(notebook: NotebookNode) -> int: search_terms = [ "```{autolink-concat}", ":::{autolink-concat}", @@ -188,9 +197,7 @@ def _test_multiple_autolink_concat(notebook: NotebookNode, filename: str) -> Non cell_content: str = cell["source"] if any(term in cell_content for term in search_terms): count += 1 - if count > 1: - msg = f"Multiple cells with autolink-concat found in {filename}" - raise PrecommitError(msg) + return count def _skip_notebook(notebook: NotebookNode, ignore_comment: str) -> bool: From 5030621ca3ee1d6a728575722f666983c1cd097b Mon Sep 17 00:00:00 2001 From: Remco de Boer <29308176+redeboer@users.noreply.github.com> Date: Sun, 8 Feb 2026 20:10:42 +0100 Subject: [PATCH 5/9] FIX: check all cells --- src/compwa_policy/set_nb_cells.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/compwa_policy/set_nb_cells.py b/src/compwa_policy/set_nb_cells.py index a202d9df..d1e654cc 100644 --- a/src/compwa_policy/set_nb_cells.py +++ b/src/compwa_policy/set_nb_cells.py @@ -172,12 +172,15 @@ def _insert_autolink_concat(notebook: NotebookNode) -> bool: ``` """).strip() + if any( + expected_cell_content in cell["source"] + for cell in notebook["cells"] + if cell["cell_type"] == "markdown" + ): + return False for cell_id, cell in enumerate(notebook["cells"]): if cell["cell_type"] != "markdown": continue - cell_content: str = cell["source"] - if expected_cell_content in cell_content: - return False new_cell = nbformat.v4.new_markdown_cell(expected_cell_content) del new_cell["id"] # following nbformat_minor = 4 notebook["cells"].insert(cell_id, new_cell) From 7da9e1aa080af1105a0c229ff842fc9fea821846 Mon Sep 17 00:00:00 2001 From: Remco de Boer <29308176+redeboer@users.noreply.github.com> Date: Sun, 8 Feb 2026 20:10:43 +0100 Subject: [PATCH 6/9] FIX: check if more than one --- src/compwa_policy/set_nb_cells.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compwa_policy/set_nb_cells.py b/src/compwa_policy/set_nb_cells.py index d1e654cc..bcd9ce0c 100644 --- a/src/compwa_policy/set_nb_cells.py +++ b/src/compwa_policy/set_nb_cells.py @@ -119,7 +119,7 @@ def main(argv: Sequence[str] | None = None) -> int: notebook, ignore_comment="" ): updated |= _insert_autolink_concat(notebook) - if n_autolink := _count_autolink_concat(notebook): + if (n_autolink := _count_autolink_concat(notebook)) > 1: failed |= True print( # noqa: T201 f"Found {n_autolink} autolink-concat cells in {filename}, should be" From 133f8b0dbb0be2f307f7153269ffdf430e2eb1d5 Mon Sep 17 00:00:00 2001 From: Remco de Boer <29308176+redeboer@users.noreply.github.com> Date: Sun, 8 Feb 2026 20:28:45 +0100 Subject: [PATCH 7/9] ENH: autoformat `autolink-concat` --- src/compwa_policy/set_nb_cells.py | 41 ++++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/src/compwa_policy/set_nb_cells.py b/src/compwa_policy/set_nb_cells.py index bcd9ce0c..138b665c 100644 --- a/src/compwa_policy/set_nb_cells.py +++ b/src/compwa_policy/set_nb_cells.py @@ -1,12 +1,12 @@ """Add or update standard cells in a Jupyter notebook. Notebook servers like Google Colaboratory and Deepnote do not install a package -automatically, so this has to be done through a code cell. At the same time, -this cell needs to be hidden from the documentation pages, when viewing through -Jupyter Lab (Binder), and when viewing as Jupyter slides. +automatically, so this has to be done through a code cell. At the same time, this cell +needs to be hidden from the documentation pages, when viewing through Jupyter Lab +(Binder), and when viewing as Jupyter slides. -This scripts sets the IPython InlineBackend.figure_formats option to SVG. This -is because the Sphinx configuration can't set this externally. +This scripts sets the IPython InlineBackend.figure_formats option to SVG. This is +because the Sphinx configuration can't set this externally. Notebooks can be ignored by making the first cell a `Markdown cell `_ @@ -24,7 +24,7 @@ import sys from functools import lru_cache from textwrap import dedent -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, cast import nbformat from nbformat import NotebookNode @@ -53,6 +53,11 @@ "tags": ["remove-cell", "skip-execution"], # https://github.com/executablebooks/jupyter-book/issues/833 } +__AUTOLINK_CONCAT = dedent(""" +```{autolink-concat} + +``` +""").strip() def main(argv: Sequence[str] | None = None) -> int: @@ -118,6 +123,7 @@ def main(argv: Sequence[str] | None = None) -> int: if args.autolink_concat and not _skip_notebook( notebook, ignore_comment="" ): + updated |= _format_autolink_concat(notebook) updated |= _insert_autolink_concat(notebook) if (n_autolink := _count_autolink_concat(notebook)) > 1: failed |= True @@ -166,6 +172,29 @@ def _update_cell( return True +def _format_autolink_concat(notebook: NotebookNode) -> bool: + candidates = [ + dedent(""" + ```{autolink-concat} + ``` + """).strip(), + dedent(""" + :::{autolink-concat} + ::: + """).strip(), + ] + updated = False + for cell in notebook["cells"]: + if cell["cell_type"] != "markdown": + continue + for pattern in candidates: + source = cast("str", cell["source"]) + if pattern in source: + cell["source"] = source.replace(pattern, __AUTOLINK_CONCAT) + updated |= True + return updated + + def _insert_autolink_concat(notebook: NotebookNode) -> bool: expected_cell_content = dedent(""" ```{autolink-concat} From f7cb076dd6cf0468410fd237807d0bc9869ff202 Mon Sep 17 00:00:00 2001 From: Remco de Boer <29308176+redeboer@users.noreply.github.com> Date: Sun, 8 Feb 2026 20:30:28 +0100 Subject: [PATCH 8/9] FIX: print which notebooks were formatter --- src/compwa_policy/set_nb_cells.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/compwa_policy/set_nb_cells.py b/src/compwa_policy/set_nb_cells.py index 138b665c..1b1e32ae 100644 --- a/src/compwa_policy/set_nb_cells.py +++ b/src/compwa_policy/set_nb_cells.py @@ -133,6 +133,7 @@ def main(argv: Sequence[str] | None = None) -> int: file=sys.stderr, ) if updated: + print(f"Updated {filename}", file=sys.stderr) # noqa: T201 nbformat.validate(notebook) nbformat.write(notebook, filename) failed |= True From c1d4d2b3881ba22b9e8e9b61ef7622500cbc0991 Mon Sep 17 00:00:00 2001 From: Remco de Boer <29308176+redeboer@users.noreply.github.com> Date: Sun, 8 Feb 2026 20:33:45 +0100 Subject: [PATCH 9/9] MAINT: remove redundant `strip()` --- src/compwa_policy/set_nb_cells.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compwa_policy/set_nb_cells.py b/src/compwa_policy/set_nb_cells.py index 1b1e32ae..407e8d57 100644 --- a/src/compwa_policy/set_nb_cells.py +++ b/src/compwa_policy/set_nb_cells.py @@ -53,11 +53,11 @@ "tags": ["remove-cell", "skip-execution"], # https://github.com/executablebooks/jupyter-book/issues/833 } -__AUTOLINK_CONCAT = dedent(""" +__AUTOLINK_CONCAT = """ ```{autolink-concat} ``` -""").strip() +""".strip() def main(argv: Sequence[str] | None = None) -> int: