Also known as lock files.
Been working on this for almost 6 months with @pradyunsg and @uranusjr. If this gets accepted I think it would mean rejecting PEP 650 as redundant (the installer API PEP).
PEP: 665
Title: Specifying Installation Requirements for Python Projects
Author: Brett Cannon brett@python.org,
Pradyun Gedam pradyunsg@gmail.com,
Tzu-ping Chung uranusjr@gmail.com
PEP-Delegate:
Discussions-To: PEP 665: Specifying Installation Requirements for Python Projects
Status: Draft
Type: Standards Track
Content-Type: text/x-rst
Created: 29-Jul-2021
Post-History: 29-Jul-2021
Resolution:
========
Abstract
This PEP specifies a file format to list the Python package
installation requirements for a project. The list of projects is
considered exhaustive for the installation target and thus
locked down, not requiring any information beyond the platform being
installed for and the lock file listing the required dependencies
to perform a successful installation of dependencies.
==========
Motivation
Thanks to PEP 621, projects have a way to list their direct/top-level
dependencies which they need to have installed. But PEP 621 also
(purposefully) omits two key details that often become important for
projects:
#. A listing of all indirect/transitive dependencies
#. Specifying (at least) specific versions of dependencies for
reproducible installations
Both needs can be important for various reasons when creating a new
environment. Consider a project which
is an application that is deployed somewhere (either to users as a
desktop app or to a server). Without a complete listing of all
dependencies and the specific versions to use, there can be a skew
between developers of the same project, or developer and user, based on
what versions of a projectās dependencies happen to be available at the
time of installation in a new environment. For instance, a dependency may
have v1 as the newest version on Monday when one developer installed the
dependency, while v2 comes out on Wednesday when another developer
installs the same dependency. Now the two developers are working against
two different versions of the same dependency, which can lead to different
outcomes. This is the use-case of developing a desktop or server
application where one might have a requirements.txt
file which
specifies exact versions of various packages.
Another important reason for reproducible installations is for
security purposes. Guaranteeing that the same binary data is
downloaded and installed for all installations of an app makes sure that no
bad actor has somehow changed a dependencyās binary data in a malicious
way. A lock file can assist in this guarantee by recording the exact
details of what should be installed and how to verify that those
dependencies have not changed any bytes unexpectedly. This is the use-case
of developing a secure application using a requirements.txt
file which
specifies the hash of all the packages that should be installed.
Tied into this concept of reproducibility is the speed at which an
environment can be recreated. If you created a lock file as part of
your local development, it can be used to speed up recreating that
development environment by minimizing having to query the network or the
scope of the possible resolution of dependencies. This makes recreating
your local development environment faster as the amount of work required
to calculate what dependencies to install has been minimized. This is the
use-case of when you are working on a library or some such project where
the lock file is not committed to version control and the lock file used as
a local cache of installation resolution details, such as an uncommitted
poetry.lock
file.
The community itself has also shown a need for lock files based on the
fact that multiple tools have independently created their own lock
file formats:
#. PDM_
#. pip-tools
_
#. Pipenv_
#. Poetry_
#. Pyflow_
Other programming language communities have also shown the usefulness
of lock files by developing their own solution to this problem. Some
of those communities include:
#. Dart_
#. npm_/Node
#. Rust_
Below, we identify some use-cases applicable to stakeholders in the
Python community and anyone who interacts with Python package
installers who are the ultimate consumers of a lock file (this is not
considered exhaustive and is borrowed from PEP 650).
Providers
Providers are the parties (organization, person, community, etc.) that
supply a service or software tool which interacts with Python
packaging. Two different types of providers are considered:
Platform/Infrastructure Providers
Platform providers (cloud environments, application hosting, etc.) and
infrastructure service providers need to support package installers
for their users to install Python dependencies. Most only support
requirements.txt
files and a smattering of other file formats for
listing a projectās dependencies. Most providers do not want to maintain
support for more than one dependency specification format
because of the complexity it adds to their software or service and the
resources it takes to do so (e.g. not all platform providers have
the staffing to support pip-tools, Poetry, Pipenv, etc.).
This PEP would allow platform providers to declare support for this PEP
and thus only have to support one dependency specification format. What
this would mean is developers could use whatever toolchain they preferred
for development as long as they could emit a file that implemented this
PEP. This then allows developers to not have to align with what their
platform providers supports as long as everyone agrees to implementing
this PEP.
IDE Providers
Integrated development environments may interact with Python package
installation and management. Most only support select few tools, and
users are required to find work arounds to install
their dependencies using other package installers. Similar to the
situation with PaaS & IaaS providers, IDE providers do not want to
maintain support for N different formats. Instead, tools would only
need to be able to read files which implement this PEP to perform various
actions (e.g. list all the dependencies of the open project, which ones
are missing, install dependencies, generate the lock file, etc.).
As an example, the Python extension for VS Code has to have custom support
for each installer tool people may use: pip, Poetry, Pipenv, etc. This is
not only tedious by having to track multiple projects and any changes they
make, but it also locks out newer tools whose popularity isnāt great
enough to warrant inclusion in the extension.
Developers
Developers are teams, people, or communities that code and use Python
package installers and Python packages. Three different types of
developers are considered:
Developers using PaaS & IaaS providers
Most PaaS and IaaS providers only support one Python package
installer: requirements.txt
. This dictates the installers that
developers can use while working with these providers, which might not
be optimal for their application or workflow.
Developers adopting this PEP would be able to use third party
platforms/infrastructure without having to
worry about which Python package installer they are required to use as
long as the provider also supports this PEP.
Developers using IDEs
Most IDEs only support pip or a few Python package installers.
Consequently, developers must use workarounds or hacky methods to
install their dependencies if they use an unsupported package
installer.
If the IDE uses/supports this PEP it would allow for
any developer to use whatever tooling they wanted to generate
their lock file while the IDE can use whatever tooling it wants to
performs actions with/on the lock file.
Developers working with other developers
Developers want to be able to use the installer of their choice while
working with other developers, but currently have to synchronize their
installer choice for compatibility of dependency installation. If all
preferred installers instead implemented the specified interface, it
would allow for cross use of installers, allowing developers to choose
an installer regardless of their collaboratorās preference.
Upgraders & Package Infrastructure Providers
Package upgraders and package infrastructure in CI/CD such as
Dependabot_, PyUP_, etc. currently support a few formats. They work
by parsing and editing the dependency files with
relevant package information such as upgrades, downgrades, or new
hashes. Similar to Platform and IDE providers, most of these providers
do not want to support N different formats.
Currently, these services/bots have to implement support for each
format individually. Inevitably, the most popular
formats are supported first, and less popular tools are often never
supported. By implementing this specification, these services/bots can
support one format, allowing users to select the tool
of their choice to generate the file. This will allow for more innovation
in the space, as platforms and IDEs are no longer forced to prematurely
select a āwinnerā tool which generates a lock file.
Open Source Community
Specifying installer requirements and adopting this PEP will reduce
the friction between Python package installers and peopleās workflows.
Consequently, it will reduce the friction between Python package
installers and 3rd party infrastructure/technologies such as PaaS or
IDEs. Overall, it will allow for easier development, deployment and
maintenance of Python projects as Python package installation becomes
simpler and more interoperable.
Specifying a single file format can also increase the pace of innovation
around installers and the generation of dependency graphs. By
decoupling generating the dependency graph details from installation It
allows for each area to grow and innovate independently. It also allows
more flexibilty in tool selection on either end of the dependency graph
and installation ends of this process.
=========
Rationale
To begin, two key terms should be defined. A locker is a tool
which produces a lock file. An installer is a tool which
consumes a lock file to install the appropriate dependencies.
The expected information flow to occur if this PEP were accepted, from
the specification of top-level dependencies to all necessary
dependencies being installed in a fresh environment, is:
- Read top-level dependencies from
pyproject.toml
(PEP 621). - Generate a lock file via a locker in
pyproject-lock.d/
. - Install the appropriate dependencies based entirely on information
contained in the lock file via an installer.
Goals
The file format should be machine-readable, machine-writable, and
human-readable. Since the assumption is the vast majority of lock
file will be generated by a locker tool, the format should be easy
to write by a locker. As install tools will be consuming the lock
file, the format also needs to be easily read by an installer. But the
format should also be readable by a person as people will inevitably
be performing audits on lock files. Having a format that does not lend
itself towards being read by people would hinder that. This includes
changes to a lock file being readable in a diff format for auditing
changes. It also means that understanding why something is in
the lock file should be comprehensible in a diff to assist in auditing
changes.
The lock file format needs to be general enough to support
cross-platform and cross-environment specifications of dependencies.
This allows having a single lock file which can work on a myriad of
platforms and environments when that makes sense. This has been shown
as a necessary feature by the various tools in the Python packaging
ecosystem which already have a lock file format (e.g. Pipenv_,
Poetry_, PDM_). This can be accomplished by allowing (but not
requiring) lockers to defer marker evaluation to the installer, and
thus permitting the locker to include a wider range of possible
dependencies that the installer has to work with.
The lock file also needs to support reproducible installations. If
one wants to restrict what the lock file covers to a single platform
to guarantee the exact dependencies and files which will be installed,
that should be doable. This can be critical in security contexts for
projects like SecureDrop_.
When a computation could be performed either in the locker or
installer, the preference is to perform the computation in the
locker. This is because the assumption is a locker will be executed
less frequently than an installer.
The installer should be able to resolve what to install based entirely
on platform/environment information and what is contained within the
lock file. There should be
no need to use network or other file system I/O in order to resolve
what to install.
The lock file should provide enough flexibility to allow lockers and
installers to innovate. While the lock file specification provides a
common denominator of functionality, it should not act as a ceiling
for functionality.
Non-Goals
Because of the expected size of lock files, no effort was put into
making lock files human-writable.
This PEP makes no attempt to make this work in any special way for
installers to use a lock file to install into a preexisting environment.
The assumption is the installer is installing into a new/fresh
environment.
=============
Specification
Details
Lock files MUST use the TOML_ file format thanks to its adoption by
PEP 518 for pyproject.toml
. This not only prevents the need to
have another file format in the Python packaging ecosystem, but it
also assists in making lock files human-readable.
Lock files MUST be kept in a directory named pyproject-lock.d
.
Lock files MUST end with a .toml
file extension. Projects may have
as many lock files as they want using whatever file name stems they
choose. This PEP prescribes no specific way to automatically select
between multiple lock files and installers SHOULD avoid guessing which
lock file is ābest-fittingā (this does not preclude situations where
only a single lock file with a certain name is expected to exist and
will be used by default, e.g. a documentation hosting site always
using a lock file named pyproject-lock.d/rftd.toml
when provided).
The following are the top-level keys of the TOML file data format.
version
The version of the lock file being used. The key MUST be specified and
it MUST be set to 1
. The number MUST always be an integer and it
MUST only increment in future updates to the specification. What
consistitutes a version number increase is left to future PEPs or
standards changes.
Tools reading a lock file whose version they donāt support MUST raise
an error.
[tool]
Tools may create their own sub-tables under the tool
table. The
rules for this table match those for pyproject.toml
and its
[tool]
table from the build system declaration spec
_.
[metadata]
A table containing data applying to the overall lock file.
metadata.marker
An optional key storing a string containing an environment marker as
specified in the dependency specifier spec
_.
The locker MAY specify an environment marker which specifies any
restrictions the lock file was generated under (e.g. specific Python
versions supported).
If the installer is installing for an environment which does not
satisfy the specified environment marker, the installer MUST raise an
error as the lock file does not support the environment.
metadata.tags
An optional array of inline tables representing
platform compatibility tags
_ that the lock file supports. The locker
MAY specify tables in the array which represent the compatibility the
lock file was generated for.
The tables have the possible keys of:
interpreter
abi
platform
representing the parts of the platform compatibility tags. Each key is
optional in a table. These keys MUST represent a single value, i.e.
the values are exploded and not compressed in wheel tag parlance.
If the environment an installer is installing for does not match
any table in the array (missing keys in the table means implicit
support for that part of the compatibility), the installer MUST raise
an error as the lock file does not support the environment.
metadata.needs
An array of strings representing the package specifiers for the
top-level/direct dependencies of the lock file as defined by the
dependency specifier spec
_ (i.e. the root of the dependency graph
for the lock file).
Lockers MUST only allow specifiers which may be satisfiable by the
lock file and the dependency graph the lock file encodes. Lockers MUST
normalize project names according to the simple repository API
_.
[package]
A table containing arrays of tables for each dependency recorded
in the lock file.
Each key of the table is the name of a package which MUST be
normalized according to the simple repository API
_. If extras are
specified as part of the project to install, the extras are to be
included in the key name and are to be sorted in lexicographic order.
Within the file, the tables for the projects MUST be
sorted by:
#. Project/key name in lexicographic order
#. Package version, newest/highest to older/lowest according to the
version specifiers spec
_
#. Extras via lexicographic order
package.<name>.version
A required string of the version of the package as specified by the
version specifiers spec
_.
package.<name>.needs
An optional key containing an array of strings following the
dependency specifier spec
_ which specify what other packages this
package depends on. See metadata.needs
for full details.
package.<name>.needed-by
A key containing an array of package names which depend on this
package. The package names MUST match the package name as used in the
package
table.
The lack of a needed-by
key infers that the package is a
top-level package listed in metadata.needs
.
package.<name>.code
An array of tables listing files that are available to satisfy
the installation of the package for the specified version in the
version
key.
Each table has a type
key which specifies how the code is stored.
All other keys in the table are dependent on the value set for
type
. The acceptable values for type
are listed below; all
other possible values are reserved for future use.
Tables in the array MUST be sorted in lexicographic order of the value
of type
, then lexicographic order for the value of url
.
When recording a table, the fields SHOULD be listed in the order
the fields are listed in this specification for consistency to make
diffs of a lock file easier to read.
For all types other than āwheelā, an INSTALLER MAY refuse to install
code to avoid arbitrary code execution during installation.
An installer MUST verify the hash of any specified file.
type="wheel"
āāāāāāāāāāāāāāāā
A wheel file
_ for the package version.
Supported keys in the table are:
-
url
: a string of location of the wheel file (use the
file:
protocol for the local file system) -
hash-algorithm
: a string of the algorithm used to generate the
hash value stored inhash-value
-
hash-value
: a string of the hash of the file contents -
interpreter-tag
: (optional) a string of the interpreter portion
of the wheel tag as specified by theplatform compatibility tags
_
spec -
abi-tag
: (optional) a string of the ABI portion of the wheel tag
as specified by theplatform compatibility tags
_ spec -
platform-tag
: (optional) a string of the platform portion of the
wheel tag as specified by theplatform compatibility tags
_ spec
If the keys related to platform compatibility tags
_ are absent then
the installer MUST infer the tags from the URLās file name. If any of
the platform compatibility tags
_ are specified by a key in the table
then a locker MUST provide all three related keys. The values of the
keys may be compressed tags.
type="sdist"
āāāāāāāāāāāāāāāā
A source distribution file
_ (sdist) for the package version.
-
url
: a string of location of the sdist file (use the
file:
protocol for the local file system) -
hash-algorithm
: a string of the algorithm used to generate the
hash value stored inhash-value
-
hash-value
: a string of the hash of the file contents
type="git"
āāāāāāāāāāāāāā
A Git_ version control repository for the package.
-
url
: a string of location of the repository (use the
file:
protocol for the local file system) -
commit
: a string of the commit of the repository which
represents the version of the package
The repository MUST follow the source distribution file
_ spec
for source trees, otherwise an error is to be raised by the locker.
As the commit ID for a Git repository is a hash of the repositoryās
contents, there is no hash to verify.
type="source tree"
āāāāāāāāāāāāāāāāāāāāāā
A source tree which can be used to build a wheel.
-
url
: a string of location of the source tree (use the
file:
protocol for the local file system) -
mime-type
: (optional) a string representing the MIME type of the
URL -
hash-algorithm
: (optional for a local directory) a string of the
algorithm used to generate the hash value stored inhash-value
-
hash-value
: (optional for a local directory) a string of the
hash of the file contents
The collection of files MUST follow the source distribution file
_
spec for source trees, otherwise an error is to be raised by the
locker.
Installers MAY use the file extension, MIME type from HTTP headers,
etc. to infer whether they support the storage mechanism used for the
source tree. If the MIME type cannot be inferred and it is not
specified via mime-type
then an error MUST be raised.
If the source tree is NOT a local directory, then an installer MUST
verify the hash value. Otherwise if the source tree is a local
directory then the hash-algorithm
and hash-value
keys MUST be
left out. The installer MAY warn the user of the use of a local
directory due to the potential change in code since the lock file
was created.
Example
::
version = 1
[tool]
# Tool-specific table ala PEP 518's `[tool]` table.
[metadata]
marker = "python_version>='3.6'"
needs = ["mousebender"]
[[package.attrs]]
version = "21.2.0"
needed-by = ["mousebender"]
[[package.attrs.code]]
type = "wheel"
url = "https://files.pythonhosted.org/packages/20/a9/ba6f1cd1a1517ff022b35acd6a7e4246371dfab08b8e42b829b6d07913cc/attrs-21.2.0-py2.py3-none-any.whl"
hash-algorithm="sha256"
hash-value = "149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"
[[package.mousebender]]
version = "2.0.0"
needs = ["attrs>=19.3", "packaging>=20.3"]
[[package.mousebender.code]]
type = "sdist"
url = "https://files.pythonhosted.org/packages/35/bc/db77f8ca1ccf85f5c3324e4f62fc74bf6f6c098da11d7c30ef6d0f43e859/mousebender-2.0.0.tar.gz"
hash-algorithm = "sha256"
hash-value = "c5953026378e5dcc7090596dfcbf73aa5a9786842357273b1df974ebd79bd760"
[[package.mousebender.code]]
type = "wheel"
url = "https://files.pythonhosted.org/packages/f4/b3/f6fdbff6395e9b77b5619160180489410fb2f42f41272994353e7ecf5bdf/mousebender-2.0.0-py3-none-any.whl"
hash-algorithm = "sha256"
hash-value = "a6f9adfbd17bfb0e6bb5de9a27083e01dfb86ed9c3861e04143d9fd6db373f7c"
[[package.packaging]]
version = "20.9"
needs = ["pyparsing>=2.0.2"]
needed-by = ["mousebender"]
[[package.packaging.code]]
type = "git"
url = "https://github.com/pypa/packaging.git"
commit = "53fd698b1620aca027324001bf53c8ffda0c17d1"
[[package.pyparsing]]
version = "2.4.7"
needed-by = ["packaging"]
[[package.pyparsing.code]]
type="wheel"
url = "https://files.pythonhosted.org/packages/8a/bb/488841f56197b13700afd5658fc279a2025a39e22449b7cf29864669b15d/pyparsing-2.4.7-py2.py3-none-any.whl"
hash-algorithm="sha256"
hash-value="ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
interpreter-tag = "py2.py3"
abi-tag = "none"
platform-tag = "any"
Installer Expectations
Installers MUST implement the
direct URL origin of installed distributions spec
_ as all packages
installed from a lock file inherently originate from a URL and not a
search of an index by package name and version.
Installers MUST error out if they encounter something they are unable
to handle (e.g. lack of environment marker support).
Example Flow
#. Have the user specify which lock file they would like to use in
pyproject-lock.d
(e.g. dev
, prod
)
#. Check if the environment supports what is specified in
metadata.tags
; error out if it doesnāt
#. Check if the environment supports what is specified in
metadata.marker
; error out if it doesnāt
#. Gather the list of package names from metadata.needs
, and for
each listed package ā¦
#. Resolve any markers to find the appropriate package to install
#. Find the most appropriate code to install for the package
#. Repeat the above steps for packages listed in the needs
key
for each package found to install
#. For each project collected to install ā¦
#. Gather the specified code for the package
#. Verify hashes of code
#. Install the packages (if necessary)
=======================
Backwards Compatibility
As there is no pre-existing specification regarding lock files, there
are no explicit backwards compatibility concerns.
As for pre-existing tools that have their own lock file, some updating
will be required. Most document the lock file name, but not its
contents, in which case the file name of the lock file(s) is the
important part. For projects which do not commit their lock file to
version control, they will need to update the equivalent of their
.gitignore
file. For projects that do commit their lock file to
version control, what file(s) get committed will need an update.
For projects which do document their lock file format like pipenv_,
they will very likely need a new major version release.
Specifically for Poetry_, it has an
export command <https://python-poetry.org/docs/cli/#export>
_ which
should allow Poetry to support this lock file format even if the
project chose not to adopt this PEP as Poetryās primary lock file
format.
=====================
Security Implications
A lock file should not introduce security issues but instead help
solve them. By requiring the recording of hashes of code, a lock file
is able to help prevent tampering with code since the hash details
were recorded. A lock file also helps prevent unexpected package
updates being installed which may be malicious.
=================
How to Teach This
Teaching of this PEP will very much be dependent on the lockers and
installers being used for day-to-day use. Conceptually, though, users
could be taught that the pyproject-lock.d
directory contains files
which specify what should be installed for a project to work. The
benefits of consistency and security should be emphasized to help
users realize why they should care about lock files.
========================
Reference Implementation
No proof-of-concept or reference implementation currently exists.
==============
Rejected Ideas
File Formats Other Than TOML
JSON_ was briefly considered, but due to:
#. TOML already being used for pyproject.toml
#. TOML being more human-readable
#. TOML leading to better diffs
the decision was made to go with TOML. There was some concern over
Pythonās standard library lacking a TOML parser, but most packaging
tools already use a TOML parser thanks to pyproject.toml
so this
issue did not seem to be a showstopper. Some have also argued against
this concern in the past by the fact that if packaging tools abhor
installing dependencies and feel they canāt vendor a package then the
packaging ecosystem has much bigger issues to rectify than needing to
depend on a third-party TOML parser.
Alternative Name to pyproject-lock.d
The name __lockfile__
was briefly considered, but the directory
would not sort next to pyproject.toml
in instances where files
and directories were sorted together in lexicographic order. The
current naming is also more obvious in terms of its relationship
to pyproject.toml
.
Supporting a Single Lock File
At one point the idea of not using a directory of lock files but a
single lock file which contained all possible lock information was
considered. But it quickly became apparent that trying to devise a
data format which could encompass both a lock file format which could
support multiple environments as well as strict lock outcomes for
reproducible builds would become quite complex and cumbersome.
The idea of supporting a directory of lock files as well as a single
lock file named pyproject-lock.toml
was also considered. But any
possible simplicity from skipping the directory in the case of a
single lock file seemed unnecessary. Trying to define appropriate
logic for what should be the pyproject-lock.toml
file and what
should go into pyproject-lock.d
seemed unnecessarily complicated.
Using a Flat List Instead of a Dependency Graph
The first version of this PEP proposed that the lock file have no
concept of a dependency graph. Instead, the lock file would list
exactly what should be installed for a specific platform such that
installers did not have to make any decisions about what to install,
only validating that the lock file would work for the target platform.
This idea was eventually rejected due to the number of combinations
of potential PEP 508 environment markers. The decision was made that
trying to have lockers generate all possible combinations when a
project wants to be cross-platform would be too much.
Being Concerned About Different Dependencies Per Wheel File For a Project
It is technically possible for a project to specify different
dependencies between its various wheel files. Taking that into
consideration would then require the lock file to operate not
per-project but per-file. Luckily, specifying different dependencies
in this way is very rare and frowned upon and so it was deemed not
worth supporting.
Use Wheel Tags in the File Name
Instead of having the metadata.tags
field there was a suggestion
of encoding the tags into the file name. But due to the addition of
the metadata.marker
field and what to do when no tags were needed,
the idea was dropped.
Using Semantic Versioning for version
Instead of a monotonically increasing integer, using a float was
considered to attempt to convey semantic versioning. In the end,
though, it was deemed more hassle than it was worth as adding a new
key would likely constitute a āmajorā version change (only if the
key was entirely optional would it be considered āminorā), and
experience with the core metadata spec
_ suggests thereās a bigger
chance parsing will be relaxed and made more strict which is also a
āmajorā change. As such, the simplicity of using an integer made
sense.
Alternative Names for needs
Some other names for what became needs
were installs
and
dependencies
. In the end a Python beginner was asked which term
they preferred and they found needs
clearer. Since there wasnāt
any reason to disagree with that, the decision was to go with
needs
.
Alternative Names for needed-by
Other names that were considered were dependents
, depended-by
,
, supports
and required-by
. In the end, needed-by
made
sense and tied into needs
.
Only Allowing a Single Code Location For a Project
While reproducibility is serviced better by only allowing a single
code location, it limits usability for situations where one wants to
support multiple platforms with a single lock file (which the community
has shown is desired).
Support for Branches and Tags for Git
Due to the direct URL origin of installed distributions spec
_
supporting the specification of branches and tags, it was suggested
that lock files support the same thing. But because branches and tags
can change what commit they point to between locking and installation,
that was viewed as a security concern (Git commit IDs are hashes of
metadata and thus are viewed as immutable).
Accepting PEP 650
PEP 650 was an earlier attempt at trying to tackle this problem by
specifying an API for installers instead of standardizing on a lock file
format (ala PEP 517). The
initial response <https://discuss.python.org/t/pep-650-specifying-installer-requirements-for-python-projects/6657/>
__
to PEP 650 could be considered mild/lukewarm. People seemed to be
consistently confused over which tools should provide what functionality
to implement the PEP. It also potentially incurred more overhead as
it would require executing Python APIs to perform any actions involving
packaging.
This PEP chose to standardize around an artifact instead of an API
(ala PEP 621). This would allow for more tool integrations as it
removes the need to specifically use Python to do things such as
create a lock file, update it, or even install packages listed in
a lock file. It also allows for easier introspection by forcing
dependency graph details to be written in a human-readable format.
It also allows for easier sharing of knowledge by standardizing what
people need to know more (e.g. tutorials become more portable between
tools when it comes to understanding the artifact they produce). Itās
also simply the approach other language communities have taken and seem
to be happy with.
===========
Open Issues
Allow for Tool-Specific type
Values
It has been suggested to allow for custom type
values in the
code
table. They would be prefixed with x-
and followed by
the toolās name and then the type, i.e. x-<tool>-<type>
. This
would provide enough flexibility for things such as other version
control systems, innovative container formats, etc. to be officially
usable in a lock file.
Support Variable Expansion in the url
field
This could include predefined variables like PROJECT_ROOT
for the
directory containing pyproject-lock.d
so URLs to local directories
and files could be relative to the project itself.
Environment variables could be supported to avoid hardcoding things
such as user credentials for Git.
Donāt Require Lock Files Be in a pyproject-lock.d
directory
It has been suggested that since installers may very well allow users
to specify the path to a lock file that having this PEP say that
"MUST be kept in a directory named pyproject-lock.d
" is pointless
as it is bound to be broken. As such, the suggestion is to change
āMUSTā to āSHOULDā.
Record the Date of When the Lock File was Generated
Since the modification date is not guaranteed to match when the lock
file was generated, it has been suggested to record the date as part
of the fileās metadata. The question, though, is how useful is this
information and can lockers that care put it into their [tool]
table instead of mandating it be set?
Locking Build Dependencies
Thanks to PEP 518, source trees and sdists can specify what build
tools must be installed in order to build a wheel (or sdist in the
case of a source tree). It has been suggested that the lock file also
record such packages so to increase how reproducible an installation
can be.
There is nothing currently in this PEP, though, that prohibits a
locker from recording build tools thanks to metadata.needs
acting
as the entry point for calculating what to install. There is also a
cost in downloading all potential sdists and source trees, reading
their pyproject.toml
files, and then calculating their build
dependencies for locking purposes for which not everyone will want to
pay the cost for.
Recording the Requires-Dist
Input to the Lockerās Resolver
While the needs
key allows for recording dependency specifiers,
this PEP does not currently require the needs
key to record the
exact Requires-Dist
metadata that was used to calculate the
lock file. It has been suggested that recording the inputs would help
in auditing the outcome of the lock file.
If this were to be done, it would be an key named requested
which
lived along side needs
and would only be specified if it would
differ from what is specified in needs
.
===============
Acknowledgments
Thanks to Frost Ming of PDM_ and SƩbastien Eustace of Poetry_ for
providing input around dynamic install-time resolution of PEP 508
requirements.
Thanks to Kushal Das for making sure reproducible builds stayed a
concern for this PEP.
Thanks to Andrea McInnes for settling the bikeshedding and choosing
the paint colour of needs
.
=========
Copyright
This document is placed in the public domain or under the
CC0-1.0-Universal license, whichever is more permissive.
⦠_build system declaration spec: Declaring build system dependencies ā Python Packaging User Guide
⦠_core metadata spec: Core metadata specifications ā Python Packaging User Guide
⦠_Dart: https://dart.dev/
⦠_Dependabot: https://dependabot.com/
⦠_dependency specifier spec: Dependency specifiers ā Python Packaging User Guide
⦠_direct URL origin of installed distributions spec: Recording the Direct URL Origin of installed distributions ā Python Packaging User Guide
⦠_Git: https://git-scm.com/
⦠_JSON: https://www.json.org/
⦠_npm: https://www.npmjs.com/
⦠_PDM: pdm · PyPI
⦠_pip-tools: pip-tools · PyPI
⦠_Pipenv: pipenv · PyPI
⦠_platform compatibility tags: Platform compatibility tags ā Python Packaging User Guide
⦠_Poetry: poetry · PyPI
⦠_Pyflow: pyflow · PyPI
⦠_PyUP: https://pyup.io/
⦠_Rust: https://www.rust-lang.org/
⦠_SecureDrop: https://securedrop.org/
⦠_simple repository API: Simple repository API ā Python Packaging User Guide
⦠_source distribution file: Source distribution format ā Python Packaging User Guide
⦠_TOML: https://toml.io
⦠_version specifiers spec: Version specifiers ā Python Packaging User Guide
⦠_wheel file: Binary distribution format ā Python Packaging User Guide
ā¦
Local Variables:
mode: indented-text
indent-tabs-mode: nil
sentence-end-double-space: t
fill-column: 70
coding: utf-8
End: