Packaging: pyproject.toml and the src Layout
About 20 minutes
Why this matters
A single .py file is easy to share. A real project, with several modules, tests and dependencies, needs a standard shape so anyone can install it with pip install . and tools know how to build it. Python’s standard answer is a file called pyproject.toml, plus a tidy folder layout.
The idea
The common src layout:
text-tools/
├── pyproject.toml
├── README.md
├── src/
│ └── text_tools/
│ └── __init__.py
└── tests/
└── test_text_tools.py
Putting the code under src/ stops tests from accidentally importing the folder instead of the installed package. Note the two spellings: the project (distribution) is text-tools, but the importable package must be a valid Python name, text_tools.
pyproject.toml is written in TOML, a simple settings format: key = value lines grouped under [table] headings. Python 3.11 can read it with the tomllib module.
[build-system]
requires = ["setuptools>=68"]
build-backend = "setuptools.build_meta"
[project]
name = "text-tools"
version = "0.1.0"
description = "Little helpers for text."
readme = "README.md"
requires-python = ">=3.11"
dependencies = ["requests>=2"]
[tool.setuptools.packages.find]
where = ["src"]
[build-system]says which tool builds the package.[project]is the metadata: name, version, what it needs.dependencieslists the packages yours needs. Add version limits only when you must.
On your own computer, pip install -e . installs the project in “editable” mode while you work, and python -m build makes the files you could upload to an index (you will do that in the capstone).
Expose the version in one place the code can also read: __version__ = "0.1.0" in __init__.py.
Try it
Run the example with tomllib.loads(text) and print data["project"]["name"] to see that TOML becomes a normal dictionary.
Your turn
Write two functions:
pyproject_text(name, version, description, dependencies)returns the text of apyproject.tomllike the one above. It raisesValueErrorifnameis not made of letters, digits,.,_or-(starting with a letter or digit). Quote text values withjson.dumps, which produces valid TOML strings.create_project(root, name, version, description, dependencies)creates the folder layout underrootwith four files:pyproject.toml,README.md,src/<package>/__init__.py(a docstring and__version__) andtests/test_<package>.py(a test that checks__version__). It raisesFileExistsErrorifrootalready has apyproject.toml, and returns the sorted list of created file paths, relative toroot, written with/.
Recap
pyproject.tomlholds the build settings and the project metadata.- The
srclayout keeps importable code apart from tests. - The project name may contain
-; the package name uses_. tomllibreads TOML;pip install -e .andpython -m builduse it.
Common mistakes
- Using the project name as the import name when it contains hyphens.
- Forgetting
__init__.py, so the package is not found. - Hard-coding the version in several places so they drift apart.