PEP 723: Finalize (#3297)
Co-authored-by: Adam Turner <9087854+aa-turner@users.noreply.github.com>
This commit is contained in:
parent
b03237ebc8
commit
875160e4d3
525
pep-0723.rst
525
pep-0723.rst
|
@ -11,6 +11,7 @@ Content-Type: text/x-rst
|
|||
Created: 04-Aug-2023
|
||||
Post-History: `04-Aug-2023 <https://discuss.python.org/t/30979>`__,
|
||||
`06-Aug-2023 <https://discuss.python.org/t/31151>`__,
|
||||
`23-Aug-2023 <https://discuss.python.org/t/32149>`__,
|
||||
Replaces: 722
|
||||
|
||||
|
||||
|
@ -55,23 +56,21 @@ and not in an external file.
|
|||
|
||||
We choose to follow the latest developments of other modern packaging
|
||||
ecosystems (namely `Go`__ and provisionally `Rust`__) by embedding the existing
|
||||
`metadata standard <pyproject metadata_>`_ that is used to describe
|
||||
projects.
|
||||
file used to describe projects, in our case ``pyproject.toml``.
|
||||
|
||||
__ https://github.com/erning/gorun
|
||||
__ https://rust-lang.github.io/rfcs/3424-cargo-script.html
|
||||
|
||||
The format is intended to bridge the gap between different types of users
|
||||
of Python. Knowledge of how to write project metadata will be directly
|
||||
transferable to all use cases, whether writing a script or maintaining a
|
||||
project that is distributed via PyPI. Additionally, users will benefit from
|
||||
seamless interoperability with tools that are already familiar with the format.
|
||||
of Python. Users will benefit from seamless interoperability with tools that
|
||||
already work with TOML.
|
||||
|
||||
One of the central themes we discovered from the recent
|
||||
`packaging survey <https://discuss.python.org/t/22420>`__ is that users have
|
||||
begun getting frustrated with the lack of unification regarding both tooling
|
||||
and specs. Adding yet another way to define metadata, even for a currently
|
||||
unsatisfied use case, would further fragment the community.
|
||||
and specs. Adding yet another metadata format (like :pep:`722` syntax for a
|
||||
list of dependencies), even for a currently unsatisfied use case, would
|
||||
further fragment the community.
|
||||
|
||||
The following are some of the use cases that this PEP wishes to support:
|
||||
|
||||
|
@ -79,19 +78,15 @@ The following are some of the use cases that this PEP wishes to support:
|
|||
an example, the interface would be simply
|
||||
``hatch run /path/to/script.py [args]`` and Hatch will manage the
|
||||
environment for that script. Such tools could be used as shebang lines on
|
||||
non-Windows systems e.g. ``#!/usr/bin/env hatch run``. You would also be
|
||||
able to enter a shell into that environment like other projects by doing
|
||||
``hatch -p /path/to/script.py shell`` since the project flag would learn
|
||||
that project metadata could be read from a single file.
|
||||
non-Windows systems e.g. ``#!/usr/bin/env hatch run``.
|
||||
* A script that desires to transition to a directory-type project. A user may
|
||||
be rapidly prototyping locally or in a remote REPL environment and then
|
||||
decide to transition to a more formal project layout if their idea works
|
||||
out. This intermediate script stage would be very useful to have fully
|
||||
reproducible bug reports. By using the same metadata format, the user can
|
||||
simply copy and paste the metadata into a ``pyproject.toml`` file and
|
||||
continue working without having to learn a new format. More likely, even, is
|
||||
that tooling will eventually support this transformation with a single
|
||||
command.
|
||||
reproducible bug reports. By using the same format, the user can simply copy
|
||||
and paste the metadata into a ``pyproject.toml`` file and continue working
|
||||
without having to learn a new format. More likely, even, is that tooling will
|
||||
eventually support this transformation with a single command.
|
||||
* Users that wish to avoid manual dependency management. For example, package
|
||||
managers that have commands to add/remove dependencies or dependency update
|
||||
automation in CI that triggers based on new versions or in response to
|
||||
|
@ -101,50 +96,68 @@ The following are some of the use cases that this PEP wishes to support:
|
|||
Specification
|
||||
=============
|
||||
|
||||
Any Python script may assign a variable named ``__pyproject__`` to a multi-line
|
||||
*double-quoted* string literal (``"""``) containing a valid TOML document. The
|
||||
variable MUST start at the beginning of the line and the opening of the string
|
||||
MUST be on the same line as the assignment. The closing of the string MUST be
|
||||
on a line by itself, and MUST NOT be indented.
|
||||
This PEP defines a metadata comment block format loosely inspired [2]_ by
|
||||
`reStructuredText Directives`__.
|
||||
|
||||
When there are multiple ``__pyproject__`` variables defined, tools MUST produce
|
||||
an error.
|
||||
__ https://docutils.sourceforge.io/docs/ref/rst/directives.html
|
||||
|
||||
The TOML document MUST NOT contain multi-line double-quoted strings, as that
|
||||
would conflict with the Python string containing the document. Single-quoted
|
||||
multi-line TOML strings may be used instead.
|
||||
Any Python script may have top-level comment blocks that start with the line
|
||||
``# /// TYPE`` where ``TYPE`` determines how to process the content, and ends
|
||||
with the line ``# ///``. Every line between these two lines MUST be a comment
|
||||
starting with ``#``. If there are characters after the ``#`` then the first
|
||||
character MUST be a space. The embedded content is formed by taking away the
|
||||
first two characters of each line if the second character is a space, otherwise
|
||||
just the first character (which means the line consists of only a single
|
||||
``#``).
|
||||
|
||||
This is the canonical regular expression that MUST be used to parse the
|
||||
metadata:
|
||||
|
||||
.. code:: text
|
||||
|
||||
(?ms)^__pyproject__ *= *"""\\?$(.+?)^"""$
|
||||
|
||||
In circumstances where there is a discrepancy between the regular expression
|
||||
and the text specification, the regular expression takes precedence.
|
||||
When there are multiple comment blocks of the same ``TYPE`` defined, tools MUST
|
||||
produce an error.
|
||||
|
||||
Tools reading embedded metadata MAY respect the standard Python encoding
|
||||
declaration. If they choose not to do so, they MUST process the file as UTF-8.
|
||||
|
||||
This document MAY include the ``[project]``, ``[tool]`` and ``[build-system]``
|
||||
tables.
|
||||
This is the canonical regular expression that MAY be used to parse the
|
||||
metadata:
|
||||
|
||||
The ``[project]`` table differs in the following ways:
|
||||
.. code:: text
|
||||
|
||||
* The ``name`` and ``version`` fields are not required and MAY be defined
|
||||
dynamically by tools if the user does not define them
|
||||
* These fields do not need to be listed in the ``dynamic`` array
|
||||
(?m)^# /// (?P<type>[a-zA-Z0-9-]+)$\s(?P<content>(^#(| .*)$\s)+)^# ///$
|
||||
|
||||
Non-script running tools MAY choose to alter their behavior based on
|
||||
configuration that is stored in their expected ``[tool]`` sub-table.
|
||||
In circumstances where there is a discrepancy between the text specification
|
||||
and the regular expression, the text specification takes precedence.
|
||||
|
||||
Build frontends SHOULD NOT use the backend defined in the ``[build-system]``
|
||||
table to build scripts with embedded metadata. This requires a new PEP because
|
||||
the current methods defined in :pep:`517` act upon a directory, not a file.
|
||||
We use ``SHOULD NOT`` instead of ``MUST NOT`` in order to allow tools to
|
||||
experiment [2]_ with such functionality before we standardize (indeed this
|
||||
would be a requirement).
|
||||
Tools MUST NOT read from metadata blocks with types that have not been
|
||||
standardized by this PEP or future ones.
|
||||
|
||||
pyproject type
|
||||
--------------
|
||||
|
||||
The first type of metadata block is named ``pyproject`` which represents
|
||||
content similar to [3]_ what one would see in a ``pyproject.toml`` file.
|
||||
|
||||
This document MAY include the ``[run]`` and ``[tool]`` tables.
|
||||
|
||||
The :pep:`tool table <518#tool-table>` MAY be used by any tool, script runner
|
||||
or otherwise, to configure behavior.
|
||||
|
||||
The ``[run]`` table MAY include the following optional fields:
|
||||
|
||||
* ``dependencies``: A list of strings that specifies the runtime dependencies
|
||||
of the script. Each entry MUST be a valid :pep:`508` dependency.
|
||||
* ``requires-python``: A string that specifies the Python version(s) with which
|
||||
the script is compatible. The value of this field MUST be a valid
|
||||
:pep:`version specifier <440#version-specifiers>`.
|
||||
* ``version``: A string that specifies the version of the script. The value of
|
||||
this field MUST be a valid :pep:`440` version.
|
||||
|
||||
Any future PEPs that define additional fields for the ``[run]`` table when used
|
||||
in a ``pyproject.toml`` file MUST include the aforementioned fields exactly as
|
||||
specified. The fields defined by this PEP are equally as applicable to
|
||||
full-fledged projects as they are to single-file scripts.
|
||||
|
||||
Script runners MUST error if the specified ``dependencies`` cannot be provided.
|
||||
Script runners SHOULD error if no version of Python that satisfies the specified
|
||||
``requires-python`` can be provided.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
@ -153,14 +166,14 @@ The following is an example of a script with an embedded ``pyproject.toml``:
|
|||
|
||||
.. code:: python
|
||||
|
||||
__pyproject__ = """
|
||||
[project]
|
||||
requires-python = ">=3.11"
|
||||
dependencies = [
|
||||
"requests<3",
|
||||
"rich",
|
||||
]
|
||||
"""
|
||||
# /// pyproject
|
||||
# [run]
|
||||
# requires-python = ">=3.11"
|
||||
# dependencies = [
|
||||
# "requests<3",
|
||||
# "rich",
|
||||
# ]
|
||||
# ///
|
||||
|
||||
import requests
|
||||
from rich.pretty import pprint
|
||||
|
@ -169,9 +182,9 @@ The following is an example of a script with an embedded ``pyproject.toml``:
|
|||
data = resp.json()
|
||||
pprint([(k, v["title"]) for k, v in data.items()][:10])
|
||||
|
||||
The following is an example of a proposed syntax for single-file Rust project
|
||||
that embeds their equivalent of ``pyproject.toml``,
|
||||
which is called ``Cargo.toml``:
|
||||
The following [4]_ is an example of a proposed syntax for single-file Rust
|
||||
projects that embeds their equivalent of ``pyproject.toml``, which is called
|
||||
``Cargo.toml``:
|
||||
|
||||
.. code:: rust
|
||||
|
||||
|
@ -187,26 +200,6 @@ which is called ``Cargo.toml``:
|
|||
println!("Did our date match? {}", re.is_match("2014-01-01"));
|
||||
}
|
||||
|
||||
One important thing to note is that the metadata is embedded in a
|
||||
`doc-comment`_ (their equivalent of docstrings).
|
||||
`Other syntaxes <cargo embedded manifest_>`_ are under consideration
|
||||
within the Rust project,
|
||||
including using attributes which are somewhat like a
|
||||
syntactically recognized equivalent of dunder variables,
|
||||
with the key difference between Rust's choice and this PEP being that
|
||||
any valid Rust syntax will be allowed,
|
||||
requiring one of the Rust syntax parsers to work with it, like `syn`__.
|
||||
|
||||
__ https://crates.io/crates/syn
|
||||
|
||||
We argue that our choice, in comparison to the `doc-comment`_ approach,
|
||||
is easier to read and provides easier edits for humans by virtue
|
||||
of the contents starting at the beginning of lines so would precisely match
|
||||
the contents of a ``pyproject.toml`` file.
|
||||
It is also is easier for tools to parse and modify this continuous block
|
||||
of text which was `one of the concerns <cargo embedded manifest_>`_
|
||||
raised in the Rust pre-RFC.
|
||||
|
||||
Reference Implementation
|
||||
========================
|
||||
|
||||
|
@ -215,34 +208,53 @@ higher.
|
|||
|
||||
.. code:: python
|
||||
|
||||
import re, tomllib
|
||||
import re
|
||||
import tomllib
|
||||
|
||||
REGEX = r'(?ms)^__pyproject__ *= *"""\\?$(.+?)^"""$'
|
||||
REGEX = r'(?m)^# /// (?P<type>[a-zA-Z0-9-]+)$\s(?P<content>(^#(| .*)$\s)+)^# ///$'
|
||||
|
||||
def read(script: str) -> dict | None:
|
||||
matches = list(re.finditer(REGEX, script))
|
||||
if len(matches) > 1:
|
||||
raise ValueError('Multiple __pyproject__ definitions found')
|
||||
elif len(matches) == 1:
|
||||
return tomllib.loads(matches[0])
|
||||
else:
|
||||
return None
|
||||
def read(script: str) -> dict | None:
|
||||
name = 'pyproject'
|
||||
matches = list(
|
||||
filter(lambda m: m.group('type') == name, re.finditer(REGEX, script))
|
||||
)
|
||||
if len(matches) > 1:
|
||||
raise ValueError(f'Multiple {name} blocks found')
|
||||
elif len(matches) == 1:
|
||||
return tomllib.loads(matches[0])
|
||||
else:
|
||||
return None
|
||||
|
||||
Often tools will edit dependencies like package managers or dependency update
|
||||
automation in CI. The following is a crude example of modifying the content
|
||||
using the ``tomlkit`` library.
|
||||
using the ``tomlkit`` library__.
|
||||
|
||||
__ https://tomlkit.readthedocs.io/en/latest/
|
||||
|
||||
.. code:: python
|
||||
|
||||
import re, tomlkit
|
||||
import re
|
||||
|
||||
def add(script: str, dependency: str) -> str:
|
||||
match = re.search(r'(?ms)^__pyproject__ *= *"""\\?$(.+?)^"""$', script)
|
||||
config = tomlkit.parse(match.group(1))
|
||||
config['project']['dependencies'].append(dependency)
|
||||
import tomlkit
|
||||
|
||||
start, end = match.span(1)
|
||||
return script[:start] + tomlkit.dumps(config) + script[end:]
|
||||
REGEX = r'(?m)^# /// (?P<type>[a-zA-Z0-9-]+)$\s(?P<content>(^#(| .*)$\s)+)^# ///$'
|
||||
|
||||
def add(script: str, dependency: str) -> str:
|
||||
match = re.search(REGEX, script)
|
||||
content = ''.join(
|
||||
line[2:] if line.startswith('# ') else line[1:]
|
||||
for line in match.group('content').splitlines(keepends=True)
|
||||
)
|
||||
|
||||
config = tomlkit.parse(content)
|
||||
config['project']['dependencies'].append(dependency)
|
||||
new_content = ''.join(
|
||||
f'# {line}' if line.strip() else f'#{line}'
|
||||
for line in tomlkit.dumps(config).splitlines(keepends=True)
|
||||
)
|
||||
|
||||
start, end = match.span('content')
|
||||
return script[:start] + new_content + script[end:]
|
||||
|
||||
Note that this example used a library that preserves TOML formatting. This is
|
||||
not a requirement for editing by any means but rather is a "nice to have"
|
||||
|
@ -252,26 +264,11 @@ feature.
|
|||
Backwards Compatibility
|
||||
=======================
|
||||
|
||||
At the time of writing, the ``__pyproject__`` variable only appears five times
|
||||
`on GitHub`__ and four of those belong to a user who appears to already be
|
||||
using this PEP's exact format.
|
||||
At the time of writing, the ``# /// pyproject`` block comment starter does not
|
||||
appear `on GitHub`__. Therefore, there is little risk of existing scripts being
|
||||
broken by this PEP.
|
||||
|
||||
__ https://github.com/search?q=__pyproject__&type=code
|
||||
|
||||
For example, `this script`__ uses ``matplotlib`` and ``pandas`` to plot a
|
||||
timeseries. It is a good example of a script that you would see in the wild:
|
||||
self-contained and short.
|
||||
|
||||
__ https://github.com/cjolowicz/scripts/blob/31c61e7dad8d17e0070b080abee68f4f505da211/python/plot_timeseries.py
|
||||
|
||||
This user's tooling invokes scripts by creating a project at runtime using the
|
||||
embedded metadata and then uses an entry point that references the main
|
||||
function.
|
||||
|
||||
This PEP allows this user's tooling to remove that extra step of indirection.
|
||||
|
||||
This PEP's author has discovered after writing a draft that this pattern is
|
||||
used in the wild by others (sent private messages).
|
||||
__ https://github.com/search?q=%22%23+%2F%2F%2F+pyproject%22&type=code
|
||||
|
||||
|
||||
Security Implications
|
||||
|
@ -284,34 +281,69 @@ installed in the user's environment.
|
|||
The risk here is part of the functionality of the tool being used to run the
|
||||
script, and as such should already be addressed by the tool itself. The only
|
||||
additional risk introduced by this PEP is if an untrusted script with a
|
||||
embedded metadata is run, when a potentially malicious dependency might be
|
||||
installed.
|
||||
embedded metadata is run, when a potentially malicious dependency or transitive
|
||||
dependency might be installed.
|
||||
|
||||
This risk is addressed by the normal good practice of reviewing code
|
||||
before running it. Additionally, tools may be able to provide locking
|
||||
functionality when configured by their ``[tool]`` sub-table to, for example,
|
||||
add the resolution result as managed metadata somewhere in the script (this
|
||||
is what Go's ``gorun`` can do).
|
||||
before running it. Additionally, tools may be able to provide
|
||||
`locking functionality <723-tool-configuration_>`__ to ameliorate this risk.
|
||||
|
||||
|
||||
How to Teach This
|
||||
=================
|
||||
|
||||
Since the format chosen is the same as the official metadata standard, we can
|
||||
have a page that describes how to embed the metadata in scripts and to learn
|
||||
about metadata itself direct users to the living document that describes
|
||||
`project metadata <pyproject metadata_>`_.
|
||||
To embed metadata in a script, define a comment block that starts with the
|
||||
line ``# /// pyproject`` and ends with the line ``# ///``. Every line between
|
||||
those two lines must be a comment and the full content is derived by removing
|
||||
the first two characters. The ``pyproject`` type indicates that the content
|
||||
is TOML and resembles a ``pyproject.toml`` file.
|
||||
|
||||
We will document that the name and version fields in the ``[project]`` table
|
||||
may be elided for simplicity. Additionally, we will have guidance explaining
|
||||
that single-file scripts cannot (yet) be built into a wheel via standard means.
|
||||
.. code:: python
|
||||
|
||||
We will explain that it is up to individual tools whether or not their behavior
|
||||
is altered based on the embedded metadata. For example, every script runner may
|
||||
not be able to provide an environment for specific Python versions as defined
|
||||
by the ``requires-python`` field.
|
||||
# /// pyproject
|
||||
# [run]
|
||||
# dependencies = [
|
||||
# "requests<3",
|
||||
# "rich",
|
||||
# ]
|
||||
# requires-python = ">=3.11"
|
||||
# version = "0.1.0"
|
||||
# ///
|
||||
|
||||
Finally, we may want to list some tools that support this PEP's format.
|
||||
The two allowed tables are ``[run]`` and ``[tool]``. The ``[run]`` table may
|
||||
contain the following fields:
|
||||
|
||||
.. list-table::
|
||||
|
||||
* - Field
|
||||
- Description
|
||||
- Tool behavior
|
||||
|
||||
* - ``dependencies``
|
||||
- A list of strings that specifies the runtime dependencies of the script.
|
||||
Each entry must be a valid :pep:`508` dependency.
|
||||
- Tools will error if the specified dependencies cannot be provided.
|
||||
|
||||
* - ``requires-python``
|
||||
- A string that specifies the Python version(s)
|
||||
with which the script is compatible.
|
||||
The value of this field must be a valid
|
||||
:pep:`version specifier <440#version-specifiers>`.
|
||||
- Tools might error if no version of Python that satisfies
|
||||
the constraint can be executed.
|
||||
|
||||
* - ``version``
|
||||
- A string that specifies the version of the script.
|
||||
The value of this field must be a valid :pep:`440` version.
|
||||
- Tools may use this however they wish, if defined.
|
||||
|
||||
It is up to individual tools whether or not their behavior is altered based on
|
||||
the embedded metadata. For example, every script runner may not be able to
|
||||
provide an environment for specific Python versions as defined by the
|
||||
``requires-python`` field.
|
||||
|
||||
The :pep:`tool table <518#tool-table>` may be used by any tool, script runner
|
||||
or otherwise, to configure behavior.
|
||||
|
||||
|
||||
Recommendations
|
||||
|
@ -321,28 +353,6 @@ Tools that support managing different versions of Python should attempt to use
|
|||
the highest available version of Python that is compatible with the script's
|
||||
``requires-python`` metadata, if defined.
|
||||
|
||||
For projects that have large multi-line external metadata to embed like a
|
||||
README file, it is recommended that they become directories with a
|
||||
``pyproject.toml`` file. While this is technically allowed, it is strongly
|
||||
discouraged to have large chunks of multi-line metadata and is indicative
|
||||
of the fact that a script has graduated to a more traditional layout.
|
||||
|
||||
If the content is small, for example in the case of internal packages, it is
|
||||
recommended that multi-line *single-quoted* TOML strings (``'''``) be used.
|
||||
For example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
__pyproject__ = """
|
||||
[project]
|
||||
readme.content-type = "text/markdown"
|
||||
readme.text = '''
|
||||
# Some Project
|
||||
Please refer to our corporate docs
|
||||
for more information.
|
||||
'''
|
||||
"""
|
||||
|
||||
|
||||
Tooling buy-in
|
||||
==============
|
||||
|
@ -366,35 +376,6 @@ have committed to implementing support should it be accepted:
|
|||
Rejected Ideas
|
||||
==============
|
||||
|
||||
Why not limit to specific metadata fields?
|
||||
------------------------------------------
|
||||
|
||||
By limiting the metadata to a specific set of fields, for example just
|
||||
``dependencies``, we would prevent legitimate use cases both known and unknown.
|
||||
The following are examples of known use cases:
|
||||
|
||||
* ``requires-python``: For tools that support managing Python installations,
|
||||
this allows users to target specific versions of Python for new syntax
|
||||
or standard library functionality.
|
||||
* ``version``: It is quite common to version scripts for persistence even when
|
||||
using a VCS like Git. When not using a VCS it is even more common to version,
|
||||
for example the author has been in multiple time sensitive debugging sessions
|
||||
with customers where due to the airgapped nature of the environment, the only
|
||||
way to transfer the script was via email or copying and pasting it into a
|
||||
chat window. In these cases, versioning is invaluable to ensure that the
|
||||
customer is using the latest (or a specific) version of the script.
|
||||
* ``description``: For scripts that don't need an argument parser, or if the
|
||||
author has never used one, tools can treat this as help text which can be
|
||||
shown to the user.
|
||||
|
||||
By not allowing the ``[tool]`` section, we would prevent especially script
|
||||
runners from allowing users to configure behavior. For example, a script runner
|
||||
may support configuration instructing to run scripts in containers for
|
||||
situations in which there is no cross-platform support for a dependency or if
|
||||
the setup is too complex for the average user like when requiring Nvidia
|
||||
drivers. Situations like this would allow users to proceed with what they want
|
||||
to do whereas otherwise they may stop at that point altogether.
|
||||
|
||||
.. _723-comment-block:
|
||||
|
||||
Why not use a comment block resembling requirements.txt?
|
||||
|
@ -436,9 +417,9 @@ would live as single-file scripts:
|
|||
executable or script runner.
|
||||
|
||||
This PEP argues that reusing our TOML-based metadata format is the best for
|
||||
each category of user and that the block comment is only approachable for
|
||||
those who have familiarity with ``requirements.txt``, which represents a
|
||||
small subset of users.
|
||||
each category of user and that the requirements-like block comment is only
|
||||
approachable for those who have familiarity with ``requirements.txt``, which
|
||||
represents a small subset of users.
|
||||
|
||||
* For the average person automating a task or the data scientist, they are
|
||||
already starting with zero context and are unlikely to be familiar with
|
||||
|
@ -447,9 +428,9 @@ small subset of users.
|
|||
of a chat bot or direct code completion software. Searching for Python
|
||||
metadata formatting will lead them to the TOML-based format that already
|
||||
exists which they can reuse. The author tested GitHub Copilot with this
|
||||
PEP and it already supports auto-completion of fields and dependencies.
|
||||
In contrast, a new format may take years of being trained on the Internet
|
||||
for models to learn.
|
||||
PEP and it already supports auto-completion of ``dependencies``. In contrast,
|
||||
a new format may take years of being trained on the Internet for models to
|
||||
learn.
|
||||
|
||||
Additionally, these users are most susceptible to formatting quirks and
|
||||
syntax errors. TOML is a well-defined format with existing online
|
||||
|
@ -464,7 +445,7 @@ small subset of users.
|
|||
with TOML since they are used to structured data formats and there would be
|
||||
less perceived magic in their systems.
|
||||
|
||||
Additionally, for maintenance of their systems ``__pyproject__`` would be
|
||||
Additionally, for maintenance of their systems ``/// pyproject`` would be
|
||||
much easier to search for from a shell than a block comment with potentially
|
||||
numerous extensions over time.
|
||||
* For the SRE types, they are likely to be familiar with TOML already from
|
||||
|
@ -490,11 +471,12 @@ small subset of users.
|
|||
Studio Code would be able to provide TOML syntax highlighting much more
|
||||
easily than each writing custom logic for this feature.
|
||||
|
||||
Additionally, since the original block comment alternative format went against
|
||||
the recommendation of :pep:`8` and as a result linters and IDE auto-formatters
|
||||
that respected the recommendation would
|
||||
Additionally, since the original block comment alternative format (double
|
||||
``#``) went against the recommendation of :pep:`8` and as a result linters
|
||||
and IDE auto-formatters that respected the recommendation would
|
||||
`fail by default <https://discuss.python.org/t/29905/247>`__, the final
|
||||
proposal uses standard comments starting with a single ``#`` character.
|
||||
proposal uses standard comments starting with a single ``#`` character without
|
||||
any obvious start nor end sequence.
|
||||
|
||||
The concept of regular comments that do not appear to be intended for machines
|
||||
(i.e. `encoding declarations`__) affecting behavior would not be customary to
|
||||
|
@ -517,47 +499,89 @@ would be multiple ways to achieve the same thing which goes against our
|
|||
foundational principle of "there should be one - and preferably only one -
|
||||
obvious way to do it".
|
||||
|
||||
Why not consider scripts as projects without wheels?
|
||||
----------------------------------------------------
|
||||
Why not use a multi-line string?
|
||||
--------------------------------
|
||||
|
||||
There is `an ongoing discussion <pyproject without wheels_>`_ about how to
|
||||
use ``pyproject.toml`` for projects that are not intended to be built as
|
||||
wheels. This PEP considers the discussion only tangentially related.
|
||||
A previous version of this PEP proposed that the metadata be stored as follows:
|
||||
|
||||
The use case described in that thread is primarily talking about projects that
|
||||
represent applications like a Django app or a Flask app. These projects are
|
||||
often installed on each server in a virtual environment with strict dependency
|
||||
pinning e.g. a lock file with some sort of hash checking. Such projects would
|
||||
never be distributed as a wheel (except for maybe a transient editable one
|
||||
that is created when doing ``pip install -e .``).
|
||||
.. code:: python
|
||||
|
||||
In contrast, scripts are managed loosely by their runners and would almost
|
||||
always have relaxed dependency constraints. Additionally, there may be a future
|
||||
in which there is `a standard way <723-limit-build-backend_>`_ to ship projects
|
||||
that are in the form of a single file.
|
||||
__pyproject__ = """
|
||||
...
|
||||
"""
|
||||
|
||||
.. _723-limit-build-backend:
|
||||
The most significant problem with this proposal is that the embedded TOML would
|
||||
be limited in the following ways:
|
||||
|
||||
Why not limit build backend behavior?
|
||||
-------------------------------------
|
||||
* It would not be possible to use multi-line double-quoted strings in the TOML
|
||||
as that would conflict with the Python string containing the document. Many
|
||||
TOML writers do not preserve style and may potentially produce output that
|
||||
would be malformed.
|
||||
* The way in which character escaping works in Python strings is not quite the
|
||||
way it works in TOML strings. It would be possible to preserve a one-to-one
|
||||
character mapping by enforcing raw strings, but this ``r`` prefix requirement
|
||||
may be potentially confusing to users.
|
||||
|
||||
A previous version of this PEP proposed that the ``[build-system]`` table
|
||||
mustn't be defined. The rationale was that builds would never occur so it
|
||||
did not make sense to allow this section.
|
||||
Why not reuse core metadata fields?
|
||||
-----------------------------------
|
||||
|
||||
We removed that limitation based on
|
||||
`feedback <https://discuss.python.org/t/31151/9>`__ stating that there
|
||||
are already tools that exist in the wild that build wheels and source
|
||||
distributions from single files.
|
||||
A previous version of this PEP proposed to reuse the existing
|
||||
`metadata standard <pyproject metadata_>`_ that is used to describe projects.
|
||||
|
||||
The author of the Rust RFC for embedding metadata
|
||||
`mentioned to us <https://discuss.python.org/t/29905/179>`__ that they are
|
||||
actively looking into that as well based on user feedback saying that there
|
||||
is unnecessary friction with managing small projects, which we have also
|
||||
heard in the Python community.
|
||||
There are two significant problems with this proposal:
|
||||
|
||||
There has been `a commitment <https://discuss.python.org/t/31151/15>`__ to
|
||||
support this by at least one major build system.
|
||||
* The ``name`` and ``version`` fields are required and changing that would
|
||||
require its own PEP
|
||||
* Reusing the data is `fundamentally a misuse of it`__
|
||||
|
||||
__ https://snarky.ca/differentiating-between-writing-down-dependencies-to-use-packages-and-for-packages-themselves/
|
||||
|
||||
Why not limit to specific metadata fields?
|
||||
------------------------------------------
|
||||
|
||||
By limiting the metadata to a specific set of fields, for example just
|
||||
``dependencies``, we would prevent legitimate known use cases:
|
||||
|
||||
* ``requires-python``: For tools that support managing Python installations,
|
||||
this allows users to target specific versions of Python for new syntax
|
||||
or standard library functionality.
|
||||
* ``version``: It is quite common to version scripts for persistence even when
|
||||
using a VCS like Git. When not using a VCS it is even more common to version,
|
||||
for example the author has been in multiple time sensitive debugging sessions
|
||||
with customers where due to the airgapped nature of the environment, the only
|
||||
way to transfer the script was via email or copying and pasting it into a
|
||||
chat window. In these cases, versioning is invaluable to ensure that the
|
||||
customer is using the latest (or a specific) version of the script.
|
||||
|
||||
.. _723-tool-configuration:
|
||||
|
||||
Why not limit tool configuration?
|
||||
---------------------------------
|
||||
|
||||
By not allowing the ``[tool]`` table, we would prevent known functionality
|
||||
that would benefit users. For example:
|
||||
|
||||
* A script runner may support injecting of dependency resolution data for an
|
||||
embedded lock file (this is what Go's ``gorun`` can do).
|
||||
* A script runner may support configuration instructing to run scripts in
|
||||
containers for situations in which there is no cross-platform support for a
|
||||
dependency or if the setup is too complex for the average user like when
|
||||
requiring Nvidia drivers. Situations like this would allow users to proceed
|
||||
with what they want to do whereas otherwise they may stop at that point
|
||||
altogether.
|
||||
* Tools may wish to experiment with features to ease development burden for
|
||||
users such as the building of single-file scripts into packages. We received
|
||||
`feedback <https://discuss.python.org/t/31151/9>`__ stating that there are
|
||||
already tools that exist in the wild that build wheels and source
|
||||
distributions from single files.
|
||||
|
||||
The author of the Rust RFC for embedding metadata
|
||||
`mentioned to us <https://discuss.python.org/t/29905/179>`__ that they are
|
||||
actively looking into that as well based on user feedback saying that there
|
||||
is unnecessary friction with managing small projects.
|
||||
|
||||
There has been `a commitment <https://discuss.python.org/t/31151/15>`__ to
|
||||
support this by at least one major build system.
|
||||
|
||||
Why not limit tool behavior?
|
||||
----------------------------
|
||||
|
@ -574,25 +598,6 @@ from maintainers of tools that this would be undesirable and potentially
|
|||
confusing to users. Additionally, this may allow for a universally easier
|
||||
way to configure tools in certain circumstances and solve existing issues.
|
||||
|
||||
Why not accept all valid Python expression syntax?
|
||||
--------------------------------------------------
|
||||
|
||||
There has been a suggestion that we should not restrict how the
|
||||
``__pyproject__`` variable is defined and we should parse the abstract syntax
|
||||
tree. For example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
__pyproject__ = (
|
||||
"""
|
||||
[project]
|
||||
dependencies = []
|
||||
"""
|
||||
)
|
||||
|
||||
We will not be doing this so that every language has the possibility to read
|
||||
the metadata without dependence on knowledge of every version of Python.
|
||||
|
||||
Why not just set up a Python project with a ``pyproject.toml``?
|
||||
---------------------------------------------------------------
|
||||
|
||||
|
@ -682,7 +687,7 @@ detail.
|
|||
So in order to make a standard, two things would be required:
|
||||
|
||||
1. A standardised replacement for the requirements file format.
|
||||
2. A standard for how to locate the requiements file for a given script.
|
||||
2. A standard for how to locate the requirements file for a given script.
|
||||
|
||||
The first item is a significant undertaking. It has been discussed on a number
|
||||
of occasions, but so far no-one has attempted to actually do it. The most
|
||||
|
@ -705,7 +710,7 @@ script (for example, publishing it on a text file sharing service like Github's
|
|||
gist, or a corporate intranet) may not allow for deriving the location of an
|
||||
associated requirements file from the script's location (tools like ``pipx``
|
||||
support running a script directly from a URL, so "download and unpack a zip of
|
||||
the script and itsdependencies" may not be an appropriate requirement).
|
||||
the script and its dependencies" may not be an appropriate requirement).
|
||||
|
||||
Essentially, though, the issue here is that there is an explicitly stated
|
||||
requirement that the format supports storing dependency data *in the script
|
||||
|
@ -765,8 +770,6 @@ References
|
|||
==========
|
||||
|
||||
.. _pyproject metadata: https://packaging.python.org/en/latest/specifications/declaring-project-metadata/
|
||||
.. _doc-comment: https://doc.rust-lang.org/stable/book/ch14-02-publishing-to-crates-io.html#making-useful-documentation-comments
|
||||
.. _cargo embedded manifest: https://github.com/epage/cargo-script-mvs/blob/main/0000-cargo-script.md#embedded-manifest-format
|
||||
.. _pip-run issue: https://github.com/jaraco/pip-run/issues/44
|
||||
.. _pyproject without wheels: https://discuss.python.org/t/projects-that-arent-meant-to-generate-a-wheel-and-pyproject-toml/29684
|
||||
|
||||
|
@ -779,8 +782,20 @@ Footnotes
|
|||
projects that require special maintenance like the
|
||||
`AWS CLI <https://github.com/aws/aws-cli/tree/4393dcdf044a5275000c9c193d1933c07a08fdf1/scripts>`__
|
||||
or `Calibre <https://github.com/kovidgoyal/calibre/tree/master/setup>`__.
|
||||
.. [2] For example, projects like Hatch and Poetry have their own backends
|
||||
and may wish to support this use case only when their backend is used.
|
||||
.. [2] The syntax is taken directly from the final resolution of the
|
||||
`Blocks extension`__ to `Python Markdown`__.
|
||||
|
||||
__ https://github.com/facelessuser/pymdown-extensions/discussions/1973
|
||||
__ https://github.com/Python-Markdown/markdown
|
||||
.. [3] A future PEP that officially introduces the ``[run]`` table to
|
||||
``pyproject.toml`` files will make this PEP not just similar but a strict
|
||||
subset.
|
||||
.. [4] One important thing to note is that the metadata is embedded in a
|
||||
`doc-comment`__ (their equivalent of docstrings). `Other syntaxes`__ are
|
||||
under consideration within the Rust project.
|
||||
|
||||
__ https://doc.rust-lang.org/stable/book/ch14-02-publishing-to-crates-io.html#making-useful-documentation-comments
|
||||
__ https://github.com/epage/cargo-script-mvs/blob/main/0000-cargo-script.md#embedded-manifest-format
|
||||
|
||||
|
||||
Copyright
|
||||
|
|
Loading…
Reference in New Issue