Movatterモバイル変換


[0]ホーム

URL:


Following system colour schemeSelected dark colour schemeSelected light colour scheme

Python Enhancement Proposals

PEP 751 – A file format to record Python dependencies for installation reproducibility

Author:
Brett Cannon <brett at python.org>
Status:
Final
Type:
Standards Track
Topic:
Packaging
Created:
24-Jul-2024
Post-History:
25-Jul-202430-Oct-202415-Jan-2025
Replaces:
665
Resolution:
31-Mar-2025

Table of Contents

Important

This PEP is a historical document. The up-to-date, canonical spec,pylock.toml Specification, is maintained on thePyPA specs page.

×

See thePyPA specification update process for how to propose changes.

Abstract

This PEP proposes a new file format for specifying dependenciesto enable reproducible installation in a Python environment. The format isdesigned to be human-readable and machine-generated. Installers consuming thefile should be able to calculate what to install without the need for dependencyresolution at install-time.

Motivation

Currently, no standard exists to create an immutable record, such as a lockfile, which specifies what direct and indirect dependencies should be installedinto a virtual environment.

Considering there are at least five well-known solutions to this problem in thecommunity (PDM,pipfreeze,pip-tools,Poetry, anduv), there seems tobe an appetite for lock files in general.

Those tools also vary in what locking scenarios they support. For instance,pipfreeze and pip-tools only generate single-use lock files for the currentenvironment while PDM, Poetry, and uv can/try to lock for multiple environmentsand use-cases at once. There’s also concerns around the lack of secure defaultsin the face of supply chain attacks by some tools (e.g. including hashes forfiles).

The lack of a standard also has some drawbacks. For instance, any tooling thatwants to work with lock files must choose which format to support, potentiallyleaving users unsupported (e.g.Dependabot only supporting select tools,same for cloud providers who can do dependency installations on your behalf,etc.). It also impacts portability between tools, which causes vendor lock-in.By not having compatibility and interoperability it fractures tooling aroundlock files where both users and tools have to choose what lock file format touse upfront, making it costly to use/switch to other formats (e.g. toolingaround auditing a lock file). Rallying around a single format removes thiscost/barrier.

The closest the community has to a standard are pip’srequirements fileswhich all the aforementioned tools either use directly as their file format orexport to (i.e.requirements.txt). Unfortunately, the format is not astandard but is supported by convention. It’s also designed very much for pip’sneeds, limiting its flexibility and ease of use (e.g. it’s a bespoke fileformat). Lastly, it is not secure by default (e.g. file hash support isentirely an opt-in feature, you have to tell pip to not look for otherdependencies outside of what’s in the requirements file, etc.).

Note

Much of the motivation fromPEP 665 also applies to this PEP.

Rationale

The file format proposed by this PEP is designed to be human-readable. This isso that the contents of the file can be audited by a human to make sure noundesired dependencies end up being included in the lock file.

The file format is also designed to not require a resolver at install time. Thisgreatly simplifies reasoning about what would be installed when consuming a lockfile. It should also lead to faster installs which are much more frequent thancreating a lock file.

The data in the file should be consumable by tools not written in Python. Thisallows for e.g. cloud hosting providers to write their own tool to performinstallations in their preferred programming language. This introduces theconcept oflockers which write the lock file, andinstallers which installfrom a lock file (they can be the same tool).

The file format should promote good security defaults. As the format is notmeant to be human-writable, this means having tools provide security-relateddetails is reasonable and not a costly burden.

The contents of a lock file should be able to replace the vast majority of usesofrequirements files when used as a lock file (e.g. whatpip-tools andpipfreeze emit). This means the file format specified bythis PEP can, at minimum, act as an export target for tools which have their owninternal lock file format.

Lock files can besingle-use andmulti-use. Single-use lock files are thingslikerequirements.txt files, which serve a single use-case/purpose (hencewhy it isn’t uncommon for a project to have multiple requirements files, eachfor a different use-case). Multi-use lock files represent multiple use-caseswithin a single file, often expressed throughextras andDependency Groups. As such, this PEP supports additions toEnvironment Markers that allows forspecifying extras and dependency groups as appropriate. This allowsfor a single lock file to support those cases. This not only cuts down on thenumber of potential lock files, but it also makes it easier when a singlepackage is meant to be consistent across all use-cases (with multiple single-uselock files it requires coordinating across multiple lock files). This supportalso means that this PEP supports all package installation-related data that canbe recorded in apyproject.toml file. The hope is that this support willallow some tools to drop their internal lock file entirely and solely rely onwhat this PEP specifies.

Specification

File Name

A lock file MUST be namedpylock.toml or match the regular expressionr"^pylock\.([^.]+)\.toml$" if a name for the lock file is desired or if multiplelock files exist. The use of the.toml file extension is to make syntaxhighlighting in editors easier and to reinforce the fact that the file format ismeant to be human-readable. The prefix and suffix of a named file MUST belowercase when possible, for easy detection and removal,e.g.:

iflen(filename)>11andfilename.startswith("pylock.")andfilename.endswith(".toml"):name=filename.removeprefix("pylock.").removesuffix(".toml")

The expectation is that services that automatically install from lock files willsearch for:

  1. The lock file with the service’s name and doing the default install
  2. A multi-usepylock.toml with a dependency group with the name of the service
  3. The default install ofpylock.toml

E.g. a cloud host service named “spam” would first look forpylock.spam.toml to install from, and if that file didn’t exist then installfrompylock.toml and look for a dependency group named “spam” to use ifpresent.

The lock file(s) SHOULD be located in the directory as appropriate for the scopeof the lock file. Locking against a singlepyproject.toml, for instance,would place thepylock.toml in the same directory. If the lock file coveredmultiple projects in a monorepo, then the expectation is thepylock.tomlfile would be in the directory that held all the projects being locked.

File Format

The format of the file isTOML.

