Move build system from setuptools to uv, wire uv into CI/CD
CI / lint:ruff (push) Successful in 25s
CI / format:ruff (push) Successful in 14s
CI / typecheck:ty (push) Successful in 15s
CI / vulnerabilities:pip-audit (push) Successful in 17s
CI / test:pytest (push) Successful in 24s

Convert both pyproject.toml files to the uv_build backend and turn the
repo into a proper uv workspace (plotstyle moved to src layout, since
uv_build requires the module in its own subdirectory). Dev tooling
moves from an optional-dependencies extra to a dependency-group, and
plotstyle's matplotlib dependency is now its own rather than a
gallery "plotting" extra.

Gitea Actions workflows and the Dockerfile now use astral-sh/setup-uv,
uv sync, uv run, uv build, and uv publish throughout instead of pip,
build, and twine. Also fixes .dockerignore, which was excluding
uv.lock, deploy/, and README.md and would have broken even the
previous Dockerfile's COPY of deploy/entrypoint.sh.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-24 10:30:20 +02:00
parent 2020ac4883
commit 5109b6505c
17 changed files with 1081 additions and 757 deletions
-3
View File
@@ -10,8 +10,5 @@ build
*.egg-info *.egg-info
.claude .claude
config.yaml config.yaml
deploy/
docs/ docs/
README.md
CLAUDE.md CLAUDE.md
uv.lock
+20 -25
View File
@@ -10,52 +10,48 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-python@v5 - uses: astral-sh/setup-uv@v9.0.0
with: with:
python-version: "3.11" python-version: "3.11"
cache: pip enable-cache: true
cache-dependency-path: pyproject.toml - run: uv sync --all-packages
- run: pip install -e ".[dev,plotting]" - run: uv run ruff check gallery plotstyle tests
- run: ruff check gallery plotstyle tests
format: format:
name: format:ruff name: format:ruff
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-python@v5 - uses: astral-sh/setup-uv@v9.0.0
with: with:
python-version: "3.11" python-version: "3.11"
cache: pip enable-cache: true
cache-dependency-path: pyproject.toml - run: uv sync --all-packages
- run: pip install -e ".[dev,plotting]" - run: uv run ruff format --check gallery plotstyle tests
- run: ruff format --check gallery plotstyle tests
typecheck: typecheck:
name: typecheck:ty name: typecheck:ty
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-python@v5 - uses: astral-sh/setup-uv@v9.0.0
with: with:
python-version: "3.11" python-version: "3.11"
cache: pip enable-cache: true
cache-dependency-path: pyproject.toml - run: uv sync --all-packages
- run: pip install -e ".[dev,plotting]" - run: uv run ty check gallery plotstyle
- run: ty check gallery plotstyle
vulnerabilities: vulnerabilities:
name: vulnerabilities:pip-audit name: vulnerabilities:pip-audit
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-python@v5 - uses: astral-sh/setup-uv@v9.0.0
with: with:
python-version: "3.11" python-version: "3.11"
cache: pip enable-cache: true
cache-dependency-path: pyproject.toml - run: uv sync --all-packages
- run: pip install -e ".[dev,plotting]" - run: uv run pip-audit --skip-editable
- run: pip-audit --skip-editable
test: test:
name: test:pytest name: test:pytest
@@ -63,10 +59,9 @@ jobs:
needs: [lint, format, typecheck, vulnerabilities] needs: [lint, format, typecheck, vulnerabilities]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-python@v5 - uses: astral-sh/setup-uv@v9.0.0
with: with:
python-version: "3.11" python-version: "3.11"
cache: pip enable-cache: true
cache-dependency-path: pyproject.toml - run: uv sync --all-packages
- run: pip install -e ".[dev,plotting]" - run: uv run pytest tests/ -v
- run: python -m pytest tests/ -v
+39
View File
@@ -0,0 +1,39 @@
name: Publish plotstyle
on:
push:
tags:
- "plotstyle-v*"
permissions:
contents: read
packages: write
jobs:
publish:
name: build-and-publish
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v9.0.0
with:
python-version: "3.11"
- name: Verify tag matches package version
run: |
tag_version="${GITHUB_REF_NAME#plotstyle-v}"
pkg_version="$(uv version --short --package plotstyle)"
if [ "$tag_version" != "$pkg_version" ]; then
echo "Tag plotstyle-v$tag_version does not match pyproject.toml version $pkg_version" >&2
exit 1
fi
- name: Build wheel and sdist
run: uv build --package plotstyle -o dist
- name: Publish to Gitea package registry
env:
UV_PUBLISH_USERNAME: ${{ github.actor }}
UV_PUBLISH_PASSWORD: ${{ secrets.GITHUB_TOKEN }}
run: uv publish --publish-url "${{ github.server_url }}/api/packages/${{ github.repository_owner }}/pypi" dist/*
+23 -21
View File
@@ -8,39 +8,38 @@ A Python package that generates responsive static HTML galleries from scientific
The repo also ships `plotstyle`, a standalone matplotlib styling toolkit (KIT corporate-design theme + building-block functions) for producing the PDF figures that feed into a `gallery` source directory. `gallery` never imports it — the only connection is the PDF files and `metadata.yaml` on disk. **See `plotstyle/CLAUDE.md` for full agent-facing usage docs, the API reference, and the metadata.yaml workflow.** The repo also ships `plotstyle`, a standalone matplotlib styling toolkit (KIT corporate-design theme + building-block functions) for producing the PDF figures that feed into a `gallery` source directory. `gallery` never imports it — the only connection is the PDF files and `metadata.yaml` on disk. **See `plotstyle/CLAUDE.md` for full agent-facing usage docs, the API reference, and the metadata.yaml workflow.**
This repo is a **uv workspace** (root `pyproject.toml` has `[tool.uv.workspace] members = ["plotstyle"]`) and both projects build with uv's own `uv_build` backend — there is no setuptools anywhere in this repo. Use `uv`/`uv run` for everything; don't reach for `pip install` here.
## Commands ## Commands
```bash ```bash
# Install the package (editable) # Install everything (gallery + plotstyle + dev tools) into the shared workspace venv
pip install -e ".[dev]" uv sync --all-packages
# Also install plotstyle's dependency (matplotlib) if working on plot-producing scripts
pip install -e ".[dev,plotting]" # or: uv sync --extra dev --extra plotting
# Run all tests # Run all tests
pytest tests/ uv run pytest tests/
# Run a single test file # Run a single test file
pytest tests/test_generate_gallery.py -v uv run pytest tests/test_generate_gallery.py -v
pytest tests/test_plotstyle.py -v uv run pytest tests/test_plotstyle.py -v
# Run a single test by name # Run a single test by name
pytest tests/test_generate_gallery.py::test_needs_update_missing_target -v uv run pytest tests/test_generate_gallery.py::test_needs_update_missing_target -v
# Generate gallery # Generate gallery
gallery generate --verbose uv run gallery generate --verbose
# Generate with a non-default config # Generate with a non-default config
gallery --config config.yaml generate --verbose uv run gallery --config config.yaml generate --verbose
# Incremental update for one source only # Incremental update for one source only
gallery generate --source /path/to/plots --verbose uv run gallery generate --source /path/to/plots --verbose
# Clean regeneration # Clean regeneration
gallery generate --clean --verbose uv run gallery generate --clean --verbose
# Launch TUI # Launch TUI
gallery tui uv run gallery tui
# Serve output locally # Serve output locally
python -m http.server 8000 -d /web/kschmidt/public_html/ python -m http.server 8000 -d /web/kschmidt/public_html/
@@ -49,10 +48,10 @@ python -m http.server 8000 -d /web/kschmidt/public_html/
Code style: ruff (lint + format), `line-length = 120`. Type-checked with `ty`. Code style: ruff (lint + format), `line-length = 120`. Type-checked with `ty`.
```bash ```bash
ruff check gallery plotstyle tests uv run ruff check gallery plotstyle tests
ruff format gallery plotstyle tests uv run ruff format gallery plotstyle tests
ty check gallery plotstyle uv run ty check gallery plotstyle
pip-audit --skip-editable uv run pip-audit --skip-editable
``` ```
**Before committing**, run the same checks CI (`.gitea/workflows/ci.yml`) runs and make sure they pass — `ruff check`, `ruff format --check`, `ty check`, `pip-audit`, and `pytest tests/`. Catching a failure locally is faster than waiting on the pipeline. **Before committing**, run the same checks CI (`.gitea/workflows/ci.yml`) runs and make sure they pass — `ruff check`, `ruff format --check`, `ty check`, `pip-audit`, and `pytest tests/`. Catching a failure locally is faster than waiting on the pipeline.
@@ -90,7 +89,8 @@ generate() [api.py]
| `gallery/assets/js/` | Vanilla JS modules loaded as ES modules; `GalleryApp` in `gallery-app.js` orchestrates all managers | | `gallery/assets/js/` | Vanilla JS modules loaded as ES modules; `GalleryApp` in `gallery-app.js` orchestrates all managers |
| `gallery/assets/css/` | Modular CSS; `main.css` imports all others via `@import` | | `gallery/assets/css/` | Modular CSS; `main.css` imports all others via `@import` |
| `config.yaml` | Local deployment config (paths are machine-specific) | | `config.yaml` | Local deployment config (paths are machine-specific) |
| `plotstyle/` | Standalone matplotlib styling toolkit for producing plots (see `plotstyle/CLAUDE.md`) — not imported by `gallery/` | | `plotstyle/` | Standalone matplotlib styling toolkit for producing plots (see `plotstyle/CLAUDE.md`) — not imported by `gallery/`; a separate uv workspace member with its own `pyproject.toml`, code under `plotstyle/src/plotstyle/` (src layout) |
| `.gitea/workflows/publish-plotstyle.yml` | Builds and publishes `plotstyle` to the Gitea package registry on `plotstyle-v*` tags |
| `examples/plotstyle_showcase.ipynb` | Rendered, runnable tour of every `plotstyle` function | | `examples/plotstyle_showcase.ipynb` | Rendered, runnable tour of every `plotstyle` function |
### Config File Format ### Config File Format
@@ -125,7 +125,9 @@ Fields are freeform YAML (no fixed schema); `title`, `description`, `plot_type`,
### Plot-Producing Companion (`plotstyle`) ### Plot-Producing Companion (`plotstyle`)
`plotstyle` (top-level package, optional `plotting` extra) is how plots destined for a `gallery` source directory should be produced — a KIT corporate-design matplotlib theme plus building blocks (`new_figure`, `colorbar`, `style_legend`, `panel_label`, `savefig`). It has no code dependency on `gallery`; the two only meet on disk, via the PDFs and `metadata.yaml` files a `plotstyle` script writes into a `gallery` source directory. **Full usage docs, API reference, best practices, and the metadata.yaml workflow live in `plotstyle/CLAUDE.md`** — read that file before writing or reviewing any script that `import plotstyle`. `examples/plotstyle_showcase.ipynb` is a rendered, runnable tour of every function. `plotstyle` (a separate uv workspace member, see below) is how plots destined for a `gallery` source directory should be produced — a KIT corporate-design matplotlib theme plus building blocks (`new_figure`, `colorbar`, `style_legend`, `panel_label`, `savefig`). It has no code dependency on `gallery`; the two only meet on disk, via the PDFs and `metadata.yaml` files a `plotstyle` script writes into a `gallery` source directory. **Full usage docs, API reference, best practices, and the metadata.yaml workflow live in `plotstyle/CLAUDE.md`** — read that file before writing or reviewing any script that `import plotstyle`. `examples/plotstyle_showcase.ipynb` is a rendered, runnable tour of every function.
`plotstyle` has its own `plotstyle/pyproject.toml` (own `uv_build` project, code lives in `plotstyle/src/plotstyle/`) and is a member of this repo's uv workspace (`[tool.uv.workspace] members = ["plotstyle"]` in the root `pyproject.toml`) — `uv sync --all-packages` installs both `gallery` and `plotstyle` (and matplotlib) into one shared venv, which is why `import plotstyle` works from `tests/` and the example notebook without `gallery` ever depending on it. It's also independently publishable: pushing a tag matching `plotstyle-v*` (e.g. `plotstyle-v0.1.0`) runs `.gitea/workflows/publish-plotstyle.yml`, which runs `uv build --package plotstyle` and `uv publish` to this repo's Gitea package registry (`{server}/api/packages/{owner}/pypi`) using the workflow's auto-generated token. The tag's version suffix must match `plotstyle/pyproject.toml`'s `version` field or the workflow fails fast — bump that version before tagging a new release.
### Frontend (Static JS/CSS) ### Frontend (Static JS/CSS)
@@ -133,6 +135,6 @@ The frontend is vanilla ES modules — no build step. `assets/js/main.js` import
### Deployment ### Deployment
The project ships a `Dockerfile` plus `docker-compose.yml` (a `generator` service that runs `gallery generate` on an interval, and an `nginx`-based `web` service serving the output — see `deploy/entrypoint.sh` and `deploy/nginx.conf`). This suits a dedicated VM/server you fully control. CI (`.gitea/workflows/ci.yml`, run via Gitea Actions) runs `ruff check`, `ruff format --check`, `ty check`, `pip-audit`, and pytest on the default runner image with Python 3.11 via `actions/setup-python` — no container build/publish in CI. For local development the `.venv` (or `uv`) is sufficient. The project ships a `Dockerfile` plus `docker-compose.yml` (a `generator` service that runs `gallery generate` on an interval, and an `nginx`-based `web` service serving the output — see `deploy/entrypoint.sh` and `deploy/nginx.conf`). This suits a dedicated VM/server you fully control. The `Dockerfile` is a two-stage build: a builder stage copies in the official `ghcr.io/astral-sh/uv` binary and runs `uv build --package gallery` to produce a wheel, then the final stage `pip install`s just that wheel (no dev deps, no workspace/lockfile needed at runtime). CI (`.gitea/workflows/ci.yml`, run via Gitea Actions) uses `astral-sh/setup-uv` + `uv sync --all-packages` and runs every check (`ruff check`, `ruff format --check`, `ty check`, `pip-audit`, `pytest`) via `uv run` — no plain `pip`/`actions/setup-python` anywhere in CI. For local development, `uv sync --all-packages` is all you need.
On shared HPC login nodes without a Docker daemon (e.g. KIT ETP, where `public_html` is already auto-served) use a plain venv install plus a `systemd --user` timer instead — see `deploy/systemd/README.md`. On shared HPC login nodes without a Docker daemon (e.g. KIT ETP, where `public_html` is already auto-served) use a plain venv install plus a `systemd --user` timer instead — see `deploy/systemd/README.md`.
+14 -4
View File
@@ -1,3 +1,15 @@
FROM python:3.11-slim AS builder
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /usr/local/bin/
WORKDIR /app
COPY pyproject.toml uv.lock README.md ./
COPY plotstyle/pyproject.toml plotstyle/README.md plotstyle/
COPY gallery/ gallery/
RUN uv build --package gallery -o dist
FROM python:3.11-slim FROM python:3.11-slim
RUN apt-get update \ RUN apt-get update \
@@ -6,12 +18,10 @@ RUN apt-get update \
WORKDIR /app WORKDIR /app
COPY pyproject.toml . COPY --from=builder /app/dist/*.whl /tmp/
COPY gallery/ gallery/
COPY tests/ tests/
COPY deploy/entrypoint.sh deploy/entrypoint.sh COPY deploy/entrypoint.sh deploy/entrypoint.sh
RUN pip install --no-cache-dir ".[dev]" RUN pip install --no-cache-dir /tmp/*.whl && rm /tmp/*.whl
RUN useradd -r -s /bin/false gallery RUN useradd -r -s /bin/false gallery
USER gallery USER gallery
+8 -4
View File
@@ -60,13 +60,17 @@ gallery --help
### Optional: `plotstyle` (for producing plots) ### Optional: `plotstyle` (for producing plots)
`plotstyle` is a separate package (its own `plotstyle/pyproject.toml`, a member of this repo's uv workspace) — installing `gallery` alone never pulls in `matplotlib`.
```bash ```bash
pip install -e ".[plotting]" # Working in this repo (installs gallery + plotstyle + matplotlib together):
# or uv sync --all-packages
uv sync --extra plotting
# Just the plotstyle library, standalone:
pip install "plotstyle @ git+https://git.larsbogner.de/lars/ETPlot#subdirectory=plotstyle"
``` ```
This only installs `matplotlib` — it's kept out of the core dependencies so a plain gallery install stays lightweight. `plotstyle` also requires a local LaTeX toolchain (`latex` + `dvipng`) to be installed separately; see [Producing Plots with `plotstyle`](#producing-plots-with-plotstyle). `plotstyle` also requires a local LaTeX toolchain (`latex` + `dvipng`) to be installed separately; see [Producing Plots with `plotstyle`](#producing-plots-with-plotstyle).
### Shell Completion (optional) ### Shell Completion (optional)
+9 -6
View File
@@ -21,15 +21,18 @@ produces. Everything below is the reference; the notebook is the demo.
## Install ## Install
`plotstyle` is its own project (`plotstyle/pyproject.toml`, `uv_build`
backend, code under `plotstyle/src/plotstyle/`) and a member of this repo's
uv workspace (`[tool.uv.workspace]` in the root `pyproject.toml`) — not a
`gallery` extra.
```bash ```bash
uv sync --extra plotting # this repo's uv workflow uv sync --all-packages # this repo's uv workflow — installs gallery + plotstyle together
# or
pip install -e ".[plotting]"
``` ```
`plotstyle` is an optional extra (`matplotlib>=3.7`) so core `gallery` `matplotlib>=3.7` is `plotstyle`'s own direct dependency — don't add it to
installs stay lightweight — don't add matplotlib to `gallery`'s own `gallery`'s dependencies to support this package; `gallery` should stay
unconditional dependencies to support this package. installable without ever pulling in matplotlib.
**Hard requirement: a working local LaTeX toolchain (`latex` + `dvipng`).** **Hard requirement: a working local LaTeX toolchain (`latex` + `dvipng`).**
`plotstyle.use()` sets `text.usetex = True` unconditionally — there is no `plotstyle.use()` sets `text.usetex = True` unconditionally — there is no
+40
View File
@@ -0,0 +1,40 @@
# plotstyle
A KIT (Karlsruhe Institute of Technology) corporate-design matplotlib
styling toolkit — a validated color palette, consistent spines/ticks/
gridlines, LaTeX text in a modern sans font, and a few building-block
functions (figure titles with a parameters subtitle, a same-size colorbar
helper, an outside-axes legend, panel labels) for producing figures that
look consistent across a thesis and a slide deck.
Requires a local LaTeX toolchain (`latex` + `dvipng`) — `plotstyle` always
renders text through real LaTeX, there's no fallback.
```python
import numpy as np
import plotstyle as ps
ps.use() # once, before creating any figure
fig, ax = ps.new_figure(
"thesis-single",
title="Measured signal",
params={"N": 512, "seed": 42},
)
x = np.linspace(0, 10, 200)
ax.plot(x, np.sin(x), label="signal")
ax.set_xlabel("Time (s)")
ax.set_ylabel(r"Amplitude $A(t)$")
ps.style_legend(ax, title="Series")
ps.savefig(fig, "plots/measured_signal", formats=("pdf",))
```
This package is developed as part of the
[ETPlot](https://git.larsbogner.de/lars/ETPlot) monorepo, where it is paired
with `gallery`, a static HTML gallery generator that turns directories of
plot PDFs into a browsable website — `plotstyle` has no code dependency on
`gallery`, the two only meet on disk via the PDF (and optional
`metadata.yaml`) files a `plotstyle` script writes out. See that repo's
`examples/plotstyle_showcase.ipynb` for a fully rendered tour of every
function, and `plotstyle/CLAUDE.md` for the full API reference.
+37
View File
@@ -0,0 +1,37 @@
[build-system]
requires = ["uv_build>=0.11.19,<0.12.0"]
build-backend = "uv_build"
[project]
name = "plotstyle"
version = "0.1.0"
description = "KIT corporate-design matplotlib styling toolkit for consistent scientific figures"
readme = "README.md"
requires-python = ">=3.8"
license = {text = "MIT"}
authors = [
{name = "K. Schmidt"},
]
keywords = [
"matplotlib",
"plotting",
"scientific-computing",
"styling",
]
classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: Science/Research",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Scientific/Engineering :: Visualization",
]
dependencies = [
"matplotlib>=3.7",
]
+10 -13
View File
@@ -1,6 +1,6 @@
[build-system] [build-system]
requires = ["setuptools>=83.0.0", "wheel"] requires = ["uv_build>=0.11.19,<0.12.0"]
build-backend = "setuptools.build_meta" build-backend = "uv_build"
[project] [project]
name = "gallery" name = "gallery"
@@ -43,25 +43,22 @@ dependencies = [
"textual>=0.50", "textual>=0.50",
] ]
[project.optional-dependencies] [project.scripts]
gallery = "gallery.cli:main"
[dependency-groups]
dev = [ dev = [
"pytest>=7.0", "pytest>=7.0",
"ruff>=0.6", "ruff>=0.6",
"ty>=0.0.1", "ty>=0.0.1",
"pip-audit>=2.7", "pip-audit>=2.7",
"setuptools>=83.0.0",
]
plotting = [
"matplotlib>=3.7",
] ]
[project.scripts] [tool.uv.workspace]
gallery = "gallery.cli:main" members = ["plotstyle"]
[tool.setuptools] [tool.uv.build-backend]
packages = ["gallery", "gallery.utils", "gallery.config", "plotstyle"] module-root = ""
package-data = {gallery = ["templates/*", "assets/css/*", "assets/js/*", "config/*"], plotstyle = ["assets/*.mplstyle"]}
include-package-data = true
[tool.ruff] [tool.ruff]
line-length = 120 line-length = 120
Generated
+881 -681
View File
File diff suppressed because it is too large Load Diff