pyproject.toml and project metadata
Overview
`pyproject.toml` is the modern standard configuration file for Python projects, introduced in PEP 518. It replaces older files like `setup.py`, `requirements.txt`, and `setup.cfg` by providing a single declarative file. Think of it as Python's equivalent to Node's `package.json`. It centralizes three core areas of your project: - Metadata: Defines the package name, version, and authors inside the `[project]` table. - Dependencies: Lists the external packages required to run your code. - Tool configurations: Stores settings for formatting and testing (like `black` or `pytest`) inside `[tool.*]` tables. import tomllib config = tomllib.loads("[tool.poetry]\nname = 'app'") # Raises KeyError! Standard parsers expect [project] # name = config["project"]["name"] Trap: Older tools historically ignored the standard `[project]` table and stored metadata in custom sections like `[tool.poetry]`. This transition to standardized metadata is covered fully in poetry.
Historically, Python tooling was fragmented, with each tool requiring its own config file and `setup.py` allowing arbitrary code execution during installation. `pyproject.toml` provides a safe, standard, declarative way to configure everything in one place.
Where used: Modern Python libraries, Application dependency management, Tool configuration (`black`, `ruff`, `pytest`)
Why learn this
- Standardize how your Python project is built and packaged
- Configure code quality tools (linters, formatters) in a single place
- Move away from legacy, insecure `setup.py` scripts
Code walkthrough
import tomllib
pyproject_toml = '''
[build-system]
requires = ['setuptools>=61.0']
build-backend = 'setuptools.build_meta'
[project]
name = 'example_pkg'
version = '0.1.0'
'''
parsed = tomllib.loads(pyproject_toml)
print(parsed['project']['name'])
Focus: parsed = tomllib.loads(pyproject_toml)
Aha moment
import tomllib
toml_str = '''
[tool.pytest.ini_options]
addopts = '-ra -q'
[tool.ruff]
line-length = 120
'''
config = tomllib.loads(toml_str)
print(list(config.keys()))
Prediction: What are the top-level keys in the parsed configuration?
Common guess: ['tool.pytest.ini_options', 'tool.ruff']
TOML dot-notation like `[tool.ruff]` automatically creates nested dictionaries. The only top-level key is `'tool'`, which contains `'pytest'` and `'ruff'`.
Common mistakes
- Using `setup.py` instead of `pyproject.toml`: Many older tutorials still teach `setup.py`. For new projects, you should almost always use `pyproject.toml` as it is the official modern standard.
- Syntax errors in TOML: TOML is not JSON or YAML. Quotes around strings are required, and nested tables (like `[tool.pytest.ini_options]`) must be formatted correctly.
Glossary
- linters
- Tools that automatically scan your code to find stylistic errors, bugs, or suspicious constructs. Example: `flake8`.
- arbitrary code execution
- When a program allows a user or external script to run any command or code they choose, often posing a security risk. Example: `os.system('rm -rf /')`.
Recall questions
- What is the purpose of the `pyproject.toml` file?
- Which older Python configuration files does `pyproject.toml` aim to replace?
- Why might a modern parser fail to find the project name in an older `pyproject.toml` file generated by tools like Poetry?
Understanding checks
A team member asks why they can't just put logic like `import os; version = os.getenv('VERSION')` inside `pyproject.toml` like they used to in `setup.py`. What is the fundamental difference?
`pyproject.toml` is purely declarative data, whereas `setup.py` was an executable script.
This declarative nature makes building and analyzing packages much safer and more reliable, as tools don't have to execute arbitrary Python code just to read metadata.
What does this code print when parsing the TOML configuration?
88
The TOML table `[tool.black]` is parsed into nested dictionaries in Python, so the key `line-length` is accessed inside the `black` dict, which is inside the `tool` dict.
Practice tasks
Access a nested TOML configuration
Given this script that parses a `pyproject.toml` string, modify the `print` statement to extract and print the minimum `pytest` requirement from the optional dependencies list.
Challenge
Parse and validate project metadata
Write a script that parses the provided TOML string and prints `True` if the project name is 'my-tool' AND it has exactly 2 dependencies listed, otherwise print `False`.