TRENDING
A mountain gorilla looks at the camera from green forest undergrowth
October 28, 2026
How to Compress Time-Series Metrics in Python With Gorilla Delta-of-Delta and XOR Encoding
A bronze-framed hourglass with white sand beside a small Ferris wheel ornament on a wooden table against a teal wall
October 28, 2026
NIST’s Final Post-Quantum Transition Report Puts Every Quantum-Vulnerable Public-Key Scheme on One 2030 and 2035 Clock, and Three Federal Documents Word the Deadline Three Ways
Six weathered stone columns of the Temple of Apollo at Delphi, Greece, standing on a stone platform above a valley of olive groves
October 28, 2026
Honeypots Recorded Probes Against Oracle E-Business Suite CVE-2026-101767 Seven Days After Its Patch, and the Finder’s Write-Up Is Dated Patch Day
Vintage silver stopwatch with a chain on a black background, its dial showing seconds from 5 to 60
October 28, 2026
How to Find a Slow Query in PostgreSQL 18 With pg_stat_statements, Read Its EXPLAIN ANALYZE Plan, and Fix It With the Right Index
A wooden Tower of Hanoi puzzle on a market table, with three pegs and stacked discs of different sizes
October 28, 2026
Two Labs’ Claimed Refutations of the Exponential Time Hypothesis Turn Verification Into a Statement-Reading Problem, and Their Lean Theorems Promise Only That a Constant Exists
28 Oct 2026
SXZ.io SXZ.io
  • Home
Search the Site
Popular Searches:
Technology Amazon AI
Recent Posts
A flock of snow geese in flight, seen as dark silhouettes against an orange and red sunset sky with layered clouds
How to Run Zero-Downtime Schema Migrations on PostgreSQL 18 and Block Unsafe ALTER TABLE Statements With a Python Lint Gate
October 28, 2026
An old green steel truss bridge resting on temporary supports beside a new arch bridge under completion on a river in Portland, Oregon
The Angular 2 Rewrite Turns a Migration Lesson Into a Security Bill: AngularJS Still Draws 533,480 Downloads a Week, and 10 Advisories Have No Open-Source Fix
October 28, 2026
Hundreds of yellow rubber ducks floating in a river enclosure during a duck race
WordPress 7.1.4 Closes an Unauthenticated Race in Its Own Update Routine, but 19 Older Branches Had No Backport Hours After Release
October 28, 2026
SXZ.io SXZ.io
  • Home

Categories

Articles 270 Posts
News 273 Posts
Learning Hub 243 Posts
Home/Learning Hub/How to Lock Python Dependencies With pip’s pylock.toml (PEP 751)
Learning Hub

How to Lock Python Dependencies With pip’s pylock.toml (PEP 751)

A hands-on tutorial that generates a PEP 751 pylock.toml file with pip's experimental lock command, explains its structure and hash pinning, and proves it works across tools by installing the same...

July 27, 2026 10 Min Read
72

Every Python project eventually hits the same problem: pip install -r requirements.txt can install different package versions today than it did last month, even though the file itself never changed. A line like requests>=2.30 is a promise about a minimum version, not a guarantee of an exact one, so two developers running the same command on two different days can end up with two different environments. That gap between “what the file says” and “what actually got installed” is where a lock file comes in: a separate, machine-generated record of the exact versions (and exact files) that were installed, so the next install reproduces precisely the same environment.

Table Of Content

  • What Problem Does a Lock File Actually Solve?
  • Prerequisites
  • Step 1: Check Your pip Version, and Upgrade It if Needed
  • Step 2: Create a Scratch Project to Lock
  • Step 3: Generate Your First pylock.toml With pip lock
  • Step 4: Read and Understand the pylock.toml Structure
  • Why the Hash Matters
  • Step 5: Reproduce the Exact Environment From the Lock File
  • Verify the versions match exactly
  • Step 6: Prove pylock.toml Is Tool-Agnostic by Installing It With uv
  • How pylock.toml Differs From requirements.txt
  • Common Mistakes and Gotchas
  • How to Confirm This All Worked End to End
  • Next Steps

