# Python Packaging 101 tutorial
Welcome! This is a hands-on introduction to packaging Python projects using modern tooling.
## Learning outcomes
By the end of this tutorial, you will:
- Understand the structure of a modern Python package.
- Build source and wheel distributions with `python -m build`.
- Upload to TestPyPI using twine and an API token.
- Install and verify your package from TestPyPI.
## Setup and requirements
- Computer connected to the Internet
- Access to your personal GitHub account
- Python 3.9+, `pip`, `venv` or `virtualenv` installed
- Access to your personal [TestPyPI](https://test.pypi.org) account (optional, we will have time to do it in class)
## Prerequisites
To get the most out of this tutorial, you should be comfortable with:
- Writing basic Python code
- Using virtual environments
- Using terminal
# Overview
## Why package?
- Make your code reusable and shareable.
- Control versions and dependencies.
- Enable reproducible installs.
# 1. Structure of an Installable Module
[acpackage](https://github.com/albuscode/acpackage) is a demo package we will use for this tutorial.
Let's look at the file directory:
```
acpackage/
src/
acpackage/
__init__.py
example.py
docs/
tests/
pyproject.toml
README.md
LICENSE
.gitignore
```
A file directory is a hierarchical structure, like a physical filing cabinet, that organizes files into folders.
`.gitignore`, `docs/`, `tests/` are optional, but highly recommended
**src/acpackage/example.py**
This module contains a function that adds two integers and returns the result. It includes a detailed docstring explaining parameters and examples.
```python
def add_num(a: int, b: int) -> int:
"""
Add two numbers.
Parameters
----------
a : int
The first number to be added.
b : int
The second number to be added.
Returns
-------
int
The sum of the two input numbers (a + b).
Examples
--------
>>> add_num(2, 4)
6
>>> add_num(-2, 4)
2
"""
return a + b
```
**src/acpackage/`__init__.py`**
`__init__.py` acts as the "constructor" for a Python package, enabling its proper recognition, initialization, and controlled exposure of its contents.
```python
from .example import add_num
__all__ = ["add_num"]
```
# 2. Anatomy of `pyproject.toml`
Every modern Python package should include a pyproject.toml file. This file is the foundation and primary source of truth for package configuration. If it’s correct, your package will install, build, and work as expected with modern tools.
- You **must** include `[project]`, `[build-system]`, and the `name`, `version`, and `build-backend`.
- It's **highly recommended** to include `requires-python`.
- pyproject.toml is not just for builds, it’s a central configuration file for tools and environments.
```toml
[build-system]
requires = ["uv_build >= 0.8.20, <0.9.0"]
build-backend = "uv_build"
[project]
name = "acpackage"
description = "A package for performing basic addition"
readme = "README.md"
version = "0.0.1"
requires-python = ">=3.10"
license = { file = "LICENSE" }
keywords = []
authors = [
{ name = "Inessa Pawson", email = "inessa@albuscode.org" },
]
classifiers = [
"Development Status :: 4 - Beta",
"Programming Language :: Python :: 3",
"Operating System :: OS Independent"
]
dependencies = []
[project.urls]
Homepage = "https://github.com/albuscode/acpackage"
Documentation = ""
Issues = "https://github.com/albuscode/acpackage/issues"
Source = "https://github.com/albuscode/acpackage"
```
# 3. Build & Install Locally
### Download `acpackage` as .zip file:
https://github.com/albuscode/acpackage/archive/refs/heads/main.zip
*Note:* Package names on Test PyPI must be unique. Rename the downloaded package to `acpackageYOURNAME`.
### Move to the package directory
```bash
cd acpackageYOURNAME
```
### Install build tools:
```bash
python -m pip install --upgrade build
```
### Build distributions:
```bash
python -m build
ls -lah dist
```
This produces both a source archive (*.tar.gz) and a wheel (*.whl) in dist/.
### Install the wheel to test:
```bash
pip install dist/acpackageYOURNAME-0.0.1-py3-none-any.whl
python -c "import acpackageYOURNAME; print(acpackageYOURNAME.add_num(2, 3))"
```
### Try **editable installs** (PEP 660):
```bash
pip install -e .
```
Editable mode is useful during development as you can change code without reinstalling. Not always supported by all build backends, but `uv_build` supports it.
# 4. Upload to TestPyPI
Create an account and API token at [TestPyPI](https://test.pypi.org/) , then upload with Twine.
```bash
pip install --upgrade twine
twine upload --repository testpypi dist/*
```
# 5. Install from TestPyPI & Verify
```bash
pip install --index-url https://test.pypi.org/simple/ \
--extra-index-url https://pypi.org/simple \
acpackage-YOURNAME
python -c "import acpackageYOURNAME; print(acpackageYOURNAME.add_num(4, 5))"
```
If you exposed a console script:
```bash
acpackage-add 7 8
```
# 6. Metadata, Dependencies, & Extras
```toml
[project]
dependencies = ["simplejson>=3.19"]
[project.optional-dependencies]
dev = ["pytest", "ruff"]
cli = ["click>=8"]
```
Example CLI using Click:
```python
import sys
try:
import click
except Exception:
print("Install optional extra: pip install 'acpackage-yourname[cli]' ")
raise
@click.command()
@click.argument("a", type=int)
@click.argument("b", type=int)
def main(a, b):
from acpackage import add
print(add(a, b))
if __name__ == "__main__":
main()
```
# 7. Advanced Topics
## Namespace packages
- Implicit namespace packages (PEP 420).
## Binary extensions
- C/C++/Cython → platform wheels.
## Plugin entry points
- Use `project.entry-points` for discoverable plugins.
<!-- 08-workflows.md -->
# 8. Workflows & Best Practices
Example GitHub Actions release workflow:
```yaml
name: Release
on:
push:
tags:
- 'v*'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.x'
- run: python -m pip install --upgrade pip build twine
- run: python -m build
- run: twine check dist/*
- env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
run: twine upload dist/*
```
# 9. Resources
- Packaging Python Projects by PyPA: packaging.python.org
- Python Packaging 101 by PyOpenSci: https://www.pyopensci.org/python-package-guide/tutorials/intro.html