A person triumphantly raises a key in front of a vault labeled pylock.toml, with a rack of colored keys, wooden crates marked with hashes, and a Python chip alongside.

Tool-Agnostic Python Lock Files With PEP 751 and pylock.toml

by Cameron Riddell Updated Reading time estimate 18m intermediate tools

The Python lock file, pylock.toml, records exact dependencies your project needs so installs come out the same every time. It isn’t the first of its kind, though—most modern package tools already define their own lock formats. This has led to a fragmented landscape where reproducibility is often limited to a single tool’s workflow, with each ecosystem effectively speaking its own lock file dialect.

From this fragmentation comes a shared standard: pylock.toml. Defined in PEP 751, pylock.toml standardizes reproducible dependency management across different tools, eliminating vendor lock-in and fragmented requirements.txt workflows. Its purpose is to capture a fully resolved set of dependencies in a format that every tool can understand.

But doesn’t requirements.txt already do this? Not exactly. requirements.txt isn’t so much a standardized specification as an implementation detail of pip. The Requirements File Format specifies that “requirements files serve as a list of items to be installed by pip, when using pip install.”

Still, both files exist to enumerate dependencies that should end up in an environment. The difference lies in how that information is represented, either as tool-specific instructions or as a structured, tool-agnostic record of a resolved state:

If you… pylock.toml requirements.txt
Want to share a single lock file with teammates who use different tools (uv, pip, pdm)
Need reproducible, hash-verified installs in CI
Want to install without resolving dependencies on the machine
Need to pass pip flags like --index-url or --no-deps from the file
Want to jot down a couple of dependencies for a quick script by hand

This means pylock.toml is the better choice when you care about reproducibility across tools, the integrity of installed artifacts, or a single shared source of truth that can be consumed in different workflows. It’s especially useful in team settings or continuous integration (CI) pipelines where consistency matters more than manual control.

In contrast, requirements.txt remains a good fit for lightweight workflows, quick experiments, or cases where direct control over pip behavior and flags is more important than cross-tool interoperability or strict locking guarantees.

Take the Quiz: Test your knowledge with our interactive “Tool-Agnostic Python Lock Files With PEP 751 and pylock.toml” quiz. You’ll receive a score upon completion to help you track your learning progress:


Interactive Quiz

Tool-Agnostic Python Lock Files With PEP 751 and pylock.toml

Test your understanding of Python lock files and PEP 751, from generating a pylock.toml with pip to installing it across uv and pdm.

Generating a Python Lock File With pip

With pip version 25.1 or newer, you can generate a pylock.toml file from a requirements.txt file using the experimental pip lock command. At its core, the pylock.toml will serve as a snapshot of every dependency pip would install.

Before you can use pip lock, you’ll need to define a small set of explicit dependencies in a standard requirements file:

Language: Python Requirements Filename: requirements.txt
pandas
scipy

This file expresses only the top-level packages to be installed in your environment. It doesn’t describe how pip resolves those packages or what additional dependencies they need to work.

From here, you can have pip produce a fully resolved lock file:

Language: Shell
$ python -m pip lock --requirement requirements.txt
WARNING: pip lock is currently an experimental command. It may be
⮑ removed/changed in a future release without prior warning.
Collecting pandas (from -r requirements.txt)
  Downloading pandas-3.0.2-cp314-cp314-manylinux_2_24_x86_64...
Collecting scipy (from -r requirements.txt)
  Downloading scipy-1.17.1-cp314-cp314-manylinux_2_27_x86_64...
Collecting numpy>=2.3.3 (from pandas->-r requirements.txt)
  Downloading numpy-2.4.4-cp314-cp314-manylinux_2_27_x86_64...
Collecting python-dateutil>=2.8.2 (from pandas->-r requirements.txt)
  Downloading python_dateutil-2.9.0.post0-py2.py3-none-any...
Collecting six>=1.5 (from python-dateutil>=2.8.2->pandas->-r requirements.txt)
  Downloading six-1.17.0-py2.py3-none-any.whl.metadata (1.7 kB)

This triggers the resolution process. pip builds a complete dependency graph starting from the explicit packages (pandas, scipy), resolves all required transitive dependencies, and downloads compatible versions for the current Python environment and platform.

This means the result contains both:

  1. Explicit dependencies: What you specified directly, like pandas and scipy
  2. Transitive dependencies: What those packages require to function, like numpy

