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
.claude
config.yaml
deploy/
docs/
README.md
CLAUDE.md
uv.lock
+20 -25
View File
@@ -10,52 +10,48 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: astral-sh/setup-uv@v9.0.0
with:
python-version: "3.11"
cache: pip
cache-dependency-path: pyproject.toml
- run: pip install -e ".[dev,plotting]"
- run: ruff check gallery plotstyle tests
enable-cache: true
- run: uv sync --all-packages
- run: uv run ruff check gallery plotstyle tests
format:
name: format:ruff
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: astral-sh/setup-uv@v9.0.0
with:
python-version: "3.11"
cache: pip
cache-dependency-path: pyproject.toml
- run: pip install -e ".[dev,plotting]"
- run: ruff format --check gallery plotstyle tests
enable-cache: true
- run: uv sync --all-packages
- run: uv run ruff format --check gallery plotstyle tests
typecheck:
name: typecheck:ty
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: astral-sh/setup-uv@v9.0.0
with:
python-version: "3.11"
cache: pip
cache-dependency-path: pyproject.toml
- run: pip install -e ".[dev,plotting]"
- run: ty check gallery plotstyle
enable-cache: true
- run: uv sync --all-packages
- run: uv run ty check gallery plotstyle
vulnerabilities:
name: vulnerabilities:pip-audit
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: astral-sh/setup-uv@v9.0.0
with:
python-version: "3.11"
cache: pip
cache-dependency-path: pyproject.toml
- run: pip install -e ".[dev,plotting]"
- run: pip-audit --skip-editable
enable-cache: true
- run: uv sync --all-packages
- run: uv run pip-audit --skip-editable
test:
name: test:pytest
@@ -63,10 +59,9 @@ jobs:
needs: [lint, format, typecheck, vulnerabilities]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: astral-sh/setup-uv@v9.0.0
with:
python-version: "3.11"
cache: pip
cache-dependency-path: pyproject.toml
- run: pip install -e ".[dev,plotting]"
- run: python -m pytest tests/ -v
enable-cache: true
- run: uv sync --all-packages
- run: uv run 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.**
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
```bash
# Install the package (editable)
pip install -e ".[dev]"
# Also install plotstyle's dependency (matplotlib) if working on plot-producing scripts
pip install -e ".[dev,plotting]" # or: uv sync --extra dev --extra plotting
# Install everything (gallery + plotstyle + dev tools) into the shared workspace venv
uv sync --all-packages
# Run all tests
pytest tests/
uv run pytest tests/
# Run a single test file
pytest tests/test_generate_gallery.py -v
pytest tests/test_plotstyle.py -v
uv run pytest tests/test_generate_gallery.py -v
uv run pytest tests/test_plotstyle.py -v
# 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
gallery generate --verbose
uv run gallery generate --verbose
# 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
gallery generate --source /path/to/plots --verbose
uv run gallery generate --source /path/to/plots --verbose
# Clean regeneration
gallery generate --clean --verbose
uv run gallery generate --clean --verbose
# Launch TUI
gallery tui
uv run gallery tui
# Serve output locally
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`.
```bash
ruff check gallery plotstyle tests
ruff format gallery plotstyle tests
ty check gallery plotstyle
pip-audit --skip-editable
uv run ruff check gallery plotstyle tests
uv run ruff format gallery plotstyle tests
uv run ty check gallery plotstyle
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.
@@ -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/css/` | Modular CSS; `main.css` imports all others via `@import` |
| `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 |
### Config File Format
@@ -125,7 +125,9 @@ Fields are freeform YAML (no fixed schema); `title`, `description`, `plot_type`,
### 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)
@@ -133,6 +135,6 @@ The frontend is vanilla ES modules — no build step. `assets/js/main.js` import
### 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`.
+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
RUN apt-get update \
@@ -6,12 +18,10 @@ RUN apt-get update \
WORKDIR /app
COPY pyproject.toml .
COPY gallery/ gallery/
COPY tests/ tests/
COPY --from=builder /app/dist/*.whl /tmp/
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
USER gallery
+8 -4
View File
@@ -60,13 +60,17 @@ gallery --help
### 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
pip install -e ".[plotting]"
# or
uv sync --extra plotting
# Working in this repo (installs gallery + plotstyle + matplotlib together):
uv sync --all-packages
# 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)
+9 -6
View File
@@ -21,15 +21,18 @@ produces. Everything below is the reference; the notebook is the demo.
## 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
uv sync --extra plotting # this repo's uv workflow
# or
pip install -e ".[plotting]"
uv sync --all-packages # this repo's uv workflow — installs gallery + plotstyle together
```
`plotstyle` is an optional extra (`matplotlib>=3.7`) so core `gallery`
installs stay lightweight — don't add matplotlib to `gallery`'s own
unconditional dependencies to support this package.
`matplotlib>=3.7` is `plotstyle`'s own direct dependency — don't add it to
`gallery`'s dependencies to support this package; `gallery` should stay
installable without ever pulling in matplotlib.
**Hard requirement: a working local LaTeX toolchain (`latex` + `dvipng`).**
`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]
requires = ["setuptools>=83.0.0", "wheel"]
build-backend = "setuptools.build_meta"
requires = ["uv_build>=0.11.19,<0.12.0"]
build-backend = "uv_build"
[project]
name = "gallery"
@@ -43,25 +43,22 @@ dependencies = [
"textual>=0.50",
]
[project.optional-dependencies]
[project.scripts]
gallery = "gallery.cli:main"
[dependency-groups]
dev = [
"pytest>=7.0",
"ruff>=0.6",
"ty>=0.0.1",
"pip-audit>=2.7",
"setuptools>=83.0.0",
]
plotting = [
"matplotlib>=3.7",
]
[project.scripts]
gallery = "gallery.cli:main"
[tool.uv.workspace]
members = ["plotstyle"]
[tool.setuptools]
packages = ["gallery", "gallery.utils", "gallery.config", "plotstyle"]
package-data = {gallery = ["templates/*", "assets/css/*", "assets/js/*", "config/*"], plotstyle = ["assets/*.mplstyle"]}
include-package-data = true
[tool.uv.build-backend]
module-root = ""
[tool.ruff]
line-length = 120
Generated
+881 -681
View File
File diff suppressed because it is too large Load Diff