Building and publishing¶
Your package works locally; now you want anyone with pip to be
able to install it. That means producing distribution artefacts and
uploading them to a package index — TestPyPI for practice, then PyPI
for real.
Time commitment: 15 minutes
Prerequisites:
- A working package from Authoring a package,
installable with
pip install -e .
Learning objectives¶
By the end of this tutorial, you will be able to:
- Build sdist and wheel artefacts with
python -m build - Upload to TestPyPI and PyPI with
twine - Bump versions and re-release safely
!!! note "Browser note" The commands below run in your terminal, not in this page's browser kernel. Pyodide doesn't expose a shell, so the bash blocks here are for reference — copy them into a real terminal to follow along.
Every command in this notebook is a shell command — none of
them run in Pyodide. Read through, then try the workflow on
a project of your own.
Two artefacts: sdist and wheel¶
A Python package is published as two files:
- sdist (
my-package-0.1.0.tar.gz) — a source distribution. A tarball of your project as it appears on disk, includingpyproject.toml.pipcan install from this by re-running the build, but it's slow. - wheel (
my_package-0.1.0-py3-none-any.whl) — a built distribution. Files are already in the layoutpipwill install them in. Fast to install; the formatpipprefers when both are available.
Most pure-Python packages ship one wheel that works on every platform. Packages with C extensions ship multiple wheels — one per platform/Python-version combination. The Why wheels exist concept piece digs into the format.
Building¶
The standard tool is build (a small package, run as a module):
pip install build
python -m build
Run it from your project root. It reads pyproject.toml, calls the
configured build backend (hatchling, in our case), and writes
artefacts to a dist/ directory:
dist/
my_package-0.1.0-py3-none-any.whl
my-package-0.1.0.tar.gz
That's everything you need to publish.
Inspecting a wheel before you upload¶
A wheel is a zip file. You can peek inside without installing:
python -m zipfile -l dist/my_package-0.1.0-py3-none-any.whl
Look for the files you expect, in the import-name directory. Common bugs — missing modules, vendored data files left behind, accidental inclusion of tests — show up here before they show up for users.
TestPyPI first¶
TestPyPI (test.pypi.org) is a separate
instance of PyPI for trying things out. Publishing there first is a
habit worth forming — it catches metadata mistakes (a bad
description, a missing README) before they're permanently in
front of real users.
You'll need an account on TestPyPI and an API token from your account settings. Then:
pip install twine
python -m twine upload --repository testpypi dist/*
Verify the install works from a clean venv:
pip install --index-url https://test.pypi.org/simple/ my-package
Publishing to PyPI¶
With TestPyPI happy, the real upload is the same shape:
python -m twine upload dist/*
Within a minute or two, pip install my-package works for anyone in
the world. The package page is live at https://pypi.org/project/my-package/.
Two things to know:
- Names are first-come-first-served. Once you've uploaded
my-package, nobody else can use that name. Conversely, if a name is already taken,twine uploadwill tell you on the first attempt — pick a different one. - You can't overwrite a release. Once
0.1.0is on PyPI, that file's bytes are fixed. To fix a mistake, bump the version and upload again.
Bumping the version¶
The release workflow once 0.1.0 is out:
- Edit
pyproject.toml— bumpversionto0.1.1(patch),0.2.0(minor), or1.0.0(major). - Update
__version__in__init__.pyto match. python -m build— produces fresh artefacts indist/.twine upload dist/*— only the new files; PyPI ignores the ones it's seen.- Tag the commit in git:
git tag v0.1.1 && git push --tags.
Some teams automate this with hatch version, bump-my-version, or release-bot tooling that generates the artefacts in CI.
API tokens and trusted publishing¶
For uploads, never use your account password. Two safer options:
- API tokens — generate one in your PyPI account settings; scope
it to a single project.
twinewill prompt for the token; store it in~/.pypircor a credential manager. - Trusted publishing — for projects that release from CI, trusted publishing lets PyPI accept uploads from a specific GitHub Actions workflow without any secret at all. The recommended option for any project that's released from a public repository.
Recap and next steps¶
python -m buildproduces an sdist and a wheel indist/.- Publish to TestPyPI first; verify the install; then publish to PyPI.
- You can never overwrite a release — bump the version and re-upload.
- Use API tokens, or set up trusted publishing for CI-driven releases.
That's the consumer-to-author arc complete. From here, the Recipes walk through specific workflows — pinning dependencies, fixing import errors, and the most common packaging traps. The Concepts section is for when you want to understand why the import system, the wheel format, and PyPI work the way they do.