Skip to content

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).

View Example pyproject.toml
[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:

  1. Source Distribution (sdist): A .tar.gz archive containing your source code.
  2. Built Distribution (wheel): A .whl file 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 build

This will create a dist/ directory in your root:

View dist/ folder content
dist/
├── my_project-0.1.0-py3-none-any.whl
└── my_project-0.1.0.tar.gz

4. 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.

View GitHub Release Workflow (.yml)
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/*