780 lines
62 KiB
HTML
780 lines
62 KiB
HTML
|
||
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<meta name="color-scheme" content="light dark">
|
||
<title>PEP 722 – Dependency specification for single-file scripts | peps.python.org</title>
|
||
<link rel="shortcut icon" href="../_static/py.png">
|
||
<link rel="canonical" href="https://peps.python.org/pep-0722/">
|
||
<link rel="stylesheet" href="../_static/style.css" type="text/css">
|
||
<link rel="stylesheet" href="../_static/mq.css" type="text/css">
|
||
<link rel="stylesheet" href="../_static/pygments.css" type="text/css" media="(prefers-color-scheme: light)" id="pyg-light">
|
||
<link rel="stylesheet" href="../_static/pygments_dark.css" type="text/css" media="(prefers-color-scheme: dark)" id="pyg-dark">
|
||
<link rel="alternate" type="application/rss+xml" title="Latest PEPs" href="https://peps.python.org/peps.rss">
|
||
<meta property="og:title" content='PEP 722 – Dependency specification for single-file scripts | peps.python.org'>
|
||
<meta property="og:description" content="This PEP specifies a format for including 3rd-party dependencies in a single-file Python script.">
|
||
<meta property="og:type" content="website">
|
||
<meta property="og:url" content="https://peps.python.org/pep-0722/">
|
||
<meta property="og:site_name" content="Python Enhancement Proposals (PEPs)">
|
||
<meta property="og:image" content="https://peps.python.org/_static/og-image.png">
|
||
<meta property="og:image:alt" content="Python PEPs">
|
||
<meta property="og:image:width" content="200">
|
||
<meta property="og:image:height" content="200">
|
||
<meta name="description" content="This PEP specifies a format for including 3rd-party dependencies in a single-file Python script.">
|
||
<meta name="theme-color" content="#3776ab">
|
||
</head>
|
||
<body>
|
||
|
||
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
|
||
<symbol id="svg-sun-half" viewBox="0 0 24 24" pointer-events="all">
|
||
<title>Following system colour scheme</title>
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"
|
||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<circle cx="12" cy="12" r="9"></circle>
|
||
<path d="M12 3v18m0-12l4.65-4.65M12 14.3l7.37-7.37M12 19.6l8.85-8.85"></path>
|
||
</svg>
|
||
</symbol>
|
||
<symbol id="svg-moon" viewBox="0 0 24 24" pointer-events="all">
|
||
<title>Selected dark colour scheme</title>
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"
|
||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||
<path d="M12 3c.132 0 .263 0 .393 0a7.5 7.5 0 0 0 7.92 12.446a9 9 0 1 1 -8.313 -12.454z"></path>
|
||
</svg>
|
||
</symbol>
|
||
<symbol id="svg-sun" viewBox="0 0 24 24" pointer-events="all">
|
||
<title>Selected light colour scheme</title>
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"
|
||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<circle cx="12" cy="12" r="5"></circle>
|
||
<line x1="12" y1="1" x2="12" y2="3"></line>
|
||
<line x1="12" y1="21" x2="12" y2="23"></line>
|
||
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
|
||
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
|
||
<line x1="1" y1="12" x2="3" y2="12"></line>
|
||
<line x1="21" y1="12" x2="23" y2="12"></line>
|
||
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
|
||
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
|
||
</svg>
|
||
</symbol>
|
||
</svg>
|
||
<script>
|
||
|
||
document.documentElement.dataset.colour_scheme = localStorage.getItem("colour_scheme") || "auto"
|
||
</script>
|
||
<section id="pep-page-section">
|
||
<header>
|
||
<h1>Python Enhancement Proposals</h1>
|
||
<ul class="breadcrumbs">
|
||
<li><a href="https://www.python.org/" title="The Python Programming Language">Python</a> » </li>
|
||
<li><a href="../pep-0000/">PEP Index</a> » </li>
|
||
<li>PEP 722</li>
|
||
</ul>
|
||
<button id="colour-scheme-cycler" onClick="setColourScheme(nextColourScheme())">
|
||
<svg aria-hidden="true" class="colour-scheme-icon-when-auto"><use href="#svg-sun-half"></use></svg>
|
||
<svg aria-hidden="true" class="colour-scheme-icon-when-dark"><use href="#svg-moon"></use></svg>
|
||
<svg aria-hidden="true" class="colour-scheme-icon-when-light"><use href="#svg-sun"></use></svg>
|
||
<span class="visually-hidden">Toggle light / dark / auto colour theme</span>
|
||
</button>
|
||
</header>
|
||
<article>
|
||
<section id="pep-content">
|
||
<h1 class="page-title">PEP 722 – Dependency specification for single-file scripts</h1>
|
||
<dl class="rfc2822 field-list simple">
|
||
<dt class="field-odd">Author<span class="colon">:</span></dt>
|
||
<dd class="field-odd">Paul Moore <p.f.moore at gmail.com></dd>
|
||
<dt class="field-even">PEP-Delegate<span class="colon">:</span></dt>
|
||
<dd class="field-even">Brett Cannon <brett at python.org></dd>
|
||
<dt class="field-odd">Discussions-To<span class="colon">:</span></dt>
|
||
<dd class="field-odd"><a class="reference external" href="https://discuss.python.org/t/29905">Discourse thread</a></dd>
|
||
<dt class="field-even">Status<span class="colon">:</span></dt>
|
||
<dd class="field-even"><abbr title="Formally declined and will not be accepted">Rejected</abbr></dd>
|
||
<dt class="field-odd">Type<span class="colon">:</span></dt>
|
||
<dd class="field-odd"><abbr title="Normative PEP with a new feature for Python, implementation change for CPython or interoperability standard for the ecosystem">Standards Track</abbr></dd>
|
||
<dt class="field-even">Topic<span class="colon">:</span></dt>
|
||
<dd class="field-even"><a class="reference external" href="../topic/packaging/">Packaging</a></dd>
|
||
<dt class="field-odd">Created<span class="colon">:</span></dt>
|
||
<dd class="field-odd">19-Jul-2023</dd>
|
||
<dt class="field-even">Post-History<span class="colon">:</span></dt>
|
||
<dd class="field-even"><a class="reference external" href="https://discuss.python.org/t/29905" title="Discourse thread">19-Jul-2023</a></dd>
|
||
<dt class="field-odd">Resolution<span class="colon">:</span></dt>
|
||
<dd class="field-odd"><a class="reference external" href="https://discuss.python.org/t/pep-722-723-decision/36763/">Discourse thread</a></dd>
|
||
</dl>
|
||
<hr class="docutils" />
|
||
<section id="contents">
|
||
<details><summary>Table of Contents</summary><ul class="simple">
|
||
<li><a class="reference internal" href="#abstract">Abstract</a></li>
|
||
<li><a class="reference internal" href="#motivation">Motivation</a></li>
|
||
<li><a class="reference internal" href="#rationale">Rationale</a></li>
|
||
<li><a class="reference internal" href="#specification">Specification</a><ul>
|
||
<li><a class="reference internal" href="#example">Example</a></li>
|
||
</ul>
|
||
</li>
|
||
<li><a class="reference internal" href="#backwards-compatibility">Backwards Compatibility</a></li>
|
||
<li><a class="reference internal" href="#security-implications">Security Implications</a></li>
|
||
<li><a class="reference internal" href="#how-to-teach-this">How to Teach This</a></li>
|
||
<li><a class="reference internal" href="#recommendations">Recommendations</a></li>
|
||
<li><a class="reference internal" href="#reference-implementation">Reference Implementation</a></li>
|
||
<li><a class="reference internal" href="#rejected-ideas">Rejected Ideas</a><ul>
|
||
<li><a class="reference internal" href="#why-not-include-other-metadata">Why not include other metadata?</a></li>
|
||
<li><a class="reference internal" href="#why-not-use-a-marker-per-line">Why not use a marker per line?</a></li>
|
||
<li><a class="reference internal" href="#why-not-use-a-distinct-form-of-comment-for-the-dependency-block">Why not use a distinct form of comment for the dependency block?</a></li>
|
||
<li><a class="reference internal" href="#why-not-allow-multiple-dependency-blocks-and-merge-them">Why not allow multiple dependency blocks and merge them?</a></li>
|
||
<li><a class="reference internal" href="#why-not-use-a-more-standard-data-format-e-g-toml">Why not use a more standard data format (e.g., TOML)?</a></li>
|
||
<li><a class="reference internal" href="#why-not-use-possibly-restricted-python-syntax">Why not use (possibly restricted) Python syntax?</a></li>
|
||
<li><a class="reference internal" href="#why-not-embed-a-pyproject-toml-file-in-the-script">Why not embed a <code class="docutils literal notranslate"><span class="pre">pyproject.toml</span></code> file in the script?</a></li>
|
||
<li><a class="reference internal" href="#why-not-infer-the-requirements-from-import-statements">Why not infer the requirements from import statements?</a></li>
|
||
<li><a class="reference internal" href="#why-not-simply-manage-the-environment-at-runtime">Why not simply manage the environment at runtime?</a></li>
|
||
<li><a class="reference internal" href="#why-not-just-set-up-a-python-project-with-a-pyproject-toml">Why not just set up a Python project with a <code class="docutils literal notranslate"><span class="pre">pyproject.toml</span></code>?</a></li>
|
||
<li><a class="reference internal" href="#why-not-use-a-requirements-file-for-dependencies">Why not use a requirements file for dependencies?</a></li>
|
||
<li><a class="reference internal" href="#should-scripts-be-able-to-specify-a-package-index">Should scripts be able to specify a package index?</a></li>
|
||
<li><a class="reference internal" href="#what-about-local-dependencies">What about local dependencies?</a></li>
|
||
</ul>
|
||
</li>
|
||
<li><a class="reference internal" href="#open-issues">Open Issues</a></li>
|
||
<li><a class="reference internal" href="#copyright">Copyright</a></li>
|
||
</ul>
|
||
</details></section>
|
||
<section id="abstract">
|
||
<h2><a class="toc-backref" href="#abstract" role="doc-backlink">Abstract</a></h2>
|
||
<p>This PEP specifies a format for including 3rd-party dependencies in a
|
||
single-file Python script.</p>
|
||
</section>
|
||
<section id="motivation">
|
||
<h2><a class="toc-backref" href="#motivation" role="doc-backlink">Motivation</a></h2>
|
||
<p>Not all Python code is structured as a “project”, in the sense of having its own
|
||
directory complete with a <code class="docutils literal notranslate"><span class="pre">pyproject.toml</span></code> file, and being built into an
|
||
installable distribution package. Python is also routinely used as a scripting
|
||
language, with Python scripts as a (better) alternative to shell scripts, batch
|
||
files, etc. When used to create scripts, Python code is typically stored as a
|
||
single file, often in a directory dedicated to such “utility scripts”, which
|
||
might be in a mix of languages with Python being only one possibility among
|
||
many. Such scripts may be shared, often by something as simple as email, or a
|
||
link to a URL such as a Github gist. But they are typically <em>not</em> “distributed”
|
||
or “installed” as part of a normal workflow.</p>
|
||
<p>One problem when using Python as a scripting language in this way is how to run
|
||
the script in an environment that contains whatever third party dependencies are
|
||
required by the script. There is currently no standard tool that addresses this
|
||
issue, and this PEP does <em>not</em> attempt to define one. However, any tool that
|
||
<em>does</em> address this issue will need to know what 3rd party dependencies a script
|
||
requires. By defining a standard format for storing such data, existing tools,
|
||
as well as any future tools, will be able to obtain that information without
|
||
requiring users to include tool-specific metadata in their scripts.</p>
|
||
</section>
|
||
<section id="rationale">
|
||
<h2><a class="toc-backref" href="#rationale" role="doc-backlink">Rationale</a></h2>
|
||
<p>Because a key requirement is writing single-file scripts, and simple sharing by
|
||
giving someone a copy of the script, the PEP defines a mechanism for embedding
|
||
dependency data <em>within the script itself</em>, and not in an external file.</p>
|
||
<p>We define the concept of a <em>dependency block</em> that contains information about
|
||
what 3rd party packages a script depends on.</p>
|
||
<p>In order to identify dependency blocks, the script can simply be read as a text
|
||
file. This is deliberate, as Python syntax changes over time, so attempting to
|
||
parse the script as Python code would require choosing a specific version of
|
||
Python syntax. Also, it is likely that at least some tools will not be written
|
||
in Python, and expecting them to implement a Python parser is too much of a
|
||
burden.</p>
|
||
<p>However, to avoid needing changes to core Python, the format is designed to
|
||
appear as comments to the Python parser. It is possible to write code where a
|
||
dependency block is <em>not</em> interpreted as a comment (for example, by embedding it
|
||
in a Python multi-line string), but such uses are discouraged and can easily be
|
||
avoided assuming you are not deliberately trying to create a pathological
|
||
example.</p>
|
||
<p>A <a class="reference external" href="https://dbohdan.com/scripts-with-dependencies">review</a> of how other languages allow scripts to specify
|
||
their dependencies shows that a “structured comment” like this is a
|
||
commonly-used approach.</p>
|
||
</section>
|
||
<section id="specification">
|
||
<h2><a class="toc-backref" href="#specification" role="doc-backlink">Specification</a></h2>
|
||
<p>The content of this section will be published in the Python Packaging user
|
||
guide, PyPA Specifications section, as a document with the title “Embedding
|
||
Metadata in Script Files”.</p>
|
||
<p>Any Python script may contain a <em>dependency block</em>. The dependency block is
|
||
identified by reading the script <em>as a text file</em> (i.e., the file is not parsed
|
||
as Python source code), looking for the first line of the form:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Script Dependencies:</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>The hash character must be at the start of the line with no preceding whitespace.
|
||
The text “Script Dependencies” is recognised regardless of case, and the spaces
|
||
represent arbitrary whitespace (although at least one space must be present). The
|
||
following regular expression recognises the dependency block header line:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span>(?i)^#\s+script\s+dependencies:\s*$
|
||
</pre></div>
|
||
</div>
|
||
<p>Tools reading the dependency block MAY respect the standard Python encoding
|
||
declaration. If they choose not to do so, they MUST process the file as UTF-8.</p>
|
||
<p>After the header line, all lines in the file up to the first line that doesn’t
|
||
start with a <code class="docutils literal notranslate"><span class="pre">#</span></code> sign are considered <em>dependency lines</em> and are treated as
|
||
follows:</p>
|
||
<ol class="arabic simple">
|
||
<li>The initial <code class="docutils literal notranslate"><span class="pre">#</span></code> sign is stripped.</li>
|
||
<li>If the line contains the character sequence “ # “ (SPACE HASH SPACE), then
|
||
those characters and any subsequent characters are discarded. This allows
|
||
dependency blocks to contain inline comments.</li>
|
||
<li>Whitespace at the start and end of the remaining text is discarded.</li>
|
||
<li>If the line is now empty, it is ignored.</li>
|
||
<li>The content of the line MUST now be a valid <a class="pep reference internal" href="../pep-0508/" title="PEP 508 – Dependency specification for Python Software Packages">PEP 508</a> dependency specifier.</li>
|
||
</ol>
|
||
<p>The requirement for spaces before and after the <code class="docutils literal notranslate"><span class="pre">#</span></code> in an inline comment is
|
||
necessary to distinguish them from part of a <a class="pep reference internal" href="../pep-0508/" title="PEP 508 – Dependency specification for Python Software Packages">PEP 508</a> URL specifier (which
|
||
can contain a hash, but without surrounding whitespace).</p>
|
||
<p>Consumers MUST validate that at a minimum, all dependencies start with a
|
||
<code class="docutils literal notranslate"><span class="pre">name</span></code> as defined in <a class="pep reference internal" href="../pep-0508/" title="PEP 508 – Dependency specification for Python Software Packages">PEP 508</a>, and they MAY validate that all dependencies
|
||
conform fully to <a class="pep reference internal" href="../pep-0508/" title="PEP 508 – Dependency specification for Python Software Packages">PEP 508</a>. They MUST fail with an error if they find an
|
||
invalid specifier.</p>
|
||
<section id="example">
|
||
<h3><a class="toc-backref" href="#example" role="doc-backlink">Example</a></h3>
|
||
<p>The following is an example of a script with an embedded dependency block:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># In order to run, this script needs the following 3rd party libraries</span>
|
||
<span class="c1">#</span>
|
||
<span class="c1"># Script Dependencies:</span>
|
||
<span class="c1"># requests</span>
|
||
<span class="c1"># rich # Needed for the output</span>
|
||
<span class="c1">#</span>
|
||
<span class="c1"># # Not needed - just to show that fragments in URLs do not</span>
|
||
<span class="c1"># # get treated as comments</span>
|
||
<span class="c1"># pip @ https://github.com/pypa/pip/archive/1.3.1.zip#sha1=da9234ee9982d4bbb3c72346a6de940a148ea686</span>
|
||
|
||
<span class="kn">import</span> <span class="nn">requests</span>
|
||
<span class="kn">from</span> <span class="nn">rich.pretty</span> <span class="kn">import</span> <span class="n">pprint</span>
|
||
|
||
<span class="n">resp</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"https://peps.python.org/api/peps.json"</span><span class="p">)</span>
|
||
<span class="n">data</span> <span class="o">=</span> <span class="n">resp</span><span class="o">.</span><span class="n">json</span><span class="p">()</span>
|
||
<span class="n">pprint</span><span class="p">([(</span><span class="n">k</span><span class="p">,</span> <span class="n">v</span><span class="p">[</span><span class="s2">"title"</span><span class="p">])</span> <span class="k">for</span> <span class="n">k</span><span class="p">,</span> <span class="n">v</span> <span class="ow">in</span> <span class="n">data</span><span class="o">.</span><span class="n">items</span><span class="p">()][:</span><span class="mi">10</span><span class="p">])</span>
|
||
</pre></div>
|
||
</div>
|
||
</section>
|
||
</section>
|
||
<section id="backwards-compatibility">
|
||
<h2><a class="toc-backref" href="#backwards-compatibility" role="doc-backlink">Backwards Compatibility</a></h2>
|
||
<p>As dependency blocks take the form of a structured comment, they can be added
|
||
without altering the meaning of existing code.</p>
|
||
<p>It is possible that a comment may already exist which matches the form of a
|
||
dependency block. While the identifying header text, “Script Dependencies” is
|
||
chosen to minimise this risk, it is still possible.</p>
|
||
<p>In the rare case where an existing comment would be interpreted incorrectly as a
|
||
dependency block, this can be addressed by adding an actual dependency block
|
||
(which can be empty if the script has no dependencies) earlier in the code.</p>
|
||
</section>
|
||
<section id="security-implications">
|
||
<h2><a class="toc-backref" href="#security-implications" role="doc-backlink">Security Implications</a></h2>
|
||
<p>If a script containing a dependency block is run using a tool that automatically
|
||
installs dependencies, this could cause arbitrary code to be downloaded and
|
||
installed in the user’s environment.</p>
|
||
<p>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
|
||
dependency block is run, when a potentially malicious dependency might be
|
||
installed. This risk is addressed by the normal good practice of reviewing code
|
||
before running it.</p>
|
||
</section>
|
||
<section id="how-to-teach-this">
|
||
<h2><a class="toc-backref" href="#how-to-teach-this" role="doc-backlink">How to Teach This</a></h2>
|
||
<p>The format is intended to be close to how a developer might already specify
|
||
script dependencies in an explanatory comment. The required structure is
|
||
deliberately minimal, so that formatting rules are easy to learn.</p>
|
||
<p>Users will need to know how to write Python dependency specifiers. This is
|
||
covered by <a class="pep reference internal" href="../pep-0508/" title="PEP 508 – Dependency specification for Python Software Packages">PEP 508</a>, but for simple examples (which is expected to be the norm
|
||
for inexperienced users) the syntax is either just a package name, or a name and
|
||
a version restriction, which is fairly well-understood syntax.</p>
|
||
<p>Users will also know how to <em>run</em> a script using a tool that interprets
|
||
dependency data. This is not covered by this PEP, as it is the responsibility of
|
||
such a tool to document how it should be used.</p>
|
||
<p>Note that the core Python interpreter does <em>not</em> interpret dependency blocks.
|
||
This may be a point of confusion for beginners, who try to run <code class="docutils literal notranslate"><span class="pre">python</span>
|
||
<span class="pre">some_script.py</span></code> and do not understand why it fails. This is no different than
|
||
the current status quo, though, where running a script without its dependencies
|
||
present will give an error.</p>
|
||
<p>In general, it is assumed that if a beginner is given a script with dependencies
|
||
(regardless of whether they are specified in a dependency block), the person
|
||
supplying the script should explain how to run that script, and if that involves
|
||
using a script runner tool, that should be noted.</p>
|
||
</section>
|
||
<section id="recommendations">
|
||
<h2><a class="toc-backref" href="#recommendations" role="doc-backlink">Recommendations</a></h2>
|
||
<p>This section is non-normative and simply describes “good practices” when using
|
||
dependency blocks.</p>
|
||
<p>While it is permitted for tools to do minimal validation of requirements, in
|
||
practice they should do as much “sanity check” validation as possible, even if
|
||
they cannot do a full check for <a class="pep reference internal" href="../pep-0508/" title="PEP 508 – Dependency specification for Python Software Packages">PEP 508</a> syntax. This helps to ensure that
|
||
dependency blocks that are not correctly terminated are reported early. A good
|
||
compromise between the minimal approach of checking just that the requirement
|
||
starts with a name, and full <a class="pep reference internal" href="../pep-0508/" title="PEP 508 – Dependency specification for Python Software Packages">PEP 508</a> validation, is to check for a bare name,
|
||
or a name followed by optional whitespace, and then one of <code class="docutils literal notranslate"><span class="pre">[</span></code> (extra), <code class="docutils literal notranslate"><span class="pre">@</span></code>
|
||
(urlspec), <code class="docutils literal notranslate"><span class="pre">;</span></code> (marker) or one of <code class="docutils literal notranslate"><span class="pre">(<!=>~</span></code> (version).</p>
|
||
<p>Scripts should, in general, place the dependency block at the top of the file,
|
||
either immediately after any shebang line, or straight after the script
|
||
docstring. In particular, the dependency block should always be placed before
|
||
any executable code in the file. This makes it easy for the human reader to
|
||
locate it.</p>
|
||
</section>
|
||
<section id="reference-implementation">
|
||
<h2><a class="toc-backref" href="#reference-implementation" role="doc-backlink">Reference Implementation</a></h2>
|
||
<p>Code to implement this proposal in Python is fairly straightforward, so the
|
||
reference implementation can be included here.</p>
|
||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">re</span>
|
||
<span class="kn">import</span> <span class="nn">tokenize</span>
|
||
<span class="kn">from</span> <span class="nn">packaging.requirements</span> <span class="kn">import</span> <span class="n">Requirement</span>
|
||
|
||
<span class="n">DEPENDENCY_BLOCK_MARKER</span> <span class="o">=</span> <span class="sa">r</span><span class="s2">"(?i)^#\s+script\s+dependencies:\s*$"</span>
|
||
|
||
<span class="k">def</span> <span class="nf">read_dependency_block</span><span class="p">(</span><span class="n">filename</span><span class="p">):</span>
|
||
<span class="c1"># Use the tokenize module to handle any encoding declaration.</span>
|
||
<span class="k">with</span> <span class="n">tokenize</span><span class="o">.</span><span class="n">open</span><span class="p">(</span><span class="n">filename</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
|
||
<span class="c1"># Skip lines until we reach a dependency block (OR EOF).</span>
|
||
<span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">f</span><span class="p">:</span>
|
||
<span class="k">if</span> <span class="n">re</span><span class="o">.</span><span class="n">match</span><span class="p">(</span><span class="n">DEPENDENCY_BLOCK_MARKER</span><span class="p">,</span> <span class="n">line</span><span class="p">):</span>
|
||
<span class="k">break</span>
|
||
<span class="c1"># Read dependency lines until we hit a line that doesn't</span>
|
||
<span class="c1"># start with #, or we are at EOF.</span>
|
||
<span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">f</span><span class="p">:</span>
|
||
<span class="k">if</span> <span class="ow">not</span> <span class="n">line</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">"#"</span><span class="p">):</span>
|
||
<span class="k">break</span>
|
||
<span class="c1"># Remove comments. An inline comment is introduced by</span>
|
||
<span class="c1"># a hash, which must be preceded and followed by a</span>
|
||
<span class="c1"># space.</span>
|
||
<span class="n">line</span> <span class="o">=</span> <span class="n">line</span><span class="p">[</span><span class="mi">1</span><span class="p">:]</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">" # "</span><span class="p">,</span> <span class="n">maxsplit</span><span class="o">=</span><span class="mi">1</span><span class="p">)[</span><span class="mi">0</span><span class="p">]</span>
|
||
<span class="n">line</span> <span class="o">=</span> <span class="n">line</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
|
||
<span class="c1"># Ignore empty lines</span>
|
||
<span class="k">if</span> <span class="ow">not</span> <span class="n">line</span><span class="p">:</span>
|
||
<span class="k">continue</span>
|
||
<span class="c1"># Try to convert to a requirement. This will raise</span>
|
||
<span class="c1"># an error if the line is not a PEP 508 requirement</span>
|
||
<span class="k">yield</span> <span class="n">Requirement</span><span class="p">(</span><span class="n">line</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>A format similar to the one proposed here is already supported <a class="reference external" href="https://github.com/pypa/pipx/pull/916">in pipx</a> and in <a class="reference external" href="https://pypi.org/project/pip-run/">pip-run</a>.</p>
|
||
</section>
|
||
<section id="rejected-ideas">
|
||
<h2><a class="toc-backref" href="#rejected-ideas" role="doc-backlink">Rejected Ideas</a></h2>
|
||
<section id="why-not-include-other-metadata">
|
||
<h3><a class="toc-backref" href="#why-not-include-other-metadata" role="doc-backlink">Why not include other metadata?</a></h3>
|
||
<p>The core use case addressed by this proposal is that of identifying what
|
||
dependencies a standalone script needs in order to run successfully. This is a
|
||
common real-world issue that is currently solved by script runner tools, using
|
||
implementation-specific ways of storing the data. Standardising the storage
|
||
format improves interoperability by not typing the script to a particular
|
||
runner.</p>
|
||
<p>While it is arguable that other forms of metadata could be useful in a
|
||
standalone script, the need is largely theoretical at this point. In practical
|
||
terms, scripts either don’t use other metadata, or they store it in existing,
|
||
widely used (and therefore de facto standard) formats. For example, scripts
|
||
needing README style text typically use the standard Python module docstring,
|
||
and scripts wanting to declare a version use the common convention of having a
|
||
<code class="docutils literal notranslate"><span class="pre">__version__</span></code> variable.</p>
|
||
<p>One case which was raised during the discussion on this PEP, was the ability to
|
||
declare a minimum Python version that a script needed to run, by analogy with
|
||
the <code class="docutils literal notranslate"><span class="pre">Requires-Python</span></code> core metadata item for packages. Unlike packages,
|
||
scripts are normally only run by one user or in one environment, in contexts
|
||
where multiple versions of Python are uncommon. The need for this metadata is
|
||
therefore much less critical in the case of scripts. As further evidence of
|
||
this, the two key script runners currently available, <code class="docutils literal notranslate"><span class="pre">pipx</span></code> and <code class="docutils literal notranslate"><span class="pre">pip-run</span></code>
|
||
do not offer a means of including this data in a script.</p>
|
||
<p>Creating a standard “metadata container” format would unify the various
|
||
approaches, but in practical terms there is no real need for unification, and
|
||
the disruption would either delay adoption, or more likely simply mean script
|
||
authors would ignore the standard.</p>
|
||
<p>This proposal therefore chooses to focus just on the one use case where there is
|
||
a clear need for something, and no existing standard or common practice.</p>
|
||
</section>
|
||
<section id="why-not-use-a-marker-per-line">
|
||
<h3><a class="toc-backref" href="#why-not-use-a-marker-per-line" role="doc-backlink">Why not use a marker per line?</a></h3>
|
||
<p>Rather than using a comment block with a header, another possibility would be to
|
||
use a marker on each line, something like:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Script-Dependency: requests</span>
|
||
<span class="c1"># Script-Dependency: click</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>While this makes it easier to parse lines individually, it has a number of
|
||
issues. The first is simply that it’s rather verbose, and less readable. This is
|
||
clearly affected by the chosen keyword, but all of the suggested options were
|
||
(in the author’s opinion) less readable than the block comment form.</p>
|
||
<p>More importantly, this form <em>by design</em> makes it impossible to require that the
|
||
dependency specifiers are all together in a single block. As a result, it’s not
|
||
possible for a human reader, without a careful check of the whole file, to be
|
||
sure that they have identified all of the dependencies. See the question below,
|
||
“Why not allow multiple dependency blocks and merge them?”, for further
|
||
discussion of this problem.</p>
|
||
<p>Finally, as the reference implementation demonstrates, parsing the “comment
|
||
block” form isn’t, in practice, significantly more difficult than parsing this
|
||
form.</p>
|
||
</section>
|
||
<section id="why-not-use-a-distinct-form-of-comment-for-the-dependency-block">
|
||
<h3><a class="toc-backref" href="#why-not-use-a-distinct-form-of-comment-for-the-dependency-block" role="doc-backlink">Why not use a distinct form of comment for the dependency block?</a></h3>
|
||
<p>A previous version of this proposal used <code class="docutils literal notranslate"><span class="pre">##</span></code> to identify dependency blocks.
|
||
Unfortunately, however, the flake8 linter implements a rule requiring that
|
||
comments must have a space after the initial <code class="docutils literal notranslate"><span class="pre">#</span></code> sign. While the PEP author
|
||
considers that rule misguided, it is on by default and as a result would cause
|
||
checks to fail when faced with a dependency block.</p>
|
||
<p>Furthermore, the <code class="docutils literal notranslate"><span class="pre">black</span></code> formatter, although it allows the <code class="docutils literal notranslate"><span class="pre">##</span></code> form, does
|
||
add a space after the <code class="docutils literal notranslate"><span class="pre">#</span></code> for most other forms of comment. This means that if
|
||
we chose an alternative like <code class="docutils literal notranslate"><span class="pre">#%</span></code>, automatic reformatting would corrupt the
|
||
dependency block. Forms including a space, like <code class="docutils literal notranslate"><span class="pre">#</span> <span class="pre">#</span></code> are possible, but less
|
||
natural for the average user (omitting the space is an obvious mistake to make).</p>
|
||
<p>While it is possible that linters and formatters could be changed to recognise
|
||
the new standard, the benefit of having a dedicated prefix did not seem
|
||
sufficient to justify the transition cost, or the risk that users might be using
|
||
older tools.</p>
|
||
</section>
|
||
<section id="why-not-allow-multiple-dependency-blocks-and-merge-them">
|
||
<h3><a class="toc-backref" href="#why-not-allow-multiple-dependency-blocks-and-merge-them" role="doc-backlink">Why not allow multiple dependency blocks and merge them?</a></h3>
|
||
<p>Because it’s too easy for the human reader to miss the fact that there’s a
|
||
second dependency block. This could simply result in the script runner
|
||
unexpectedly downloading extra packages, or it could even be a way to smuggle
|
||
malicious packages onto a user’s machine (by “hiding” a second dependency block
|
||
in the body of the script).</p>
|
||
<p>While the principle of “don’t run untrusted code” applies here, the benefits
|
||
aren’t sufficient to be worth the risk.</p>
|
||
</section>
|
||
<section id="why-not-use-a-more-standard-data-format-e-g-toml">
|
||
<h3><a class="toc-backref" href="#why-not-use-a-more-standard-data-format-e-g-toml" role="doc-backlink">Why not use a more standard data format (e.g., TOML)?</a></h3>
|
||
<p>First of all, the only practical choice for an alternative format is TOML.
|
||
Python packaging has standardised on TOML for structured data, and using a
|
||
different format, such as YAML or JSON, would add complexity and confusion for
|
||
no real benefit.</p>
|
||
<p>So the question is essentially, “why not use TOML?”</p>
|
||
<p>The key idea behind the “dependency block” format is to define something that
|
||
reads naturally as a comment in the script. Dependency data is useful both for
|
||
tools and for the human reader, so having a human readable format is beneficial.
|
||
On the other hand, TOML of necessity has a syntax of its own, which distracts
|
||
from the underlying data.</p>
|
||
<p>It is important to remember that developers who <em>write</em> scripts in Python are
|
||
often <em>not</em> experienced in Python, or Python packaging. They are often systems
|
||
administrators, or data analysts, who may simply be using Python as a “better
|
||
batch file”. For such users, the TOML format is extremely likely to be
|
||
unfamiliar, and the syntax will be obscure to them, and not particularly
|
||
intuitive. Such developers may well be copying dependency specifiers from
|
||
sources such as Stack Overflow, without really understanding them. Having to
|
||
embed such a requirement into a TOML structure is an additional complexity –
|
||
and it is important to remember that the goal here is to make using 3rd party
|
||
libraries <em>easy</em> for such users.</p>
|
||
<p>Furthermore, TOML, by its nature, is a flexible format intended to support very
|
||
general data structures. There are <em>many</em> ways of writing a simple list of
|
||
strings in it, and it will not be clear to inexperienced users which form to use.</p>
|
||
<p>Another potential issue is that using a generalised TOML parser can <a class="reference external" href="https://discuss.python.org/t/pep-722-dependency-specification-for-single-file-scripts/29905/275">in some cases</a>
|
||
result in a measurable performance overhead. Startup time is often quoted as an
|
||
issue when running small scripts, so this may be a problem for script runners that
|
||
are aiming for high performance.</p>
|
||
<p>And finally, there will be tools that expect to <em>write</em> dependency data into
|
||
scripts – for example, an IDE with a feature that automatically adds an import
|
||
and a dependency specifier when you reference a library function. While
|
||
libraries exist that allow editing TOML data, they are not always good at
|
||
preserving the user’s layout. Even if libraries exist which do an effective job
|
||
at this, expecting all tools to use such a library is a significant imposition
|
||
on code supporting this PEP.</p>
|
||
<p>By choosing a simple, line-based format with no quoting rules, dependency data
|
||
is easy to read (for humans and tools) and easy to write. The format doesn’t
|
||
have the flexibility of something like TOML, but the use case simply doesn’t
|
||
demand that sort of flexibility.</p>
|
||
</section>
|
||
<section id="why-not-use-possibly-restricted-python-syntax">
|
||
<h3><a class="toc-backref" href="#why-not-use-possibly-restricted-python-syntax" role="doc-backlink">Why not use (possibly restricted) Python syntax?</a></h3>
|
||
<p>This would typically involve storing the dependencies as a (runtime) list
|
||
variable with a conventional name, such as:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">__requires__</span> <span class="o">=</span> <span class="p">[</span>
|
||
<span class="s2">"requests"</span><span class="p">,</span>
|
||
<span class="s2">"click"</span><span class="p">,</span>
|
||
<span class="p">]</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Other suggestions include a static multi-line string, or including the
|
||
dependencies in the script’s docstring.</p>
|
||
<p>The most significant problem with this proposal is that it requires all
|
||
consumers of the dependency data to implement a Python parser. Even if the
|
||
syntax is restricted, the <em>rest</em> of the script will use the full Python syntax,
|
||
and trying to define a syntax which can be successfully parsed in isolation from
|
||
the surrounding code is likely to be extremely difficult and error-prone.</p>
|
||
<p>Furthermore, Python’s syntax changes in every release. If extracting dependency
|
||
data needs a Python parser, the parser will need to know which version of Python
|
||
the script is written for, and the overhead for a generic tool of having a
|
||
parser that can handle <em>multiple</em> versions of Python is unsustainable.</p>
|
||
<p>Even if the above issues could be addressed, the format would give the
|
||
impression that the data could be altered at runtime. However, this is not the
|
||
case in general, and code that tries to do so will encounter unexpected and
|
||
confusing behaviour.</p>
|
||
<p>And finally, there is no evidence that having dependency data available at
|
||
runtime is of any practical use. Should such a use be found, it is simple enough
|
||
to get the data by parsing the source - <code class="docutils literal notranslate"><span class="pre">read_dependency_block(__file__)</span></code>.</p>
|
||
<p>It is worth noting, though, that the <code class="docutils literal notranslate"><span class="pre">pip-run</span></code> utility does implement (an
|
||
extended form of) this approach. <a class="reference external" href="https://github.com/jaraco/pip-run/issues/44">Further discussion</a> of
|
||
the <code class="docutils literal notranslate"><span class="pre">pip-run</span></code> design is available on the project’s issue tracker.</p>
|
||
</section>
|
||
<section id="why-not-embed-a-pyproject-toml-file-in-the-script">
|
||
<h3><a class="toc-backref" href="#why-not-embed-a-pyproject-toml-file-in-the-script" role="doc-backlink">Why not embed a <code class="docutils literal notranslate"><span class="pre">pyproject.toml</span></code> file in the script?</a></h3>
|
||
<p>First of all, <code class="docutils literal notranslate"><span class="pre">pyproject.toml</span></code> is a TOML based format, so all of the previous
|
||
concerns around TOML as a format apply. However, <code class="docutils literal notranslate"><span class="pre">pyproject.toml</span></code> is a
|
||
standard used by Python packaging, and re-using an existing standard is a
|
||
reasonable suggestion that deserves to be addressed on its own merits.</p>
|
||
<p>The first issue is that the suggestion rarely implies that <em>all</em> of
|
||
<code class="docutils literal notranslate"><span class="pre">pyproject.toml</span></code> is to be supported for scripts. A script is not intended to
|
||
be “built” into any sort of distributable artifact like a wheel (see below for
|
||
more on this point), so the <code class="docutils literal notranslate"><span class="pre">[build-system]</span></code> section of <code class="docutils literal notranslate"><span class="pre">pyproject.toml</span></code>
|
||
makes little sense, for example. And while the tool-specific sections of
|
||
<code class="docutils literal notranslate"><span class="pre">pyproject.toml</span></code> might be useful for scripts, it’s not at all clear that a
|
||
tool like <a class="reference external" href="https://beta.ruff.rs/docs/">ruff</a> would want to support per-file
|
||
configuration in this way, leading to confusion when users <em>expect</em> it to work,
|
||
but it doesn’t. Furthermore, this sort of tool-specific configuration is just as
|
||
useful for individual files in a larger project, so we have to consider what it
|
||
would mean to embed a <code class="docutils literal notranslate"><span class="pre">pyproject.toml</span></code> into a single file in a larger project
|
||
that has its own <code class="docutils literal notranslate"><span class="pre">pyproject.toml</span></code>.</p>
|
||
<p>In addition, <code class="docutils literal notranslate"><span class="pre">pyproject.toml</span></code> is currently focused on projects that are to be
|
||
built into wheels. There is <a class="reference external" href="https://discuss.python.org/t/projects-that-arent-meant-to-generate-a-wheel-and-pyproject-toml/29684">an ongoing discussion</a>
|
||
about how to use <code class="docutils literal notranslate"><span class="pre">pyproject.toml</span></code> for projects that are not intended to be
|
||
built as wheels, and until that question is resolved (which will likely require
|
||
some PEPs of its own) it seems premature to be discussing embedding
|
||
<code class="docutils literal notranslate"><span class="pre">pyproject.toml</span></code> into scripts, which are <em>definitely</em> not intended to be built
|
||
and distributed in that manner.</p>
|
||
<p>The conclusion, therefore (which has been stated explicitly in some, but not
|
||
all, cases) is that this proposal is intended to mean that we would embed <em>part
|
||
of</em> <code class="docutils literal notranslate"><span class="pre">pyproject.toml</span></code>. Typically this is the <code class="docutils literal notranslate"><span class="pre">[project]</span></code> section from
|
||
<a class="pep reference internal" href="../pep-0621/" title="PEP 621 – Storing project metadata in pyproject.toml">PEP 621</a>, or even just the <code class="docutils literal notranslate"><span class="pre">dependencies</span></code> item from that section.</p>
|
||
<p>At this point, the first issue is that by framing the proposal as “embedding
|
||
<code class="docutils literal notranslate"><span class="pre">pyproject.toml</span></code>”, we would be encouraging the sort of confusion discussed in
|
||
the previous paragraphs - developers will expect the full capabilities of
|
||
<code class="docutils literal notranslate"><span class="pre">pyproject.toml</span></code>, and be confused when there are differences and limitations.
|
||
It would be better, therefore, to consider this suggestion as simply being a
|
||
proposal to use an embedded TOML format, but specifically re-using the
|
||
<em>structure</em> of a particular part of <code class="docutils literal notranslate"><span class="pre">pyproject.toml</span></code>. The problem then becomes
|
||
how we describe that structure, <em>without</em> causing confusion for people familiar
|
||
with <code class="docutils literal notranslate"><span class="pre">pyproject.toml</span></code>. If we describe it with reference to <code class="docutils literal notranslate"><span class="pre">pyproject.toml</span></code>,
|
||
the link is still there. But if we describe it in isolation, people will be
|
||
confused by the “similar but different” nature of the structure.</p>
|
||
<p>It is also important to remember that a key part of the target audience for this
|
||
proposal is developers who are simply using Python as a “better batch file”
|
||
solution. These developers will generally not be familiar with Python packaging
|
||
and its conventions, and are often the people most critical of the “complexity”
|
||
and “difficulty” of packaging solutions. As a result, proposals based on those
|
||
existing solutions are likely to be unwelcome to that audience, and could easily
|
||
result in people simply continuing to use existing adhoc solutions, and ignoring
|
||
the standard that was intended to make their lives easier.</p>
|
||
</section>
|
||
<section id="why-not-infer-the-requirements-from-import-statements">
|
||
<h3><a class="toc-backref" href="#why-not-infer-the-requirements-from-import-statements" role="doc-backlink">Why not infer the requirements from import statements?</a></h3>
|
||
<p>The idea would be to automatically recognize <code class="docutils literal notranslate"><span class="pre">import</span></code> statements in the source
|
||
file and turn them into a list of requirements.</p>
|
||
<p>However, this is infeasible for several reasons. First, the points above about
|
||
the necessity to keep the syntax easily parsable, for all Python versions, also
|
||
by tools written in other languages, apply equally here.</p>
|
||
<p>Second, PyPI and other package repositories conforming to the Simple Repository
|
||
API do not provide a mechanism to resolve package names from the module names
|
||
that are imported (see also <a class="reference external" href="https://discuss.python.org/t/record-the-top-level-names-of-a-wheel-in-metadata/29494">this related discussion</a>).</p>
|
||
<p>Third, even if repositories did offer this information, the same import name may
|
||
correspond to several packages on PyPI. One might object that disambiguating
|
||
which package is wanted would only be needed if there are several projects
|
||
providing the same import name. However, this would make it easy for anyone to
|
||
unintentionally or malevolently break working scripts, by uploading a package to
|
||
PyPI providing an import name that is the same as an existing project. The
|
||
alternative where, among the candidates, the first package to have been
|
||
registered on the index is chosen, would be confusing in case a popular package
|
||
is developed with the same import name as an existing obscure package, and even
|
||
harmful if the existing package is malware intentionally uploaded with a
|
||
sufficiently generic import name that has a high probability of being reused.</p>
|
||
<p>A related idea would be to attach the requirements as comments to the import
|
||
statements instead of gathering them in a block, with a syntax such as:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">numpy</span> <span class="k">as</span> <span class="nn">np</span> <span class="c1"># requires: numpy</span>
|
||
<span class="kn">import</span> <span class="nn">rich</span> <span class="c1"># requires: rich</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>This still suffers from parsing difficulties. Also, where to place the comment
|
||
in the case of multiline imports is ambiguous and may look ugly:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">PyQt5.QtWidgets</span> <span class="kn">import</span> <span class="p">(</span>
|
||
<span class="n">QCheckBox</span><span class="p">,</span> <span class="n">QComboBox</span><span class="p">,</span> <span class="n">QDialog</span><span class="p">,</span> <span class="n">QDialogButtonBox</span><span class="p">,</span>
|
||
<span class="n">QGridLayout</span><span class="p">,</span> <span class="n">QLabel</span><span class="p">,</span> <span class="n">QSpinBox</span><span class="p">,</span> <span class="n">QTextEdit</span>
|
||
<span class="p">)</span> <span class="c1"># requires: PyQt5</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Furthermore, this syntax cannot behave as might be intuitively expected
|
||
in all situations. Consider:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">platform</span>
|
||
<span class="k">if</span> <span class="n">platform</span><span class="o">.</span><span class="n">system</span><span class="p">()</span> <span class="o">==</span> <span class="s2">"Windows"</span><span class="p">:</span>
|
||
<span class="kn">import</span> <span class="nn">pywin32</span> <span class="c1"># requires: pywin32</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Here, the user’s intent is that the package is only required on Windows, but
|
||
this cannot be understood by the script runner (the correct way to write
|
||
it would be <code class="docutils literal notranslate"><span class="pre">requires:</span> <span class="pre">pywin32</span> <span class="pre">;</span> <span class="pre">sys_platform</span> <span class="pre">==</span> <span class="pre">'win32'</span></code>).</p>
|
||
<p>(Thanks to Jean Abou-Samra for the clear discussion of this point)</p>
|
||
</section>
|
||
<section id="why-not-simply-manage-the-environment-at-runtime">
|
||
<h3><a class="toc-backref" href="#why-not-simply-manage-the-environment-at-runtime" role="doc-backlink">Why not simply manage the environment at runtime?</a></h3>
|
||
<p>Another approach to running scripts with dependencies is simply to manage those
|
||
dependencies at runtime. This can be done by using a library that makes packages
|
||
available. There are many options for implementing such a library, for example
|
||
by installing them directly into the user’s environment or by manipulating
|
||
<code class="docutils literal notranslate"><span class="pre">sys.path</span></code> to make them available from a local cache.</p>
|
||
<p>These approaches are not incompatible with this PEP. An API such as</p>
|
||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="n">env_mgr</span><span class="o">.</span><span class="n">install</span><span class="p">(</span><span class="s2">"rich"</span><span class="p">)</span>
|
||
<span class="n">env_mgr</span><span class="o">.</span><span class="n">install</span><span class="p">(</span><span class="s2">"click"</span><span class="p">)</span>
|
||
|
||
<span class="kn">import</span> <span class="nn">rich</span>
|
||
<span class="kn">import</span> <span class="nn">click</span>
|
||
|
||
<span class="o">...</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>is certainly feasible. However, such a library could be written without the need
|
||
for any new standards, and as far as the PEP author is aware, this has not
|
||
happened. This suggests that an approach like this is not as attractive as it
|
||
first seems. There is also the bootstrapping issue of making the <code class="docutils literal notranslate"><span class="pre">env_mgr</span></code>
|
||
library available in the first place. And finally, this approach doesn’t
|
||
actually offer any interoperability benefits, as it does not use a standard form
|
||
for the dependency list, and so other tools cannot access the data.</p>
|
||
<p>In any case, such a library could still benefit from this proposal, as it could
|
||
include an API to read the packages to install from the script dependency block.
|
||
This would give the same functionality while allowing interoperability with
|
||
other tools that support this specification.</p>
|
||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="c1"># Script Dependencies:</span>
|
||
<span class="c1"># rich</span>
|
||
<span class="c1"># click</span>
|
||
<span class="n">env_mgr</span><span class="o">.</span><span class="n">install_dependencies</span><span class="p">(</span><span class="vm">__file__</span><span class="p">)</span>
|
||
|
||
<span class="kn">import</span> <span class="nn">rich</span>
|
||
<span class="kn">import</span> <span class="nn">click</span>
|
||
|
||
<span class="o">...</span>
|
||
</pre></div>
|
||
</div>
|
||
</section>
|
||
<section id="why-not-just-set-up-a-python-project-with-a-pyproject-toml">
|
||
<h3><a class="toc-backref" href="#why-not-just-set-up-a-python-project-with-a-pyproject-toml" role="doc-backlink">Why not just set up a Python project with a <code class="docutils literal notranslate"><span class="pre">pyproject.toml</span></code>?</a></h3>
|
||
<p>Again, a key issue here is that the target audience for this proposal is people
|
||
writing scripts which aren’t intended for distribution. Sometimes scripts will
|
||
be “shared”, but this is far more informal than “distribution” - it typically
|
||
involves sending a script via an email with some written instructions on how to
|
||
run it, or passing someone a link to a gist.</p>
|
||
<p>Expecting such users to learn the complexities of Python packaging is a
|
||
significant step up in complexity, and would almost certainly give the
|
||
impression that “Python is too hard for scripts”.</p>
|
||
<p>In addition, if the expectation here is that the <code class="docutils literal notranslate"><span class="pre">pyproject.toml</span></code> will somehow
|
||
be designed for running scripts in place, that’s a new feature of the standard
|
||
that doesn’t currently exist. At a minimum, this isn’t a reasonable suggestion
|
||
until the <a class="reference external" href="https://discuss.python.org/t/projects-that-arent-meant-to-generate-a-wheel-and-pyproject-toml/29684">current discussion on Discourse</a> about
|
||
using <code class="docutils literal notranslate"><span class="pre">pyproject.toml</span></code> for projects that won’t be distributed as wheels is
|
||
resolved. And even then, it doesn’t address the “sending someone a script in a
|
||
gist or email” use case.</p>
|
||
</section>
|
||
<section id="why-not-use-a-requirements-file-for-dependencies">
|
||
<h3><a class="toc-backref" href="#why-not-use-a-requirements-file-for-dependencies" role="doc-backlink">Why not use a requirements file for dependencies?</a></h3>
|
||
<p>Putting your requirements in a requirements file, doesn’t require a PEP. You can
|
||
do that right now, and in fact it’s quite likely that many adhoc solutions do
|
||
this. However, without a standard, there’s no way of knowing how to locate a
|
||
script’s dependency data. And furthermore, the requirements file format is
|
||
pip-specific, so tools relying on it are depending on a pip implementation
|
||
detail.</p>
|
||
<p>So in order to make a standard, two things would be required:</p>
|
||
<ol class="arabic simple">
|
||
<li>A standardised replacement for the requirements file format.</li>
|
||
<li>A standard for how to locate the requirements file for a given script.</li>
|
||
</ol>
|
||
<p>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 likely
|
||
approach would be for standards to be developed for individual use cases
|
||
currently addressed with requirements files. One option here would be for this
|
||
PEP to simply define a new file format which is simply a text file containing
|
||
<a class="pep reference internal" href="../pep-0508/" title="PEP 508 – Dependency specification for Python Software Packages">PEP 508</a> requirements, one per line. That would just leave the question of how
|
||
to locate that file.</p>
|
||
<p>The “obvious” solution here would be to do something like name the file the same
|
||
as the script, but with a <code class="docutils literal notranslate"><span class="pre">.reqs</span></code> extension (or something similar). However,
|
||
this still requires <em>two</em> files, where currently only a single file is needed,
|
||
and as such, does not match the “better batch file” model (shell scripts and
|
||
batch files are typically self-contained). It requires the developer to remember
|
||
to keep the two files together, and this may not always be possible. For
|
||
example, system administration policies may require that <em>all</em> files in a
|
||
certain directory are executable (the Linux filesystem standards require this of
|
||
<code class="docutils literal notranslate"><span class="pre">/usr/bin</span></code>, for example). And some methods of sharing a 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 <code class="docutils literal notranslate"><span class="pre">pipx</span></code> support running a script
|
||
directly from a URL, so “download and unpack a zip of the script and its
|
||
dependencies” may not be an appropriate requirement).</p>
|
||
<p>Essentially, though, the issue here is that there is an explicitly stated
|
||
requirement that the format supports storing dependency data <em>in the script file
|
||
itself</em>. Solutions that don’t do that are simply ignoring that requirement.</p>
|
||
</section>
|
||
<section id="should-scripts-be-able-to-specify-a-package-index">
|
||
<h3><a class="toc-backref" href="#should-scripts-be-able-to-specify-a-package-index" role="doc-backlink">Should scripts be able to specify a package index?</a></h3>
|
||
<p>Dependency metadata is about <em>what</em> package the code depends on, and not <em>where</em>
|
||
that package comes from. There is no difference here between metadata for
|
||
scripts, and metadata for distribution packages (as defined in
|
||
<code class="docutils literal notranslate"><span class="pre">pyproject.toml</span></code>). In both cases, dependencies are given in “abstract” form,
|
||
without specifying how they are obtained.</p>
|
||
<p>Some tools that use the dependency information may, of course, need to locate
|
||
concrete dependency artifacts - for example if they expect to create an
|
||
environment containing those dependencies. But the way they choose to do that
|
||
will be closely linked to the tool’s UI in general, and this PEP does not try to
|
||
dictate the UI for tools.</p>
|
||
<p>There is more discussion of this point, and in particular of the UI choices made
|
||
by the <code class="docutils literal notranslate"><span class="pre">pip-run</span></code> tool, in <a class="reference external" href="https://github.com/jaraco/pip-run/issues/44">the previously mentioned pip-run issue</a>.</p>
|
||
</section>
|
||
<section id="what-about-local-dependencies">
|
||
<h3><a class="toc-backref" href="#what-about-local-dependencies" role="doc-backlink">What about local dependencies?</a></h3>
|
||
<p>These can be handled without needing special metadata and tooling, simply by
|
||
adding the location of the dependencies to <code class="docutils literal notranslate"><span class="pre">sys.path</span></code>. This PEP simply isn’t
|
||
needed for this case. If, on the other hand, the “local dependencies” are actual
|
||
distributions which are published locally, they can be specified as usual with a
|
||
<a class="pep reference internal" href="../pep-0508/" title="PEP 508 – Dependency specification for Python Software Packages">PEP 508</a> requirement, and the local package index specified when running a
|
||
tool by using the tool’s UI for that.</p>
|
||
</section>
|
||
</section>
|
||
<section id="open-issues">
|
||
<h2><a class="toc-backref" href="#open-issues" role="doc-backlink">Open Issues</a></h2>
|
||
<p>None at this point.</p>
|
||
</section>
|
||
<section id="copyright">
|
||
<h2><a class="toc-backref" href="#copyright" role="doc-backlink">Copyright</a></h2>
|
||
<p>This document is placed in the public domain or under the
|
||
CC0-1.0-Universal license, whichever is more permissive.</p>
|
||
</section>
|
||
</section>
|
||
<hr class="docutils" />
|
||
<p>Source: <a class="reference external" href="https://github.com/python/peps/blob/main/peps/pep-0722.rst">https://github.com/python/peps/blob/main/peps/pep-0722.rst</a></p>
|
||
<p>Last modified: <a class="reference external" href="https://github.com/python/peps/commits/main/peps/pep-0722.rst">2023-10-21 10:30:17 GMT</a></p>
|
||
|
||
</article>
|
||
<nav id="pep-sidebar">
|
||
<h2>Contents</h2>
|
||
<ul>
|
||
<li><a class="reference internal" href="#abstract">Abstract</a></li>
|
||
<li><a class="reference internal" href="#motivation">Motivation</a></li>
|
||
<li><a class="reference internal" href="#rationale">Rationale</a></li>
|
||
<li><a class="reference internal" href="#specification">Specification</a><ul>
|
||
<li><a class="reference internal" href="#example">Example</a></li>
|
||
</ul>
|
||
</li>
|
||
<li><a class="reference internal" href="#backwards-compatibility">Backwards Compatibility</a></li>
|
||
<li><a class="reference internal" href="#security-implications">Security Implications</a></li>
|
||
<li><a class="reference internal" href="#how-to-teach-this">How to Teach This</a></li>
|
||
<li><a class="reference internal" href="#recommendations">Recommendations</a></li>
|
||
<li><a class="reference internal" href="#reference-implementation">Reference Implementation</a></li>
|
||
<li><a class="reference internal" href="#rejected-ideas">Rejected Ideas</a><ul>
|
||
<li><a class="reference internal" href="#why-not-include-other-metadata">Why not include other metadata?</a></li>
|
||
<li><a class="reference internal" href="#why-not-use-a-marker-per-line">Why not use a marker per line?</a></li>
|
||
<li><a class="reference internal" href="#why-not-use-a-distinct-form-of-comment-for-the-dependency-block">Why not use a distinct form of comment for the dependency block?</a></li>
|
||
<li><a class="reference internal" href="#why-not-allow-multiple-dependency-blocks-and-merge-them">Why not allow multiple dependency blocks and merge them?</a></li>
|
||
<li><a class="reference internal" href="#why-not-use-a-more-standard-data-format-e-g-toml">Why not use a more standard data format (e.g., TOML)?</a></li>
|
||
<li><a class="reference internal" href="#why-not-use-possibly-restricted-python-syntax">Why not use (possibly restricted) Python syntax?</a></li>
|
||
<li><a class="reference internal" href="#why-not-embed-a-pyproject-toml-file-in-the-script">Why not embed a <code class="docutils literal notranslate"><span class="pre">pyproject.toml</span></code> file in the script?</a></li>
|
||
<li><a class="reference internal" href="#why-not-infer-the-requirements-from-import-statements">Why not infer the requirements from import statements?</a></li>
|
||
<li><a class="reference internal" href="#why-not-simply-manage-the-environment-at-runtime">Why not simply manage the environment at runtime?</a></li>
|
||
<li><a class="reference internal" href="#why-not-just-set-up-a-python-project-with-a-pyproject-toml">Why not just set up a Python project with a <code class="docutils literal notranslate"><span class="pre">pyproject.toml</span></code>?</a></li>
|
||
<li><a class="reference internal" href="#why-not-use-a-requirements-file-for-dependencies">Why not use a requirements file for dependencies?</a></li>
|
||
<li><a class="reference internal" href="#should-scripts-be-able-to-specify-a-package-index">Should scripts be able to specify a package index?</a></li>
|
||
<li><a class="reference internal" href="#what-about-local-dependencies">What about local dependencies?</a></li>
|
||
</ul>
|
||
</li>
|
||
<li><a class="reference internal" href="#open-issues">Open Issues</a></li>
|
||
<li><a class="reference internal" href="#copyright">Copyright</a></li>
|
||
</ul>
|
||
|
||
<br>
|
||
<a id="source" href="https://github.com/python/peps/blob/main/peps/pep-0722.rst">Page Source (GitHub)</a>
|
||
</nav>
|
||
</section>
|
||
<script src="../_static/colour_scheme.js"></script>
|
||
<script src="../_static/wrap_tables.js"></script>
|
||
<script src="../_static/sticky_banner.js"></script>
|
||
</body>
|
||
</html> |