This document explains how to set up automatic publishing to PyPI for SpiPy releases.
The repository includes a GitHub Actions workflow (.github/workflows/publish-pypi.yml) that automatically builds and publishes the package to PyPI when you create a GitHub release.
- Create an account at https://pypi.org/account/register/
- Verify your email address
- Enable 2FA (required for new projects)
Trusted Publishing is the modern, secure way to publish to PyPI without API tokens. It uses OpenID Connect (OIDC) to verify that the package is being uploaded from an authorized GitHub Actions workflow.
- Log into https://pypi.org
- Go to "Your projects" → "Publishing" (or go directly to https://pypi.org/manage/account/publishing/)
- Click "Add a new pending publisher"
- Fill in the form:
- PyPI Project Name:
spires - Owner:
edwardbair - Repository name:
SpiPy - Workflow name:
publish-pypi.yml - Environment name: (leave empty)
- PyPI Project Name:
- Click "Add"
No additional setup needed! The workflow already has the required permissions: id-token: write.
If you prefer using an API token instead of trusted publishing:
- Go to https://pypi.org/manage/account/token/
- Click "Add API token"
- Name it (e.g., "GitHub Actions - SpiPy")
- Scope: "Project: spires" (or "Entire account" if the project doesn't exist yet)
- Copy the token (starts with
pypi-)
Then add it to GitHub:
- Go to the GitHub repository settings
- Navigate to "Secrets and variables" → "Actions"
- Click "New repository secret"
- Name:
PYPI_API_TOKEN - Value: paste your PyPI token
- Click "Add secret"
Finally, modify .github/workflows/publish-pypi.yml:
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
password: ${{ secrets.PYPI_API_TOKEN }} # Add this line
verbose: trueBefore publishing to the real PyPI, test with TestPyPI:
- Create an account at https://test.pypi.org
- Set up trusted publishing there (same steps as above)
- In
.github/workflows/publish-pypi.yml, uncomment the line:repository-url: https://test.pypi.org/legacy/
- Create a test release
- Verify the package appears at
https://test.pypi.org/project/spires/ - Try installing:
pip install --index-url https://test.pypi.org/simple/ spires - Once confirmed working, remove the
repository-urlline to publish to real PyPI
Once setup is complete, publishing is automatic:
-
Create a git tag:
git tag v0.2.2 git push upstream v0.2.2
-
Create a GitHub Release:
- Go to https://github.com/edwardbair/SpiPy/releases/new
- Choose your tag (e.g.,
v0.2.2) - Fill in release notes
- Click "Publish release"
-
Automatic workflow:
- GitHub Actions will automatically trigger
- Builds wheels for Linux and macOS
- Builds source distribution
- Publishes to PyPI
- Check progress at: https://github.com/edwardbair/SpiPy/actions
-
Verify publication:
- Package appears at: https://pypi.org/project/spires/
- Install with:
pip install spires
.github/workflows/publish-pypi.yml: Builds wheels and publishes to PyPI on releases.github/workflows/build.yml: Tests builds on every push/PR (does not publish).github/workflows/docs.yml: Builds documentation
The workflow uses:
- cibuildwheel: Builds binary wheels for multiple Python versions and platforms
- setuptools-scm: Automatically determines version from git tags
- SWIG: Generates Python bindings for C++ code
- nlopt: Required C++ dependency for optimization
Wheels are built for:
- Python 3.9, 3.10, 3.11, 3.12
- Linux (manylinux)
- macOS (ARM64 and x86_64)
The workflow installs nlopt automatically. If builds fail:
- Check system package installation in workflow logs
- Verify nlopt is available:
yum list nlopt-devel(Linux) orbrew info nlopt(macOS)
You're trying to upload a version that already exists on PyPI. Versions are immutable:
- Delete the git tag:
git tag -d v0.2.2 && git push --delete upstream v0.2.2 - Create a new version:
git tag v0.2.3 - Or use post-release versioning (automatic with setuptools-scm)
Build and test the package locally:
# Build wheel
pip install build
python -m build --wheel
# Install and test
pip install dist/spires-*.whl
python -c "import spires; print(spires.__version__)"