Your pyproject.toml asks for httpx>=0.27. Say a fresh install today
picks 0.28.1. Next month a new release comes out, a fresh install in CI picks it,
and a test fails although nobody changed your code. The range allows both
versions, so both installs were correct.
A lockfile fixes this. It records the exact version of every package an install used, including the dependencies of your dependencies. Later installs read the lockfile instead of choosing again, so CI, your laptop and production get the same versions.
Install from the lockfile everywhere
| File or state | What it describes |
|---|---|
pyproject.toml |
Your direct dependencies and the ranges they may use |
| Lockfile | The exact versions chosen for every package, and where each comes from |
| Installed environment | The packages actually present when your program runs |
A lockfile helps only if every install uses it. Check that CI and deployment both install from it, and that neither one chooses versions again.
An application deploys the locked versions it tested. A library is different. It can keep a lockfile so its own tests are stable, but its users install it from its ranges and never see the lockfile. So a library also needs tests across its ranges, for example against the oldest and the newest versions it allows.
uv.lock covers every platform
uv.lock is uv's lockfile. One file covers every Python version and
platform the project supports. Where they need different packages, the
lockfile records each choice with a marker, such as
sys_platform == 'win32'. uv installs the choice that matches the machine. So a Windows install and a
Linux install can get different packages from the same file.
Commit uv.lock, and change it only through uv. uv lock updates it after
you edit pyproject.toml, and uv lock --upgrade-package httpx moves one
package to its newest allowed version. Review a lockfile change in a pull
request as you would review code. In CI, run uv sync --locked, as in the
uv article, so the job fails when the lockfile is out of date. See
uv's resolution documentation.
pylock.toml is the standard lockfile format from PEP 751, which different
tools can share. Its name is pylock.toml, or a named form such as
pylock.dev.toml. Check that both the tool that writes it and the tool that
installs from it support the format. uv export --format pylock.toml
writes one from uv.lock. pip lock writes one too. pip marks that
command as experimental, and its output is valid only for the Python
version and platform it ran on. See the
pylock specification.
Check your understanding
Why is the output of pip freeze not a lockfile?
Show answer
Hide answer
pip freeze lists every package in the current environment. It
does not read your project's dependencies, so it can include packages that
someone installed by hand and leave out packages that another platform
needs. By default it records no hashes either. Use it to describe one
environment, not to reproduce your project's install somewhere else. See
the freeze documentation.
Hashes check the bytes you download
A lockfile can store a hash of each package file. The installer computes the hash of the file it downloads and rejects the file if the two differ. That catches a file that changed after you locked it. It cannot tell you that the file you locked was safe, so review new versions when the lockfile changes.
pip checks hashes from a requirements file:
python3.14 -m pip install --require-hashes -r requirements.txt
In this mode, pin every requirement with ==, and give every package a
hash, including the dependencies of your dependencies. One
--hash option in the file turns the mode on by itself, but the flag makes
it explicit in CI. Generate the file with a tool, such as
uv export --format requirements.txt, which includes hashes by default.
See pip's secure installs guide.
Check your understanding Production runs a dependency version that your tests never saw. What should you check? Show answer Hide answer
Follow the install commands through CI and deployment. Did one of them update the lockfile, or skip it? Did they choose different extras, groups, Python versions or platforms? Did a later step install more packages on top? A requirements file exported from the lockfile is fine, so find out which command wrote each file, whatever its name. Then compare the package versions installed in each environment.