You can explore that split for yourself in the interactive graph below. Start with only pandas declared to see the full set it resolves to, then toggle scipy on and watch your two requested packages expand into a larger set of pinned dependencies:

Interactive diagram — enable JavaScript to view.

Pay attention to numpy as you toggle scipy on and off. The install log above attributes numpy to pandas, but scipy depends on it too, so the resolver doesn’t lock it twice—it records a single, shared numpy entry that satisfies both. That’s exactly what a lock file does for you: it turns the short list of packages you asked for into the complete, deduplicated set that will actually be installed.

By capturing the complete dependency graph, pylock.toml enables reproducible and verifiable installations across environments.

Inspecting and Understanding pylock.toml

When you open the generated pylock.toml file, you’ll notice that there’s a lot of information packed into it. At a high level, it contains shared metadata and a list of entries for each package in the resolved dependency graph. The shared metadata describes properties of the environment as a whole, such as the Python version constraints the lock file was generated for and information about the tool that created it.

Right now, your pylock.toml includes only a small subset of the possible metadata. That metadata notes the version of the specification it follows and the tool that generated it:

Language: TOML Filename: pylock.toml
lock-version = "1.0"
created-by = "pip"
...

Following this metadata, the file lists each package in the resolved dependency graph. Each entry records the package name and the exact version selected during resolution:

Language: TOML
[[packages]]
name = "numpy"
version = "2.4.4"
...

This is the high-level identifier. It tells you what package and which version. Then, if the package was installed from a wheel file, which is common, you’ll also see the following:

  • The wheel filename
  • The download URL, which provides source provenance
  • A cryptographic hash for verification

Together, this metadata identifies not only the package version, but also the exact artifact that was installed and where it came from.

If you look more closely at the [[packages.wheels]] table nested under the numpy package entry, you’ll see this:

Language: TOML
[[packages.wheels]]
name = "numpy-2.4.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl"
url = "https://files.pythonhosted.org/packages/98/7c/2125205067661262544..."

[packages.wheels.hashes]
sha256 = "27a8d92cd10f1382a67d7cf4db7ce18341b66438bdd9f691d7b0e48d104c2a9d"

All of this information together provides the foundation for a reproducible software environment. Specifically, by recording each wheel’s filename, download URL, and cryptographic hash, the lock file guarantees the following:

  • Installations can be verified for integrity.
  • Tools can audit dependencies without re-resolving them.
  • Environments can be reproduced exactly.

Together, this shifts the lock file from a list of versions to a fully specified record of installable artifacts. Instead of naming what to install in general terms—for example, a version number—it captures exactly which artifact will be installed and where it comes from.

Comparing pylock.toml and requirements.txt

The wheel metadata, hashes, and provenance information stored in pylock.toml all support making installations more reproducible.

For years, the Python ecosystem has pursued that same goal through requirements.txt-based workflows. One of the standard tools to accomplish this is pip-tools, which provides a pip-compile command that generates a fully pinned requirements file, similar to the pylock.toml file you saw earlier:

Language: Shell
$ python -m venv .venv
$ source .venv/bin/activate
(.venv) $ python -m pip install pip-tools
(.venv) $ python -m piptools compile --generate-hashes \
> -o requirements.hashed.txt requirements.txt
...
numpy==2.4.4 \
    --hash=sha256:07077278157d02f65c43b1b26a3886bce886f95d20aabd11f879327... \
    ...
    # via
    #   pandas
    #   scipy
pandas==3.0.2 \
    --hash=sha256:01f31a546acd5574ef77fe199bc90b55527c225c20ccda6601cf6b0... \
    ...
    # via -r requirements.txt
python-dateutil==2.9.0.post0 \
    --hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a3... \
    ...
    # via pandas
scipy==1.17.1 \
    --hash=sha256:010f4333c96c9bb1a4516269e33cb5917b08ef2166d5556ca2fd9f0... \
    ...
    # via -r requirements.txt
six==1.17.0 \
    --hash=sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be897... \
    ...
    # via python-dateutil

This produces a fully resolved requirements file with pinned versions and hashes for each dependency. It improves reproducibility within the requirements.txt model by ensuring installs are version-pinned and integrity-checked.

