Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions .github/workflows/publish_pypi.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# .github/workflows/publish_pypi.yml
---
name: publish-pypi
on:
workflow_dispatch:
inputs:
tag:
description: Tag to publish, for example v1.4.0
required: true
type: string
jobs:
pypi-publish:
name: Upload release to PyPI
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/p/choreographer
# Signs this workflow so pypi trusts it
permissions:
id-token: write
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ inputs.tag }}
# setuptools-git-versioning reads the tags to set the version
fetch-depth: 0
- uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
- run: uv sync --locked --all-extras --no-sources
- run: git diff --quiet HEAD || { echo "Working tree dirty"; exit 1; }
- run: uv build
- name: Verify that the build matches the tag
run: |
expected="${{ inputs.tag }}"
expected="${expected#v}"
wheel="dist/choreographer-$expected-py3-none-any.whl"
sdist="dist/choreographer-$expected.tar.gz"
if [ ! -f "$wheel" ] || [ ! -f "$sdist" ]; then
echo "Expected version $expected, but dist/ holds:"
ls -1 dist/
exit 1
fi
echo "Built $expected from ${{ inputs.tag }}"
- name: Publish package distributions to PyPI
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
with:
# A re-run of a tag already on PyPI skips instead of failing
skip-existing: true
8 changes: 5 additions & 3 deletions .github/workflows/ruff.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
---
name: ruff-wf
name: ruff
on: pull_request
jobs:
ruff:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/ruff-action@v3
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: astral-sh/ruff-action@278981a28ce3188b1e39527901f38254bf3aac89 # v4.1.0
with:
src: 'src'
# Keep this in step with the rev in .pre-commit-config.yaml
version: 0.16.8
16 changes: 10 additions & 6 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
name: test-wf
name: test
on:
pull_request:
push:
Expand All @@ -8,15 +8,19 @@ on:
jobs:
test-all:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
# The oldest and the newest supported python: version-specific
# breakage lives at the floor, and the tag workflow covers the middle
python_v: ['3.9', '3.14']
runs-on: ${{ matrix.os }}
env:
UV_PYTHON: ${{ matrix.python_v }}
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v5
- uses: actions/setup-python@v5
with:
python-version-file: "pyproject.toml"
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
- run: uv python install ${{ matrix.python_v }}