Tools SHOULD write their lock files in a consistent way to minimize noise indiff output. Keys in tables – including the top-level table – SHOULD berecorded in a consistent order (if inspiration is desired, this PEP has tried towrite down keys in a logical order). As well, tools SHOULD sort arrays inconsistent order. Usage of inline tables SHOULD also be kept consistent.

lock-version

  • Type: string; value of"1.0"
  • Required?: yes
  • Inspiration:Metadata-Version
  • Record the file format version that the file adheres to.
  • This PEP specifies the initial version – and only valid value until futureupdates to the standard change it – as"1.0".
  • If a tool supports the major version but not the minor version, a toolSHOULD warn when an unknown key is seen.
  • If a tool doesn’t support a major version, it MUST raise an error.

environments

  • Type: Array of strings
  • Required?: no
  • Inspiration:uv
  • A list ofEnvironment Markers forwhich the lock file is considered compatible with.
  • Tools SHOULD write exclusive/non-overlapping environment markers to ease inunderstanding.

requires-python

  • Type: string
  • Required?: no
  • Inspiration:PDM,Poetry,uv
  • Specifies theRequires-Python for the minimumPython version compatible for any environment supported by the lock file(i.e. the minimum viable Python version for the lock file).

extras

  • Type: Array of strings
  • Required?: no; defaults to[]
  • Inspiration:Provides-Extra (multiple use)
  • The list ofextras supportedby this lock file.
  • Lockers MAY choose to not support writing lock files that support extras anddependency groups (i.e. tools may only support exporting a single-use lockfile).
  • Tools supporting extras MUST also support dependency groups.
  • Tools should explicitly set this key to an empty array to signal that theinputs used to generate the lock file had no extras (e.g. apyproject.tomlfile had no[project.optional-dependencies] table), signalling that thelock file is, in effect, multi-use even if it only looks to be single-use.

dependency-groups

  • Type: Array of strings
  • Required?: no; defaults to[]
  • Inspiration:Arbitrary tool configuration: the [tool] table
  • The list ofDependency Groups publicly supported by this lockfile (i.e. dependency groups users are expected to be able to specify via atool’s UI).
  • Lockers MAY choose to not support writing lock files that support extras anddependency groups (i.e. tools may only support exporting a single-use lockfile).
  • Tools supporting dependency groups MUST also support extras.
  • Tools SHOULD explicitly set this key to an empty array to signal that theinputs used to generate the lock file had no dependency groups (e.g. apyproject.tomlfile had no[dependency-groups] table), signalling that the lock fileis, in effect, multi-use even if it only looks to be single-use.

default-groups

  • Type: Array of strings
  • Required?: no; defaults to[]
  • Inspiration:Poetry,PDM
  • The name of synthetic dependency groups to represent what should be installedby default (e.g. whatproject.dependencies implicitly represents).
  • Meant to be used in situations wherepackages.marker necessitates such agroup to exist.
  • The groups listed by this key SHOULD NOT be listed independency-groups asthe groups are not meant to be directly exposed to users by name but insteadvia an installer’s UI.

created-by

  • Type: string
  • Required?: yes
  • Inspiration: Tools with their name in their lock file name
  • Records the name of the tool used to create the lock file.
  • Tools MAY use the[tool] table to record enough details that it can beinferred what inputs were used to create the lock file.
  • Tools SHOULD record the normalized name of the tool if it is available as aPython package to facilitate finding the tool.

[[packages]]

  • Type: array of tables
  • Required?: yes
  • Inspiration:PDM,Poetry,uv
  • An array containing all packages thatmay be installed.
  • Packages MAY be listed multiple times with varying data, but all packages tobe installed MUST narrow down to a single entry at install time.
packages.name
  • Type: string
  • Required?: yes
  • Inspiration:Name
  • The name of the packagenormalized.
packages.version
  • Type: string
  • Required?: no
  • Inspiration:Version
  • The version of the package.
  • The version SHOULD be specified when the version is known to be stable(i.e. when ansdist orwheels are specified).
  • The version MUST NOT be included when it cannot be guaranteed to be consistentwith the code used (i.e. when asource tree isused).
packages.marker
  • Type: string
  • Required?: no
  • Inspiration:PDM
  • Theenvironment markerwhich specify when the package should be installed.