Compared to this, pylock.toml differs in a few structural ways:

  • It represents resolved dependencies as structured data, not line-based declarations.
  • It preserves artifact provenance explicitly (wheels, archives, VCS, local paths).
  • It encodes resolution results in a tool-agnostic format rather than pip-specific syntax.

Furthermore, pylock.toml also provides space to list dependency-groups, meaning that a single pylock.toml file may contain multiple sets of resolved packages. In contrast, the requirements format is single-purpose, so it can only ever contain a single set of package specifications.

If you rely on pip-tools and the requirements format to reproduce sets of dependencies for your users, developers, and CI systems, then you should be prepared to manage a requirements.txt, a requirements-dev.txt, and even a requirements-ci.txt. Unfortunately, most packaging tools have yet to support ingesting or emitting pylock.toml dependency groups, so you’ll need to wait a bit longer to see this feature in action.

That said, pip-compile strengthens the reproducibility of the requirements format, but it doesn’t change its underlying representation model. pylock.toml operates at a different level: it captures the fully resolved state of your dependencies in a well-defined, structured format that’s deliberately tool-agnostic and designed to be read and written without assuming any particular tool produced it.

Working With pylock.toml Across Tools

That portability is what enables cross-tool workflows. Because pylock.toml is a standardized format, the lock file generated by one tool can be consumed by another. The examples below use the same lock file with pip, uv, and pdm to create equivalent environments.

As of pip version 26.1 or newer, you can install packages specified within a pylock.toml file:

Language: Shell
(.venv) $ python -m pip install --upgrade pip # Get the latest version of pip
(.venv) $ python -m pip install -r pylock.toml
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 numpy==2.4.4 (from pylock.toml)
  Using cached numpy-2.4.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_...
Collecting pandas==3.0.2 (from pylock.toml)
  Using cached pandas-3.0.2-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28...
Collecting python-dateutil==2.9.0.post0 (from pylock.toml)
  Using cached python_dateutil-2.9.0.post0-py2.py3-none-any.whl (229 kB)
Collecting scipy==1.17.1 (from pylock.toml)
  Using cached scipy-1.17.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28...
Collecting six==1.17.0 (from pylock.toml)
  Using cached six-1.17.0-py2.py3-none-any.whl (11 kB)
Installing collected packages: six, numpy, scipy, python-dateutil, pandas
Successfully installed numpy-2.4.4 pandas-3.0.2 python-dateutil-2.9.0.post0
⮑ scipy-1.17.1 six-1.17.0

(.venv) $ deactivate

Another common option in Python is to use a package management tool like uv. At the moment, uv has limited support for importing pylock.toml files. It can install packages from a lock file, but this workflow doesn’t update your pyproject.toml or integrate the result into uv’s broader project management model. In practice, this makes pylock.toml imports useful for reproducing environments, but not for maintaining them.

To install the contents of a pylock.toml file using uv into a Python virtual environment, you can run the uv pip sync command:

Language: Shell
$ uv venv --clear .venv
$ uv pip sync pylock.toml --preview-features pylock
Prepared 5 packages in 4ms
Installed 5 packages in 32ms
 + numpy==2.4.4
 + pandas==3.0.2
 + python-dateutil==2.9.0.post0
 + scipy==1.17.1
 + six==1.17.0

$ uv run python -c 'import numpy; print(numpy.__version__)'
2.4.4

Notice the --preview-features pylock flag in the command above. Because pylock.toml support in uv is still experimental, omitting this flag triggers a warning message that the lock file support may change without notice.

Where uv becomes more compelling is in its ability to export cross-platform lock files. Its import support may be limited, but uv can generate a cross-platform pylock.toml file from any environment it manages, making it a strong bridge between tools. To do this, you’ll create a pyproject.toml file and copy the direct dependencies from your requirements.txt into it:

Language: Shell
$ uv init --bare
$ uv add -r requirements.txt # `uv pip sync` previously already installed these!
Resolved 7 packages in 568ms
Checked 5 packages in 0.15ms

Here, uv records your explicit dependencies in the pyproject.toml and resolves the full environment. Since you’ve already installed these packages via the previous uv pip sync pylock.toml command, uv doesn’t perform any further installation. From this state, it can produce a pylock.toml file that captures everything needed to recreate the environment elsewhere.

To produce a pylock.toml file, you can use the uv export command:

Language: Shell
$ uv export --format pylock.toml -o pylock.uv.toml