Python has lacked a standard, tool-agnostic lock file format for most of its history. Tools like Poetry, PDM, and uv each built their own, but a lock file from one tool was meaningless to another. PEP 751, written by Python packaging contributor Brett Cannon and accepted as a Final standard in March 2025, defines a shared format for this: pylock.toml. pip added an experimental implementation, and this tutorial builds one from scratch: generating a pylock.toml with pip, reading what is actually inside it, reproducing an environment from it, and proving the “tool-agnostic” part by installing that same file with a completely different tool, uv. Every command below was run against a real installation on Ubuntu 26.04 LTS with Python 3.11.15, pip 26.1.2, and uv 0.11.16 while writing this tutorial, and the file format itself was cross-checked against the official PEP 751 specification.

What Problem Does a Lock File Actually Solve?

A requirements.txt file mixes two different jobs into one document: it declares what your project depends on, and, if you pin exact versions, it can also serve as a rough record of what got installed. Loosely pinned requirements make upgrades easy but installs unpredictable. Tightly pinned requirements make installs predictable but say nothing about a package’s dependencies, so a sub-dependency can still drift. Neither variant records which specific file (wheel or source archive) was downloaded, or a hash to prove that file has not been tampered with.

The PEP 751 specification itself frames the motivation partly around security: it points to “concerns around the lack of secure defaults in the face of supply chain attacks” as one of the reasons a standard format was worth building, alongside the fact that at least five separate tools (PDM, plain pip freeze, pip-tools, Poetry, and uv) had already independently invented their own answers to the same problem. A pylock.toml file is designed to close that gap: it records every package, its exact version, the exact file that should be downloaded, and a cryptographic hash of that file, in a format any compliant tool can read without needing to re-run dependency resolution. That last part matters for speed and security alike: an installer reading a lock file does not need to contact a package index and work out which versions satisfy which constraints, it just installs exactly what the file says.

Prerequisites

  • A Linux, macOS, or Windows machine with a terminal. This tutorial was verified on Ubuntu 26.04 LTS, but nothing here is Linux-specific.
  • Python 3.11 or newer already installed, with access to python3 -m venv for creating virtual environments.
  • Comfort running basic shell commands (mkdir, cd) and editing a text file. No prior experience with lock files, TOML, or Python packaging internals is assumed; every concept is explained the first time it comes up.
  • An internet connection, since pip and uv both need to download real packages from PyPI to demonstrate this.
  • Optional but recommended for Step 6: the uv package manager installed, to prove the lock file works outside of pip. Everything through Step 5 only needs pip.

Step 1: Check Your pip Version, and Upgrade It if Needed

The pip lock command is brand new and experimental, so it is only available in recent pip releases. Check what you currently have:

python3 -m pip --version

If that reports something older than pip 25, or if you try the lock command later in this tutorial and see ERROR: unknown command "lock", upgrade pip first. That exact error is not hypothetical: testing this tutorial started on a machine with pip 24.0, which has no knowledge of pip lock at all. Upgrading resolved it immediately:

python3 -m pip install --upgrade pip
python3 -m pip --version

Expected output: something like pip 26.1.2 from /path/to/site-packages/pip (python 3.11). The exact version number will keep climbing after this tutorial is published, and that is fine; what matters is that it reports 25 or newer.

Step 2: Create a Scratch Project to Lock

Work in an isolated virtual environment so nothing here touches your system Python or any other project:

# Context: any working directory, Ubuntu 26.04 LTS or equivalent
mkdir pylock-tutorial && cd pylock-tutorial
python3 -m venv .venv
source .venv/bin/activate
pip install --upgrade pip

Now write a small, deliberately loose requirements file, the kind almost every Python project starts with:

echo "requests==2.32.3" > requirements.txt
cat requirements.txt

