Skip to content

Publish pywry to PyPI #66

Publish pywry to PyPI

Publish pywry to PyPI #66

Workflow file for this run

name: Publish pywry to PyPI
on:
workflow_dispatch:
inputs:
skip_tests:
description: 'Skip running tests (use with caution)'
required: false
default: false
type: boolean
publish_to_pypi:
description: 'Publish wheels to PyPI after build + test'
required: false
default: false
type: boolean
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
defaults:
run:
working-directory: pywry
env:
PYTHON_VERSION: '3.12'
jobs:
# =============================================================================
# Step 1: Run tests first and fail fast
# =============================================================================
test:
name: Test - ${{ matrix.os }} - Python ${{ matrix.python-version }}
if: ${{ !inputs.skip_tests }}
runs-on: ${{ matrix.os }}
permissions:
contents: read
strategy:
fail-fast: true
matrix:
include:
- os: ubuntu-24.04
python-version: '3.10'
- os: ubuntu-24.04
python-version: '3.14'
- os: windows-2025
python-version: '3.12'
- os: macos-15
python-version: '3.12'
steps:
- uses: actions/checkout@v5
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
allow-prereleases: true
cache: 'pip'
cache-dependency-path: pywry/pyproject.toml
- name: Set up Rust (Linux only)
if: runner.os == 'Linux'
uses: dtolnay/rust-toolchain@stable
- name: Install Linux dependencies
if: runner.os == 'Linux'
run: |
for i in 1 2 3; do sudo apt-get update && break || sleep 15; done
for i in 1 2 3; do sudo apt-get install -y --fix-missing xvfb dbus-x11 at-spi2-core libxkbcommon-x11-0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-xfixes0 libxcb-shape0 libgl1 libegl1 libwebkit2gtk-4.1-dev libgtk-3-dev && break || { sleep 30; sudo apt-get update; }; done
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install ".[dev]"
- name: Run tests (Linux)
if: runner.os == 'Linux'
env:
PYWRY_HEADLESS: "1"
NO_AT_BRIDGE: "1"
run: |
dbus-run-session -- xvfb-run --auto-servernum --server-args="-screen 0 1920x1080x24" python -m pytest -c pytest.ini tests/ -v --tb=short -x
- name: Run tests (Windows/macOS)
if: runner.os != 'Linux'
env:
PYWRY_HEADLESS: "1"
PYWRY_DEPLOY__STATE_BACKEND: "memory"
run: |
python -m pytest -c pytest.ini tests/ -v --tb=short -x --ignore=tests/test_state_redis_integration.py --ignore=tests/test_auth_rbac_integration.py --ignore=tests/test_deploy_mode_integration.py --ignore=tests/test_e2e_deploy_mode.py --ignore=tests/test_e2e_rbac_widgets.py -m "not redis and not container"
# =============================================================================
# Step 2: Build platform-specific wheels - SEPARATE JOBS PER PLATFORM/ARCH
# =============================================================================
build-linux-x86_64:
name: Build wheels - Linux x86_64
needs: [test]
if: ${{ !cancelled() && (needs.test.result == 'success' || needs.test.result == 'skipped') }}
runs-on: ubuntu-24.04
permissions:
contents: read
strategy:
matrix:
python-version: ['3.10', '3.11', '3.12', '3.13', '3.14']
steps:
- uses: actions/checkout@v5
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
allow-prereleases: true
cache: 'pip'
cache-dependency-path: pywry/pyproject.toml
- name: Install Linux dependencies
run: |
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.1-0 libgtk-3-0
- name: Extract version
id: version
shell: bash
run: |
VERSION=$(grep -oP '^version\s*=\s*"\K[^"]+' pyproject.toml)
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "📦 Building pywry version: $VERSION"
- name: Clean vendor directory
shell: bash
run: rm -rf pywry/_vendor/pytauri_wheel dist/
- name: Build sdist
if: matrix.python-version == '3.12'
run: |
pip install build
python -m build --sdist
- name: Build wheel
run: |
pip install build hatchling pytauri-wheel
python -m build --wheel
- name: List dist contents
shell: bash
run: ls -la dist/
- name: Upload wheels
uses: actions/upload-artifact@v7
with:
name: wheels-linux-x86_64-py${{ matrix.python-version }}
path: pywry/dist/*.whl
retention-days: 30
- name: Upload sdist
if: matrix.python-version == '3.12'
uses: actions/upload-artifact@v7
with:
name: sdist
path: pywry/dist/*.tar.gz
retention-days: 30
outputs:
version: ${{ steps.version.outputs.version }}
build-linux-aarch64:
name: Build wheels - Linux aarch64
needs: [test]
if: ${{ !cancelled() && (needs.test.result == 'success' || needs.test.result == 'skipped') }}
runs-on: ubuntu-24.04-arm
permissions:
contents: read
strategy:
matrix:
python-version: ['3.10', '3.11', '3.12', '3.13', '3.14']
steps:
- uses: actions/checkout@v5
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
allow-prereleases: true
cache: 'pip'
cache-dependency-path: pywry/pyproject.toml
- name: Install Linux dependencies
run: |
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.1-0 libgtk-3-0
- name: Clean vendor directory
shell: bash
run: rm -rf pywry/_vendor/pytauri_wheel dist/
- name: Build wheel
run: |
pip install build hatchling pytauri-wheel
python -m build --wheel
- name: List dist contents
shell: bash
run: ls -la dist/
- name: Upload wheels
uses: actions/upload-artifact@v7
with:
name: wheels-linux-aarch64-py${{ matrix.python-version }}
path: pywry/dist/*.whl
retention-days: 30
build-macos-x86_64:
name: Build wheels - macOS x86_64
needs: [test]
if: ${{ !cancelled() && (needs.test.result == 'success' || needs.test.result == 'skipped') }}
runs-on: macos-26-intel
permissions:
contents: read
strategy:
matrix:
python-version: ['3.10', '3.11', '3.12', '3.13', '3.14']
steps:
- uses: actions/checkout@v5
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
allow-prereleases: true
cache: 'pip'
cache-dependency-path: pywry/pyproject.toml
- name: Clean vendor directory
shell: bash
run: rm -rf pywry/_vendor/pytauri_wheel dist/
- name: Build wheel
run: |
pip install build hatchling pytauri-wheel
python -m build --wheel
- name: List dist contents
shell: bash
run: ls -la dist/
- name: Upload wheels
uses: actions/upload-artifact@v7
with:
name: wheels-macos-x86_64-py${{ matrix.python-version }}
path: pywry/dist/*.whl
retention-days: 30
build-macos-arm64:
name: Build wheels - macOS arm64
needs: [test]
if: ${{ !cancelled() && (needs.test.result == 'success' || needs.test.result == 'skipped') }}
runs-on: macos-26
permissions:
contents: read
strategy:
matrix:
python-version: ['3.10', '3.11', '3.12', '3.13', '3.14']
steps:
- uses: actions/checkout@v5
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
allow-prereleases: true
cache: 'pip'
cache-dependency-path: pywry/pyproject.toml
- name: Clean vendor directory
shell: bash
run: rm -rf pywry/_vendor/pytauri_wheel dist/
- name: Build wheel
run: |
pip install build hatchling pytauri-wheel
python -m build --wheel
- name: List dist contents
shell: bash
run: ls -la dist/
- name: Upload wheels
uses: actions/upload-artifact@v7
with:
name: wheels-macos-arm64-py${{ matrix.python-version }}
path: pywry/dist/*.whl
retention-days: 30
build-windows-amd64:
name: Build wheels - Windows AMD64
needs: [test]
if: ${{ !cancelled() && (needs.test.result == 'success' || needs.test.result == 'skipped') }}
runs-on: windows-2025
permissions:
contents: read
strategy:
matrix:
python-version: ['3.10', '3.11', '3.12', '3.13', '3.14']
steps:
- uses: actions/checkout@v5
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
allow-prereleases: true
cache: 'pip'
cache-dependency-path: pywry/pyproject.toml
- name: Clean vendor directory
shell: bash
run: rm -rf pywry/_vendor/pytauri_wheel dist/
- name: Build wheel
run: |
pip install build hatchling pytauri-wheel
python -m build --wheel
- name: List dist contents
shell: bash
run: ls -la dist/
- name: Upload wheels
uses: actions/upload-artifact@v7
with:
name: wheels-windows-amd64-py${{ matrix.python-version }}
path: pywry/dist/*.whl
retention-days: 30
build-windows-arm64:
name: Build wheels - Windows ARM64
needs: [test]
if: ${{ !cancelled() && (needs.test.result == 'success' || needs.test.result == 'skipped') }}
runs-on: windows-11-arm
permissions:
contents: read
strategy:
matrix:
python-version: ['3.11', '3.12', '3.13', '3.14']
steps:
- uses: actions/checkout@v5
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
allow-prereleases: true
cache: 'pip'
cache-dependency-path: pywry/pyproject.toml
- name: Clean vendor directory
shell: bash
run: rm -rf pywry/_vendor/pytauri_wheel dist/
- name: Build wheel
run: |
pip install build hatchling pytauri-wheel
python -m build --wheel
- name: List dist contents
shell: bash
run: ls -la dist/
- name: Upload wheels
uses: actions/upload-artifact@v7
with:
name: wheels-windows-arm64-py${{ matrix.python-version }}
path: pywry/dist/*.whl
retention-days: 30
# =============================================================================
# Step 3: Merge all artifacts into single dist
# =============================================================================
merge-artifacts:
name: Merge artifacts
needs: [build-linux-x86_64, build-linux-aarch64, build-macos-x86_64, build-macos-arm64, build-windows-amd64, build-windows-arm64]
if: ${{ !cancelled() && needs.build-linux-x86_64.result == 'success' }}
runs-on: ubuntu-24.04
defaults:
run:
working-directory: .
permissions:
contents: read
outputs:
version: ${{ needs.build-linux-x86_64.outputs.version }}
steps:
- name: Download all wheel artifacts
uses: actions/download-artifact@v8.0.1
with:
pattern: wheels-*
path: artifacts/
- name: Download sdist
uses: actions/download-artifact@v8.0.1
with:
name: sdist
path: artifacts/sdist/
- name: Consolidate artifacts and fail on duplicate wheel names
shell: bash
run: |
python - <<'PY'
import hashlib
import shutil
import sys
from pathlib import Path
artifacts_dir = Path("artifacts")
dist_dir = Path("dist")
dist_dir.mkdir(parents=True, exist_ok=True)
def sha256(path: Path) -> str:
h = hashlib.sha256()
with path.open("rb") as f:
for chunk in iter(lambda: f.read(1024 * 1024), b""):
h.update(chunk)
return h.hexdigest()
seen: dict[str, tuple[str, Path]] = {}
copied = 0
skipped_identical = 0
for wheel in sorted(artifacts_dir.rglob("*.whl")):
name = wheel.name
digest = sha256(wheel)
if name in seen:
prev_digest, prev_path = seen[name]
if digest == prev_digest:
skipped_identical += 1
print(
f"::warning::Duplicate wheel filename with identical content; "
f"keeping first: {name} ({prev_path} == {wheel})"
)
continue
print(f"::error::Conflicting duplicate wheel filename: {name}")
print(f" first: {prev_path} ({prev_digest})")
print(f" second: {wheel} ({digest})")
sys.exit(1)
shutil.copy2(wheel, dist_dir / name)
seen[name] = (digest, wheel)
copied += 1
for sdist in sorted((artifacts_dir / "sdist").rglob("*.tar.gz")):
shutil.copy2(sdist, dist_dir / sdist.name)
print(f"Copied {copied} unique wheels to dist/")
if skipped_identical:
print(f"Skipped {skipped_identical} identical duplicate wheel artifact(s)")
PY
- name: Validate wheel archives for duplicate ZIP members
shell: bash
run: |
python - <<'PY'
import collections
import glob
import sys
import zipfile
failed = False
for wheel in sorted(glob.glob('dist/*.whl')):
with zipfile.ZipFile(wheel) as zf:
names = [entry.filename for entry in zf.infolist()]
duplicates = [name for name, count in collections.Counter(names).items() if count > 1]
if duplicates:
failed = True
print(f"::error file={wheel}::duplicate ZIP members detected")
for name in duplicates[:20]:
print(f" - {name}")
if failed:
sys.exit(1)
print('All wheel archives passed duplicate ZIP member validation.')
PY
- name: List all artifacts
run: |
echo "📦 All distribution artifacts:"
ls -la dist/
- name: Upload merged dist
uses: actions/upload-artifact@v7
with:
name: dist
path: dist/
retention-days: 30
# =============================================================================
# Step 4: Test built wheels on all platforms
# =============================================================================
test-wheels:
name: Test wheel - ${{ matrix.os }}
needs: [merge-artifacts]
if: ${{ !cancelled() && needs.merge-artifacts.result == 'success' && github.event_name == 'workflow_dispatch' }}
runs-on: ${{ matrix.os }}
defaults:
run:
working-directory: .
permissions:
contents: read
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-24.04
- os: ubuntu-24.04-arm
- os: macos-26-intel
- os: macos-26
- os: windows-2025
- os: windows-11-arm
steps:
- uses: actions/checkout@v5
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: ${{ env.PYTHON_VERSION }}
cache: 'pip'
cache-dependency-path: pywry/pyproject.toml
- name: Set up Rust (Linux only)
if: runner.os == 'Linux'
uses: dtolnay/rust-toolchain@stable
- name: Set up Rust (Windows ARM64)
if: runner.os == 'Windows' && runner.arch == 'ARM64'
uses: dtolnay/rust-toolchain@stable
with:
targets: aarch64-pc-windows-msvc
- name: Install Linux dependencies
if: runner.os == 'Linux'
run: |
for i in 1 2 3; do sudo apt-get update && break || sleep 15; done
for i in 1 2 3; do sudo apt-get install -y --fix-missing xvfb dbus-x11 at-spi2-core libxkbcommon-x11-0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-xfixes0 libxcb-shape0 libgl1 libegl1 libwebkit2gtk-4.1-dev libgtk-3-dev && break || { sleep 30; sudo apt-get update; }; done
- name: Cache vcpkg packages (Windows ARM)
if: runner.os == 'Windows' && runner.arch == 'ARM64'
uses: actions/cache@v5
with:
path: C:/vcpkg/installed
key: vcpkg-arm64-windows-openssl-static-md-lua-${{ hashFiles('.github/workflows/publish-pywry.yml') }}
restore-keys: |
vcpkg-arm64-windows-openssl-static-md-lua-
- name: Install OpenSSL via vcpkg (Windows ARM)
if: runner.os == 'Windows' && runner.arch == 'ARM64'
shell: pwsh
run: |
$env:VCPKG_ROOT = "$env:VCPKG_INSTALLATION_ROOT"
& "$env:VCPKG_ROOT\vcpkg" install openssl:arm64-windows-static-md
$opensslDir = "$env:VCPKG_ROOT\installed\arm64-windows-static-md"
echo "OPENSSL_DIR=$opensslDir" >> $env:GITHUB_ENV
echo "OPENSSL_LIB_DIR=$opensslDir\lib" >> $env:GITHUB_ENV
echo "OPENSSL_INCLUDE_DIR=$opensslDir\include" >> $env:GITHUB_ENV
- name: Setup build environment (Windows ARM)
if: runner.os == 'Windows' && runner.arch == 'ARM64'
shell: pwsh
run: |
$buildTemp = "C:\buildtmp"
New-Item -ItemType Directory -Force -Path $buildTemp | Out-Null
echo "TMP=$buildTemp" >> $env:GITHUB_ENV
echo "TEMP=$buildTemp" >> $env:GITHUB_ENV
$env:VCPKG_ROOT = "$env:VCPKG_INSTALLATION_ROOT"
& "$env:VCPKG_ROOT\vcpkg" install lua:arm64-windows
$luaDir = "$env:VCPKG_ROOT\installed\arm64-windows"
echo "LUA_DIR=$luaDir" >> $env:GITHUB_ENV
echo "LUA_LIB=$luaDir\lib" >> $env:GITHUB_ENV
echo "LUA_INC=$luaDir\include" >> $env:GITHUB_ENV
- name: Build cryptography from source (Windows ARM)
if: runner.os == 'Windows' && runner.arch == 'ARM64'
shell: pwsh
run: |
pip install --no-cache-dir --no-binary=cryptography cryptography
python -c "from cryptography.hazmat.bindings._rust import openssl; print('cryptography OK')"
- name: Download dist artifacts
uses: actions/download-artifact@v8.0.1
with:
name: dist
path: ${{ runner.temp }}/dist/
- name: Install from built wheel
shell: bash
run: |
PYWRY_VERSION="${{ needs.merge-artifacts.outputs.version }}"
pip install --find-links "$RUNNER_TEMP/dist/" "pywry[all]==$PYWRY_VERSION"
pip install pytest pytest-asyncio pytest-timeout httpx plotly fakeredis testcontainers pandas
- name: Install cryptography (non-Windows-ARM)
if: "!(runner.os == 'Windows' && runner.arch == 'ARM64')"
shell: bash
run: pip install cryptography
- name: Configure Docker (macOS Intel)
if: runner.os == 'macOS' && runner.arch == 'X64'
shell: bash
run: |
set -euo pipefail
if docker info >/dev/null 2>&1; then
echo "Docker daemon already available"
else
if [ -d "/Applications/Docker.app" ]; then
echo "Starting Docker Desktop"
open -a Docker
for _ in {1..30}; do
if docker info >/dev/null 2>&1; then
break
fi
sleep 5
done
fi
fi
if ! docker info >/dev/null 2>&1; then
echo "Falling back to Colima"
brew install docker colima
colima start --runtime docker --arch x86_64 --cpu 2 --memory 4 --disk 20
fi
if [ -S "$HOME/.colima/default/docker.sock" ]; then
sudo mkdir -p /var/run
sudo ln -sf "$HOME/.colima/default/docker.sock" /var/run/docker.sock
fi
echo "DOCKER_HOST=unix:///var/run/docker.sock" >> "$GITHUB_ENV"
docker info
- name: Verify installation
shell: bash
run: |
python -c "import pywry; print(f'pywry {pywry.__version__} installed successfully')"
python -c "import pywry._vendor.pytauri_wheel.ext_mod as ext_mod; print('pytauri wheel ext_mod:', ext_mod.__file__)"
python -c "from pywry import PyWry; print('PyWry class imported')"
python -c "from pywry.toolbar import Button, Toolbar; print('Toolbar components imported')"
- name: Copy tests outside source tree
shell: bash
run: |
# Copy tests + config to a temp location so the checkout pywry/ directory
# does NOT shadow the installed wheel package on sys.path.
cp -r pywry/tests "$RUNNER_TEMP/tests"
cp pywry/pytest.ini "$RUNNER_TEMP/pytest.ini"
- name: Run tests (Linux)
if: runner.os == 'Linux'
working-directory: ${{ runner.temp }}
env:
PYWRY_TEST_USE_INSTALLED_WHEEL: "1"
PYWRY_HEADLESS: "1"
NO_AT_BRIDGE: "1"
run: |
dbus-run-session -- xvfb-run --auto-servernum --server-args="-screen 0 1920x1080x24" python -m pytest -c pytest.ini tests/ -v --tb=short -x
- name: Run tests (Windows x64)
if: runner.os == 'Windows' && runner.arch == 'X64'
working-directory: ${{ runner.temp }}
env:
PYWRY_TEST_USE_INSTALLED_WHEEL: "1"
PYWRY_HEADLESS: "1"
PYWRY_DEPLOY__STATE_BACKEND: "memory"
PYTHONUTF8: "1"
run: |
python -m pytest -c pytest.ini tests/ -v --tb=short -x --ignore=tests/test_state_redis_integration.py --ignore=tests/test_auth_rbac_integration.py --ignore=tests/test_deploy_mode_integration.py --ignore=tests/test_e2e_deploy_mode.py --ignore=tests/test_e2e_rbac_widgets.py -m "not redis and not container"
- name: Run tests (Windows ARM)
if: runner.os == 'Windows' && runner.arch == 'ARM64'
working-directory: ${{ runner.temp }}
env:
PYWRY_TEST_USE_INSTALLED_WHEEL: "1"
PYWRY_HEADLESS: "1"
PYWRY_DEPLOY__STATE_BACKEND: "memory"
PYTHONUTF8: "1"
run: |
python -m pytest -c pytest.ini tests/ -v --tb=short -x --ignore=tests/test_state_redis_integration.py --ignore=tests/test_auth_rbac_integration.py --ignore=tests/test_deploy_mode_integration.py --ignore=tests/test_e2e_deploy_mode.py --ignore=tests/test_e2e_rbac_widgets.py --ignore=tests/test_inline_ssl.py -m "not redis and not container"
- name: Run tests (macOS ARM)
if: runner.os == 'macOS' && runner.arch == 'ARM64'
working-directory: ${{ runner.temp }}
env:
PYWRY_TEST_USE_INSTALLED_WHEEL: "1"
PYWRY_HEADLESS: "1"
PYWRY_DEPLOY__STATE_BACKEND: "memory"
run: |
python -m pytest -c pytest.ini tests/ -v --tb=short -x --ignore=tests/test_state_redis_integration.py --ignore=tests/test_auth_rbac_integration.py --ignore=tests/test_deploy_mode_integration.py --ignore=tests/test_e2e_deploy_mode.py --ignore=tests/test_e2e_rbac_widgets.py -m "not redis and not container"
- name: Run tests (macOS Intel)
if: runner.os == 'macOS' && runner.arch == 'X64'
working-directory: ${{ runner.temp }}
env:
PYWRY_TEST_USE_INSTALLED_WHEEL: "1"
PYWRY_HEADLESS: "1"
PYWRY_DEPLOY__STATE_BACKEND: "memory"
run: |
python -m pytest -c pytest.ini tests/ -v --tb=short -x --ignore=tests/test_deploy_mode_integration.py --ignore=tests/test_e2e_deploy_mode.py --ignore=tests/test_e2e_rbac_widgets.py --deselect=tests/test_window_modes.py::TestMultiWindowMode::test_update_specific_window
# =============================================================================
# Step 5: Create draft release with all artifacts (workflow_dispatch only)
# =============================================================================
create-release:
name: Create Draft Release
needs: [merge-artifacts]
if: ${{ !cancelled() && needs.merge-artifacts.result == 'success' && github.event_name == 'workflow_dispatch' }}
runs-on: ubuntu-24.04
defaults:
run:
working-directory: .
permissions:
contents: write
steps:
- name: Create draft release
uses: softprops/action-gh-release@v2
with:
tag_name: pywry-${{ needs.merge-artifacts.outputs.version }}
name: pywry ${{ needs.merge-artifacts.outputs.version }}
draft: true
prerelease: false
generate_release_notes: true
body: |
## pywry ${{ needs.merge-artifacts.outputs.version }}
### Installation
```bash
pip install pywry==${{ needs.merge-artifacts.outputs.version }}
```
### Supported Platforms
| Platform | Architecture | Wheel |
|----------|--------------|-------|
| Linux | x86_64 | ✅ |
| Linux | aarch64 | ✅ |
| macOS | x86_64 (Intel) | ✅ |
| macOS | arm64 (Apple Silicon) | ✅ |
| Windows | AMD64 | ✅ |
| Windows | ARM64 | ✅ |
### Python Versions
- Python 3.10, 3.11, 3.12, 3.13, 3.14
---
*This is a draft release. Wheels are published to PyPI separately.*
# =============================================================================
# Step 6: Publish to PyPI - Trusted Publisher (workflow_dispatch only)
# =============================================================================
publish-pypi:
name: Publish to PyPI
needs: [merge-artifacts, test-wheels]
if: ${{ !cancelled() && needs.merge-artifacts.result == 'success' && (needs.test-wheels.result == 'success' || needs.test-wheels.result == 'skipped') && inputs.publish_to_pypi }}
runs-on: ubuntu-24.04
defaults:
run:
working-directory: .
permissions:
id-token: write
steps:
- name: Download dist artifacts
uses: actions/download-artifact@v8.0.1
with:
name: dist
path: dist/
- name: List distribution files
run: |
echo "📦 Publishing to PyPI:"
ls -la dist/
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
verbose: true