Packaging & Distribution
📦 Packaging & Distribution
pip install, venv. This chapter covers the other side: turning your own code into a real, installable, distributable Python package, from project structure through pyproject.toml to actually publishing on PyPI.
🗂️ Project Structure
The src/ layout (package code inside a src/ folder, separate from tests and config) is the modern recommended structure — it prevents accidentally importing your package from the current working directory instead of the actually-installed version, a subtle bug the older "flat" layout could hide. __init__.py is what makes a plain folder importable as a package at all, a detail Course 1, Chapter 9's import coverage didn't need to mention since it only covered using existing packages.
⚙️ pyproject.toml
pyproject.toml is the modern, standardized project config file — it replaced the older setup.py approach, and plays the same role as Node's package.json or Rust's Cargo.toml. The [project] table declares your package's metadata and dependencies in the exact package==version style from Course 1, Chapter 9's own requirements.txt coverage, just declared alongside the package itself rather than in a separate file.
🔨 Build Tools: setuptools vs Poetry
setuptools vs Poetry
| setuptools | Poetry | |
|---|---|---|
| Role | The traditional, most widely used build backend — does one job, building the package | An all-in-one tool: dependency management, virtual environments, building, and publishing together |
| Dependency locking | No built-in lock file — needs a separate tool (like pip-tools) for reproducible installs | Built-in poetry.lock, similar to package-lock.json or Cargo.lock |
| Best for | Simple packages, or when you want minimal tooling and full control | Larger projects wanting one consistent tool for the whole dependency/build/publish workflow |
Both read from pyproject.toml — they differ in scope, not in the config file format itself. setuptools is the safer default to learn first since it's what most existing open-source packages already use; Poetry is worth reaching for once a project's dependency management gets complex enough to want a lock file.
💻 Building and Installing Locally
pip install -e . is an editable install — it installs your package by linking directly to your source folder, rather than copying it. Changes to your code take effect immediately, with no reinstall needed, making it the standard way to develop and test a package locally before publishing it.
🚀 Publishing to PyPI
python -m build produces the actual distributable files in a dist/ folder; twine uploads them. TestPyPI is a full separate instance of the registry meant exactly for practice runs — publishing there costs nothing and can't be undone by mistake on the real thing.
Once a package name is taken on PyPI, it's taken — there's no reserving a name in advance, and abandoned/typo'd releases are very rarely removed. Check pypi.org/project/<name> before committing to a name. Also double-check what actually gets included in your built package before publishing — a stray .env file or credentials accidentally bundled into a public release is effectively permanent too, since older versions typically stay downloadable even after a fix.
Package Publishing: Go vs Kotlin vs Python
| Language | Approach |
|---|---|
| Go | No central registry or "publish" step at all — a module is just a public Git repository; go get fetches directly from the repo URL and its tags. |
| Kotlin | Maven Central (or an equivalent) — a central registry model much like PyPI, with a similar build-then-upload workflow via Gradle/Maven. |
| Python | PyPI — a central registry, built and uploaded via pyproject.toml + build + twine. |
Go's repository-is-the-package model is a genuinely different philosophy from the central-registry approach both Kotlin and Python share — no separate publishing step exists in Go at all, just tagging a commit in a public repo.
pyproject.toml
The modern standard config file — metadata, dependencies, and build backend in one place.
setuptools / Poetry
Two build-tool options — a minimal backend vs. an all-in-one workflow tool.
Editable install
pip install -e . — develop locally with changes taking effect immediately.
build + twine
python -m build creates distributables; twine upload publishes them to PyPI.
💻 Coding Challenges
Challenge 1: Write a Minimal pyproject.toml
Write a complete pyproject.toml for a package called text_utils, version 0.1.0, that depends on requests>=2.28.0, using setuptools as the build backend.
Goal: Practice writing the [build-system] and [project] tables correctly from scratch.
Challenge 2: Design a src/ Layout
Sketch out (as plain text, a folder tree — no code needed to run) the src/ layout for a package called calculator, containing two modules, basic.py and advanced.py, plus one test file for each.
Goal: Get comfortable with the standard src/ project structure this chapter recommends.
Challenge 3: Trace the Publishing Steps
List, in the correct order, every command-line step needed to go from a finished local package to it being live and installable from the real PyPI — including the safe practice step this chapter recommends before the real upload.
Goal: Solidify the full build-and-publish workflow as a sequence, not just isolated commands.
🎯 What's Next
Next chapter: Performance — profiling with cProfile, a Cython/PyPy overview, and common optimization patterns.