This pins the top-level package, requests, but says nothing about its own dependencies (urllib3, certifi, idna, and charset-normalizer), which is exactly the gap a lock file exists to close.

Step 3: Generate Your First pylock.toml With pip lock

Run the new command against the requirements file:

pip lock -r requirements.txt -o pylock.toml

Expected output starts with a warning, then a normal-looking dependency resolution:

WARNING: pip lock is currently an experimental command. It may be removed/changed in a future release without prior warning.
Collecting requests==2.32.3 (from -r requirements.txt (line 1))
Collecting charset-normalizer<4,>=2 (from requests==2.32.3->-r requirements.txt (line 1))
Collecting idna<4,>=2.5 (from requests==2.32.3->-r requirements.txt (line 1))
Collecting urllib3<3,>=1.21.1 (from requests==2.32.3->-r requirements.txt (line 1))
Collecting certifi>=2017.4.17 (from requests==2.32.3->-r requirements.txt (line 1))

Notice pip resolved the full dependency tree, not just the one package you listed. That resolution work happens once, at lock time; anyone installing from the resulting file later skips it entirely. Confirm the file exists before moving on:

ls -la pylock.toml

If instead you saw ERROR: unknown command "lock", go back to Step 1 and upgrade pip; this command genuinely does not exist before pip added experimental support for it.

Step 4: Read and Understand the pylock.toml Structure

Open the file pip just generated. It is plain TOML, readable without any special tooling:

cat pylock.toml

The top of the file has two lines that apply to the whole document:

lock-version = "1.0"
created-by = "pip"

lock-version identifies which version of the PEP 751 format this file follows, so a future installer can tell whether it understands the file before trying to use it. created-by records which tool generated it; this field is part of why the format is tool-agnostic, an installer does not need to know or care that pip specifically wrote this file to be able to consume it, but the provenance is preserved anyway.

Below that, every dependency gets its own [[packages]] block. Here is the entry for certifi, straight from a real generated file (the download URL is shortened below for readability; the real one is a single long line):

[[packages]]
name = "certifi"
version = "2026.7.22"

[[packages.wheels]]
name = "certifi-2026.7.22-py3-none-any.whl"
url = "https://files.pythonhosted.org/packages/0b/a7/.../certifi-2026.7.22-py3-none-any.whl"

[packages.wheels.hashes]
sha256 = "62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775"

Why the Hash Matters

The sha256 value under packages.wheels.hashes is a cryptographic fingerprint of that exact wheel file. When you later install from this lock file, pip downloads the wheel from the recorded URL, hashes it, and refuses to install it if the hash does not match. That is a real defense against a specific, documented category of supply chain attack: a compromised or malicious index serving a different file under the same package name and version than the one that was actually reviewed and locked. A loosely pinned requirements.txt offers no such check; a fully pinned one with exact versions still does not, unless you separately maintain a hashes file yourself. In pylock.toml, the hash is not optional extra work, it is generated automatically every time.

Step 5: Reproduce the Exact Environment From the Lock File

The real test of a lock file is whether a second, completely fresh environment installs identically from it. Create one:

cd ..
python3 -m venv .venv-reproduce
.venv-reproduce/bin/pip install --upgrade pip
.venv-reproduce/bin/pip install -r pylock-tutorial/pylock.toml

Expected output again opens with an experimental-feature warning, this time for reading rather than generating:

WARNING: Using pylock.toml as a requirements source is an experimental feature. It may be removed/changed in a future release without prior warning.
Collecting certifi==2026.7.22 (from pylock.toml)
Collecting charset-normalizer==3.4.9 (from pylock.toml)
Collecting idna==3.18 (from pylock.toml)
Collecting requests==2.32.3 (from pylock.toml)
Collecting urllib3==2.7.0 (from pylock.toml)

Successfully installed certifi-2026.7.22 charset-normalizer-3.4.9 idna-3.18 requests-2.32.3 urllib3-2.7.0

Verify the versions match exactly

.venv-reproduce/bin/pip freeze

