Dependency declarations
Navigate a project and describe how its interpreter, dependencies and configuration are selected.
THE IDEA
Declare what you want, lock what you got
Two different things are both called "dependencies". The ones you choose (for example requests) and the full set the installer resolved, which also includes everything those packages need. A reproducible project records both: your intent, and the exact result.
requests>=2.31,<3
Says which versions are acceptable. Many versions qualify, and which one you get depends on when you install.
requests==2.31.0 plus every dependency, each pinned.
Says what was actually installed, so another machine can get the same set.
With plain pip, pip freeze > requirements.lock records everything installed in the active environment. Tools such as uv do this for you: uv keeps your direct dependencies in pyproject.toml and writes the resolved set to uv.lock.
Why a range is not enough
>=1,<24 versions allowed. A fresh install picks 1.9.1.
Records pkg==1.4.2. Every install gets exactly that, today and next year.
Toggle the new release: with a range, tomorrow's install can differ from today's. The lock does not move until you deliberately update it.
What reproducible means
A run is determined by more than the code: the source revision, the interpreter, the resolved dependencies, the input data and the configuration. A lock file pins only the dependency part. It does not preserve your datasets or your settings.
Predict first. Does requests>=1,<2 identify one exact installation?
Chapter context
A terminal runs commands in a current working directory. An environment makes the program's interpreter, dependencies and configuration explicit so a successful run can be repeated elsewhere.
TRY IT YOURSELF
The package index here is simulated: names are real, version numbers are illustrative. The goal is how pinning, locking and ranges behave, not which release is newest.
- In an activated .venv, install exactly requests 2.31.0
- Record everything installed, dependencies included, into requirements.lock
- Create a second environment .venv2 and rebuild it from the lock file
- Create .venv3 and install requests with a range instead (>=2.31). Quote the spec. See which version you get
requirements.lock
(not created yet)
Stuck, or something not working? You can come back to it any time.
CHECK YOUR UNDERSTANDING
Does a range such as >=1,<2 identify one exact installation?
Chapter reference: math, worked example & code
This section belongs to the whole chapter, so it is the same on every lesson in it and may cover a different lesson's topic. The code for the experiment you just ran is inside the experiment itself.
Math and notation
A reproducible run can be described as (source revision, interpreter, dependency resolution, input data, configuration). Equal source code alone is insufficient. For pipelines, compare the records emitted at each stage rather than assuming every command preserves row counts.
Worked example
A project processes 12 lines, filters to 5 matching lines, then counts them. The final count is 5, not 12. In a path puzzle, joining /work/project with data and train.csv identifies the input without changing the real filesystem.
Reference code
The following example uses the Python standard library and does not call a remote service.
from pathlib import PurePosixPath
path = PurePosixPath('/work/project') / 'data' / 'train.csv'
assert str(path) == '/work/project/data/train.csv'
lines = ['keep' if i < 5 else 'drop' for i in range(12)]
assert sum(line == 'keep' for line in lines) == 5
Python examples are provided for study and local execution; this page does not execute Python.
Common mistakes
A virtual environment is not a security sandbox. A lockfile does not preserve external input datasets. Ignoring a secret after committing it does not undo exposure.