pyproject.toml: describe the project
One file tells every tool what your project is: its name and version, the Python it needs, the packages it depends on, and the commands it installs. tomllib reads that file into ordinary dictionaries.
- Recognise TOML's tables, keys, quoted strings and arrays, and what the
[project]table holds - Load TOML text with
tomllib.loads, or a file opened in binary mode withtomllib.load - Read optional tables safely and keep your own tool's settings under
[tool.<name>]
pip, build tools, linters and test runners all need to know something about your project: what it's called, which version this is, which Python it supports and which packages it needs. Since 2021 the standard place for all of it is `pyproject.toml` in the project's top folder.
The file is written in TOML. A line [project] starts a table, a group of settings; [project.scripts] is a table inside project. Each setting is key = value. Text is always in quotes, numbers and true/false aren't, and ["a", "b"] is an array. Python's tomllib module (3.11 and later) turns the whole file into dictionaries, lists, strings, numbers and booleans.
Run each example to see what it prints. Running examples doesn't count toward progress.
import tomllib pyproject = """ [project] name = "market-reports" version = "1.4.2" requires-python = ">=3.11" dependencies = [ "requests>=2.31", "pandas>=2.2,<3", ] [project.optional-dependencies] charts = ["matplotlib>=3.8"] """ data = tomllib.loads(pyproject) project = data["project"] print(project["name"], project["version"]) print(project["requires-python"]) print(project["dependencies"]) print(project["optional-dependencies"]["charts"])
TOML is stricter than it looks. A version number is text, so it needs quotes; without them the parser stops and says where.
import tomllib text = """ [project] name = "market-reports" version = 1.4.2 """ data = tomllib.loads(text) print(data)
Real files live on disk. tomllib.load reads an open file, and it needs the file opened in binary mode, "rb", because TOML defines its own text encoding (UTF-8).
Not every project has every table, so read optional ones with get and a default. Tools keep their own settings in [tool.<name>] tables ([tool.pytest.ini_options], [tool.ruff]): your own scripts can do the same instead of inventing another settings file.
import tomllib from pathlib import Path Path("pyproject.toml").write_text(""" [project] name = "market-reports" version = "1.4.2" [project.scripts] market-report = "market_reports.cli:main" [tool.market-reports] currency = "USD" top_n = 5 """, encoding="utf-8") def load_pyproject(path): with open(path, "rb") as f: return tomllib.load(f) def commands(data): scripts = data["project"].get("scripts", {}) lines = [] for command, target in scripts.items(): module, function = target.split(":") lines.append(f"{command}: runs {function}() from {module}") return lines data = load_pyproject("pyproject.toml") print(commands(data)) print(data["tool"]["market-reports"]) print(commands({"project": {"name": "invoice-tools"}}))
When to use it: whenever a program needs facts about the project (its version for a --version flag, its settings), read them from pyproject.toml instead of copying them into code. Mistakes to avoid: unquoted text values; opening the file without "rb"; and assuming an optional table such as [project.scripts] or [tool.x] exists.
pyproject.tomldescribes the project:[project]has name, version, requires-python, dependencies and more.tomllib.loads(text)ortomllib.load(open(path, "rb"))gives dictionaries whose values keep their TOML types.- Read optional tables with
getand a default; keep your own settings in[tool.<name>]. - Next: what a dependency line like
pandas>=2.2,<3means, and how to check what is actually installed.