Skip to content

Why Should I Choose pyproject.toml over requirements.txt for managing dependencies?

requirements.txt lists packages to install. pyproject.tomlThe standard configuration file for Python projects. Declares the project name, version, dependencies, build system, and tool settings in one place. Learn more → defines a project: its dependenciesAn external package your project needs, listed in pyproject.toml so tools can install it automatically. , the Python version it requires, its metadata, and the tools that build and check it. That distinction shapes how dependencies get managed, how environments get reproduced, and how projects scale.

For new projects, use pyproject.toml. The rest of this page explains why, compares the two formats side by side, and covers the cases where requirements.txt still makes sense.

Compare the two formats

requirements.txt pyproject.toml
Format Plain text, one package per line TOML with standardized sections
Dependencies Flat list Structured [project.dependencies] table
Python version constraint None requires-python field
Dev/test separation Ad-hoc files (requirements-dev.txt) Dependency groups
Project metadata Lives elsewhere (setup.py, setup.cfg) Built in: name, version, authors, license
Tool configuration Separate config files per tool [tool.*] tables in the same file
LockfileA file that records the exact version of every installed package, so everyone working on the project gets identical installs. support Manual pinning or pip-compile uv lock / uv sync from the same file
Reproducibility Depends on discipline lockfileA file that records the exact version of every installed package, so everyone working on the project gets identical installs. tracks exact versions and hashes
Standardization Informal convention PEP 621 specification

Where requirements.txt still wins

The format is plain text, one package per line:

requests>=2.28.0
pandas~=2.0.0

Any tool that speaks pip can consume it. For a script that only needs requests, a one-line text file is the right tool.

Where requirements.txt breaks down

The workflow around requirements.txt demands manual coordination. A developer creates a virtual environmentAn isolated folder where Python installs packages for one project, keeping them separate from other projects and your system Python. Learn more → , activates it, installs packages with pip, then remembers to update the file by hand. Each step is a chance for the file and the environment to drift apart.

The format also has structural gaps:

  • No Python version constraint. Nothing stops installation into an incompatible interpreterThe program that reads and executes Python code. When you run "python3 hello.py", python3 is the interpreter. .
  • No dev/prod separation. Splitting dependencies requires multiple files (requirements-dev.txt, requirements-test.txt) with no standard convention.
  • No project metadata. Name, version, authors, and build configuration live elsewhere (setup.py, setup.cfg), spreading a project’s identity across files.

Tools like pip-tools solve some of these problems: pip-compile generates pinned requirements from abstract dependencies, adding lockfile-like reproducibility. pip-tools improves the workflow without fixing the format’s structural gaps: no metadata, no Python version constraints, no dev/test separation.

How pyproject.toml consolidates the workflow

PEP 621 standardized project metadata in pyproject.toml. Dependencies, Python version constraints, project metadata, and tool configuration all live in one file. Tools like flit, hatch, pdm, poetry, and uv (recommended) all read it.

With uv, creating a project and adding dependencies collapses to two commands:

uv init myproject && cd myproject
uv add requests pandas

uv add creates the virtual environmentAn isolated folder where Python installs packages for one project, keeping them separate from other projects and your system Python. Learn more → , installs packages, and records them in pyproject.toml. When another developer clones the repo, uv sync reproduces the environment from the lockfileA file that records the exact version of every installed package, so everyone working on the project gets identical installs. .

Replace scattered config with one file

pyproject.toml replaces the constellation of setup.py, setup.cfg, tox.ini, and requirements files with a single source of truth. The same file works whether the project is an application or an installable library. Dev dependenciesA package needed during development (testing, linting, formatting) that is not shipped to users of your project. Installed with the --dev flag. and dependency groups live alongside production dependencies, governed by a standard rather than ad-hoc file naming.

Bridge the gap with uv export

Switching to pyproject.toml does not mean abandoning every tool that reads requirements.txt. uv export generates a requirements.txt from pyproject.toml and its lockfileA file that records the exact version of every installed package, so everyone working on the project gets identical installs. :

uv export --format requirements-txt > requirements.txt

This covers deployment targets that expect requirements.txt: Docker images, cloud functions (AWS Lambda, Google Cloud Run), and CI caches. The source of truth stays in pyproject.toml; the requirements.txt becomes a generated artifact.

Tip

How to use uv in a Dockerfile and Build a production Docker image for a uv project show how to install directly from pyproject.toml inside Docker without generating requirements.txt at all.

Where does setup.py fit?

setup.py was the original way to declare project metadata and build instructions. PEP 621 moved that metadata into pyproject.toml, making setup.py unnecessary for new projects. Existing setup.py projects can migrate with How to migrate from setup.py to pyproject.toml.

Start new projects with pyproject.toml

Use pyproject.toml for new projects. Keep requirements.txt for single-file scripts where a full project structure is unnecessary, or as a generated deployment artifact via uv export.

Pick your next step

This handbook is free, independent, and ad-free. If it saved you time, consider sponsoring it on GitHub.

Last updated on