Packaging4 min read

pylock.toml Records a Python Dependency Resolution

What the pylock.toml 1.0 standard records, how installers select packages, and where reproducibility still depends on environment and artifacts.

  • dependency locking
  • pylock.toml
  • Python packaging

pylock.toml is the standardized file format for recording packages selected for reproducible installation into a Python environment. It stores a resolver’s output, including package candidates, artifacts, hashes, environment limits, extras, and dependency groups. It does not replace the abstract dependency declarations in pyproject.toml.

The current PyPA pylock.toml specification uses format version 1.0 and was originally accepted as PEP 751. This article describes that format as published on August 23, 2026. Tool commands and feature coverage differ, so consult the chosen locker’s and installer’s documentation before adopting it.

Requirements and locks answer different questions

An abstract requirement such as httpx>=0.27 describes acceptable direct dependencies. A resolver combines those constraints with transitive dependencies, Python compatibility, environment markers, available distributions, indexes, and tool policy. A lock records the concrete candidates and files chosen from that input.

This distinction affects maintenance. Change the project’s declared requirement when the application needs a different compatibility range. Regenerate the lock when that input, the target environments, the index contents, or the update policy changes. Hand-editing one version in a lock can leave dependency references, markers, artifacts, and hashes inconsistent.

The standard requires the basename pylock.toml, or a purpose-specific name matching pylock.<name>.toml. A lock for one pyproject.toml should normally sit beside it. Multiple locks can represent deployment targets that cannot share one resolution.

Version 1.0 separates scope from packages

This shortened file shows the main layers. It is illustrative rather than a complete lock because real artifact hashes and URLs must come from the resolver that downloaded or selected them.

lock-version = "1.0"
requires-python = ">=3.12,<3.15"
environments = [
  "sys_platform == 'linux' and platform_machine == 'x86_64'",
]
extras = []
dependency-groups = ["test"]
default-groups = []
created-by = "example-locker"

[[packages]]
name = "example"
version = "2.4.1"
marker = "'test' in dependency_groups"

[[packages.wheels]]
name = "example-2.4.1-py3-none-any.whl"
url = "https://packages.example.invalid/example-2.4.1-py3-none-any.whl"

[packages.wheels.hashes]
sha256 = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"

lock-version is required and currently accepts "1.0". requires-python limits the Python versions supported by the whole lock. Each expression in environments describes an environment the file supports; installation must satisfy at least one when the key is present.

The top-level extras and dependency-groups list selectable sets supported by the lock. default-groups declares which dependency groups an installer selects when the user does not choose explicitly. A package-level marker can then refer to the selected extras and dependency_groups sets.

Package entries list installable candidates

[[packages]] is required, even for an empty environment. A normalized package name can appear more than once for different versions, sources, or markers, but installation must narrow it to one non-conflicting entry.

Package dependency entries are for audit and graph inspection. The specification says installers must not use those edges to decide what to install. Instead, they iterate over the complete package list, evaluate package markers and Python constraints, and select the matching entries.

A package may point to a version-control checkout, local directory, archive, source distribution, or one or more wheels. Those source forms are mutually constrained by the specification. Installers must support wheels, while support for VCS, directories, archives, and source distributions can vary by tool.

Hashes belong to the recorded artifacts. They let an installer check that fetched bytes match the lock. A hash does not make a missing URL available, certify that upstream code is safe, or prove that a build from an sdist will be identical on every machine.

Reproducibility has a boundary

A lock can still fail on the wrong Python version or operating system. Artifact URLs can disappear. A selected wheel may not cover a new platform. A VCS or local-directory entry brings source and build behavior that a wheel hash would have fixed more tightly. Native code can also depend on system libraries outside Python package metadata.

For deployments that must survive index changes, retain the approved artifacts in a controlled repository as well as the lock. Regenerate with a documented resolver version, review lock diffs, and install in every environment named by the file. The lock makes a resolution inspectable. Reproducibility also depends on keeping its inputs and artifacts available.

Python 3.11 and later can parse the TOML syntax with tomllib, but parsing only proves that the file is valid TOML. Use a pylock.toml-aware tool to validate the schema and perform the installation rules.