From 7aa412cf3a472f1b372cd643274d21e38d17bb57 Mon Sep 17 00:00:00 2001 From: Sebastian Mohr Date: Fri, 13 Mar 2026 18:44:39 +0100 Subject: [PATCH 1/2] Added notebook check to workflows. --- .github/workflows/mypy_notebooks.sh | 36 ++++++++++ .github/workflows/python.yml | 4 ++ pyproject.toml | 2 +- uv.lock | 103 ++++++++++++++++++++++++++++ 4 files changed, 144 insertions(+), 1 deletion(-) create mode 100755 .github/workflows/mypy_notebooks.sh diff --git a/.github/workflows/mypy_notebooks.sh b/.github/workflows/mypy_notebooks.sh new file mode 100755 index 00000000..93e46377 --- /dev/null +++ b/.github/workflows/mypy_notebooks.sh @@ -0,0 +1,36 @@ +#!/bin/bash +FAILED=0 + +while IFS= read -r nb; do + [[ -z "$nb" || ! -f "$nb" ]] && continue + if git check-ignore -q "$nb"; then + echo -e "\033[90m=== Skipped $nb (gitignore) ===\033[0m" + continue + fi + + + NB_DIR=$(dirname "$nb") + NB_BASE=$(basename "$nb" .ipynb) + PY_FILE="${NB_DIR}/_${NB_BASE}.py" + + echo "=== Checking $nb -> $PY_FILE ===" + + # Convert to _ prefixed filename (simple!) + jupyter nbconvert --to python "$nb" \ + --output "_${NB_BASE}" \ + --log-level ERROR + + if ! uv run mypy "$PY_FILE" --config-file pyproject.toml; then + ((FAILED++)) + fi + + rm -f "$PY_FILE" +done < <(find . -name "*.ipynb") + +if [ $FAILED -eq 0 ]; then + echo "🎉 All notebooks passed mypy!" + exit 0 +else + echo "❌ $FAILED notebooks failed mypy!" + exit 1 +fi diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index a948a9c0..eb07b7ce 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -107,6 +107,10 @@ jobs: - name: Check type hints with mypy run: | uv run mypy --show-error-codes --check-untyped-defs --config-file ./pyproject.toml ./ + - name: Check notebook typehints with mypy + run: | + ./.github/workflows/mypy_notebooks.sh + test: name: Run tests runs-on: [self-hosted, linux] diff --git a/pyproject.toml b/pyproject.toml index b05d6bbc..65f42428 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,7 +48,7 @@ dev = [ { include-group = "typed" }, "pre-commit>=4.5.1", ] -lint = ["ruff>=0.14.11", "nbstripout>=0.9.1"] +lint = ["ruff>=0.14.11", "nbstripout>=0.9.1", "nbconvert"] test = [ "beets>=2.5.1", "pytest>=9.0.2", diff --git a/uv.lock b/uv.lock index b3b2e07e..8bca1b19 100644 --- a/uv.lock +++ b/uv.lock @@ -134,6 +134,23 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/94/50/a2ca5609cffc918a339be4ec5070c6d1d9fab600e6d9164df18040137383/binapy-0.8.0-py3-none-any.whl", hash = "sha256:8af1e1e856900ef8b79ef32236e296127c9cf26414ec355982ff7ce5f173504d", size = 16993, upload-time = "2024-01-19T11:57:18.876Z" }, ] +[[package]] +name = "bleach" +version = "6.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "webencodings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/07/18/3c8523962314be6bf4c8989c79ad9531c825210dd13a8669f6b84336e8bd/bleach-6.3.0.tar.gz", hash = "sha256:6f3b91b1c0a02bb9a78b5a454c92506aa0fdf197e1d5e114d2e00c6f64306d22", size = 203533, upload-time = "2025-10-27T17:57:39.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl", hash = "sha256:fe10ec77c93ddf3d13a73b035abaac7a9f5e436513864ccdad516693213c65d6", size = 164437, upload-time = "2025-10-27T17:57:37.538Z" }, +] + +[package.optional-dependencies] +css = [ + { name = "tinycss2" }, +] + [[package]] name = "certifi" version = "2026.1.4" @@ -534,6 +551,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190, upload-time = "2025-02-24T04:41:32.565Z" }, ] +[[package]] +name = "defusedxml" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520, upload-time = "2021-03-08T10:59:26.269Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604, upload-time = "2021-03-08T10:59:24.45Z" }, +] + [[package]] name = "distlib" version = "0.4.0" @@ -953,6 +979,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl", hash = "sha256:ebf87fdc6073d142e114c72c9e29a9d7ca03fad818c5d300ce2adc1fb0743407", size = 29032, upload-time = "2025-10-16T19:19:16.783Z" }, ] +[[package]] +name = "jupyterlab-pygments" +version = "0.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/90/51/9187be60d989df97f5f0aba133fa54e7300f17616e065d1ada7d7646b6d6/jupyterlab_pygments-0.3.0.tar.gz", hash = "sha256:721aca4d9029252b11cfa9d185e5b5af4d54772bb8072f9b7036f4170054d35d", size = 512900, upload-time = "2023-11-23T09:26:37.44Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl", hash = "sha256:841a89020971da1d8693f1a99997aefc5dc424bb1b251fd6322462a1b8842780", size = 15884, upload-time = "2023-11-23T09:26:34.325Z" }, +] + [[package]] name = "jwskate" version = "0.12.2" @@ -1381,6 +1416,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/49/6a/79843e3a58a3387730312ce1283ee5dce3619821ad26facc3a219067c7bc/mediafile-0.14.0-py3-none-any.whl", hash = "sha256:6f5c3c9e1535bfdb4f299d1558862f063bf3e8a4f1e0ec9df04a3c5bdc620ecc", size = 29065, upload-time = "2025-12-28T11:03:18.879Z" }, ] +[[package]] +name = "mistune" +version = "3.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/55/d01f0c4b45ade6536c51170b9043db8b2ec6ddf4a35c7ea3f5f559ac935b/mistune-3.2.0.tar.gz", hash = "sha256:708487c8a8cdd99c9d90eb3ed4c3ed961246ff78ac82f03418f5183ab70e398a", size = 95467, upload-time = "2025-12-23T11:36:34.994Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl", hash = "sha256:febdc629a3c78616b94393c6580551e0e34cc289987ec6c35ed3f4be42d0eee1", size = 53598, upload-time = "2025-12-23T11:36:33.211Z" }, +] + [[package]] name = "musicbrainzngs" version = "0.7.1" @@ -1500,6 +1544,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/83/a0/5b0c2f11142ed1dddec842457d3f65eaf71a0080894eb6f018755b319c3a/nbclient-0.10.4-py3-none-any.whl", hash = "sha256:9162df5a7373d70d606527300a95a975a47c137776cd942e52d9c7e29ff83440", size = 25465, upload-time = "2025-12-23T07:45:44.51Z" }, ] +[[package]] +name = "nbconvert" +version = "7.17.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "beautifulsoup4" }, + { name = "bleach", extra = ["css"] }, + { name = "defusedxml" }, + { name = "jinja2" }, + { name = "jupyter-core" }, + { name = "jupyterlab-pygments" }, + { name = "markupsafe" }, + { name = "mistune" }, + { name = "nbclient" }, + { name = "nbformat" }, + { name = "packaging" }, + { name = "pandocfilters" }, + { name = "pygments" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/47/81f886b699450d0569f7bc551df2b1673d18df7ff25cc0c21ca36ed8a5ff/nbconvert-7.17.0.tar.gz", hash = "sha256:1b2696f1b5be12309f6c7d707c24af604b87dfaf6d950794c7b07acab96dda78", size = 862855, upload-time = "2026-01-29T16:37:48.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/4b/8d5f796a792f8a25f6925a96032f098789f448571eb92011df1ae59e8ea8/nbconvert-7.17.0-py3-none-any.whl", hash = "sha256:4f99a63b337b9a23504347afdab24a11faa7d86b405e5c8f9881cd313336d518", size = 261510, upload-time = "2026-01-29T16:37:46.322Z" }, +] + [[package]] name = "nbformat" version = "5.10.4" @@ -1673,6 +1742,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, ] +[[package]] +name = "pandocfilters" +version = "1.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/70/6f/3dd4940bbe001c06a65f88e36bad298bc7a0de5036115639926b0c5c0458/pandocfilters-1.5.1.tar.gz", hash = "sha256:002b4a555ee4ebc03f8b66307e287fa492e4a77b4ea14d3f934328297bb4939e", size = 8454, upload-time = "2024-01-18T20:08:13.726Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl", hash = "sha256:93be382804a9cdb0a7267585f157e5d1731bbe5545a85b268d6f5fe6232de2bc", size = 8663, upload-time = "2024-01-18T20:08:11.28Z" }, +] + [[package]] name = "parso" version = "0.8.5" @@ -1752,6 +1830,7 @@ dev = [ { name = "mypy" }, { name = "myst-nb" }, { name = "myst-parser" }, + { name = "nbconvert" }, { name = "nbmake" }, { name = "nbstripout" }, { name = "pre-commit" }, @@ -1784,6 +1863,7 @@ docs = [ { name = "sphinxcontrib-typer", extra = ["html"] }, ] lint = [ + { name = "nbconvert" }, { name = "nbstripout" }, { name = "ruff" }, ] @@ -1828,6 +1908,7 @@ dev = [ { name = "mypy", specifier = ">=1.19.1" }, { name = "myst-nb", specifier = ">=1.3.0" }, { name = "myst-parser", specifier = ">=4.0.1" }, + { name = "nbconvert" }, { name = "nbmake", specifier = ">=1.5.5" }, { name = "nbstripout", specifier = ">=0.9.1" }, { name = "pre-commit", specifier = ">=4.5.1" }, @@ -1860,6 +1941,7 @@ docs = [ { name = "sphinxcontrib-typer", extras = ["html"], specifier = ">=0.7.2" }, ] lint = [ + { name = "nbconvert" }, { name = "nbstripout", specifier = ">=0.9.1" }, { name = "ruff", specifier = ">=0.14.11" }, ] @@ -2784,6 +2866,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f", size = 35252, upload-time = "2022-10-06T17:21:44.262Z" }, ] +[[package]] +name = "tinycss2" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "webencodings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7a/fd/7a5ee21fd08ff70d3d33a5781c255cbe779659bd03278feb98b19ee550f4/tinycss2-1.4.0.tar.gz", hash = "sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7", size = 87085, upload-time = "2024-10-24T14:58:29.895Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl", hash = "sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289", size = 26610, upload-time = "2024-10-24T14:58:28.029Z" }, +] + [[package]] name = "tinytag" version = "2.2.0" @@ -3069,6 +3163,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b5/b5/3bd0b038d80950ec13e6a2c8d03ed8354867dc60064b172f2f4ffac8afbe/webdriver_manager-4.0.2-py2.py3-none-any.whl", hash = "sha256:75908d92ecc45ff2b9953614459c633db8f9aa1ff30181cefe8696e312908129", size = 27778, upload-time = "2024-07-25T08:13:47.917Z" }, ] +[[package]] +name = "webencodings" +version = "0.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/02/ae6ceac1baeda530866a85075641cec12989bd8d31af6d5ab4a3e8c92f47/webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923", size = 9721, upload-time = "2017-04-05T20:21:34.189Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", size = 11774, upload-time = "2017-04-05T20:21:32.581Z" }, +] + [[package]] name = "websocket-client" version = "1.9.0" From 299f8a725808e14e43bd2686bb08eab1312a9905 Mon Sep 17 00:00:00 2001 From: Sebastian Mohr Date: Fri, 13 Mar 2026 18:51:21 +0100 Subject: [PATCH 2/2] Adjusted job triggers. --- .github/workflows/mypy_notebooks.sh | 2 +- .github/workflows/python.yml | 28 +++++++++++++++++++++++++--- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/.github/workflows/mypy_notebooks.sh b/.github/workflows/mypy_notebooks.sh index 93e46377..4c130801 100755 --- a/.github/workflows/mypy_notebooks.sh +++ b/.github/workflows/mypy_notebooks.sh @@ -16,7 +16,7 @@ while IFS= read -r nb; do echo "=== Checking $nb -> $PY_FILE ===" # Convert to _ prefixed filename (simple!) - jupyter nbconvert --to python "$nb" \ + uv run jupyter nbconvert --to python "$nb" \ --output "_${NB_BASE}" \ --log-level ERROR diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index eb07b7ce..beb1dd2c 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -13,7 +13,7 @@ jobs: name: Get changed files outputs: any_python_changed: ${{ steps.raw-changed-python-files.outputs.any_changed }} - changed_python_files: ${{ steps.changed-python-files.outputs.all_changed_files }} + all_changed_files: ${{ steps.changed-python-files.outputs.all_changed_files }} steps: - uses: actions/checkout@v4 - name: Get changed python files @@ -22,6 +22,10 @@ jobs: with: files: | **.py + **.ipynb + pyproject.toml + uv.lock + - name: Check changed python files id: changed-python-files env: @@ -44,6 +48,7 @@ jobs: - name: Check style with Ruff run: | uv run ruff check --output-format=github . + format: name: Check format with Ruff runs-on: [self-hosted, linux] @@ -59,8 +64,10 @@ jobs: - name: Check format with Ruff run: | uv run ruff format --check --diff . - check-notebooks: + + verify-nbstripout: runs-on: [self-hosted, linux] + if: needs.changed-files.outputs.any_python_changed == 'true' steps: - name: Checkout code uses: actions/checkout@v6 @@ -107,7 +114,21 @@ jobs: - name: Check type hints with mypy run: | uv run mypy --show-error-codes --check-untyped-defs --config-file ./pyproject.toml ./ - - name: Check notebook typehints with mypy + + mypy-notebooks: + name: Check notebook type hints with mypy + runs-on: [self-hosted, linux] + needs: changed-files + if: needs.changed-files.outputs.any_python_changed == 'true' + steps: + - uses: actions/checkout@v6 + - name: Install uv + uses: astral-sh/setup-uv@v7 + - name: Install dependencies + run: | + uv sync --all-extras + uv pip install jupyter + - name: Check notebook type hints with mypy run: | ./.github/workflows/mypy_notebooks.sh @@ -144,6 +165,7 @@ jobs: uses: codecov/test-results-action@v1 with: token: ${{ secrets.CODECOV_TOKEN }} + docs: name: Build Sphinx docs runs-on: [self-hosted, linux]