name: Python Wheels
on:
workflow_dispatch:
inputs:
publish_testpypi:
description: "Upload built wheels to TestPyPI"
required: false
default: "false"
pull_request:
branches: ["main"]
paths:
- "src/**"
- "vantadb-python/**"
- "Cargo.toml"
- "Cargo.lock"
- ".github/workflows/python_wheels.yml"
push:
branches: ["main"]
tags: ["v*.*.*"]
paths:
- "src/**"
- "vantadb-python/**"
- "Cargo.toml"
- "Cargo.lock"
- ".github/workflows/python_wheels.yml"
permissions:
contents: read
concurrency:
group: python-wheels-${{ github.ref }}
cancel-in-progress: ${{ !startsWith(github.ref, 'refs/tags/') }}
env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: 0
jobs:
build-wheels:
name: Build Python wheel (${{ matrix.os }})
runs-on: ${{ matrix.os }}
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.11"
- name: Set up Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
- name: Rust cache
uses: Swatinem/rust-cache@v2
with:
cache-bin: "false"
- name: Install Python build tools
run: python -m pip install --upgrade pip pytest
- name: Validate version coherence
run: cargo test --test version_coherence --no-default-features --features fjall
- name: Build wheel
uses: PyO3/maturin-action@v1.51.0
with:
command: build
args: --release --out dist --manifest-path ./vantadb-python/Cargo.toml
manylinux: 2_28
before-script-linux: |
yum install -y clang clang-devel
- name: Install & smoke test (Unix)
if: matrix.os != 'windows-latest'
shell: bash
run: |
wheel="$(ls -t ./dist/vantadb_py-*.whl | head -n 1)"
echo "Installing wheel: $wheel"
python -m venv /tmp/.smoke-venv
/tmp/.smoke-venv/bin/pip install --upgrade pip
/tmp/.smoke-venv/bin/pip install "$wheel" pytest numpy
/tmp/.smoke-venv/bin/python -c "import vantadb_py; print('Import OK:', vantadb_py.__version__)"
/tmp/.smoke-venv/bin/pytest vantadb-python/tests/test_sdk.py -v
- name: Install & smoke test (Windows)
if: matrix.os == 'windows-latest'
shell: pwsh
run: |
$wheel = (Get-ChildItem .\dist\vantadb_py-*.whl | Sort-Object LastWriteTime -Descending | Select-Object -First 1).FullName
Write-Output "Installing wheel: $wheel"
python -m venv "$env:TEMP\.smoke-venv"
& "$env:TEMP\.smoke-venv\Scripts\pip" install --upgrade pip
& "$env:TEMP\.smoke-venv\Scripts\pip" install $wheel pytest numpy
& "$env:TEMP\.smoke-venv\Scripts\python" -c "import vantadb_py; print('Import OK:', vantadb_py.__version__)"
& "$env:TEMP\.smoke-venv\Scripts\pytest" vantadb-python/tests/test_sdk.py -v
- name: Upload wheel artifact
uses: actions/upload-artifact@v4
with:
name: wheels-${{ matrix.os }}
path: ./dist/*.whl
if-no-files-found: error
publish-testpypi:
name: Publish wheels to TestPyPI
runs-on: ubuntu-latest
needs: build-wheels
timeout-minutes: 10
if: |
(github.event_name == 'push' && github.ref == 'refs/heads/main') ||
(github.event_name == 'workflow_dispatch' && inputs.publish_testpypi == 'true')
environment: testpypi
permissions:
id-token: write
steps:
- name: Download wheels
uses: actions/download-artifact@v4
with:
pattern: wheels-*
path: ./dist
merge-multiple: true
- name: Publish to TestPyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
repository-url: https://test.pypi.org/legacy/
verbose: true
skip-existing: true
publish-pypi:
name: Publish wheels to PyPI (Production)
runs-on: ubuntu-latest
needs: build-wheels
timeout-minutes: 10
if: startsWith(github.ref, 'refs/tags/v')
environment: pypi
permissions:
id-token: write
contents: write
attestations: write
steps:
- name: Download wheels
uses: actions/download-artifact@v4
with:
pattern: wheels-*
path: ./dist
merge-multiple: true
- name: Attest Build Provenance for Wheels
uses: actions/attest-build-provenance@v4
with:
subject-path: './dist/*.whl'
- name: Attach wheel artifacts to GitHub Release
uses: softprops/action-gh-release@v3
with:
files: |
./dist/*.whl
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: dist/
verbose: true
verify-testpypi-install:
name: Verify TestPyPI install
runs-on: ubuntu-latest
needs: publish-testpypi
timeout-minutes: 10
if: |
(github.event_name == 'push' && github.ref == 'refs/heads/main') ||
(github.event_name == 'workflow_dispatch' && inputs.publish_testpypi == 'true')
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.11"
- name: Extract version from tag or Cargo.toml
id: pkg_version
shell: bash
run: |
if [[ "$GITHUB_REF" == refs/tags/v* ]]; then
echo "version=${GITHUB_REF#refs/tags/v}" >> "$GITHUB_OUTPUT"
else
version=$(grep '^version' Cargo.toml | head -1 | sed 's/.*= *"//' | sed 's/".*//')
echo "version=${version}" >> "$GITHUB_OUTPUT"
fi
- name: Wait for TestPyPI CDN propagation
run: sleep 30
- name: Install from TestPyPI
run: |
python -m pip install \
--index-url https://test.pypi.org/simple/ \
--extra-index-url https://pypi.org/simple/ \
"vantadb-py==${{ steps.pkg_version.outputs.version }}"
- name: Functional smoke test - verify published wheel
env:
VANTADB_EXPECTED_VERSION: ${{ steps.pkg_version.outputs.version }}
run: python vantadb-python/verify_published_wheel.py
verify-pypi-install:
name: Verify PyPI production install + Attest provenance
runs-on: ubuntu-latest
needs: publish-pypi
timeout-minutes: 15
if: startsWith(github.ref, 'refs/tags/v')
permissions:
id-token: write
attestations: read
steps:
- name: Checkout (for attestation verification)
uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.11"
- name: Extract version from tag
id: pkg_version
run: echo "version=${GITHUB_REF#refs/tags/v}" >> "$GITHUB_OUTPUT"
- name: Wait for PyPI CDN propagation
run: |
echo "Waiting 90 seconds for PyPI CDN propagation..."
sleep 90
- name: Install from PyPI (clean environment)
run: |
python -m pip install --no-cache-dir \
"vantadb-py==${{ steps.pkg_version.outputs.version }}"
- name: Functional smoke test - verify published wheel
env:
VANTADB_EXPECTED_VERSION: ${{ steps.pkg_version.outputs.version }}
run: python vantadb-python/verify_published_wheel.py
- name: Verify build provenance (GitHub Attestations)
env:
GH_TOKEN: ${{ github.token }}
run: |
# Download the wheel that was just installed to verify its provenance
pip download --no-deps --dest /tmp/verify_whl \
"vantadb-py==${{ steps.pkg_version.outputs.version }}"
wheel_file=$(ls /tmp/verify_whl/vantadb_py-*.whl | head -1)
echo "Verifying provenance for: ${wheel_file}"
gh attestation verify "${wheel_file}" \
--repo "${{ github.repository }}" \
--format json | python -c "
import sys, json
data = json.load(sys.stdin)
verifications = data if isinstance(data, list) else [data]
assert len(verifications) > 0, 'No attestation found'
print(f'Attestations verified: {len(verifications)}')
print('Build provenance attestation: VALID')
"
echo "Supply-chain integrity: VERIFIED"