Compare that output against the versions recorded in pylock.toml from Step 4. Every version should match precisely, not “close enough”: that exact match, reproduced in a brand new environment with no other input than the lock file, is the entire point.

Step 6: Prove pylock.toml Is Tool-Agnostic by Installing It With uv

This is the step that actually tests the “tool-agnostic” claim in PEP 751’s own name: a file pip generated, read by a completely different tool with its own independent implementation. If you have uv installed, try it:

cd pylock-tutorial
uv venv .venv-uv
uv pip sync pylock.toml --python .venv-uv/bin/python3

Expected output:

warning: The `--pylock` option is experimental and may change without warning. Pass `--preview-features pylock` to disable this warning.
Prepared 5 packages in 93ms
Installed 5 packages in 10ms
 + certifi==2026.7.22
 + charset-normalizer==3.4.9
 + idna==3.18
 + requests==2.32.3
 + urllib3==2.7.0

uv correctly recognized and parsed the file as a pylock.toml, without being told which tool generated it, and installed the exact same five packages at the exact same versions. Neither tool needed to know anything about the other; the file format is the entire contract between them. That is a meaningfully different guarantee than requirements.txt ever offered, where subtle differences in how different tools parsed edge cases could produce different results from the “same” file.

How pylock.toml Differs From requirements.txt

  • Structure. requirements.txt is loosely defined line-oriented text that grew organically and that different tools parse with slightly different edge-case behavior. pylock.toml is standard TOML with a specification that defines every field.
  • Hashes by default. pip lock records a hash for every package automatically. Hash-checking with requirements.txt exists, but it is opt-in and easy to skip.
  • No resolution needed at install time. A pylock.toml file already contains the fully resolved dependency tree. An installer reading it does not need to contact a package index and work out which versions satisfy which constraints; it can go straight to downloading and verifying the exact files listed.
  • Scoped validity. This is a real limitation, stated directly in pip’s own pip lock --help output: “The generated lock file is only guaranteed to be valid for the current python version and platform.” A lock file generated on Linux with Python 3.11 is not automatically safe to reuse on Windows or with Python 3.9; PEP 751 does define optional fields for multi-environment locking, but a plain pip lock run like the one in this tutorial only captures the environment it ran on.

Common Mistakes and Gotchas

  • Assuming any recent pip supports this. The plain “unknown command” error from Step 1 is exactly what you get on pip 24.0 and earlier. There is no flag or workaround; the command simply does not exist yet in older releases.
  • Upgrading pip just enough to unlock pip lock, but not enough to read the result. According to pip’s own changelog, experimental support for generating a lock file and experimental support for installing from one (pip install -r pylock.toml) were added in separate releases, with read support landing later. If pip lock works but pip install -r pylock.toml fails with a format error, upgrade pip again rather than assuming the file itself is broken.
  • Treating this as stable. Both warnings you saw in this tutorial say plainly that the feature “may be removed/changed in a future release without prior warning.” Pin your pip and uv versions in CI if you depend on this workflow, so an upstream change does not silently break a pipeline.
  • Forgetting uv needs an explicit target environment. uv pip sync does not always guess which environment you mean; passing --python pointing at the interpreter inside your target virtual environment, as shown in Step 6, avoids installing into the wrong place or failing outright.
  • Confusing this with a tool’s native lock file. If your project already uses Poetry’s poetry.lock, PDM’s pdm.lock, or uv’s own uv.lock, those remain that tool’s primary workflow. pylock.toml is an interchange format, most useful for exporting a lock file so a different tool, or a plain pip install, can consume it, not necessarily a wholesale replacement for a tool’s native format.

