Course Progress98%
🍎 Python Professional Python Topic 98 / 100
⏳ 8 min read

Packaging a Python Project

Structure a distributable package, write pyproject.toml, install it locally for development, and publish it to PyPI.

"A package is code you can share. Structure your project correctly once, write a pyproject.toml, and anyone on Earth can install your work with a single pip install command."

— Shurai

What Makes a Python Package?

Any directory containing an __init__.py file is a Python package. That file can be empty — its presence is what tells Python "this directory is a package that can be imported":

Recommended project layout (src layout):
my_project/
├── src/
│   └── mylib/
│       ├── __init__.py      ← makes it a package
│       ├── core.py          ← main logic
│       └── utils.py         ← helpers
├── tests/
│   ├── test_core.py
│   └── test_utils.py
├── pyproject.toml       ← project metadata + build config
└── README.md

__init__.py — Your Package’s Public API

What you put in __init__.py is what users get when they import your package. Think of it as the front door:

python — src/mylib/__init__.py
# Import the things you want users to access directly
from .core  import calculate, process
from .utils import format_result

__version__ = "0.1.0"
__author__  = "Riya Sharma"

# __all__ controls what "from mylib import *" exposes
__all__ = ["calculate", "process", "format_result"]
python — user's code after installing
from mylib import calculate        # works because of __init__.py
import mylib
print(mylib.__version__)           # "0.1.0"

pyproject.toml — Modern Project Configuration

The single file that defines everything about your project — its name, version, dependencies, and how to build it:

toml — pyproject.toml (annotated)
[build-system]
requires      = ["setuptools>=68", "wheel"]
build-backend = "setuptools.backends.legacy:build"

[project]
name            = "mylib"           # pip install mylib
version         = "0.1.0"
description     = "A useful Python library"
authors         = [{name = "Riya Sharma", email = "riya@example.com"}]
readme          = "README.md"       # shown on PyPI
requires-python = ">=3.10"
license         = {text = "MIT"}

# These get pip-installed automatically when someone installs mylib
dependencies = [
    "requests>=2.31",
    "click>=8.0",
]

# pip install mylib[dev]  — installs dev tools too
[project.optional-dependencies]
dev = ["pytest", "black", "mypy"]

# Creates a terminal command:  mylib-cli  →  calls mylib.cli:main()
[project.scripts]
mylib-cli = "mylib.cli:main"

Install Locally for Development

terminal — the most important command for development
# Install in editable mode — source changes are reflected instantly
# Run this ONCE from your project root directory
pip install -e .

# Now you can import your package anywhere on your machine:
python -c "from mylib import calculate; print('Works!')"

# And update: just edit the source file — no reinstall needed

Build and Publish to PyPI

1
Install build tools
pip install build twine
2
Build distribution packages
python -m build
Creates dist/mylib-0.1.0.tar.gz and dist/mylib-0.1.0-py3-none-any.whl
3
Upload to PyPI (free account at pypi.org)
twine upload dist/*
Anyone can now run: pip install mylib
terminal — full build + publish
pip install build twine

python -m build
# Successfully built mylib-0.1.0.tar.gz and mylib-0.1.0-py3-none-any.whl

twine upload dist/*
# Uploading distributions to https://upload.pypi.org/legacy/
# Uploading mylib-0.1.0-py3-none-any.whl
# View at: https://pypi.org/project/mylib/
Test on TestPyPI before publishing to the real thing

PyPI has a test server at test.pypi.org — publish there first with twine upload --repository testpypi dist/* and install with pip install --index-url https://test.pypi.org/simple/ mylib. Confirm everything works before pushing to the real PyPI.

"pip install -e . is the single most useful command during development. It installs your package in editable mode — you edit a source file and the change is live immediately, no reinstall needed."

— Shurai

🧠 Quiz — Q1

What makes a directory a Python package that can be imported?

🧠 Quiz — Q2

What is the purpose of pyproject.toml?

🧠 Quiz — Q3

What does pip install -e . do, and why is it useful for development?

🧠 Quiz — Q4

After running python -m build, what files are created?