Your project's dependency list is abstract: names and version limits that say what the code minimally needs. A requirements file is concrete: it can name exact versions and even the index to fetch from. Libraries need the first, deployed applications also need the second.
The distinction in the packaging guide
The Python Packaging User Guide calls the install requirements of a package a listing of abstract requirements, just names and version restrictions, with no source named. Where packages come from is decided at install time with pip options.
Requirements files do the opposite. They list pip install arguments and often include options such as an index URL or a link location, which the guide says make requirements concrete.
Comparison
| Project dependency list | requirements.txt | |
|---|---|---|
| Scope | One project | Often a whole environment |
| Strictness | Minimal, avoid exact pins | Often an exhaustive list of pinned versions |
| Read when | pip installs the project, automatically | Someone runs install with the requirements file |
| Names a source | No | Can |
The guide page describes the older setup script keyword, so check your own tool's documentation for how modern project files behave. The idea carries over, but the details are the tool's to state.
The common pattern
- Write loose ranges in the project file, tested lower bounds, and upper bounds only where you know of a break.
- Generate a fully pinned file or lock from those ranges for deployment.
- Install production from the pinned file, ideally with hashes.
- Rebuild the pinned file on purpose, in a pull request that someone reviews.
If you only keep the loose list, every deploy resolves afresh and can differ. If you only keep the pinned file, your library tells other people exactly one version of everything, which breaks their builds.
Why the concrete file is a security control
An exact pin means a new upload under the same name cannot enter your build silently. Add hashes and a swapped file under the same version fails as well. The loose list gives you neither. See pinning Python dependencies: requirements.txt vs Poetry and pip-tools lock files for the tool choices.
A small worked example
Say your service imports a web library and a database driver. Your project file says the web library at a tested minimum and the driver at a compatible range. A lock tool resolves those and the packages they need, perhaps thirty in total, into one pinned file with hashes. Your Dockerfile installs from that pinned file only. When someone adds a package, the project file changes by one line and the pinned file by many, and both appear in the same pull request for review.
Quick check
Can I use requirements.txt instead of project metadata for a library?
Not well. A published library needs abstract requirements that installers read automatically.
Should a deployed app have loose ranges only?
No. It also needs a pinned file, so each deploy installs the same set.
Safeguard's SBOM and dependency scanning reads either file, and a free scan takes your manifest.
Sources
Last reviewed: 19 October 2026.