requirements.txt vs pyproject.toml
View requirements.txt as a sequence of shell commands for pip, including install instructions, --index-url flags, and file:// paths. pyproject.toml, on the other hand, is a structured, declarative configuration file that defines your project's metadata and build requirements in a standardized, machine-readable format.
The Setup
You are setting up a shared internal Python library. You want developers to easily install it while ensuring your build tools (like linters, compilers, and test runners) use the exact same declarative metadata.
What Does This Print?
# setup.py (Old way) or manual requirements.txt parsing inside setup.py
# This is fragile and breaks modern standards PEP 517 / PEP 518.
from setuptools import setup
with open("requirements.txt") as f:
# Reading requirements.txt inside setup.py is a classic anti-pattern
install_requires = f.read().splitlines()
setup(
name="my-legacy-package",
version="1.0.0",
install_requires=install_requires,
)
The Output
The execution fails because requirements.txt is an instruction file containing arbitrary command-line flags meant for the pip installer, not a list of declarative package dependencies. Parsing this file inside setup.py breaks standard package installation pipelines because abstract metadata fields cannot process these command-line flags. This creates fragile build configurations that crash during modern containerization and publishing steps. Standardizing on pyproject.toml separates installer-specific configuration from the explicit package metadata, ensuring compliance with modern build standards. It also prevents execution of arbitrary logic during the metadata extraction phase of a package, resolving a long-standing security and compatibility issue in the packaging ecosystem.
Why Python Does This
Historically, package metadata in Python was defined imperatively inside setup.py files. This required executing Python code simply to discover a package's name and dependencies. To fix this, the community introduced PEP 518 and PEP 621, which standardized pyproject.toml as a declarative configuration file. The build backend reads metadata statically without executing untrusted code. By defining project requirements inside pyproject.toml, you allow the installer to verify the dependency graph before setting up the build environment. This removes the need for custom parsing scripts and ensures interoperability across all packaging tools.
The Fix
# pyproject.toml — the modern, declarative alternative.
# Abstract dependencies only: package names and version constraints.
# No --index-url flags, no -r includes, no arbitrary pip instructions.
PYPROJECT_CONTENT = """
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "my-modern-package"
version = "1.0.0"
dependencies = [
"fastapi>=0.110.0",
"pydantic>=2.0"
]
"""
# Verify that abstract deps parse cleanly (no ValueError from pip flags):
from packaging.requirements import Requirement
for dep in ["fastapi>=0.110.0", "pydantic>=2.0"]:
req = Requirement(dep)
print(f"Valid dep: {req.name} {req.specifier}")
# Valid dep: fastapi >=0.110.0
# Valid dep: pydantic >=2.0
pyproject.toml explicitly defines project dependencies using a standardized TOML format (e.g., in the [project] or [tool.poetry.dependencies] sections), making them machine-readable and parsable by build backends and dependency managers without needing to interpret pip-specific command-line arguments.
How This Fails in Real Systems
A financial calculations service had an automated deployment pipeline that parsed requirements.txt into an installer. A developer added a -r dev-requirements.txt flag into the file. The setup helper crashed during compilation, blocking a critical security patch deployment for 6 hours because the build toolchain could not handle CLI-only arguments in metadata fields.
Key Takeaway
requirements.txt as a declarative list of package dependencies suitable for programmatic parsing, rather than an instruction script meant exclusively for the pip installer which can contain arbitrary CLI flags.