From a3355c1018c3938266c2808afd4259dfa32369a6 Mon Sep 17 00:00:00 2001 From: David Quarel Date: Thu, 17 Sep 2026 12:47:23 +0000 Subject: [PATCH 1/2] CI: tests on three OSes, and the demo notebook built for Colab - .github/workflows/tests.yml runs the suite on ubuntu / macOS / Windows with Python 3.10 and 3.12, on pushes to main and on pull requests. The SIGINT test is POSIX-only (os.kill with SIGINT terminates a process on Windows). - .github/workflows/demo-notebook.yml converts examples/demo.py (a `# %%` cell file) to a notebook with jupytext. On pull requests it only builds, as a check; on pushes to main it force-pushes the notebook to the orphan `demo` branch, so main never carries generated files and the notebook cannot go stale. - examples/demo.py gets a first cell that pip-installs liveplot on Colab (plain Python, no magics) and is a no-op elsewhere. - README: "Open in Colab" badge pointing at the demo branch, and a tests badge. Verified locally: the converted notebook executes in a kernel end to end (10 cells, 7 live plots, no errors); the workflows parse; the suite passes. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01G9zs684RFxHvW6cNWqTPSF --- .github/workflows/demo-notebook.yml | 55 +++++++++++++++++++++++++++++ .github/workflows/tests.yml | 23 ++++++++++++ README.md | 4 +++ examples/demo.py | 21 ++++++++--- tests/test_liveplot.py | 1 + 5 files changed, 100 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/demo-notebook.yml create mode 100644 .github/workflows/tests.yml diff --git a/.github/workflows/demo-notebook.yml b/.github/workflows/demo-notebook.yml new file mode 100644 index 0000000..a948f73 --- /dev/null +++ b/.github/workflows/demo-notebook.yml @@ -0,0 +1,55 @@ +name: demo notebook + +# Builds examples/demo.py (a `# %%` cell file) into examples/demo.ipynb with jupytext, so it can be +# opened in Colab. On pushes to main the notebook is force-pushed to the orphan `demo` branch, which +# the README's "Open in Colab" badge points at; on pull requests it is only built, as a check. + +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +permissions: + contents: write + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + - run: python -m pip install jupytext nbformat + - name: Convert the cell files to notebooks + run: | + set -e + for f in examples/*.py; do + case "$f" in examples/make_gif.py) continue;; esac + jupytext --to ipynb "$f" + done + ls -la examples/*.ipynb + - name: Publish to the demo branch + if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request' + run: | + set -e + mkdir -p /tmp/demo/examples + cp examples/*.ipynb /tmp/demo/examples/ + cp README.md LICENSE /tmp/demo/ + cd /tmp/demo + git init -q -b demo + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + cat > README.md <<'EOF' + # liveplot demo notebooks + + Generated by CI from `examples/*.py` on `main`; do not edit here. + Open in Colab: https://colab.research.google.com/github/ARENA-education/liveplot/blob/demo/examples/demo.ipynb + EOF + git add -A + git commit -q -m "Build demo notebooks from main ${GITHUB_SHA::7}" + git remote add origin "https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" + git push -q --force origin demo + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..4d703b3 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,23 @@ +name: tests + +on: + push: + branches: [main] + pull_request: + +jobs: + pytest: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + python: ["3.10", "3.12"] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python }} + - run: python -m pip install --upgrade pip + - run: python -m pip install -e ".[test]" tqdm ipython numpy + - run: python -m pytest -q tests diff --git a/README.md b/README.md index c26ec53..da92886 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,10 @@ Live training curves in Jupyter, Colab and the VS Code / Cursor interactive window, with a tqdm bar underneath, at (almost) no cost to the training loop. +[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/ARENA-education/liveplot/blob/demo/examples/demo.ipynb) [![tests](https://github.com/ARENA-education/liveplot/actions/workflows/tests.yml/badge.svg)](https://github.com/ARENA-education/liveplot/actions/workflows/tests.yml) + +Try it in Colab with the badge above: that notebook is [`examples/demo.py`](examples/demo.py), a cell-by-cell tour of the features, which CI converts with jupytext and publishes to the `demo` branch on every push to `main`. + ![training loss every step, eval loss and accuracy every 50 steps, on one panel with two y-axes](docs/demo.gif) The GIF is cell 2 of [`examples/demo.py`](examples/demo.py), a tour of the features in a cell-separated file: open it in the VS Code / Cursor interactive window and run it cell by cell. That cell trains a tiny numpy classifier, logging the training loss every step and the eval loss and accuracy every 50 steps, laid out as `"loss | eval_loss eval_acc"`. The GIF itself was recorded by the library: `examples/make_gif.py` runs the same loop as a plain script with `record="docs/demo.gif"`. diff --git a/examples/demo.py b/examples/demo.py index 740c132..782f541 100644 --- a/examples/demo.py +++ b/examples/demo.py @@ -1,12 +1,25 @@ # %% """ liveplot tour. A cell-separated file: run it cell by cell in the VS Code / Cursor interactive -window (or paste the cells into a notebook) to watch each plot update live. Running it as a plain -script also works, it just prints instead of drawing (except the recording cell, which renders). - - pip install git+https://github.com/ARENA-education/liveplot.git +window, open it in Colab (the README has the link: CI turns this file into a notebook on the +`demo` branch), or run it as a plain script, which prints instead of drawing (except the +recording cell, which renders). """ +# Setup: on Colab, install liveplot; anywhere else it is already installed. +import subprocess +import sys + +try: + import google.colab # noqa: F401 + + IN_COLAB = True +except ImportError: + IN_COLAB = False +if IN_COLAB: + subprocess.run([sys.executable, "-m", "pip", "install", "-q", "git+https://github.com/ARENA-education/liveplot.git"], check=True) + +# %% import math import random import time diff --git a/tests/test_liveplot.py b/tests/test_liveplot.py index e057b75..e38653c 100644 --- a/tests/test_liveplot.py +++ b/tests/test_liveplot.py @@ -181,6 +181,7 @@ def test_interrupt_inside_with_block_is_clean(fake_notebook): assert len(p.data["loss"][0]) == n_warmup + 31 and fake_notebook.frames, "final frame drawn, data kept" +@pytest.mark.skipif(sys.platform == "win32", reason="os.kill(pid, SIGINT) terminates the process on Windows") def test_render_child_ignores_sigint(fake_notebook): """Jupyter's interrupt goes to the whole process group; the renderer must shrug it off.""" p = LivePlot(refresh_seconds=0.1) From 01914ad01819c7587ccdcbf65344820a7ccded0a Mon Sep 17 00:00:00 2001 From: David Quarel Date: Thu, 17 Sep 2026 12:52:49 +0000 Subject: [PATCH 2/2] CI: one runner, one Python; that is enough for this Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01G9zs684RFxHvW6cNWqTPSF --- .github/workflows/tests.yml | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 4d703b3..fb57020 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -7,17 +7,11 @@ on: jobs: pytest: - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - python: ["3.10", "3.12"] + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: - python-version: ${{ matrix.python }} - - run: python -m pip install --upgrade pip + python-version: "3.12" - run: python -m pip install -e ".[test]" tqdm ipython numpy - run: python -m pytest -q tests