Packaging with pyproject
Turn your code into an installable, shareable package.
- Describe a project with pyproject.toml
- Explain what metadata and dependencies a package declares
- Build and (optionally) publish a package
When code outgrows a folder of scripts — you want to install it, version it, or
share it — you package it. The modern standard is a single pyproject.toml
file that describes the project: its name, version, dependencies, and how to build
it. It's the manifest from the project-shape lesson, in Python's current form.
pyproject.toml
One declarative file replaces the older scattered setup files:
[project]
name = "mytool"
version = "0.1.0"
description = "A small, useful tool"
requires-python = ">=3.11"
dependencies = [
"requests>=2.31",
]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"The [project] table is your package's identity and its dependency contract (note
the version ranges — the manifest, distinct from the pinned lockfile).
[build-system] tells tools how to build it.
Building and publishing
With that file, standard tools turn your code into distributable artifacts:
python -m build # produces a wheel (.whl) and source archive in dist/
python -m twine upload dist/* # publish to PyPI (so others can pip install it)A wheel is the built, ready-to-install format pip prefers — the packaging
equivalent of the build-and-release lesson's "build once, install anywhere."
Dependencies vs lockfile, again
Two layers, same distinction as before:
pyproject.tomldeclares dependencies as ranges — what your package needs to work.- A lockfile (from
pip freeze, or tools likeuv/poetry) records the exact versions for a reproducible build.
Libraries publish ranges so they compose with others; applications also pin exact versions so deployments are deterministic.
You don't need to publish to PyPI to benefit — pip install -e . installs your
project locally in "editable" mode, so imports work cleanly across your own
modules while you develop.
Where to go next
Last in this module, the tool that proves your package works: testing with pytest.