The resulting pylock.toml is notably larger than the one previously generated by pip. This is because uv produces a cross-platform lock file, including artifacts for multiple operating systems and architectures. The result is a highly portable, tool-agnostic specification that can be reused across a wide range of environments.

Taking a look at an annotated and abridged version of the numpy entry, you’ll see that uv resolved this package for a variety of architectures:

Language: TOML
[[packages]]
name = "numpy"
version = "2.4.4"
index = "https://pypi.org/simple"

sdist = {
    url = "https://.../numpy-2.4.4.tar.gz",
    hashes = { sha256 = "2d390634c5182175533585cc89f3608a4682ccb173cc..." }
}

# Abridged .whl specs; removed metadata like size and upload-time
wheels = [
    # macOS (Intel + ARM variants)
    { url = "https://.../numpy-2.4.4-macosx_10_15_x86_64.whl" },
    { url = "https://.../numpy-2.4.4-macosx_11_0_arm64.whl" },
    { url = "https://.../numpy-2.4.4-macosx_14_0_arm64.whl" },
    { url = "https://.../numpy-2.4.4-macosx_14_0_x86_64.whl" },

    # Linux (glibc)
    { url = "https://.../manylinux_2_27_x86_64.whl" },
    { url = "https://.../manylinux_2_27_aarch64.whl" },

    # Linux (musl)
    { url = "https://.../musllinux_1_2_x86_64.whl" },
    { url = "https://.../musllinux_1_2_aarch64.whl" },

    # Windows
    { url = "https://.../win_amd64.whl" },
    { url = "https://.../win_arm64.whl" },
    { url = "https://.../win32.whl" }
]

That same standardization is why the file uv produces isn’t tied to uv itself—other tools can consume it directly. For example, after installing pdm, you can use it to recreate the same environment from the recently exported lock file:

Language: Shell
$ python -m venv --clear .venv
$ pdm use .venv
$ pdm sync --lockfile pylock.uv.toml --no-self
WARNING: Lockfile hash doesn't match pyproject.toml, packages may be outdated
WARNING: Missing lock targets or environment field in the lock file, ...
Synchronizing working set with resolved packages: 5 to add, ...

  ✔ Install six 1.17.0 successful
  ✔ Install python-dateutil 2.9.0.post0 successful
  ✔ Install pandas 3.0.2 successful
  ✔ Install numpy 2.4.4 successful
  ✔ Install scipy 1.17.1 successful

  0:00:02 🎉 All complete! 5/5

$ pdm run python -c 'import numpy; print(numpy.__version__)'
2.4.4

This highlights one of the core advantages of a standardized lock file format. Tools like uv, pip, and pdm can already import and export it, even if support is still evolving. Other tools, such as Poetry, are beginning to adopt it as well.

If you want to dig deeper into how Python’s packaging tools handle dependency resolution, Real Python’s uv vs pip: Python Packaging and Dependency Management video course walks through both tools and the trade-offs between them.

Limitations and Gotchas

The examples above demonstrate the promise of a standardized lock file format. In many cases, a lock file generated by one tool can already be consumed by another with little or no translation.

However, it’s important to distinguish between what the specification allows and what today’s tooling fully supports. While pylock.toml introduces a well-defined way to represent dependency trees, the ecosystem is still in the early stages of adopting the full specification.

One of the most immediate limitations is adoption. Although multiple tools can read or write pylock.toml, support is still uneven: none of them yet treats it as a fully native, end-to-end workflow format the way each handles its own bespoke lock file.

Another important limitation is partial feature support. In earlier sections, you saw that pylock.toml can represent richer structures than traditional requirement files, including:

  • Structured package artifacts, including wheels with their URLs and hashes
  • Multiple source types such as VCS repositories, archives, and local directories
  • Multi-environment modeling through dependency groups and environment markers

However, many of these features aren’t yet consistently supported across tooling. In practice, most tools today focus on the core use case: installing a single resolved set of packages from wheel-based artifacts. More advanced representations, especially multi-environment groupings, are still emerging and may be ignored or partially interpreted depending on the tool.

Taken together, these limitations don’t diminish the value of the format, but they do set expectations correctly. pylock.toml offers a stable structure, even though tool support for its full range of capabilities is still maturing.

Conclusion

One of the key ideas behind pylock.toml is the separation of dependency resolution from installation. Rather than resolving dependencies every time an environment is created, a lock file captures the result of that resolution process so it can be reproduced later. This approach makes environments more predictable and portable, while also creating opportunities for interoperability across tools.

