July 23, 2026
Stop Using requirements.txt: The pylock.toml Migration Guide
PEP 751 changes Python packaging forever. Here’s what I learned switching our production environments to tool-agnostic lock files.

By Daily Python Programming
4 min read
Few days ago when our production deployment pipeline completely collapsed. Nothing in our application code had changed. We hadn't merged a single pull request in twelve hours. Yet, a fresh Docker build failed because a sub-transitive dependency buried three levels deep inside a data processing library released an unannounced minor version update that broke backward compatibility.
If you've been writing Python for more than a few months, this horror story probably sounds familiar. We've all been burned by the illusion of deterministic builds.
For years, we relied on requirements.txt to pin our environments. Later, we adopted pip-compile from pip-tools, and eventually speed-demons like uv and pdm. But we always hit the same wall: vendor lock-in and fragmented lock formats. Half our engineering team loved uv, two senior devs insisted on traditional pip, and our CI pipeline ran a Frankenstein combination of both.
Enter PEP 751 and the standard pylock.toml specification.
Over the past few months, I led the effort to migrate our core production microservices over to pylock.toml. Today, I want to share the practical lessons, the exact commands, the gotchas, and the hard-won insights from that transition so you can stop wrestling with dependency drift once and for all.
The Core Problem
Let's get one thing straight: requirements.txt was never meant to be a modern lock file specification. As the official packaging documentation notes, it was originally created as a simple CLI argument input file for pip install.
When you write a basic file like this:
pandas
scipypandas
scipyYou aren't defining an environment; you're expressing a wish. You are asking your installer to resolve dependencies on the fly at the exact moment of installation.
Even when you generate pinned hashes using pip-compile --generate-hashes, you are still using a line-based format tailored specifically around pip flags (such as --index-url or --extra-index-url).
Here is how the old world compares to the new paradigm of PEP 751:
pylock.toml shifts the philosophy entirely: it separates dependency resolution from package installation. Resolution happens once, resulting in a structured TOML document containing cryptographic hashes, exact wheel URLs, and system markers. Installation then becomes a deterministic operation that any compliant tool can execute.
Generating Your First pylock.toml
When I began testing PEP 751 locally, I wanted to see how standard tooling handled lock file creation without adding third-party abstractions right away.
Assuming you are running Python 3.12+ and pip 25.1 or newer, generating a standard lock file from your top-level dependencies is surprisingly straightforward.
1. Define Top-Level Requirements
Start with your explicit dependencies in a standard requirements.txt:
pandas
scipypandas
scipy2. Run the Lock Command
Execute the experimental locking flag:
python -m pip lock --requirement requirements.txtpython -m pip lock --requirement requirements.txtpip will resolve the full graph — including explicit targets like pandas and scipy, alongside transitive packages like numpy, python-dateutil, and six — and write out a unified pylock.toml.
💡 Lesson Learned: Do not panic when you see experimental warnings on the CLI. The underlying TOML structure follows the official PEP 751 standard, even as individual tool flags continue to mature.
What Makes pylock.toml So Resilient?
When I opened our first generated pylock.toml in VS Code, the structure immediately made sense compared to messy line-continuation backslashes in hashed requirement files.
The top of the file records global metadata:
lock-version = "1.0"
created-by = "pip"lock-version = "1.0"
created-by = "pip"Further down, each package in your dependency graph gets an explicit, structured table entry:
[[packages]]
name = "numpy"
version = "2.4.4"
[[packages.wheels]]
name = "numpy-2.4.4-cp314-cp314-manylinux_2_27_x86_64.whl"
url = "https://files.pythonhosted.org/packages/98/7c/2125205067661262544..."
[packages.wheels.hashes]
sha256 = "27a8d92cd10f1382a67d7cf4db7ce18341b66438bdd9f691d7b0e48d104c2a9d"[[packages]]
name = "numpy"
version = "2.4.4"
[[packages.wheels]]
name = "numpy-2.4.4-cp314-cp314-manylinux_2_27_x86_64.whl"
url = "https://files.pythonhosted.org/packages/98/7c/2125205067661262544..."
[packages.wheels.hashes]
sha256 = "27a8d92cd10f1382a67d7cf4db7ce18341b66438bdd9f691d7b0e48d104c2a9d"Notice what is happening here:
- Explicit Provenance: The exact download URL on PyPI is pinned.
- Integrity Auditing: The cryptographic SHA-256 hash guarantees that the package artifact cannot be tampered with or replaced upstream.
- Zero Ambiguity: Installers don't need to ask PyPI "what is the latest compatible version?" — they simply download the pinned binary.
Cross-Tool Interoperability
This was the ultimate test for our engineering team. We have developers running macOS on Apple Silicon, Linux workstations, and Windows laptops. We needed a workflow where one developer could export a lock file, and another could install it instantly using their tool of choice.
Here is how we set up cross-tool synchronization across uv, pip, and pdm.
Scenario A: Installing via standard pip
To install dependencies directly from pylock.toml into a fresh virtual environment:
python -m pip install -r pylock.tomlpython -m pip install -r pylock.tomlScenario B: High-Speed Sync with uv
If you are using uv for ultra-fast local development, you can sync the lock file directly using preview flags:
uv venv --clear .venv
uv pip sync pylock.toml --preview-features pylockuv venv --clear .venv
uv pip sync pylock.toml --preview-features pylockScenario C: Multi-Platform Exporting with uv
Where uv truly shines is generating cross-platform lock files that contain wheel targets for macOS, Linux (glibc and musl), and Windows simultaneously:
uv export --format pylock.toml -o pylock.uv.tomluv export --format pylock.toml -o pylock.uv.tomlScenario D: Consuming the Lock File in pdm
Another engineer on your team running pdm can immediately consume that exact same exported lock file without translation issues:
pdm sync --lockfile pylock.uv.toml --no-selfpdm sync --lockfile pylock.uv.toml --no-selfNo re-resolution. No platform mismatch errors during CI builds. Just clean, deterministic environment reproduction across every machine.
Production Lessons & Gotchas to Avoid
Transitioning a real-world codebase is rarely without friction. Here are three critical mistakes I made during our migration so you don't have to repeat them:
1. Forgetting Multi-Platform Wheel Targets
If you generate a pylock.toml file using standard pip lock on a macOS M-series Mac, it may only lock wheels for arm64 macOS by default. When your Docker container tries to build on x86_64 Linux inside your CI pipeline, the install will fail.
- Fix: Use cross-platform export tools like
uv export --format pylock.tomlto ensure artifacts for all target architectures are recorded in the file.
2. Mixing CLI Flags in Requirements Files
We had old custom flags like --extra-index-url embedded directly inside our legacy requirements.txt files. pylock.toml deliberately rejects tool-specific flags inside the lock file to maintain standard compliance.
- Fix: Move private repository credentials and custom index URLs to environment variables or standard tool configuration files like
pyproject.tomlorpip.conf.
3. Dependency Groups Are Still Maturing
While PEP 751 supports grouping dependencies (e.g., separating dev, test, and prod packages inside a single lock file), full end-to-end support across every major package manager is still rolling out. Start by locking your primary production runtime environment first before attempting complex nested dev-group configurations.
Final Thoughts
Standardization always wins in the long run. Just as PEP 518 brought us pyproject.toml and saved us from the chaos of setup.py, PEP 751 and pylock.toml are finally delivering the unified, tool-agnostic dependency standard the Python ecosystem has needed for a decade.
If you are tired of debugging broken CI builds at 2 AM, I strongly encourage you to try generating a pylock.toml on your next project.
Have you tried adopting pylock.toml or PEP 751 in your projects yet? Drop a comment below with your favorite packaging tools and any migration roadblocks you've run into!