packages.requires-python
[[packages.dependencies]]
  • Type: array of tables
  • Required?: no
  • Inspiration:PDM,Poetry,uv
  • Records the other entries in[[packages]] which are direct dependencies ofthis package.
  • Each entry is a table which contains the minimum information required to tellwhich other package entry it corresponds to where doing a key-by-keycomparison would find the appropriate package with no ambiguity (e.g. if thereare two entries for thespam package, then you can include the versionnumber like{name="spam",version="1.0.0"}, or by source like{name="spam",vcs={url="..."}).
  • Tools MUST NOT use this information when doing installation; it is purelyinformational for auditing purposes.
[packages.vcs]
  • Type: table
  • Required?: no; mutually-exclusive withpackages.directory,packages.archive,packages.sdist, andpackages.wheels
  • Inspiration:Direct URL Data Structure
  • Record the version control system details for thesource tree itcontains.
  • Tools MAY choose to not support version control systems, both from a lockingand/or installation perspective.
  • Tools MAY choose to only support a subset of the available VCS types.
  • Tools SHOULD provide a way for users to opt in/out of using version controlsystems.
  • Installation from a version control system is considered originating from adirect URL reference.
packages.vcs.type
  • Type: string; supported values specified inRegistered VCS
  • Required?: yes
  • Inspiration:VCS URLs
  • The type of version control system used.
packages.vcs.url
  • Type: string
  • Required?: ifpath is not specified
  • Inspiration:VCS URLs
  • The URL to the source tree.
packages.vcs.path
  • Type: string
  • Required?: ifurl is not specified
  • Inspiration:VCS URLs
  • The path to the local directory of the source tree.
  • If a relative path is used it MUST be relative to the location of this file.
  • If the path is relative it MAY use POSIX-style path separators explicitlyfor portability.
packages.vcs.requested-revision
  • Type: string
  • Required?: no
  • Inspiration:VCS URLs
  • The branch/tag/ref/commit/revision/etc. that the user requested.
  • This is purely informational and to facilitate writing theDirect URL Data Structure; it MUST NOT be used to checkoutthe repository.
packages.vcs.commit-id
  • Type: string
  • Required?: yes
  • Inspiration:VCS URLs
  • The exact commit/revision number that is to be installed.
  • If the VCS supports commit-hash based revision identifiers, such a commit-hashMUST be used as the commit ID in order to reference an immutable version ofthe source code.
packages.vcs.subdirectory
  • Type: string
  • Required?: no
  • Inspiration:Projects in subdirectories
  • The subdirectory within thesource tree wherethe project root of the project is (e.g. the location of thepyproject.toml file).
  • The path MUST be relative to the root of the source tree structure.
[packages.directory]
  • Type: table
  • Required?: no; mutually-exclusive withpackages.vcs,packages.archive,packages.sdist, andpackages.wheels
  • Inspiration:Local directories
  • Record the local directory details for thesource tree itcontains.
  • Tools MAY choose to not support local directories, both from a lockingand/or installation perspective.
  • Tools SHOULD provide a way for users to opt in/out of using local directories.
  • Installation from a directory is considered originating from adirect URL reference.
packages.directory.path
  • Type: string
  • Required?: yes
  • Inspiration:Local directories
  • The local directory where the source tree is.
  • If the path is relative it MUST be relative to the location of the lock file.
  • If the path is relative it MAY use POSIX-style path separators forportability.
packages.directory.editable
  • Type: boolean
  • Required?: no; defaults tofalse
  • Inspiration:Local directories
  • A flag representing whether the source tree was an editable install at locktime.
  • Installer MAY choose to ignore this flag if user actions or context would makean editable install unnecessary or undesirable (e.g. a container image thatwill not be mounted for development purposes but instead deployed toproduction where it would be treated at read-only).
packages.directory.subdirectory

Seepackages.vcs.subdirectory.

[packages.archive]
  • Type: table
  • Required?: no
  • Inspiration:Archive URLs
  • A direct reference to an archive file to install from(this can include wheels and sdists, as well as other archive formatscontaining a source tree).
  • Tools MAY choose to not support archive files, both from a lockingand/or installation perspective.
  • Tools SHOULD provide a way for users to opt in/out of using archive files.
  • Installation from an archive file is considered originating from adirect URL reference.
packages.archive.url

Seepackages.vcs.url.

packages.archive.path

Seepackages.vcs.path.

packages.archive.size
  • Type: integer
  • Required?: no
  • Inspiration:uv,Simple repository API
  • The size of the archive file.
  • Tools SHOULD provide the file size when reasonably possible (e.g. the filesize is available via theContent-Length header from aHEAD HTTP request).
packages.archive.upload-time
  • Type: datetime
  • Required?: no
  • Inspiration:Simple repository API
  • The time the file was uploaded.
  • The date and time MUST be recorded in UTC.
[packages.archive.hashes]
  • Type: Table of strings
  • Required?: yes
  • Inspiration:PDM,Poetry,uv,Simple repository API
  • A table listing known hash values of the file where the key is the hashalgorithm and the value is the hash value.
  • The table MUST contain at least one entry.
  • Hash algorithm keys SHOULD be lowercase.
  • At least one secure algorithm fromhashlib.algorithms_guaranteedSHOULD always be included (at time of writing, sha256 specifically isrecommended.
packages.archive.subdirectory

Seepackages.vcs.subdirectory.

packages.index
  • Type: string
  • Required?: no
  • Inspiration:uv
  • The base URL for the package index fromSimple repository APIwhere the sdist and/or wheels were found (e.g.https://pypi.org/simple/).
  • When possible, this SHOULD be specified to assist with generatingsoftware bill of materials – aka SBOMs – and to assist in finding a fileif a URL ceases to be valid.
  • Tools MAY support installing from an index if the URL recorded for a specificfile is no longer valid (e.g. returns a 404 HTTP error code).
[packages.sdist]
  • Type: table
  • Required?: no; mutually-exclusive withpackages.vcs,packages.directory, andpackages.archive
  • Inspiration:uv
  • Details of aSource distribution file name for thepackage.
  • Tools MAY choose to not support sdist files, both from a lockingand/or installation perspective.
  • Tools SHOULD provide a way for users to opt in/out of using sdist files.
packages.sdist.name
packages.sdist.upload-time

Seepackages.archive.upload-time.

packages.sdist.url

Seepackages.archive.url.

packages.sdist.path

Seepackages.archive.path.

packages.sdist.size

Seepackages.archive.size.

packages.sdist.hashes

Seepackages.archive.hashes.

[[packages.wheels]]
  • Type: array of tables
  • Required?: no; mutually-exclusive withpackages.vcs,packages.directory, andpackages.archive
  • Inspiration:PDM,Poetry,uv
  • For recording the wheel files as specified byBinary distribution format for the package.
  • Tools MUST support wheel files, both from a locking and installationperspective.
packages.wheels.name
packages.wheels.upload-time

Seepackages.archive.upload-time.

packages.wheels.url

Seepackages.archive.url.

packages.wheels.path

Seepackages.archive.path.

packages.wheels.size

Seepackages.archive.size.

packages.wheels.hashes

Seepackages.archive.hashes.

[[packages.attestation-identities]]
  • Type: array of tables
  • Required?: no
  • Inspiration:Provenance objects
  • A recording of the attestations forany file recorded for this package.
  • If available, tools SHOULD include the attestation identities found.
  • Publisher-specific keys are to be included in the table as-is(i.e. top-level), following the spec atIndex hosted attestations.
packages.attestation-identities.kind
  • Type: string
  • Required?: yes
  • Inspiration:Provenance objects
  • The unique identity of the Trusted Publisher.
[packages.tool]

[tool]

Additions to marker expression syntax

This PEP proposes adding to theEnvironment Markers specification suchthat extras and dependency group relationships for an entry in[[packages]]can be expressed inpackages.marker. The additions outlined in this PEPONLY apply in the context of lock files as defined by this PEP and not in othercontexts where marker syntax is used (e.g.METADATA,pyproject.toml).

First, two new markers will be introduced:extras anddependency_groups. They represent the extras and dependency groups that havebeen requested to be installed, respectively:

diff --git a/source/specifications/dependency-specifiers.rst b/source/specifications/dependency-specifiers.rstindex 06897da2..c9ab247f 100644--- a/source/specifications/dependency-specifiers.rst+++ b/source/specifications/dependency-specifiers.rst@@ -87,7 +87,7 @@ environments::                     'platform_system' | 'platform_version' |                     'platform_machine' | 'platform_python_implementation' |                     'implementation_name' | 'implementation_version' |-                     'extra' # ONLY when defined by a containing layer+                     'extra' | 'extras' | 'dependency_groups' # ONLY when defined by a containing layer                     )   marker_var    = wsp* (env_var | python_str)   marker_expr   = marker_var marker_op marker_var

This does NOT preclude using the same syntax parser in other contexts, onlywhere the new markers happen to be considered valid based on context.

Second, the marker specification will be changed to allow sets for values (ontop of the current support for strings and versions). ONLY the new markersintroduced in this PEP will allow for a set for their value (which defaults toan empty set). This specifically does NOT update the spec to allow for setliterals.

Third, the marker expression syntax specification will be updated to allow foroperations involving sets:

diff --git a/source/specifications/dependency-specifiers.rst b/source/specifications/dependency-specifiers.rstindex 06897da2..ac29d796 100644--- a/source/specifications/dependency-specifiers.rst+++ b/source/specifications/dependency-specifiers.rst@@ -196,15 +196,16 @@ safely evaluate it without running arbitrary code that could become a securityvulnerability. Markers were first standardised in :pep:`345`. This documentfixes some issues that were observed in the design described in :pep:`426`.-Comparisons in marker expressions are typed by the comparison operator.  The-<marker_op> operators that are not in <version_cmp> perform the same as they-do for strings in Python. The <version_cmp> operators use the version comparison-rules of the :ref:`Version specifier specification <version-specifiers>`-when those are defined (that is when both sides have a valid-version specifier). If there is no defined behaviour of this specification-and the operator exists in Python, then the operator falls back to-the Python behaviour. Otherwise an error should be raised. e.g. the following-will result in  errors::+Comparisons in marker expressions are typed by the comparison operator and the+type of the marker value. The <marker_op> operators that are not in+<version_cmp> perform the same as they do for strings or sets in Python based on+whether the marker value is a string or set itself. The <version_cmp> operators+use the version comparison rules of the+:ref:`Version specifier specification <version-specifiers>` when those are+defined (that is when both sides have a valid version specifier). If there is no+defined behaviour of this specification and the operator exists in Python, then+the operator falls back to the Python behaviour for the types involved.+Otherwise an error should be raised. e.g. the following will result in errors::   "dog" ~= "fred"   python_version ~= "surprise"

Fourth, use ofextras anddependency_groups will be considered anerror outside of lock file (much likeextra outside ofCore metadata specifications).

diff --git a/source/specifications/dependency-specifiers.rst b/source/specifications/dependency-specifiers.rstindex 06897da2..2914ef66 100644--- a/source/specifications/dependency-specifiers.rst+++ b/source/specifications/dependency-specifiers.rst@@ -235,6 +235,11 @@ no current specification for this. Regardless, outside of a context where thisspecial handling is taking place, the "extra" variable should result in anerror like all other unknown variables.+The "extras" and "dependency_groups" variables are also special. They are used+to specify any requested extras or dependency groups when installing from a lock+file. Outside of the context of lock files, these two variables should result in+an error like all other unknown variables.+.. list-table::   :header-rows: 1

These changes, along withpackages.extras/packages.dependency-groupsand marker expressions’ Boolean logic support, allow for expressing arbitrary,exhaustive requirements for when a package should be installed based on theextras and dependency groups (not) requested. For example, if a lock file hasextras=["extra-1","extra-2"], you can specify if a package should beinstalled when:

  • Any of the extras are specified('extra-1'inextrasor'extra-2'inextras)
  • Only “extra-1” is specified('extra-1'inextrasand'extra-2'notinextras)
  • None of the extras are specified('extra-1'notinextrasand'extra-2'notinextras)

(This list is not exhaustive of all possible Boolean logic expressions.)

The same flexibility applies to dependency groups.

How users tell a tool what extras and/or dependency groups they want installedis up to the tool. Installers MUST support the marker expression syntaxadditions as proposed by this PEP. Lockers MAY support writing lock files thatutilize the proposed marker expression syntax additions (i.e. lockers can chooseto only support writing single-use lock files).

Example

lock-version='1.0'environments=["sys_platform == 'win32'","sys_platform == 'linux'"]requires-python='==3.12'created-by='mousebender'[[packages]]name='attrs'version='25.1.0'requires-python='>=3.8'wheels=[{name='attrs-25.1.0-py3-none-any.whl',upload-time=2025-01-25T11:30:10.164985+00:00,url='https://files.pythonhosted.org/packages/fc/30/d4986a882011f9df997a55e6becd864812ccfcd821d64aac8570ee39f719/attrs-25.1.0-py3-none-any.whl',size=63152,hashes={sha256='c75a69e28a550a7e93789579c22aa26b0f5b83b75dc4e08fe092980051e1090a'}},][[packages.attestation-identities]]environment='release-pypi'kind='GitHub'repository='python-attrs/attrs'workflow='pypi-package.yml'[[packages]]name='cattrs'version='24.1.2'requires-python='>=3.8'dependencies=[{name='attrs'},]wheels=[{name='cattrs-24.1.2-py3-none-any.whl',upload-time=2024-09-22T14:58:34.812643+00:00,url='https://files.pythonhosted.org/packages/c8/d5/867e75361fc45f6de75fe277dd085627a9db5ebb511a87f27dc1396b5351/cattrs-24.1.2-py3-none-any.whl',size=66446,hashes={sha256='67c7495b760168d931a10233f979b28dc04daf853b30752246f4f8471c6d68d0'}},][[packages]]name='numpy'version='2.2.3'requires-python='>=3.10'wheels=[{name='numpy-2.2.3-cp312-cp312-win_amd64.whl',upload-time=2025-02-13T16:51:21.821880+00:00,url='https://files.pythonhosted.org/packages/42/6e/55580a538116d16ae7c9aa17d4edd56e83f42126cb1dfe7a684da7925d2c/numpy-2.2.3-cp312-cp312-win_amd64.whl',size=12626357,hashes={sha256='83807d445817326b4bcdaaaf8e8e9f1753da04341eceec705c001ff342002e5d'}},{name='numpy-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl',upload-time=2025-02-13T16:50:00.079662+00:00,url='https://files.pythonhosted.org/packages/39/04/78d2e7402fb479d893953fb78fa7045f7deb635ec095b6b4f0260223091a/numpy-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl',size=16116679,hashes={sha256='3b787adbf04b0db1967798dba8da1af07e387908ed1553a0d6e74c084d1ceafe'}},][tool.mousebender]command=['.','lock','--platform','cpython3.12-windows-x64','--platform','cpython3.12-manylinux2014-x64','cattrs','numpy']run-on=2025-03-06T12:28:57.760769

Installation

The following outlines the steps to be taken to install from a lock file(while the requirements are prescriptive, the general steps and order area suggestion):

  1. Gather the extras and dependency groups to install and setextras anddependency_groups for marker evaluation, respectively.
    1. extras SHOULD be set to the empty set by default.
    2. dependency_groups SHOULD be the set created fromdefault-groups bydefault.
  2. Check if the metadata version specified bylock-version is supported;an error or warning MUST be raised as appropriate.
  3. Ifrequires-python is specified, check that the environment beinginstalled for meets the requirement; an error MUST be raised if it is notmet.
  4. Ifenvironments is specified, check that at least one of the environmentmarker expressions is satisfied; an error MUST be raised if no expression issatisfied.
  5. For each package listed in[[packages]]:
    1. Ifmarker is specified, check if it is satisfied; if it isn’t,skip to the next package.
    2. Ifrequires-python is specified, check if it is satisfied; an errorMUST be raised if it isn’t.
    3. Check that no other conflicting instance of the package has been slated tobe installed; an error about the ambiguity MUST be raised otherwise.
    4. Check that the source of the package is specified appropriately (i.e.there are no conflicting sources in the package entry);an error MUST be raised if any issues are found.
    5. Add the package to the set of packages to install.
  6. For each package to be installed:
    • Ifvcs is set:
      1. Clone the repository to the commit ID specified incommit-id.
      2. Build the package, respectingsubdirectory.
      3. Install.
    • Else ifdirectory is set:
      1. Build the package, respectingsubdirectory.
      2. Install.
    • Else ifarchive is set:
      1. Get the file.
      2. Validate the file size and hash.
      3. Build the package, respectingsubdirectory.
      4. Install.
    • Else if there are entries forwheels:
      1. Look for the appropriate wheel file based onname; if one is notfound then move on tosdist or an error MUST be raised about alack of source for the project.
      2. Get the file:
        • Ifpath is set, use it.
        • Ifurl is set, try to use it; optionally tools MAY usepackages.index or some tool-specific mechanism to download theselected wheel file (tools MUST NOT try to change what wheel file todownload based on what’s available; what file to install should bedetermined in an offline fashion for reproducibility).
      3. Validate the file size and hash.
      4. Install.
    • Else if nowheel file is found orsdist is solely set:
      1. Get the file.
        • Ifpath is set, use it.
        • Ifurl is set, try to use it; tools MAY usepackages.index or some tool-specific mechanism to download thefile.
      2. Validate the file size and hash.
      3. Build the package.
      4. Install.

Semantic differences withrequirements.txt files

Ignoring formatting, there are a few differences between lock files as proposedby this PEP and those that are possible via arequirements file.

Some of the differences are in regards to security. Requiring hashes, recordingfile sizes, and where a file was found – both the index and the location of thefile itself – help with auditing and validating the files that were lockedagainst. Compare that with requirements files which can optionally includehashes, but it is an opt-in feature and can be bypassed. The optional inclusionof a file’s upload time and where the files can be found is also different.

Being explicit about the supported Python versions and environments for the fileoverall is also unique to this PEP. This is to alleviate the issue of notknowing when a requirements file targets a specific platform.

The[tool] tables don’t have a direct correlation in requirements files.They do support comments, but they are not inherently structured like the[tool] table is thanks to being in TOML.

While comments in a requirements file could record details that are helpful forauditing and understanding what the lock file contains, providing the structuredsupport to record such things makes auditing easier. Recording the requiredPython version for a package upfront helps with this as well as erroring outsooner if an install is going to fail. Recording the wheel file name separatefrom the URL or path is also to help make reading the list of wheel files easieras it encodes information that can be useful when understanding and auditing afile. Recording the sdist file name is for the same reason.

This PEP supports multi-use lock files while requirements files are single-use.

This PEP does NOT fully replace requirements files because:

Backwards Compatibility

Because there is no preexisting lock file format, there are no explicitbackwards-compatibility concerns in terms of Python packaging standards.

As for packaging tools themselves, that will be a per-tool decision as towhether they choose to support this PEP and in what way (i.e. as an exporttarget or as the primary way they record their lock file).

Security Implications

The hope is that by standardizing on a lock file format which starts from asecurity-first posture it will help make overall packaging installation safer.However, this PEP does not solve all potential security concerns.

One potential concern is tampering with a lock file. If a lock file is not keptin source control and properly audited, a bad actor could change the file innefarious ways (e.g., point to a malware version of a package). Tampering couldalso occur in transit to e.g. a cloud provider who will perform an installationon the user’s behalf. Both could be mitigated by signing the lock file eitherwithin the file in a[tool] entry or via a side channel external to the lockfile itself.

This PEP does not do anything to prevent a user from installing incorrectpackages. While including many details to help in auditing a package’s inclusion,there isn’t any mechanism to stop e.g. name confusion attacks viatyposquatting. Tools may be able to provide some UX to help with this (e.g. byproviding download counts for a package).

How to Teach This

Users should be informed that when they ask to install some package, thepackage may have its own dependencies, those dependencies may have dependencies,and so on. Without writing down what gets installed as part of installing thepackage they requested, things could change from underneath them (e.g. packageversions). Changes to the underlying dependencies can lead to accidentalbreakage of their code. Lock files help deal with that by providing a way towrite down what was installed so you can install the exact same thing in thefuture.

Having what to install written down also helps in collaborating with others. Byagreeing to a lock file’s contents, everyone ends up with the same packagesinstalled. This helps make sure no one relies on e.g. an API that’s onlyavailable in a certain version that not everyone working on the project hasinstalled.

Lock files also help with security by making sure you always get the same filesinstalled and not a malicious one that someone may have slipped in. It alsolets one be more deliberate in upgrading their dependencies and thus making surethe change is on purpose and not one slipped in by a bad actor.

Lock files can support only certainenvironments. What must be installed forthe environment they are installing for may be different compared to another,different environment. Some lock files do try to beuniversal, though, andwork for any possible environment (sdist and source tree installation successpermitting).

Lock files can besingle-use ormulti-use. Single-use lock files are forsingle use-cases. Multi-use lock files can be used for multipleuse-cases based on extras and dependency groups. It is up to the tool(s) you usethat decide whether multi-use lock files are possible. All tools dealing withlock files at least support single-use lock files. Neither type of lock fileis better or worse than the other, it just changes how much can be written downin a single file.

Lock files that follow this PEP can be installed by any installer thatimplements the specification. This allows users of a lock file to perform aninstallation to not be tied to the locker used by the person who produced thelock file. But it is not the case that using a different locker will lead to thesame result. This could be for various reasons, including using differentalgorithms to determine what to lock.

Reference Implementation

A proof-of-concept implementing single-use lock files can be found athttps://github.com/brettcannon/mousebender/tree/pep . Other tools such asPDM andPoetry implement semantically similar approaches of various parts ofthis PEP.

Rejected Ideas

Recording the dependency graph for installation purposes

A previous version of this PEP recorded the dependency graph of packages insteadof a set of packages to install. The idea was that by recording the dependencygraph you not only got more information, but it provided more flexibility bysupporting more features innately (e.g. platform-specific dependencies withoutexplicitly propagating markers).

In the end, though, it was deemed to add complexity that wasn’t worth the cost(e.g. it impacted the ease of auditing for details which were not necessaryfor this PEP to reach its goals).

Specifying a new core metadata version that requires consistent metadata across files

At one point, to handle the issue of metadata varying between files and thusrequire examining every released file for a package and version for accuratelocking results, the idea was floated to introduce a new core metadata versionwhich would require all metadata for all wheel files be the same for a singleversion of a package. Ultimately, though, it was deemed unnecessary as this PEPwill put pressure on people to make files consistent for performance reasons orto make indexes provide all the metadata separate from the wheel filesthemselves. As well, there’s no easy enforcement mechanism, and so communityexpectation would work as well as a new metadata version.

Have the installer do dependency resolution

In order to support a format more akin to how Poetry worked when this PEP wasdrafted, it was suggested that lockers effectively record the packages and theirversions which may be necessary to make an install work in any possiblescenario, and then the installer resolves what to install. But that complicatesauditing a lock file by requiring much more mental effort to know what packagesmay be installed in any given scenario. Also, one of the Poetry developerssuggestedthat markers as represented in the package locking approach of this PEP may besufficient to cover the needs of Poetry. Not having the installer do aresolution also simplifies their implementation, centralizing complexity inlockers.

Requiring minimum hash algorithm support

It was proposed to require a baseline hash algorithm for the files. This wasrejected as no other Python packaging specification requires specific hashalgorithm support. As well, the minimum hash algorithm suggested may eventuallybecome an outdated/unsafe suggestion, requiring further updates. In order topromote using the best algorithm at all times, no baseline is provided to avoidsimply defaulting to the baseline in tools without considering the securityramifications of that hash algorithm.

File naming

Using*.pylock.toml as the file name

It was proposed to put thepylock constant part of the file name after theidentifier for the purpose of the lock file. It was decided not to do this sothat lock files would sort together when looking at directory contents insteadof purely based on their purpose which could spread them out in a directory.

Using*.pylock as the file name

Not using.toml as the file extension and instead making it.pylockitself was proposed. This was decided against so that code editors would knowhow to provide syntax highlighting to a lock file without having specialknowledge about the file extension.

Not having a naming convention for the file

Having no requirements or guidance for a lock file’s name was considered, butultimately rejected. By having a standardized naming convention it makes it easyto identify a lock file for both a human and a code editor. This helpsfacilitate discovery when e.g. a tool wants to know all of the lock files thatare available.

File format

Use JSON over TOML

Since having a format that is machine-writable was a goal of this PEP, it wassuggested to use JSON. But it was deemed less human-readable than TOML whilenot improving on the machine-writable aspect enough to warrant the change.

Use YAML over TOML

Some argued that YAML met the machine-writable/human-readable requirement in abetter way than TOML. But as that’s subjective andpyproject.toml alreadyexisted as the human-writable file used by Python packaging standards it wasdeemed more important to keep using TOML.

Other keys

A single hash algorithm for the whole file

Earlier versions of this PEP proposed having a single hash algorithm bespecified per file instead of any number of algorithms per file. The thinkingwas that by specifying a single algorithm it would help with auditing the filewhen a specific hash algorithm was mandated for use.

In the end there was some objection to this idea. Typically, it centered aroundthe cost of rehashing large wheel files (e.g. PyTorch). There was also concernabout making hashing decisions upfront on the installer’s behalf which they maydisagree with. In the end it was deemed better to have flexibility and letpeople audit the lock file as they see fit.

Hashing the contents of the lock file itself

Hashing the contents of the bytes of the file and storing hash value within thefile itself was proposed at some point. This was removed to make it easierwhen merging changes to the lock file as each merge would have to recalculatethe hash value to avoid a merge conflict.

Hashing the semantic contents of the file was also proposed, but it would leadto the same merge conflict issue.

Regardless of which contents were hashed, either approach could have the hashvalue stored outside of the file if such a hash was desired.

Recording the creation date of the lock file

To know how potentially stale the lock file was, an earlier proposal suggestedrecording the creation date of the lock file. But for the same merge conflictreasons as storing the hash of the file contents, this idea was dropped.

Recording the package indexes used in searching

Recording what package indexes were used to create the lock file was considered.In the end, though, it was rejected as it was deemed unnecessary bookkeeping.

Locking build requirements for sdists

An earlier version of this PEP tried to lock the build requirements for sdistsunder apackages.build-requires key. Unfortunately, it confused enough peopleabout how it was expected to operate and there were enough edge case issues todecide it wasn’t worth trying to do in this PEP upfront. Instead, a future PEPcould propose a solution.

A dedicateddirect key

Earlier versions had a dedicatedpackages.direct key to flag when somethingshould be considered as originating from adirect URL reference. But there are explicitlyonly three cases where a direct URL reference can occur (VCS, directory, andarchive). Since all three cases are explicit in[[packages]], the setting ofthe key was technically superfluous.

The single drawback of not having this key is for wheels and sdists that nowfall underpackages.archive. With the separate key (or having it specifiedas a part ofpackages.sdist orpackages.wheels), it would allow foridentifying in the lock file itself that an archive file is an sdist or wheel.As it currently stands, an installer must infer that detail itself.

Simplification

Drop recording the package version

The package version is optional since it can only be reliably recorded when ansdist or wheel file is used. And since both sources record the version in filenames it is technically redundant.

But in discussions it was decided the version number is useful for auditingenough to still state it separately.

Drop the requirement to specify the location of an sdist and/or wheels

At least one person has commented how their work has unstable URLs for allsdists and wheels. As such, they have to search for all files at installregardless of where the file was found previously. Dropping the requirement toprovide the URL or path to a file would have helped solve the issue of recordingknown-bad information.

The decision to allow tools to look for a file in other ways beyond the URLprovided alleviated the need to make the URL optional.

Drop requiring file size and hashes

At least one person has said that their work modifies all wheels and sdists withinternal files. That means any recorded hashes and file sizes will be wrong. Bymaking the file size and hashes optional – very likely through some opt-outmechanism – then they could continue to produce lock files that meet this PEP’srequirements.

The decision was made that this weakens security too much. It also preventsinstalling files from alternative locations.

Drop recording the sdist file name

While incompatible with dropping the URL/path requirement, the packageversion, and hashes, recording the sdist file name is technically not necessaryat all (right now recording the file name is optional). The file name onlyencodes the project name and version, so no new info is conveyed about the file(when the package version is provided). And if the location is recorded thengetting the file is handled regardless of the file name.

But recording the file name can be helpful when looking for an appropriate filewhen the recorded file location is no longer available (while sdist file namesare now standardized thanks toPEP 625, that has only been true since 2020and thus there are many older sdists with names that may not be guessable).

The decision was made to require the sdist file name out of simplicity.

Makepackages.wheels a table

One could see writing out wheel file details as a table keyed on the file name.For example:

[[packages]]name="attrs"version="23.2.0"requires-python=">=3.7"index="https://pypi.org/simple/"[packages.wheels]"attrs-23.2.0-py3-none-any.whl"={upload-time=2023-12-31T06:30:30.772444Z,url="https://files.pythonhosted.org/packages/e0/44/827b2a91a5816512fcaf3cc4ebc465ccd5d598c45cefa6703fcf4a79018f/attrs-23.2.0-py3-none-any.whl",size=60752,hashes={sha256="99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}[[packages]]name="numpy"version="2.0.1"requires-python=">=3.9"index="https://pypi.org/simple/"[packages.wheels]"numpy-2.0.1-cp312-cp312-macosx_10_9_x86_64.whl"={upload-time=2024-07-21T13:37:15.810939Z,url="https://files.pythonhosted.org/packages/64/1c/401489a7e92c30db413362756c313b9353fb47565015986c55582593e2ae/numpy-2.0.1-cp312-cp312-macosx_10_9_x86_64.whl",size=20965374,hashes={sha256="6bf4e6f4a2a2e26655717a1983ef6324f2664d7011f6ef7482e8c0b3d51e82ac"}"numpy-2.0.1-cp312-cp312-macosx_11_0_arm64.whl"={upload-time=2024-07-21T13:37:36.460324Z,url="https://files.pythonhosted.org/packages/08/61/460fb524bb2d1a8bd4bbcb33d9b0971f9837fdedcfda8478d4c8f5cfd7ee/numpy-2.0.1-cp312-cp312-macosx_11_0_arm64.whl",size=13102536,hashes={sha256="7d6fddc5fe258d3328cd8e3d7d3e02234c5d70e01ebe377a6ab92adb14039cb4"}"numpy-2.0.1-cp312-cp312-macosx_14_0_arm64.whl"={upload-time=2024-07-21T13:37:46.601144Z,url="https://files.pythonhosted.org/packages/c2/da/3d8debb409bc97045b559f408d2b8cefa6a077a73df14dbf4d8780d976b1/numpy-2.0.1-cp312-cp312-macosx_14_0_arm64.whl",size=5037809,hashes={sha256="5daab361be6ddeb299a918a7c0864fa8618af66019138263247af405018b04e1"}"numpy-2.0.1-cp312-cp312-macosx_14_0_x86_64.whl"={upload-time=2024-07-21T13:37:58.784393Z,url="https://files.pythonhosted.org/packages/6d/59/85160bf5f4af6264a7c5149ab07be9c8db2b0eb064794f8a7bf6d/numpy-2.0.1-cp312-cp312-macosx_14_0_x86_64.whl",size=6631813,hashes={sha256="ea2326a4dca88e4a274ba3a4405eb6c6467d3ffbd8c7d38632502eaae3820587"}"numpy-2.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl"={upload-time=2024-07-21T13:38:19.714559Z,url="https://files.pythonhosted.org/packages/5e/e3/944b77e2742fece7da8dfba6f7ef7dccdd163d1a613f7027f4d5b/numpy-2.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",size=13623742,hashes={sha256="529af13c5f4b7a932fb0e1911d3a75da204eff023ee5e0e79c1751564221a5c8"}"numpy-2.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl"={upload-time=2024-07-21T13:38:48.972569Z,url="https://files.pythonhosted.org/packages/2c/f3/61eee37decb58e7cb29940f19a1464b8608f2cab8a8616aba75fd/numpy-2.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",size=19242336,hashes={sha256="6790654cb13eab303d8402354fabd47472b24635700f631f041bd0b65e37298a"}"numpy-2.0.1-cp312-cp312-musllinux_1_1_x86_64.whl"={upload-time=2024-07-21T13:39:19.213811Z,url="https://files.pythonhosted.org/packages/77/b5/c74cc436114c1de5912cdb475145245f6e645a6a1a29b5d08c774/numpy-2.0.1-cp312-cp312-musllinux_1_1_x86_64.whl",size=19637264,hashes={sha256="cbab9fc9c391700e3e1287666dfd82d8666d10e69a6c4a09ab97574c0b7ee0a7"}"numpy-2.0.1-cp312-cp312-musllinux_1_2_aarch64.whl"={upload-time=2024-07-21T13:39:41.812321Z,url="https://files.pythonhosted.org/packages/da/89/c8856e12e0b3f6af371ccb90d604600923b08050c58f0cd26eac9/numpy-2.0.1-cp312-cp312-musllinux_1_2_aarch64.whl",size=14108911,hashes={sha256="99d0d92a5e3613c33a5f01db206a33f8fdf3d71f2912b0de1739894668b7a93b"}"numpy-2.0.1-cp312-cp312-win32.whl"={upload-time=2024-07-21T13:39:52.932102Z,url="https://files.pythonhosted.org/packages/15/96/310c6f6d146518479b0a6ee6eb92a537954ec3b1acfa2894d1347/numpy-2.0.1-cp312-cp312-win32.whl",size=6171379,hashes={sha256="173a00b9995f73b79eb0191129f2455f1e34c203f559dd118636858cc452a1bf"}"numpy-2.0.1-cp312-cp312-win_amd64.whl"={upload-time=2024-07-21T13:40:17.532627Z,url="https://files.pythonhosted.org/packages/b5/59/f6ad378ad85ed9c2785f271b39c3e5b6412c66e810d2c60934c9f/numpy-2.0.1-cp312-cp312-win_amd64.whl",size=16255757,hashes={sha256="bb2124fdc6e62baae159ebcfa368708867eb56806804d005860b6007388df171"}

In general, though, people did not prefer this over the approach this PEP hastaken.

Self-Referential

Drop the[tool] table

The[tool] table is included as it has been found to be very useful forpyproject.toml files. Providing similar flexibility to this PEP is done inhopes that similar benefits will materialize.

But some people have been concerned that such a table will be too enticing totools and will lead to files that are tool-specific and unusable by othertools. This could cause issues for tools trying to do installation, auditing,etc. as they would not know what details in the[tool] table are somehowcritical.

As a compromise, this PEP specifies that the details recorded in[tool] mustbe disposable and not affect installation of packages.

List the requirement inputs for the file

Right now the file does not record the requirements that acted as inputs to thefile. This is for simplicity reasons and to not explicitly constrain the filein some unforeseen way (e.g., updating the file after initial creation for anew platform that has different requirements, all without having to resolvehow to write a comprehensive set of requirements).

But it may help in auditing and any recreation of the file if the originalrequirements were somehow recorded. This could be a single string or an arrayof strings if multiple requirements were used with the file.

In the end it was deemed too complicated to try and capture the inputsthat a tool used to construct the lock file in a generic fashion.

Auditing

Recording dependents

Recording the dependents of a package is not necessary to install it. As such,it has been left out of the PEP as it can be included via[tool].

But knowing how critical a package is to other packages may be beneficial. Thisinformation is included bypip-tools , so there’s prior art in including it.A flexible approach could be used to record the dependents, e.g. as muchdetail as to differentiate from any other entry for the same package in the file(inspired byuv).

In the end, though, it was decided that recording the dependencies is a betterthing to record.

Acknowledgements

Thanks to everyone who participated in the discussions on discuss.python.org.Also thanks to Randy Döring, Seth Michael Larson, Paul Moore, and Ofek Lev forproviding feedback on a draft version of this PEP before going public. Thanks toRandy Döring of Poetry, Charlie Marsh of uv, and Frost Ming of PDM for providingfeedback on behalf of their respective projects.

Copyright

This document is placed in the public domain or under theCC0-1.0-Universal license, whichever is more permissive.


Source:https://github.com/python/peps/blob/main/peps/pep-0751.rst

Last modified:2025-06-20 17:43:34 GMT


[8]ページ先頭

©2009-2025 Movatter.jp