In this tutorial, you learned how to:

  • Understand the purpose of pylock.toml and how it differs from traditional requirements.txt workflows
  • Generate a lock file using pip lock
  • Inspect the contents of a pylock.toml file, including package metadata, artifacts, and hashes
  • Compare pylock.toml with pip-compile
  • Use the same lock file with multiple tools, including pip, uv, and pdm

While support for the standard is still evolving, pylock.toml provides a foundation for more reproducible dependency management across the Python ecosystem. By standardizing how resolved dependencies are recorded, it allows lock files to be shared between tools without requiring tool-specific formats or workflows.

To see how it works in practice, try running pip lock on one of your existing projects and compare the resulting pylock.toml file with the project’s current requirements.txt. Examining the resolved dependency graph, hashes, and package metadata is a good way to understand the additional information that pylock.toml can provide.

For a deeper look at Python packaging and dependency management, check out these related Real Python tutorials:

Frequently Asked Questions

Now that you have some experience with pylock.toml in Python, you can use the questions and answers below to check your understanding and recap what you’ve learned.

These FAQs are related to the most important concepts you’ve covered in this tutorial. Click the Show/Hide toggle beside each question to reveal the answer.

A pylock.toml file is a standardized lock file that records the exact, fully resolved set of dependencies a project needs, including specific versions and verified artifacts. Defined in PEP 751, it captures this information in a tool-agnostic format that any compatible packaging tool can read and install from.

You can generate one by running the experimental pip lock command, which resolves every dependency in a requirements file and writes the result to pylock.toml. Tools like uv can also export a pylock.toml from a project they manage, producing a cross-platform version with artifacts for multiple operating systems.

The requirements.txt format is an implementation detail of pip, while pylock.toml is a tool-agnostic standard that any compatible installer can consume. A pylock.toml file also stores richer data, such as artifact hashes and download URLs, and it can hold multiple dependency groups instead of a single set of packages.

As of now, pip, uv, and pdm can all read or write pylock.toml files, though their level of support varies. Other tools such as Poetry are beginning to adopt the format too, even if none of them treats it as a fully native workflow yet.

Replacing requirements.txt makes sense when you need reproducible, hash-verified installs or want to share one lock file across teammates who use different tools. For quick scripts or workflows that rely on specific pip flags, a plain requirements.txt still does the job, so the right choice depends on your needs.

Take the Quiz: Test your knowledge with our interactive “Tool-Agnostic Python Lock Files With PEP 751 and pylock.toml” quiz. You’ll receive a score upon completion to help you track your learning progress:


Interactive Quiz

Tool-Agnostic Python Lock Files With PEP 751 and pylock.toml

Test your understanding of Python lock files and PEP 751, from generating a pylock.toml with pip to installing it across uv and pdm.

🐍 Python Tricks 💌

Get a short & sweet Python Trick delivered to your inbox every couple of days. No spam ever. Unsubscribe any time. Curated by the Real Python team.

Python Tricks Dictionary Merge

About Cameron Riddell

Cameron is a Python trainer, consultant, and open source maintainer focused on data tools. He teaches Python, SQL, Git, and open science, and he loves the Python community. You'll often find him at events and meetups, so make sure to say hi!

» More about Cameron

Each tutorial at Real Python is created by a team of developers so that it meets our high quality standards. The team members who worked on this tutorial are:

Master Real-World Python Skills With Unlimited Access to Real Python

Locked learning resources

Join us and get access to thousands of tutorials, hands-on video courses, and a community of expert Pythonistas:

Level Up Your Python Skills »

Master Real-World Python Skills
With Unlimited Access to Real Python

Locked learning resources

Join us and get access to thousands of tutorials, hands-on video courses, and a community of expert Pythonistas:

Level Up Your Python Skills »

What Do You Think?

Rate this article:

What’s your #1 takeaway or favorite thing you learned? How are you going to put your newfound skills to use? Leave a comment below and let us know.

Commenting Tips: The most useful comments are those written with the goal of learning from or helping out other students. Get tips for asking good questions and get answers to common questions in our support portal.


Looking for a real-time conversation? Visit the Real Python Community Chat or join the next “Office Hours” Live Q&A Session. Happy Pythoning!

Keep Learning

Related Topics: intermediate tools