These are shared GitHub workflows used by my open source Python repositories. The workflows rely on my standard build process, which is implemented in the run-script-framework repository.
Currently, the build process relies on the UV build tool. I migrated to UV starting in mid-2025. If you began using the run script framework prior to then, your repository probably relies on different standards, and the latest version of the workflows won't work for you. Use @v9 of the workflows instead of the latest version.
As of this writing (in mid-2025), the recommended best practice for publishing to PyPI is to use so-called Trusted Publishers, also discussed in the Python Packaging User Guide and in this postmortem of the Ultralytics supply chain attack. The Trusted Publishers mechanism allows the publishing process to use short-lived identity tokens, which are more secure than maintaining a long-lived PyPI API key in your GitHub repository secrets.
GitHub Actions is a supported publisher. Unfortunately, as discussed on the Troubleshooting page and detailed in pypi/warehouse issue #11096, the trusted publishing mechanism does not yet support reusable GitHub Actions. Starting with @v7 of the release workflow, I do rely on the official PyPI Publish shared action to publish artifacts. However, there's no way to support Trusted Publishing with full attestations for the time being.
This workflow implements the standard build and test process for my Python libraries and utilities.
The following input parameters are accepted:
| Input | Type | Required | Default | Description |
|---|---|---|---|---|
matrix-os-version |
String | Yes | JSON array as a string, a list of operating systems for the matrix build | |
matrix-python-version |
String | Yes | JSON array as a string, a list of Python versions for the matrix build | |
test-suite-command |
String | No | see below | Shell command used to execute the test suite |
timeout-minutes |
Number | No | 30 |
Job timeout in minutes |
enable-coveralls |
Boolean | No | false |
Whether to enable coverage reporting to coveralls.io |
persist-python-version |
String | No | none | Which matrix Python version to persist artifacts for, if any |
Note: the version of UV is controlled by
pyproject.toml. Setrequired-versionin the[tool.uv]section.
In order to run the release workflow (discussed below), you must set persist-python-version to persist build artifacts from at exactly one matrix build. Normally, I do this for the oldest supported Python version on the Linux platform, which is what the example below shows.
Starting with v2 of the shared workflow, the default test suite command is:
./run suite
If you need a different command, and it's more complicated than a single line like this, you should extract a script to somewhere in the repository and invoke that script in the test-suite-command. However, in any repo that follows the standard run script convention, it's best just to adjust the suite task to do what you need.
Note: The matrix versions are passed in as JSON strings because GitHub Actions does not support workflow inputs of type array. See this discussion.
This workflow implements the standard release process for my Python libraries and utilities. It is designed to be triggered by pushing a new tag to GitHub, as shown in the example workflow below. It uses the GH Release action to create a new GitHub release, and can optionally publish to PyPI via the official PyPI Publish action.
The workflow relies on build artifacts published by the build process. In your build workflow (discussed above), you must set persist-python-version to persist build artifacts from at exactly one matrix build. Normally, I do this for the oldest supported Python version on the Linux platform, which is what the example below shows.
The following input parameters are accepted:
| Input | Type | Required | Default | Description |
|---|---|---|---|---|
timeout-minutes |
Number | No | 30 |
Job timeout in minutes |
publish-pypi |
Boolean | No | false |
Whether to publish artifacts to PyPI |
# On GHA, the Linux runners are *much* faster and more reliable, so we only run the full matrix build there
name: Test Suite
on:
push:
branches:
- main
tags:
- "v*"
pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
linux-build-and-test:
name: "Linux"
uses: pronovic/gha-shared-workflows/.github/workflows/uv-build-and-test.yml@v10
secrets: inherit
with:
matrix-os-version: "[ 'ubuntu-latest' ]"
matrix-python-version: "[ '3.10', '3.11', '3.12', '3.13' ]" # run Linux tests on all supported Python versions
enable-coveralls: true # only report to coveralls.io for tests that run on Linux
persist-python-version: "3.10" # persist artifacts for the oldest supported Python version
macos-build-and-test:
name: "MacOS"
uses: pronovic/gha-shared-workflows/.github/workflows/uv-build-and-test.yml@v10
secrets: inherit
with:
matrix-os-version: "[ 'macos-latest' ]"
matrix-python-version: "[ '3.13' ]" # only run MacOS tests on latest Python
windows-build-and-test:
name: "Windows"
uses: pronovic/gha-shared-workflows/.github/workflows/uv-build-and-test.yml@v10
secrets: inherit
with:
matrix-os-version: "[ 'windows-latest' ]"
matrix-python-version: "[ '3.13' ]" # only run Windows tests on latest Python
release:
name: "Release"
if: github.ref_type == 'tag'
uses: pronovic/gha-shared-workflows/.github/workflows/release.yml@v10
needs: [ linux-build-and-test, macos-build-and-test, windows-build-and-test ]
secrets: inherit
with:
publish-pypi: true