Skip to content

Packaging & pyproject.toml

A Python package is a directory of modules that can be installed, versioned, and shared. Packaging turns your local code into something anyone can install with pip install mypackage. Understanding packaging is essential the moment your project grows beyond a single script or you want to share work with teammates or the public.

A clean src-layout keeps your source isolated from tests and tooling, preventing accidental imports of uninstalled code.

flowchart TD
  root["mypackage/"] --> src["src/"]
  src --> pkg["mypackage/"]
  pkg --> init["__init__.py"]
  pkg --> core["core.py"]
  pkg --> utils["utils.py"]
  root --> tests["tests/"]
  tests --> conftest["conftest.py"]
  tests --> testcore["test_core.py"]
  root --> pyproj["pyproject.toml"]
  root --> readme["README.md"]
Clean src-layout package structure

pyproject.toml is the single source of truth for your package. The [build-system] table specifies the build backend, and [project] declares all metadata pip and PyPI need.

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "mypackage"
version = "0.1.0"
description = "A short description of mypackage"
readme = "README.md"
requires-python = ">=3.11"
license = { text = "MIT" }
authors = [
{ name = "Your Name", email = "[email protected]" }
]
dependencies = [
"httpx>=0.27",
]
[project.optional-dependencies]
dev = [
"pytest>=8",
"mypy>=1.10",
]
[project.urls]
Homepage = "https://github.com/yourname/mypackage"

The public API is re-exported from __init__.py, keeping the import surface clean.

src/mypackage/__init__.py
from mypackage.core import greet
__all__ = ["greet"]

Business logic lives in dedicated modules such as core.py.

src/mypackage/core.py
def greet(name: str) -> str:
"""Return a greeting string."""
return f"Hello, {name}!"

Editable install, building, and publishing

Section titled “Editable install, building, and publishing”

During development you want code changes to take effect without reinstalling. The -e flag achieves this. When you are ready to distribute, build creates the artifacts and twine uploads them.

Terminal window
# editable install — changes to src/ take effect immediately
pip install -e ".[dev]"
# build source distribution + wheel
python -m build
# publish to PyPI (requires a PyPI account + token)
twine upload dist/*
# for practice, use TestPyPI first:
twine upload --repository testpypi dist/*
Which file is the modern standard for declaring Python package metadata?
What does `pip install -e .` do?
Which tool is used to upload a package to PyPI?