- name: Install Dependencies
if: ${{ matrix.os == 'ubuntu-latest' }}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# .github/workflows/publish_testpypi.yml
# .github/workflows/test_and_build.yml
---
name: test-n-build
name: test-and-build
on:
workflow_dispatch:
push:
Expand All @@ -12,17 +12,17 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
python_v: ['3.9', '3.10', '3.12', '3.13', '3.14', '3.14t']
python_v: ['3.9', '3.10', '3.11', '3.12', '3.13', '3.14', '3.14t']
# chrome_v: ['-1']
name: Build and Test
runs-on: ${{ matrix.os }}
env:
UV_PYTHON: ${{ matrix.python_v }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 1
- uses: astral-sh/setup-uv@v5
- uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
- name: Install Dependencies
if: ${{ matrix.os == 'ubuntu-latest' }}
run: sudo apt-get update && sudo apt-get install xvfb
Expand Down Expand Up @@ -68,32 +68,3 @@ jobs:
CHOREO_ENABLE_DEBUG: 1
run: xvfb-run uv run --no-sync poe debug-test
timeout-minutes: 8

testpypi-publish:
name: Upload release to TestPyPI
needs: super-test
if: ${{ !cancelled() &&
!failure() &&
github.event_name == 'push' &&
github.run_attempt == 1 }}
runs-on: ubuntu-latest
environment:
name: testpypi
url: https://test.pypi.org/p/choreographer
# Signs this workflow so pypi trusts it
permissions:
id-token: write
steps:
- name: Checkout
uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v4
- uses: actions/setup-python@v5
with:
python-version-file: "pyproject.toml"
- run: git checkout ${{ github.ref_name }}
- run: uv sync --locked --all-extras --no-sources
- run: uv build
- name: Publish package distributions to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
repository-url: https://test.pypi.org/legacy/.
6 changes: 3 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ repos:
hooks:
- id: add-trailing-comma
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.14.4
# Ruff version. Keep this in step with the version in ruff.yml
rev: v0.16.8
hooks:
# Run the linter.
- id: ruff
Expand All @@ -43,7 +43,7 @@ repos:
types: [file, yaml]
args: [
'-d',
"{ extends: default, rules: { colons: { max-spaces-after: -1 } } }",
"{ extends: default, rules: { colons: { max-spaces-after: -1 }, line-length: { max: 120 } } }",
]
- repo: https://github.com/rhysd/actionlint
rev: v1.7.8
Expand Down
1 change: 0 additions & 1 deletion .python_version

This file was deleted.

125 changes: 125 additions & 0 deletions RELEASE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# How to release choreographer

This document describes how a maintainer publishes a new version of choreographer. The primary steps are a changelog update, a git tag, and an upload to PyPI.

## Before you start

You need the following:

- Write access to <https://github.com/plotly/choreographer>
- An account on PyPI with upload permissions for the `choreographer` project
- A PyPI API token, or membership of the `pypi` deployment environment
- `uv` on your machine
- A local clone of the repository with all tags

Get the tags with this command:

```bash
git fetch --tags
```

## How the version number works

No file in the repository holds the version number. The build backend uses `setuptools-git-versioning`, so the most recent git tag sets the version. See the `[tool.setuptools-git-versioning]` table in `pyproject.toml`.

Print the version that the current checkout produces:

```bash
uv run --no-sync --with setuptools-git-versioning setuptools-git-versioning
```

On a tagged commit, the command prints the clean version, for example `1.3.0`. On any other commit, the command prints a development version, for example `1.3.0.post22+git.4a278bc2`. PyPI rejects a development version, because the version carries a local part after the plus sign. Thus only a tagged commit produces a package that you can upload.

Tag names start with `v` and follow [semantic versioning](https://semver.org). A release candidate adds `rcN` with no separator, for example `v1.4.0rc0`.

## Step 1: Update the changelog

1. Open `CHANGELOG.md`. The file follows the [keep a changelog](https://keepachangelog.com) format.
2. Add a new section under `## [Unreleased]`, in the form `## [X.Y.Z] -- YYYY-MM-DD`
3. Move each entry out of the `[Unreleased]` section into the new section. Leave the `[Unreleased]` heading in place with no entries.
4. Keep the subsection order: `Added`, `Changed`, `Removed`, `Fixed`
5. Make sure that each entry links to the pull request or the issue

Open a pull request with this change and merge the pull request into `main`. The repository uses conventional commits, so title the pull request `chore: Update files for release of vX.Y.Z`.

## Step 2: Confirm that main is ready

1. Open the Actions tab and confirm that `test` passed on `main`
2. Make sure that `uv.lock` matches `pyproject.toml`. The release workflow runs `uv sync --locked` and fails on a stale lock file.

```bash
uv lock --check
```

The command exits 0 when the two files agree, and exits 1 when they do not.

3. Make sure that your working tree is clean. The release workflow fails on a dirty tree, because a dirty tree changes the version.

## Step 3: Tag the release

Tag the merge commit of your changelog pull request. The project uses lightweight tags.

> [!NOTE]
> `setuptools-git-versioning` reads a lightweight tag correctly. But plain `git describe` skips a lightweight tag and reports an older one. Pass `--tags` to see the true most recent tag.

```bash
git checkout main
git pull
git tag v1.4.0
git push origin v1.4.0
```

> [!WARNING]
> PyPI refuses a second upload of the same version. If you must correct a release that PyPI holds already, release the next version number. A tag that no package index knows is still safe to move.

## Step 4: Watch the release workflow

The tag push starts the `test-and-build` workflow in `.github/workflows/test_and_build.yml`. The single `super-test` job builds the package on Linux, Windows, and macOS, across each supported Python version. The job reinstalls the built wheel, downloads chrome, runs `choreo_diagnose`, and runs the test suite.

The workflow publishes nothing. The workflow proves that the tagged commit builds and passes on every platform.

If `super-test` fails, read the failure before you continue. A flaky browser test does not block the release, but a build failure does.

## Step 5: Publish to PyPI

The `publish-pypi` workflow in `.github/workflows/publish_pypi.yml` uploads the package. The workflow authenticates with OIDC through the `pypi` deployment environment, so no token is necessary.

1. Open the Actions tab and select `publish-pypi`
2. Select Run workflow
3. Enter the tag, for example `v1.4.0`
4. Start the run
5. Open the run and approve the deployment under Review deployments

The `pypi` environment needs a review from the `plotly/libraries_admin` team. You can approve your own run. The job waits for that approval before GitHub issues the OIDC token, so an unapproved run never reaches PyPI.

The workflow checks out the tag, builds the package, and confirms that the built version matches the tag. The upload carries `skip-existing`, so a second run of the same tag skips the files that PyPI holds already.

PyPI marks a release candidate as a pre-release, so `pip install choreographer` continues to resolve to the most recent final version.

> [!NOTE]
> To upload from your machine instead, build from a clean clone of the tag and run `uv publish` with a PyPI API token. Clear `dist/` first. `uv publish` sends every file in `dist/`, so an old artifact there fails the upload.

## Step 6: Create the GitHub release

`CHANGELOG.md` points readers to the GitHub releases page for more context.

1. Open <https://github.com/plotly/choreographer/releases/new>
2. Select the tag that you pushed
3. Use the tag name as the title
4. Paste the new `CHANGELOG.md` section as the body
5. For a release candidate, mark the release as a pre-release

## Step 7: Confirm the release

Install the package from PyPI in a clean environment:

```bash
uv run --no-project --with choreographer==1.4.0 choreo_diagnose --no-run
```

If the release changes the public API, tell the maintainers of [kaleido](https://pypi.org/project/kaleido/), which depends on choreographer.

## Current gaps

- The `testpypi` deployment environment has no purpose now. No workflow uses that environment.
- No workflow builds or deploys the documentation. The `mkdocs` dependency group in `pyproject.toml` is commented out, and `mkdocs.yml` needs the `quimeta`, `quicopy`, and `quiapi` plugins from `mkquixote`, which installs only over SSH from a private repository. A release changes no documentation site.
Loading