From 82b6128c1f06678f7534e78623b0369f919f9296 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 21 Nov 2025 00:30:40 +0000 Subject: [PATCH 1/6] Initial plan From 5a15e5ad3cbe002ff7b0047909efb38ce0dca9ca Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 21 Nov 2025 00:38:46 +0000 Subject: [PATCH 2/6] Add CI and notebook deployment workflows with supporting files Co-authored-by: solveforceapp <98552991+solveforceapp@users.noreply.github.com> --- .flake8 | 19 ++++ .github/workflows/blank.yml | 36 ------- .github/workflows/ci.yml | 51 ++++++++++ .github/workflows/deploy-notebooks-pages.yml | 91 ++++++++++++++++++ CONTRIBUTING.md | 82 ++++++++++++++++ requirements.txt | 8 ++ scripts/build_notebooks.py | 83 ++++++++++++++++ ...ild_notebooks.cpython-312-pytest-9.0.1.pyc | Bin 0 -> 5553 bytes tests/test_build_notebooks.py | 28 ++++++ 9 files changed, 362 insertions(+), 36 deletions(-) create mode 100644 .flake8 delete mode 100644 .github/workflows/blank.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/deploy-notebooks-pages.yml create mode 100644 CONTRIBUTING.md create mode 100644 requirements.txt create mode 100755 scripts/build_notebooks.py create mode 100644 tests/__pycache__/test_build_notebooks.cpython-312-pytest-9.0.1.pyc create mode 100644 tests/test_build_notebooks.py diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..98e9694 --- /dev/null +++ b/.flake8 @@ -0,0 +1,19 @@ +[flake8] +# Ignore Jupyter notebook files +exclude = + .git, + __pycache__, + .ipynb_checkpoints, + *.ipynb, + build, + dist, + site, + gh-pages + +# Maximum line length +max-line-length = 100 + +# Ignore specific error codes if needed +# E501: line too long (handled by max-line-length) +# W503: line break before binary operator (not a real issue) +ignore = W503 diff --git a/.github/workflows/blank.yml b/.github/workflows/blank.yml deleted file mode 100644 index 01502b1..0000000 --- a/.github/workflows/blank.yml +++ /dev/null @@ -1,36 +0,0 @@ -# This is a basic workflow to help you get started with Actions - -name: CI - -# Controls when the workflow will run -on: - # Triggers the workflow on push or pull request events but only for the "main" branch - push: - branches: [ "main" ] - pull_request: - branches: [ "main" ] - - # Allows you to run this workflow manually from the Actions tab - workflow_dispatch: - -# A workflow run is made up of one or more jobs that can run sequentially or in parallel -jobs: - # This workflow contains a single job called "build" - build: - # The type of runner that the job will run on - runs-on: ubuntu-latest - - # Steps represent a sequence of tasks that will be executed as part of the job - steps: - # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v4 - - # Runs a single command using the runners shell - - name: Run a one-line script - run: echo Hello, world! - - # Runs a set of commands using the runners shell - - name: Run a multi-line script - run: | - echo Add other actions to build, - echo test, and deploy your project. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..c0a77f5 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,51 @@ +name: CI + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Lint (flake8) + run: | + flake8 . + + - name: Run unit tests (pytest) + run: | + pytest -q --maxfail=1 + + - name: Execute all notebooks (nbconvert) + run: | + python - <<'PY' + import glob, subprocess, sys + notebooks = glob.glob('**/*.ipynb', recursive=True) + if not notebooks: + print("No notebooks found.") + sys.exit(0) + for n in notebooks: + print("Executing", n) + subprocess.check_call([ + sys.executable, "-m", "jupyter", "nbconvert", + "--to", "notebook", "--execute", "--inplace", + "--ExecutePreprocessor.timeout=600", n + ]) + PY diff --git a/.github/workflows/deploy-notebooks-pages.yml b/.github/workflows/deploy-notebooks-pages.yml new file mode 100644 index 0000000..c1ecf25 --- /dev/null +++ b/.github/workflows/deploy-notebooks-pages.yml @@ -0,0 +1,91 @@ +on: + push: + branches: + - '**' + +name: Build and deploy notebooks to GitHub Pages (per-branch) + +jobs: + build-and-deploy: + runs-on: ubuntu-latest + permissions: + contents: write # needed to push gh-pages + steps: + - name: Checkout repository (current branch) + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Determine branch name + id: branch + run: | + echo "BRANCH_NAME=${GITHUB_REF#refs/heads/}" > $GITHUB_ENV + echo "branch=${GITHUB_REF#refs/heads/}" > $GITHUB_OUTPUT + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + + - name: Install build dependencies + run: | + python -m pip install --upgrade pip + if [ -f requirements.txt ]; then pip install -r requirements.txt; else pip install jupyter nbconvert nbformat; fi + + - name: Build notebooks to HTML for this branch + run: | + python -m pip install --upgrade pip + mkdir -p site/${{ env.BRANCH_NAME }} + python scripts/build_notebooks.py site/${{ env.BRANCH_NAME }} + + - name: Prepare gh-pages branch workspace + run: | + # If gh-pages exists, clone it, otherwise init an empty gh-pages branch + REPO_URL="https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git" + if git ls-remote --exit-code origin gh-pages; then + git clone --depth 1 --branch gh-pages "$REPO_URL" gh-pages || git clone "$REPO_URL" gh-pages + else + # create temporary folder, init gh-pages and push + mkdir gh-pages + cd gh-pages + git init + git remote add origin "$REPO_URL" + git checkout -b gh-pages || true + touch .nojekyll + git add .nojekyll + git commit -m "Initialize gh-pages" + git push origin gh-pages + cd .. + git clone --depth 1 --branch gh-pages "$REPO_URL" gh-pages + fi + + - name: Copy built site into gh-pages under branch folder + run: | + set -e + rsync -a --delete site/${{ env.BRANCH_NAME }}/ gh-pages/${{ env.BRANCH_NAME }}/ + # ensure a top-level index exists listing branches (optional) + python3 - <<'PY' +import os, json +root='gh-pages' +items = sorted([d for d in os.listdir(root) if os.path.isdir(os.path.join(root,d))]) +index_path = os.path.join(root, 'index.html') +with open(index_path, 'w') as f: + f.write("
{e}"
+ f.write(error_msg)
+ html_files.append(os.path.basename(error_html))
+
+ # Create an index.html listing all notebooks
+ index_path = os.path.join(output_dir, 'index.html')
+ with open(index_path, 'w') as f:
+ f.write("zfM7b0-#{&d5mZXG}#EBl*%h``I81J7zYGtWwPU~7v1LkLn}!+|kP z0edYl);AC^Quxn?<~2%cCy$`@EI?GR!C@=l*TASY8$JY3(h!B7rFf_6WMe@6A{c?@ zVvugEfb9(c@PJG?5DoOzntk1!FJ(D&rC1H=L2U5+riZY30UNxqQ@o+mm$1RRH^p*7 zu{H#0>e`OwyPH11S^%vzj#aD8I^y3_nikP=2Af%I=CC;lO&n03i8JTofbv3|xx^5I zhv>3Upx?w{W;kAqCs-ix5Hwx_8=MdOQh>(Xp^vWq>gS`}i9znfDEHbR_gcSvWstja z>*^!((1(|AU+$B|+n0ytG3W>8u@PB(NEZ9Ei$KKRAz9p-7lIghAEcLs^d5L|_qZ1b zztEN3OQURIkSz?eFW>s%BlCqmncG^pV{m8*=yL9X;DLg7h>Rek798#vky?_G8VK+L z_}eGPw_d+f?3>3&1m6dO7t9bDK}Ib&+yT6ZwxE_|q!wbxQ=iOk&2QiAo3kT=?*qXL zW{8X+qZS;tZ$@fKMrt6y>;E(GeKJd5felc~1%_og%_`i@Qu3n3_jYj962FMD_~F2H zsZ8CQ)brt*6^Q~Pl!;#kzfUDtn*}M~t+(*f7wBwl!}n@|f%n^*w<3O5cx|D$4L?(O zAbi_CO?|K4bT(@48}v<3;s0eA{wlsmsH*xeO;gF2x}vHdD8EcURx*#3++$_>Z_1H> Q>qLE5eUiDate: Fri, 21 Nov 2025 00:39:23 +0000 Subject: [PATCH 3/6] Add .gitignore and remove committed __pycache__ Co-authored-by: solveforceapp <98552991+solveforceapp@users.noreply.github.com> --- .gitignore | 50 ++++++++++++++++++ ...ild_notebooks.cpython-312-pytest-9.0.1.pyc | Bin 5553 -> 0 bytes 2 files changed, 50 insertions(+) create mode 100644 .gitignore delete mode 100644 tests/__pycache__/test_build_notebooks.cpython-312-pytest-9.0.1.pyc diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b3c1f27 --- /dev/null +++ b/.gitignore @@ -0,0 +1,50 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# Jupyter Notebook +.ipynb_checkpoints + +# Testing +.pytest_cache/ +.coverage +htmlcov/ + +# Build outputs +site/ +gh-pages/ + +# Virtual environments +venv/ +ENV/ +env/ +.venv + +# IDEs +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db diff --git a/tests/__pycache__/test_build_notebooks.cpython-312-pytest-9.0.1.pyc b/tests/__pycache__/test_build_notebooks.cpython-312-pytest-9.0.1.pyc deleted file mode 100644 index e1a4258ef3c546a2b78a016505c4544b94adc183..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5553 zcmeHL&2JmW72jPBcS){9+H~UBg(cCcQ-+R9f4fps#ZH5w76Q1 7Dk(cj24K12B-_ ?~IfQK^86 zT)bM$ywBM;Z-(=JqrXf~XB2qopXR(92}SukGV;+v;z 36 V;x{~I7rjmg^Q8VjPl_{_#A1IY+Ckbt~t(El7sm%9n=2Z*6 z%Y0T?ZPJ3j<`x=F-(6`ouNPJ}yqZ&BRqD0;a^{~H*0quvkRRLrS~U!kcWG#07Rr-9 z6O7nbrP7ykQSOGab+CB$nIv=6j;Sb)hHX#jD(m{QULaRBrK{cz6+Nx1vETa`3xC#m zh9xI@Cn4i-NC!kt(w~+V>sd*SdpY(Eg}oRX`M}shQr&2VG;W!f`f)$DY^d=4alDgk zRT=LtcAB(v^p6uA$W<@lzbL;M^F1tO V1fDHhSBRZ7W7oHSph%$`^2XlOt2B;Reu2h4Cal0$ zJz&)`v9C< J05mD z=_T7nX{vMfU6*>RZGIxg55j`G>EUT Y~epv)^HqM+EjEMANT(g7uPTXvP;gALKJUxGUtyy Gjj! zhCg1`dCMp4?AVaO5uPNm6T ^2JfUJjj Nx4-z#Apg#-w;!2@N9N4HoavJ#Xouz;C+0?E=^ o(TUSuiB$mkw^J5oy$2KuBZq+rnJaYQaaJRr+YAfd_* z)PyU@QPTmS_7gSjBVHQgiUhC3WY5(kj)EpslQ9bVpYRgdr7A>~cNe?yypsEZmvn)b z0H+?sb>8_%6xkz=fe5HeZYVvytFzz5qMU%QoK#m|Rh=|?tZH{d zfM7b0-#{&d5mZXG}#EBl*%h``I81J7zYGtWwPU~7v1LkLn}!+|kP z0edYl);AC^Quxn?<~2%cCy$`@EI?GR!C@=l*TASY8$JY3(h!B7rFf_6WMe@6A{c?@ zVvugEfb9(c@PJG?5DoOzntk1!FJ(D&rC1H=L2U5+riZY30UNxqQ@o+mm$1RRH^p*7 zu{H#0>e`OwyPH11S^%vzj#aD8I^y3_nikP=2Af%I=CC;lO&n03i8JTofbv3|xx^5I zhv>3Upx?w{W;kAqCs-ix5Hwx_8=MdOQh>(Xp^vWq>gS`}i9znfDEHbR_gcSvWstja z>*^!((1(|AU+$B|+n0ytG3W>8u@PB(NEZ9Ei$KKRAz9p-7lIghAEcLs^d5L|_qZ1b zztEN3OQURIkSz?eFW>s%BlCqmncG^pV{m8*=yL9X;DLg7h>Rek798#vky?_G8VK+L z_}eGPw_d+f?3>3&1m6dO7t9bDK}Ib&+yT6ZwxE_|q!wbxQ=iOk&2QiAo3kT=?*qXL zW{8X+qZS;tZ$@fKMrt6y>;E(GeKJd5felc~1%_og%_`i@Qu3n3_jYj962FMD_~F2H zsZ8CQ)brt*6^Q~Pl!;#kzfUDtn*}M~t+(*f7wBwl!}n@|f%n^*w<3O5cx|D$4L?(O zAbi_CO?|K4bT(@48}v<3;s0eA{wlsmsH*xeO;gF2x}vHdD8EcURx*#3++$_>Z_1H> Q>qLE5eUiDate: Fri, 21 Nov 2025 00:40:13 +0000 Subject: [PATCH 4/6] Fix flake8 linting issues in test file Co-authored-by: solveforceapp <98552991+solveforceapp@users.noreply.github.com> --- tests/test_build_notebooks.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/test_build_notebooks.py b/tests/test_build_notebooks.py index 2fa0152..806fe0c 100644 --- a/tests/test_build_notebooks.py +++ b/tests/test_build_notebooks.py @@ -2,9 +2,6 @@ Basic tests for the notebook build script. """ import os -import sys -import tempfile -import shutil from pathlib import Path @@ -19,7 +16,7 @@ def test_requirements_file_exists(): """Verify requirements.txt exists.""" req_path = Path(__file__).parent.parent / "requirements.txt" assert req_path.exists(), "requirements.txt should exist" - + # Check that it contains expected packages content = req_path.read_text() assert "jupyter" in content From d907199194dab0cefc4d412a7573d2a41f6802b8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 21 Nov 2025 00:42:06 +0000 Subject: [PATCH 5/6] Address code review feedback - remove duplicate pip upgrade, add HTML escaping, improve git clone fallback Co-authored-by: solveforceapp <98552991+solveforceapp@users.noreply.github.com> --- .github/workflows/deploy-notebooks-pages.yml | 3 +-- scripts/build_notebooks.py | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/deploy-notebooks-pages.yml b/.github/workflows/deploy-notebooks-pages.yml index c1ecf25..5e73843 100644 --- a/.github/workflows/deploy-notebooks-pages.yml +++ b/.github/workflows/deploy-notebooks-pages.yml @@ -34,7 +34,6 @@ jobs: - name: Build notebooks to HTML for this branch run: | - python -m pip install --upgrade pip mkdir -p site/${{ env.BRANCH_NAME }} python scripts/build_notebooks.py site/${{ env.BRANCH_NAME }} @@ -43,7 +42,7 @@ jobs: # If gh-pages exists, clone it, otherwise init an empty gh-pages branch REPO_URL="https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git" if git ls-remote --exit-code origin gh-pages; then - git clone --depth 1 --branch gh-pages "$REPO_URL" gh-pages || git clone "$REPO_URL" gh-pages + git clone --depth 1 --branch gh-pages "$REPO_URL" gh-pages || git clone --single-branch --branch gh-pages "$REPO_URL" gh-pages else # create temporary folder, init gh-pages and push mkdir gh-pages diff --git a/scripts/build_notebooks.py b/scripts/build_notebooks.py index f78c40e..01f8723 100755 --- a/scripts/build_notebooks.py +++ b/scripts/build_notebooks.py @@ -13,6 +13,7 @@ import os import glob import subprocess +import html def main(): @@ -60,7 +61,7 @@ def main(): output_dir, os.path.basename(nb).replace('.ipynb', '.html')) with open(error_html, 'w') as f: error_msg = f" Execution failed for {nb}
" - error_msg += f"{e}" + error_msg += f"{html.escape(str(e))}