Python Packaging
Structure, build, and publish distributable packages with pyproject.toml
Overview
Packaging turns your modules into a distributable, installable artifact that others can pip install. Modern Python standardizes on pyproject.toml (PEP 517/518/621) to declare metadata, dependencies, and the build backend. The build system produces two artifacts: a source distribution (sdist) and a platform-independent wheel.
Syntax / Usage
Declare everything in pyproject.toml. A src/ layout keeps the import package isolated from project config.
# pyproject.toml
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "mytool"
version = "0.1.0"
description = "A small CLI tool"
requires-python = ">=3.10"
dependencies = ["httpx>=0.27"]
[project.optional-dependencies]
dev = ["pytest>=8", "mypy>=1.10"]
[project.scripts]
mytool = "mytool.cli:main" # exposes a console entry point
The layout pairs this file with src/mytool/__init__.py and src/mytool/cli.py.
Examples
Define the console entry point referenced above; installing the package puts mytool on the PATH:
# src/mytool/cli.py
import sys
def main() -> int:
args = sys.argv[1:]
if not args:
print("usage: mytool <name>")
return 1
print(f"Hello, {args[0]}!")
return 0
Build and (optionally) publish from the shell:
# Build sdist + wheel into dist/, then upload
import subprocess
subprocess.run(["python", "-m", "build"], check=True)
subprocess.run(["python", "-m", "twine", "check", "dist/*"], check=True)
# subprocess.run(["python", "-m", "twine", "upload", "dist/*"], check=True)
Install locally in editable mode with pip install -e ".[dev]" so code changes take effect without reinstalling.
Common Mistakes
- Hardcoding versions in multiple places instead of a single source of truth
- Using a flat layout so tests accidentally import from the working directory rather than the installed package
- Forgetting to list runtime dependencies, so installs succeed locally but fail for users
- Publishing without bumping the version—PyPI rejects duplicate versions
- Committing build artifacts (
dist/,*.egg-info) instead of ignoring them
See Also
python-modules python-type-hints python-testing-pytest