How to Confirm This All Worked End to End

  1. Regenerate the lock file from the same requirements with pip lock -r requirements.txt -o pylock-check.toml and diff it against the original with diff pylock.toml pylock-check.toml. Identical output confirms the lock is deterministic for a given input and environment, not random each run.
  2. Run pip freeze inside the fresh environment from Step 5 and confirm every version matches the version field for that package inside pylock.toml, not just “close” versions.
  3. Run the uv sync from Step 6 a second time and confirm it reports the packages are already installed and does no extra work, proving the two tools agree on what “correct” looks like.
  4. Deliberately edit one version number inside pylock.toml to something that does not exist on PyPI, then try pip install -r pylock.toml again and confirm it fails loudly instead of silently installing something else. A lock file that fails safe on tampering is doing its job.

Next Steps

Everything above locked a plain requirements.txt, but pip lock also accepts a local project directory containing a pyproject.toml directly, for example pip lock . run from inside a project whose pyproject.toml declares its dependencies in the standard PEP 621 [project] table, which resolves and locks that project’s declared dependencies the same way. Since this whole workflow is still explicitly experimental, keep an eye on the pip changelog as it evolves toward stabilization. If the hash-verification angle in Step 4 was interesting, the natural next stop is supply chain integrity for the artifacts you ship, not just the ones you install; this tutorial on generating and signing an SBOM with Syft and Cosign covers the same trust-but-verify idea one layer up, at the container image level instead of the individual package level.

Tags:

Developer ToolingpipPythonSoftware Supply Chain

Share

A programmer reviewing Java code with unit tests on a desktop computer screen
Previous Post

The New Agency AI Stack Turns Code Review Into the Real Production Gate

Colorful macro photograph of an AMD EPYC server processors silicon die on a white background
Next Post

Canonical Frees Confidential Computing From Ubuntu’s Two-Year LTS Wait

No Comment! Be the first one.

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Latest
28 Oct
How to Run Zero-Downtime Schema Migrations on PostgreSQL 18 and Block Unsafe ALTER TABLE Statements With a Python Lint Gate
28 Oct
The Angular 2 Rewrite Turns a Migration Lesson Into a Security Bill: AngularJS Still Draws 533,480 Downloads a Week, and 10 Advisories Have No Open-Source Fix
Trending
October 28, 2026
How to Run Zero-Downtime Schema Migrations on PostgreSQL 18 and Block Unsafe ALTER TABLE Statements With a Python Lint Gate
October 28, 2026
The Angular 2 Rewrite Turns a Migration Lesson Into a Security Bill: AngularJS Still Draws 533,480 Downloads a Week, and 10 Advisories Have No Open-Source Fix
October 28, 2026
WordPress 7.1.4 Closes an Unauthenticated Race in Its Own Update Routine, but 19 Older Branches Had No Backport Hours After Release
October 28, 2026
How to Compress Time-Series Metrics in Python With Gorilla Delta-of-Delta and XOR Encoding
October 28, 2026
NIST’s Final Post-Quantum Transition Report Puts Every Quantum-Vulnerable Public-Key Scheme on One 2030 and 2035 Clock, and Three Federal Documents Word the Deadline Three Ways
October 28, 2026
Honeypots Recorded Probes Against Oracle E-Business Suite CVE-2026-101767 Seven Days After Its Patch, and the Finder’s Write-Up Is Dated Patch Day

Related Posts

A laptop wrapped in a chain and padlock, illustrating least-privilege controls for AI agents.
Learning Hub

How to Secure Tool-Using AI Agents Before They Touch Production

June 8, 2026
Colorful sticky notes arranged on an office wall, symbolizing governance checklists and planning.
Learning Hub

AI Governance for Agentic Apps: A Practical Checklist for Builders

June 8, 2026
A technician connects green fiber optic cables at a data center, representing a private production inference endpoint.
Learning Hub

How to Deploy a Fine-Tuned LLM Behind a Private Production Inference Endpoint

June 8, 2026
Narrow aisle behind black supercomputer racks in a data center
Learning Hub

Kubernetes SELinux Volume Labeling: What Cluster Operators Should Audit Before v1.37

June 8, 2026
SXZ.io SXZ.io
  • [email protected]

Categories

Articles
Learning Hub
News

All Rights Reserved by SXZ.io ©2026