pypi-publish
skill✓Publishing duroxide-python to PyPI. Use when releasing a new version, building platform wheels, or publishing to the Python Package Index.
apm::install
apm install @microsoft/pypi-publishapm::skill.md
---
name: pypi-publish
description: Publishing duroxide-python to PyPI. Use when releasing a new version, building platform wheels, or publishing to the Python Package Index.
---
# Publishing duroxide-python to PyPI
## Pre-Publish Checklist
Before publishing, verify ALL of the following:
### 1. Clean Build
```bash
cd duroxide-python
source .venv/bin/activate
# Clippy — must pass with zero warnings
cargo clippy --all-targets
# Release build via maturin
maturin develop --release
```
### 2. Tests Pass
```bash
# All 54 tests must pass (requires DATABASE_URL in .env)
pytest -v
```
### 3. Changelog Updated
- `CHANGELOG.md` must have an entry for the new version
- Follow [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) format
- Include all Added/Changed/Fixed/Removed sections as applicable
### 4. README Points to Changelog
Verify `README.md` contains a link to `CHANGELOG.md`:
```markdown
See [CHANGELOG.md](CHANGELOG.md) for release notes.
```
### 5. Version Bumped
Update version in `pyproject.toml`:
```toml
[project]
name = "duroxide"
version = "0.1.2" # ← bump this
```
Also update `Cargo.toml` version to match:
```toml
[package]
version = "0.1.2"
```
## Build Platform Wheels
PyPI uses **wheels** — one per platform + Python version combo. Unlike npm (separate packages per platform), PyPI serves all wheels under the **same package name**. `pip install duroxide` automatically picks the right one.
### Local Build (current platform only)
```bash
maturin build --release
# Output: target/wheels/duroxide-0.1.0-cp39-cp39-macosx_11_0_arm64.whl
```
### Cross-Platform Builds via GitHub Actions (Recommended)
Use maturin's official GitHub Action to build for all platforms:
```yaml
name: Publish to PyPI
on:
release:
types: [published]
jobs:
build:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: ["3.9", "3.10", "3.11", "3.12"]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: PyO3/maturin-action@v1
with:
command: build
args: --release --out dist
manylinux: auto
- uses: actions/upload-artifact@v4
with:
name: wheels-${{ matrix.os }}-${{ matrix.python-version }}
path: dist/*.whl
publish:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v4
with:
pattern: wheels-*
merge-multiple: true
path: dist
- uses: PyO3/maturin-action@v1
with:
command: upload
args: --skip-existing dist/*
env:
MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}
```
### Supported Platforms
| Platform | Target | Notes |
|----------|--------|-------|
| macOS ARM | `aarch64-apple-darwin` | Native build on M1/M2 |
| macOS Intel | `x86_64-apple-darwin` | Cross-compile from ARM |
| Linux x64 | `x86_64-unknown-linux-gnu` | Use `manylinux` Docker |
| Linux ARM | `aarch64-unknown-linux-gnu` | Cross-compile via QEMU |
| Windows x64 | `x86_64-pc-windows-msvc` | Native build on Windows |
### Docker Build for Linux (manylinux)
```bash
docker run --rm -v "$(pwd):/io" -w /io ghcr.io/pyo3/maturin build --release
# Produces manylinux-compatible wheels
```
## Publish to PyPI
### Option 1: maturin publish (build + upload in one step)
```bash
# Requires MATURIN_PYPI_TOKEN env var or ~/.pypirc config
export MATURIN_PYPI_TOKEN=pypi-...
maturin publish --skip-existing
```
### Option 2: Build then upload with twine
```bash
maturin build --release
pip install twine
twine upload target/wheels/*.whl
```
### Option 3: Test on TestPyPI first
```bash
maturin publish --repository testpypi
# Test install:
pip install --index-url https://test.pypi.org/simple/ duroxide
```
## PyPI Authentication
- Create API token at: https://pypi.org/manage/account/token/
- Scope: project-specific (recommended) or account-wide
- Set as `MATURIN_PYPI_TOKEN` env var or configure in `~/.pypirc`:
```ini
[pypi]
username = __token__
password = pypi-...
```
- Never commit tokens to source code
## Verify Published Package
```bash
# In a clean virtualenv
python3 -m venv /tmp/test-duroxide
source /tmp/test-duroxide/bin/activate
pip install duroxide
python -c "from duroxide import SqliteProvider; print('loaded successfully')"
deactivate
rm -rf /tmp/test-duroxide
```
## Key Differences from npm Publishing
| Aspect | npm (duroxide-node) | PyPI (duroxide-python) |
|--------|--------------------|-----------------------|
| Packages per platform | Separate (`@duroxide/darwin-arm64`) | One package, multiple wheels |
| Publish order | Platform packages first, then main | Just publish all wheels |
| Binary selection | `optionalDependencies` in package.json | pip auto-selects by wheel filename |
| Build tool | `npx napi build` | `maturin build` |
| Upload tool | `npm publish` | `maturin publish` or `twine upload` |
| Token type | npm Automation token | PyPI API token |
## Summary Checklist
- [ ] `cargo clippy --all-targets` — zero warnings
- [ ] `maturin develop --release` — clean build
- [ ] `pytest -v` — all 54 tests pass
- [ ] `CHANGELOG.md` — updated for new version
- [ ] `README.md` — links to CHANGELOG.md and docs
- [ ] Version bumped in `pyproject.toml` + `Cargo.toml`
- [ ] Wheels built for all target platforms (via CI or locally)
- [ ] Published to PyPI (or TestPyPI first)
- [ ] Verified with `pip install duroxide` in a clean virtualenv