← Back to Python series
🚀
Advanced
pyproject.toml · hatch · wheel · PyPI · entry_points

Week 9 — Packaging & Distribution

Turn your Python code into a distributable package. Learn pyproject.toml, build tools (hatch/setuptools), create CLI entry points, and publish to PyPI (or TestPyPI).

packagingpyproject.tomlwheelPyPICLIhatch
Duration
2.5 hours
Level
📊 Advanced
Prerequisite
🎯 Week 3
OUTCOME
Publish a Python package with a CLI entry point to TestPyPI

What you'll learn

  • 1Structure a Python package with src/ layout
  • 2Write pyproject.toml with metadata and dependencies
  • 3Build wheel and sdist distributions
  • 4Create CLI commands with entry_points.scripts
  • 5Publish to TestPyPI and install with pip

1. Package Structure

text
mypackage/
├── src/
│   └── mypackage/
│       ├── __init__.py
│       ├── core.py
│       └── cli.py
├── tests/
│   └── test_core.py
├── pyproject.toml
└── README.md

2. pyproject.toml

toml
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "mypackage"
version = "0.1.0"
description = "My first Python package"
requires-python = ">=3.10"
dependencies = ["requests>=2.28"]

[project.scripts]
mypkg = "mypackage.cli:main"

[tool.hatch.build.targets.wheel]
packages = ["src/mypackage"]

3. Build & Publish

bash
pip install hatch
hatch build          # creates dist/*.whl and dist/*.tar.gz

# Publish to TestPyPI
pip install twine
twine upload --repository testpypi dist/*

# Install from TestPyPI
pip install --index-url https://test.pypi.org/simple/ mypackage

💻 Examples

Run these examples and check the output yourself.

cli.pyCLI entry point using argparse
CODE
import argparse
from .core import process

def main():
    parser = argparse.ArgumentParser(description="MyPackage CLI")
    parser.add_argument("input", help="Input file")
    parser.add_argument("-o", "--output", default="output.txt")
    parser.add_argument("-v", "--verbose", action="store_true")
    args = parser.parse_args()

    result = process(args.input, verbose=args.verbose)
    with open(args.output, "w") as f:
        f.write(result)
    print(f"Wrote {args.output}")

if __name__ == "__main__":
    main()

📝 Exercises

Try them yourself first, then open the solution to compare.

Exercise 1

Build your own package

Goal: Package and distribute the text analyzer from Week 10 capstone.

Requirements
  • src/ layout with pyproject.toml
  • CLI entry point: analyze <file> [--top N] [--format text|csv]
  • Unit tests with pytest
  • Build a wheel with hatch build
Grading
  • · Package installs with pip — 30%
  • · CLI entry point works — 30%
  • · Tests pass — 30%
  • · Wheel file created — 10%
Example code / lecture materials

All lecture materials and example code are openly available on GitHub.

View on GitHub ↗