Publishing & Distributing Libraries

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the Python Reference bibliography.

Publishing a library means turning a project into files that anyone can install with pip install birdtally: an sdist and one or more wheels, uploaded to PyPI. Each release can be uploaded once and never changed, so the work happens before the upload. These pages follow birdtally through every step with two tool chains; every upload goes to a local pypiserver or devpi, never to PyPI or TestPyPI. PyPI hosts public packages only ("PyPI does not support publishing private packages"): internal code belongs on a private index.

The life of a release

flowchart TD P["Prepare: layout, names, metadata, license"] --> V["Version: bump or tag"] V --> B1["Build: python -m build"] V --> B2["Build: poetry build"] B1 --> C["Check: twine check --strict, install the artefacts, run the tests"] B2 --> C C --> T["Rehearse: TestPyPI (optional)"] T --> U1["Upload: Trusted Publishing from CI (pypa/gh-action-pypi-publish or twine)"] T --> U2["Upload: poetry publish (token) or build with Poetry + the PyPA action"] U1 --> R["Released on PyPI, with attestations"] U2 --> R R --> M["After: yank, archive, add a post-release, watch downloads"]
Step pip-side chain Poetry chain

Build

python -m build (build 1.6.1, any backend)

poetry build (poetry-core, or any backend since 2.1)

Check

twine check --strict

poetry check --strict, then twine check on dist/

Upload

twine upload with a token, or Trusted Publishing through the PyPA action or twine 6.1+

poetry publish with a token; no Trusted Publishing of its own

Attestations

the PyPA action (default) or twine upload --attestations

through the PyPA action only

What changed, and when

Date or release Change

setuptools 42.0.0 (2019), 75.0.0 (2024-09-15)

setup.py upload and register, then upload_docs, removed. Never use python setup.py upload.

2023-04-20

Trusted Publishing on PyPI (GitHub); GitLab, Google Cloud and ActiveState on 2024-04-17; GitLab Self-Managed in beta since 2025-11-10.

2024-01-01

Two-factor authentication mandatory for every PyPI account.

twine 5.0.0 (2024-02-10), 6.0.0 (2024-11-29)

Only __token__ accepted for PyPI and TestPyPI; then it is the default username.

gh-action-pypi-publish v1.11.0 (2024-10-30)

PEP 740 attestations generated by default (opt-in since v1.10.0).

2024-11-14

PyPI accepts digital attestations (PEP 740).

December 2024

PEP 639 in the pyproject specification: SPDX license, license-files, License :: classifiers deprecated, .dist-info/licenses/.

2024-12-30, 2025-01-30, 2025-08-14

PyPI project quarantine; project archiving; project status markers in the index APIs (PEP 792).

twine 6.1.0 (2025-01-17)

Preliminary built-in Trusted Publishing; egg, wininst and .tar.bz2 uploads dropped.

setuptools 77.0.0 (2025-03-19)

PEP 639 and Core Metadata 2.4; use 77.0.3 or later.

2025-08-07

PyPI rejects ZIP-confusion archives; wheels with an incorrect RECORD deprecated.

Poetry 2.2.0 (2025-09-14)

PEP 639 in poetry-core.

twine 6.2.0 (2025-09-04)

Trusted Publishing tokens refreshed every 15 minutes; --skip-existing workarounds for other indexes removed.

setuptools 82.0.0 (2026-02-08)

pkg_resources removed.

gh-action-pypi-publish v1.14.0 (2026-04-07)

verbose and print-hash on by default.

May 2026

Core Metadata 2.6 (PEP 808): static keys may also be listed in dynamic, and backends may only append to them.

2026-07-22

PyPI rejects new files for a release more than 14 days old (not yet "defined semantics", per the PyPI blog).

twine 7.0.0 (2026-07-27)

Metadata 2.5 accepted, "2.0" rejected, .pypirc read as UTF-8.

October 2026

Security releases: gh-action-pypi-publish v1.14.3 (GHSA-5xjx-3w67-w9x9) and cibuildwheel 4.3.2 to 4.3.4 (the compromised eMudhra CA roots).

Poetry

No native Trusted Publishing or attestations (python-poetry/poetry#7940, open since 2023-05-17).

Release checklist

  1. requires-python, dependencies, classifiers, URLs and the SPDX license are right; license-files match real files.

  2. The version is new and valid (no local … part); the changelog has an entry; the release commit is tagged.

  3. dist/ is empty before the build; build both sdist and wheel.

  4. twine check --strict dist/* passes; unzip -l and tar tzf show the expected files; the wheel’s METADATA is right.

  5. The wheel and the sdist install into fresh environments and the tests pass against them.

  6. Optional: a rehearsal on TestPyPI with a dev version.

  7. Publish from CI with Trusted Publishing, from a protected environment; never with a personal token on a laptop.

  8. Check the project page, the verified URLs and the attestations; install the release from PyPI once.

  9. If something is wrong: yank, fix, release a new version; never delete what users may have pinned.