C.W.K.
Stream
Lesson 05 of 05 · published

Distributing Code — PyPI and Wheels

~15 min · pypi, wheel, publishing, twine

Level 0Curious
0 XP0/93 lessons0/23 achievements
0/100 XP to next level100 XP to go0% complete

The PyPI ecosystem

pip install foo downloads from PyPI by default. Anyone can publish there — it's the public registry. The flow: build your package into a wheel, run twine upload, and within minutes anyone can pip install your-package. The barrier is not technical; it's just a matter of writing useful code people want.

Wheels — the binary distribution format

A wheel (.whl) is a zip with a specific layout that pip can install directly without running setup code. It's faster and safer than the older sdist format. Pure-Python packages produce one universal wheel; packages with C extensions produce platform-specific wheels (one per OS / Python version / architecture).

The publishing flow

(1) Make sure your pyproject.toml is right — name, version, dependencies. (2) python -m build creates dist/your-package-version-*.whl and a sdist. (3) twine upload dist/* pushes to PyPI (after you've registered an account and gotten a token). The first upload locks your name; subsequent versions just bump and re-upload.

Test PyPI — the staging area

Before publishing for real, push to test.pypi.org. It's a separate instance with the same shape but no real audience. Your first time, push there, install from there in a fresh venv, confirm it works, then push to real PyPI.

War Story: You can't unpublish a version once it's on PyPI — you can only yank it (mark it bad). So getting the version right matters. Common pattern: bump the version, build, test in a fresh venv, then upload. Never upload from the directory where you developed; build cleanly each time.

Code

Building wheels·bash
# Install the build tool
pip install build

# In a project with pyproject.toml
python -m build

# Result:
# dist/
#   my_package-0.1.0-py3-none-any.whl    # wheel
#   my-package-0.1.0.tar.gz               # sdist

# Inspect a wheel (it's a zip)
unzip -l dist/my_package-0.1.0-py3-none-any.whl
Publishing to Test PyPI first·bash
pip install twine

# Upload to test.pypi.org
twine upload --repository testpypi dist/*

# Install from test.pypi.org (and pull deps from real PyPI)
pip install --index-url https://test.pypi.org/simple/ \
            --extra-index-url https://pypi.org/simple/ \
            my-package

# Confirm it works in a fresh venv before going to real PyPI
Publishing to real PyPI·bash
# After testing on test.pypi.org
twine upload dist/*

# You'll need:
# 1. PyPI account at https://pypi.org/
# 2. API token (from account settings)
# 3. Configure ~/.pypirc with the token, or use TWINE_PASSWORD env var

# After upload, anyone can:
# pip install my-package
Versioning conventions — semantic versioning·python
# Version format: MAJOR.MINOR.PATCH (semver)
# 1.0.0 — first stable release
# 1.0.1 — bug fix (no new features)
# 1.1.0 — new features (backwards-compatible)
# 2.0.0 — breaking changes (callers may need to update)
#
# Pre-release: 1.0.0a1 (alpha), 1.0.0b1 (beta), 1.0.0rc1 (release candidate)
# Dev: 1.0.0.dev0
#
# Single source of truth — pyproject.toml [project] version field.
# Don't duplicate in __version__ unless you need it programmatically;
# use importlib.metadata to read it from the installed package.

External links

Exercise

Take the mini_pkg from the previous lesson's exercise. Run python -m build and inspect the generated wheel. Open it as a zip (unzip -l dist/*.whl) and confirm: it has a METADATA file with your package name, a WHEEL file, and your __init__.py and cli.py source files. Don't actually upload to PyPI — just verify the build works.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.