Table of Contents

PyPI

Changelog

  • 2025-08-22: Init

Mostly referring to this user guide to upload packages onto PyPI.

Manual quick summary

Create an account, and reserve a package namespace. Create an API token for uploading.

For the project, minimally prepare a pyproject.toml with the [build-system] and [project] tables populated.

Install the build package that provides functionality to prepare the source tarball ("source distribution") and build into a wheel. Then finally upload it using twine onto PyPI.

user:~$ python -m build
user:~$ python -m twine upload dist/*
user:~$ python -m twine upload --repository testpypi dist/*  # TestPyPI

GitHub Actions for CI

Previous workflow involved manually building, and uploading to PyPI with twine using an API token. The continuous integration method does a couple things different:

Authentication is done on the PyPI via OIDC: account on PyPI specifies from which source should a distribution push be accepted, and PyPI authenticates directly with the source itself (that acts as an identity provider).

PyPI screenshot when configuring OIDC

Additional security measures taken include:

To test installation from TestPyPI, add the extra repository url. As usual, be cautious when doing this in production, since supply chain attacks can occur when a private package name is suddenly resolvable to a package of the same name on the public repository.

pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple [PACKAGE]
  publish-pypi:
    name: Push wheels to PyPI
    needs: build-wheels
    runs-on: ubuntu-latest
    environment:
      name: release
      url: https://test.pypi.org/p/s15lib
    permissions:
      id-token: write
 
    steps:
      - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131  # v7.0.0
        with:
          pattern: cibw-*
          path: dist
          merge-multiple: true
 
      - uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e  # v1.13.0
        with:
          repository-url: https://test.pypi.org/legacy/
          verbose: true

Build system

Different possible build backends:

Some articles on choosing a backend:

Manylinux

Building PyPI-compatible wheels with C extensions require building with manylinux (see PEP600), so that the wheels will have compatible ABI. This typically involves spinning up an old OS with a sufficiently dated glibc for building. There is the cibuildwheel package that scripts this, with Docker as a dependency to pull the relevant image bases.

Some references:

user:~$ uv pip install cibuildwheel
user:~$ python -m cibuildwheel --output-dir wheelhouse  # sudo for docker

Custom platform build can be specified in pyproject.toml:

[tool.cibuildwheel]
build = "cp38-manylinux_x86_64"