name: Python Wheels
on:
push:
branches: ["**"]
tags: ["v*"]
paths:
- "bindings/python/**"
- "src/**"
- "Cargo.toml"
- "Cargo.lock"
- "pyproject.toml"
- ".github/workflows/python-wheels.yml"
pull_request:
paths:
- "bindings/python/**"
- "src/**"
- "Cargo.toml"
- "Cargo.lock"
- "pyproject.toml"
- ".github/workflows/python-wheels.yml"
release:
types: [published]
workflow_dispatch:
inputs:
publish_testpypi:
description: "Publish built wheels and sdist to TestPyPI"
required: false
default: false
type: boolean
env:
RUSTFLAGS: "-C target-cpu=generic"
jobs:
build:
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
target: x86_64
artifact: wheel-ubuntu-latest-x86_64
interpreter_args: -i python3.10 -i python3.11 -i python3.12 -i python3.13 -i python3.14
- os: ubuntu-latest
target: aarch64
artifact: wheel-ubuntu-latest-aarch64
interpreter_args: -i python3.10 -i python3.11 -i python3.12 -i python3.13 -i python3.14
- os: macos-15-intel
target: x86_64
artifact: wheel-macos-15-intel-x86_64
interpreter_args: -i python
- os: macos-14
target: aarch64
artifact: wheel-macos-14-aarch64
interpreter_args: -i python
- os: windows-latest
target: x86_64
artifact: wheel-windows-latest-x86_64
interpreter_args: -i python
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
if: runner.os != 'Linux'
with:
python-version: "3.10"
allow-prereleases: true
- name: Pre-pull manylinux image with retries
if: runner.os == 'Linux'
run: |
set -euo pipefail
MAX_RETRIES=3
IMAGE=quay.io/pypa/manylinux2014_x86_64:latest
for i in $(seq 1 $MAX_RETRIES); do
echo "Attempt $i to pull $IMAGE"
if docker pull "$IMAGE"; then
echo "Pulled $IMAGE"
break
else
echo "Pull failed"
if [ "$i" -lt "$MAX_RETRIES" ]; then
sleep $((i * 5))
else
echo "Failed to pull $IMAGE after $MAX_RETRIES attempts"
exit 1
fi
fi
done
- uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.target }}
args: --release --out dist ${{ matrix.interpreter_args }}
manylinux: auto
- uses: actions/upload-artifact@v4
with:
name: ${{ matrix.artifact }}
path: dist/*.whl
sdist:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: PyO3/maturin-action@v1
with:
command: sdist
args: --out dist
- uses: actions/upload-artifact@v4
with:
name: sdist
path: dist/*.tar.gz
test:
needs: [build]
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
allow-prereleases: true
- uses: actions/download-artifact@v4
with:
name: wheel-ubuntu-latest-x86_64
path: dist
- env:
PYTEST_DISABLE_PLUGIN_AUTOLOAD: 1
run: |
python -m pip install numpy pytest
python -m pip install --no-index --find-links dist copp-py
python -m pytest bindings/python/tests/ -v
smoke-test:
needs: [build]
strategy:
fail-fast: false
matrix:
include:
- runner: ubuntu-latest
artifact: wheel-ubuntu-latest-x86_64
- runner: ubuntu-24.04-arm
artifact: wheel-ubuntu-latest-aarch64
- runner: macos-15-intel
artifact: wheel-macos-15-intel-x86_64
- runner: macos-14
artifact: wheel-macos-14-aarch64
- runner: windows-latest
artifact: wheel-windows-latest-x86_64
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.10"
allow-prereleases: true
- uses: actions/download-artifact@v4
with:
name: ${{ matrix.artifact }}
path: dist
- env:
PYTEST_DISABLE_PLUGIN_AUTOLOAD: 1
run: |
python -m pip install numpy pytest
python -m pip install --no-index --find-links dist copp-py
python -m pytest bindings/python/tests/ -v
publish-testpypi:
name: Publish to TestPyPI
needs: [sdist, test, smoke-test]
if: (github.event_name == 'workflow_dispatch' && inputs.publish_testpypi) || (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v'))
runs-on: ubuntu-latest
environment: testpypi
permissions:
contents: read
id-token: write
steps:
- uses: actions/download-artifact@v4
with:
path: dist
merge-multiple: true
- uses: pypa/gh-action-pypi-publish@release/v1
with:
repository-url: https://test.pypi.org/legacy/
packages-dir: dist
skip-existing: true
publish-pypi:
name: Publish to PyPI
needs: [sdist, test, smoke-test]
if: github.event_name == 'release' && startsWith(github.event.release.tag_name, 'v')
runs-on: ubuntu-latest
environment: pypi
permissions:
contents: read
id-token: write
steps:
- uses: actions/download-artifact@v4
with:
path: dist
merge-multiple: true
- uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: dist