APM

>Agent Skill

@microsoft/pypi-publish

skilldevelopment

Publishing duroxide-python to PyPI. Use when releasing a new version, building platform wheels, or publishing to the Python Package Index.

pythongittestingdocumentation
apm::install
$apm install @microsoft/pypi-publish
apm::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