Python Packaging

Tutorial 64 of 65 · pythondeck.com Python course

Modern Python packaging uses pyproject.toml and a build backend like hatchling or setuptools. Build with python -m build; publish to PyPI with twine upload dist/*.

Packaging turns your Python project into something others install with pip—wheel distributions, entry points, and metadata on PyPI or private indexes.

Understanding pyproject.toml (PEP 621) and build backends (hatchling, setuptools, poetry) is essential for libraries and CLI tools.

Consumers judge libraries by install experience: clear README, stable semver, and wheels that work on their platform without compiling C extensions.

Typos in package name on PyPI are permanent—choose import name and distribution name deliberately before first upload.

pyproject.toml — project name, version, dependencies, build-system table.

src layout — package under src/ avoids import shadowing during dev.

sdist vs wheel — wheel is prebuilt; faster installs for pure Python.

entry points — console_scripts expose CLI commands on install.

versioning — semver; dynamic version from VCS tags in mature projects.

publishing — twine upload; API tokens on PyPI with 2FA.

Libraries declare compatible ranges (>=1.2,<2); applications pin exact versions. Include LICENSE, README, and classifiers for discoverability. Test installed package in CI (pip install . && pytest) not only editable mode.

Namespace packages and type hints (py.typed) improve IDE experience for consumers. For internal companies, devpi or Artifactory mirrors PyPI with approval gates.

Optional dependencies (extras_require) keep core installs lean while exposing [dev] or [docs] groups for contributors.

Changelog files (Keep a Changelog format) set consumer expectations before they pin your next major release.

Importing from tests without installing package, missing packaging bugs.

Loose dependency pins breaking downstream on fresh install.

Shipping secrets or huge data files inside wheel by mistake.

Renaming package without deprecation path, breaking importers.

Use src/ layout and one obvious import name matching PyPI project.

Run build in CI: python -m build; check twine check dist/*.

Document public API; treat undocumented modules as private.

Sign tags and enable PyPI trusted publishing from GitHub Actions.

Re-read the examples below with these ideas in mind; change variable names and inputs to match your own project.

The program below demonstrates pyproject.toml. Read the comments on each line, run the code, then change names or values to see how the output shifts.

# Example: pyproject.toml
# Run in the REPL or save as a .py file and execute with python.
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "mylib"
version = "0.1.0"
description = "Demo library"
requires-python = ">=3.9"
dependencies = ["requests"]

This sample walks through build in a small, runnable script. Paste it into the REPL or save it as a .py file before you continue to the next block.

# Example: Build
# Run in the REPL or save as a .py file and execute with python.
python -m pip install --upgrade build twine
python -m build
# dist/mylib-0.1.0-py3-none-any.whl + .tar.gz

Here is a hands-on illustration of upload. Follow the inline comments first; only then execute the snippet and compare the result with what you expected.

# Example: Upload
# Run in the REPL or save as a .py file and execute with python.
twine upload dist/*
# uses ~/.pypirc or token from env

The program below demonstrates build wheel. Read the comments on each line, run the code, then change names or values to see how the output shifts.

# pyproject.toml defines build backend (usually setuptools/hatchling)
python -m pip install --upgrade build twine  # build tools
python -m build  # creates dist/*.whl and dist/*.tar.gz
dir dist  # Windows list artifacts (ls dist on Unix)
python -m twine check dist/*  # validate metadata before upload
pip install dist/*.whl  # local install smoke test
pip show your-package-name  # confirm installed distribution
pip uninstall -y your-package-name  # remove test install

This sample walks through editable install in a small, runnable script. Paste it into the REPL or save it as a .py file before you continue to the next block.

# pip install -e . links source for rapid iteration
python -m pip install -e .  # editable mode from project root
python -c "import your_package; print(your_package.__file__)"  # points to src
pip list | findstr your  # Windows filter (grep on Unix)
python -m build --wheel  # still can build release wheel later
pip install dist/*.whl  # test non-editable artifact
pip uninstall -y your-package-name  # cleanup
echo Remember to bump version in pyproject.toml before release

« Python Virtual Env All tutorials Python OOP Advanced »