Packaging for Distribution
Learn how to turn your Python project into a distributable package using pyproject.toml and modern build backends.
Packaging for Distribution
Once your code is organized and tested, the next step is packaging it so others can install it via pip. In 2026, we have moved away from legacy files like setup.py and setup.cfg in favor of the standardized pyproject.toml.
1. The Anatomy of pyproject.toml
The pyproject.toml file contains everything a build tool needs to know about your project. It is divided into sections (tables).
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "my_project"
version = "0.1.0"
description = "A short description of my project"
readme = "README.md"
requires-python = ">=3.9"
license = {text = "MIT"}
authors = [
{name = "Ravi", email = "ravi@example.com"},
]
dependencies = [
"requests>=2.31.0",
"pydantic>=2.0.0",
]
[project.urls]
Homepage = "[https://github.com/user/my_project](https://github.com/user/my_project)"
Documentation = "[https://user.github.io/my_project](https://user.github.io/my_project)"
[project.scripts]
my-cli = "my_project.main:app"2. Choosing a Build Backend
The [build-system] section tells the installer which tool to use to package your code.
| Backend | Best For |
|---|---|
| Hatchling | High-performance, feature-rich, and works great with the src layout. |
| Flit | Minimalist; best for simple libraries that don't need complex build steps. |
| Setuptools | The legacy standard; use only if you have custom C extensions or complex requirements. |
| PDM/Poetry | All-in-one dependency managers that have their own backends. |
3. Building the Distribution
To package your repo, you need to generate two files:
- Source Distribution (sdist): A
.tar.gzarchive containing your source code. - Built Distribution (wheel): A
.whlfile that is pre-compiled and faster to install.
Using the build tool
The standard, backend-agnostic way to build is using the build package:
# Install the builder
python -m pip install build
# Run the build
python -m buildThis will create a dist/ directory in your root:
dist/
├── my_project-0.1.0-py3-none-any.whl
└── my_project-0.1.0.tar.gz4. Publishing to PyPI
To make your package available via pip install my_project, you must upload these artifacts to the Python Package Index (PyPI).
Step A: Create an Account
Register at PyPI.org and enable 2FA. Generate an API Token for secure uploads.
Step B: Upload with Twine
twine is the standard tool for securely uploading packages.
python -m pip install twine
# Upload to TestPyPI first (to check for errors)
python -m twine upload --repository testpypi dist/*
# Upload to the real PyPI
python -m twine upload dist/*5. Automation with CI/CD
You shouldn't have to run these commands manually every time. Most modern projects use GitHub Actions to automate the release process whenever a new tag is pushed.
name: Publish to PyPI
on:
release:
types: [published]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
- name: Install dependencies
run: pip install build twine
- name: Build and Publish
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
run: |
python -m build
twine upload dist/*