python-peps/pep-0668/index.html

1162 lines
96 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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 668 Marking Python base environments as “externally managed” | peps.python.org</title>
<link rel="shortcut icon" href="../_static/py.png">
<link rel="canonical" href="https://peps.python.org/pep-0668/">
<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 668 Marking Python base environments as “externally managed” | peps.python.org'>
<meta property="og:description" content="A long-standing practical problem for Python users has been conflicts between OS package managers and Python-specific package management tools like pip. These conflicts include both Python-level API incompatibilities and conflicts over file ownership.">
<meta property="og:type" content="website">
<meta property="og:url" content="https://peps.python.org/pep-0668/">
<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="A long-standing practical problem for Python users has been conflicts between OS package managers and Python-specific package management tools like pip. These conflicts include both Python-level API incompatibilities and conflicts over file ownership.">
<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> &raquo; </li>
<li><a href="../pep-0000/">PEP Index</a> &raquo; </li>
<li>PEP 668</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 668 Marking Python base environments as “externally managed”</h1>
<dl class="rfc2822 field-list simple">
<dt class="field-odd">Author<span class="colon">:</span></dt>
<dd class="field-odd">Geoffrey Thomas &lt;geofft&#32;&#97;t&#32;ldpreload.com&gt;,
Matthias Klose &lt;doko&#32;&#97;t&#32;ubuntu.com&gt;,
Filipe Laíns &lt;lains&#32;&#97;t&#32;riseup.net&gt;,
Donald Stufft &lt;donald&#32;&#97;t&#32;stufft.io&gt;,
Tzu-ping Chung &lt;uranusjr&#32;&#97;t&#32;gmail.com&gt;,
Stefano Rivera &lt;stefanor&#32;&#97;t&#32;debian.org&gt;,
Elana Hashman &lt;ehashman&#32;&#97;t&#32;debian.org&gt;,
Pradyun Gedam &lt;pradyunsg&#32;&#97;t&#32;gmail.com&gt;</dd>
<dt class="field-even">PEP-Delegate<span class="colon">:</span></dt>
<dd class="field-even">Paul Moore &lt;p.f.moore&#32;&#97;t&#32;gmail.com&gt;</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/10302">Discourse thread</a></dd>
<dt class="field-even">Status<span class="colon">:</span></dt>
<dd class="field-even"><abbr title="Normative proposal accepted for implementation">Accepted</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">18-May-2021</dd>
<dt class="field-even">Post-History<span class="colon">:</span></dt>
<dd class="field-even">28-May-2021</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/10302/44">Discourse message</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="#terminology">Terminology</a></li>
<li><a class="reference internal" href="#motivation">Motivation</a></li>
<li><a class="reference internal" href="#rationale">Rationale</a><ul>
<li><a class="reference internal" href="#use-cases">Use cases</a></li>
</ul>
</li>
<li><a class="reference internal" href="#specification">Specification</a><ul>
<li><a class="reference internal" href="#marking-an-interpreter-as-using-an-external-package-manager">Marking an interpreter as using an external package manager</a></li>
<li><a class="reference internal" href="#writing-to-only-the-target-sysconfig-scheme">Writing to only the target <code class="docutils literal notranslate"><span class="pre">sysconfig</span></code> scheme</a></li>
</ul>
</li>
<li><a class="reference internal" href="#recommendations-for-distros">Recommendations for distros</a><ul>
<li><a class="reference internal" href="#mark-the-installation-as-externally-managed">Mark the installation as externally managed</a></li>
<li><a class="reference internal" href="#guide-users-towards-virtual-environments">Guide users towards virtual environments</a></li>
<li><a class="reference internal" href="#keep-the-marker-file-in-container-images">Keep the marker file in container images</a></li>
<li><a class="reference internal" href="#create-separate-distro-and-local-directories">Create separate distro and local directories</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="#alternatives">Alternatives</a><ul>
<li><a class="reference internal" href="#marker-file">Marker file</a></li>
<li><a class="reference internal" href="#system-python">System Python</a></li>
</ul>
</li>
<li><a class="reference internal" href="#implementation-notes">Implementation Notes</a></li>
<li><a class="reference internal" href="#references">References</a></li>
<li><a class="reference internal" href="#copyright">Copyright</a></li>
</ul>
</details></section>
<div class="pep-banner canonical-pypa-spec sticky-banner admonition attention">
<p class="admonition-title">Attention</p>
<p>This PEP is a historical document. The up-to-date, canonical spec, <a class="reference external" href="https://packaging.python.org/en/latest/specifications/externally-managed-environments/#externally-managed-environments" title="(in Python Packaging User Guide)"><span>Externally Managed Environments</span></a>, is maintained on the <a class="reference external" href="https://packaging.python.org/en/latest/specifications/">PyPA specs page</a>.</p>
<p class="close-button">×</p>
<p>See the <a class="reference external" href="https://www.pypa.io/en/latest/specifications/#handling-fixes-and-other-minor-updates">PyPA specification update process</a> for how to propose changes.</p>
</div>
<section id="abstract">
<h2><a class="toc-backref" href="#abstract" role="doc-backlink">Abstract</a></h2>
<p>A long-standing practical problem for Python users has been conflicts
between OS package managers and Python-specific package management
tools like pip. These conflicts include both Python-level API
incompatibilities and conflicts over file ownership.</p>
<p>Historically, Python-specific package management tools have defaulted
to installing packages into an implicit global context. With the
standardization and popularity of virtual environments, a better
solution for most (but not all) use cases is to use Python-specific
package management tools only within a virtual environment.</p>
<p>This PEP proposes a mechanism for a Python installation to communicate
to tools like pip that its global package installation context is
managed by some means external to Python, such as an OS package
manager. It specifies that Python-specific package management tools
should neither install nor remove packages into the interpreters
global context, by default, and should instead guide the end user
towards using a virtual environment.</p>
<p>It also standardizes an interpretation of the <code class="docutils literal notranslate"><span class="pre">sysconfig</span></code> schemes so
that, if a Python-specific package manager is about to install a
package in an interpreter-wide context, it can do so in a manner that
will avoid conflicting with the external package manager and reduces
the risk of breaking software shipped by the external package manager.</p>
</section>
<section id="terminology">
<h2><a class="toc-backref" href="#terminology" role="doc-backlink">Terminology</a></h2>
<p>A few terms used in this PEP have multiple meanings in the contexts
that it spans. For clarity, this PEP uses the following terms in
specific ways:</p>
<dl>
<dt>distro</dt><dd>Short for “distribution,” a collection of various sorts of
software, ideally designed to work properly together, including
(in contexts relevant to this document) the Python interpreter
itself, software written in Python, and software written in other
languages. That is, this is the sense used in phrases such as
“Linux distro” or “Berkeley Software Distribution.”<p>A distro can be an operating system (OS) of its own, such as
Debian, Fedora, or FreeBSD. It can also be an overlay distribution
that installs on top of an existing OS, such as Homebrew or
MacPorts.</p>
<p>This document uses the short term “distro,” because the term
“distribution” has another meaning in Python packaging contexts: a
source or binary distribution package of a single piece of Python
language software, that is, in the sense of
<code class="docutils literal notranslate"><span class="pre">setuptools.dist.Distribution</span></code> or “sdist”. To avoid confusion,
this document does not use the plain term “distribution” at all.
In the Python packaging sense, it uses the full phrase
“distribution package” or just “package” (see below).</p>
<p>The provider of a distro - the team or company that collects and
publishes the software and makes any needed modifications - is its
<strong>distributor</strong>.</p>
</dd>
<dt>package</dt><dd>A unit of software that can be installed and used within Python.
That is, this refers to what Python-specific packaging tools tend
to call a “<a class="reference external" href="https://packaging.python.org/glossary/#term-Distribution-Package">distribution package</a>” or simply a “distribution”;
the colloquial abbreviation “package” is used in the sense of the
Python Package Index.<p>This document does not use “package” in the sense of an importable
name that contains Python modules, though in many cases, a
distribution package consists of a single importable package of
the same name.</p>
<p>This document generally does not use the term “package” to refer
to units of installation by a distros package manager (such as
<code class="docutils literal notranslate"><span class="pre">.deb</span></code> or <code class="docutils literal notranslate"><span class="pre">.rpm</span></code> files). When needed, it uses phrasing such as
“a distros package.” (Again, in many cases, a Python package is
shipped inside a distros package named something like <code class="docutils literal notranslate"><span class="pre">python-</span></code>
plus the Python package name.)</p>
</dd>
<dt>Python-specific package manager</dt><dd>A tool for installing, upgrading, and/or removing Python packages
in a manner that conforms to Python packaging standards (such as
<a class="pep reference internal" href="../pep-0376/" title="PEP 376 Database of Installed Python Distributions">PEP 376</a> and <a class="pep reference internal" href="../pep-0427/" title="PEP 427 The Wheel Binary Package Format 1.0">PEP 427</a>). The most popular Python-specific package
manager is pip <a class="footnote-reference brackets" href="#pip" id="id1">[1]</a>; other examples include the old Easy
Install command <a class="footnote-reference brackets" href="#easy-install" id="id2">[2]</a> as well as direct usage of a
<code class="docutils literal notranslate"><span class="pre">setup.py</span></code> command.<p>(Conda <a class="footnote-reference brackets" href="#conda" id="id3">[3]</a> is a bit of a special case, as the <code class="docutils literal notranslate"><span class="pre">conda</span></code>
command can install much more than just Python packages, making it
more like a distro package manager in some senses. Since the
<code class="docutils literal notranslate"><span class="pre">conda</span></code> command generally only operates on Conda-created
environments, most of the concerns in this document do not apply
to <code class="docutils literal notranslate"><span class="pre">conda</span></code> when acting as a Python-specific package manager.)</p>
</dd>
<dt>distro package manager</dt><dd>A tool for installing, upgrading, and/or removing a distros
packages in an installed instance of that distro, which is capable
of installing Python packages as well as non-Python packages, and
therefore generally has its own database of installed software
unrelated to <a class="pep reference internal" href="../pep-0376/" title="PEP 376 Database of Installed Python Distributions">PEP 376</a>. Examples include <code class="docutils literal notranslate"><span class="pre">apt</span></code>, <code class="docutils literal notranslate"><span class="pre">dpkg</span></code>, <code class="docutils literal notranslate"><span class="pre">dnf</span></code>,
<code class="docutils literal notranslate"><span class="pre">rpm</span></code>, <code class="docutils literal notranslate"><span class="pre">pacman</span></code>, and <code class="docutils literal notranslate"><span class="pre">brew</span></code>. The salient feature is that if
a package was installed by a distro package manager, removing or
upgrading it in a way that would satisfy a Python-specific package
manager will generally leave a distro package manager in an
inconsistent state.<p>This document also uses phrases like “external package manager” or
“systems package manager” to refer to a distro package manager in
certain contexts.</p>
</dd>
<dt>shadow</dt><dd>To shadow an installed Python package is to cause some other
package to be preferred for imports without removing any files
from the shadowed package. This requires multiple entries on
<code class="docutils literal notranslate"><span class="pre">sys.path</span></code>: if package A 2.0 installs module <code class="docutils literal notranslate"><span class="pre">a.py</span></code> in one
<code class="docutils literal notranslate"><span class="pre">sys.path</span></code> entry, and package A 1.0 installs module <code class="docutils literal notranslate"><span class="pre">a.py</span></code> in
a later <code class="docutils literal notranslate"><span class="pre">sys.path</span></code> entry, then <code class="docutils literal notranslate"><span class="pre">import</span> <span class="pre">a</span></code> returns the module
from the former, and we say that A 2.0 shadows A 1.0.</dd>
</dl>
</section>
<section id="motivation">
<h2><a class="toc-backref" href="#motivation" role="doc-backlink">Motivation</a></h2>
<p>Thanks to Pythons immense popularity, software distros (by which we
mean Linux and other OS distros as well as overlay distros like
Homebrew and MacPorts) generally ship Python for two purposes: as a
software package to be used in its own right by end users, and as a
language dependency for other software in the distro.</p>
<p>For example, Fedora and Debian (and their downstream distros, as well
as many others) ship a <code class="docutils literal notranslate"><span class="pre">/usr/bin/python3</span></code> binary which provides the
<code class="docutils literal notranslate"><span class="pre">python3</span></code> command available to end users as well as the
<code class="docutils literal notranslate"><span class="pre">#!/usr/bin/python3</span></code> shebang for Python-language software included
in the distro. Because there are no official binary releases of Python
for Linux/UNIX, almost all Python end users on these OSes use the
Python interpreter built and shipped with their distro.</p>
<p>The <code class="docutils literal notranslate"><span class="pre">python3</span></code> executable available to the users of the distro and
the <code class="docutils literal notranslate"><span class="pre">python3</span></code> executable available as a dependency for other
software in the distro are typically the same binary. This means that
if an end user installs a Python package using a tool like <code class="docutils literal notranslate"><span class="pre">pip</span></code>
outside the context of a virtual environment, that package is visible
to Python-language software shipped by the distro. If the
newly-installed package (or one of its dependencies) is a newer,
backwards-incompatible version of a package that was installed through
the distro, it may break software shipped by the distro.</p>
<p>This may pose a critical problem for the integrity of distros, which
often have package-management tools that are themselves written in
Python. For example, its possible to unintentionally break Fedoras
<code class="docutils literal notranslate"><span class="pre">dnf</span></code> command with a <code class="docutils literal notranslate"><span class="pre">pip</span> <span class="pre">install</span></code> command, making it hard to
recover.</p>
<p>This applies both to system-wide installs (<code class="docutils literal notranslate"><span class="pre">sudo</span> <span class="pre">pip</span> <span class="pre">install</span></code>) as
well as user home directory installs (<code class="docutils literal notranslate"><span class="pre">pip</span> <span class="pre">install</span> <span class="pre">--user</span></code>), since
packages in either location show up on the <code class="docutils literal notranslate"><span class="pre">sys.path</span></code> of
<code class="docutils literal notranslate"><span class="pre">/usr/bin/python3</span></code>.</p>
<p>There is a worse problem with system-wide installs: if you attempt to
recover from this situation with <code class="docutils literal notranslate"><span class="pre">sudo</span> <span class="pre">pip</span> <span class="pre">uninstall</span></code>, you may end
up removing packages that are shipped by the systems package manager.
In fact, this can even happen if you simply upgrade a package - pip
will try to remove the old version of the package, as shipped by the
OS. At this point it may not be possible to recover the system to a
consistent state using just the software remaining on the system.</p>
<p>Over the past many years, a consensus has emerged that the best way to
install Python libraries or applications (when not using a distros
package) is to use a virtual environment. This approach was
popularized by the PyPA <a class="reference external" href="https://virtualenv.pypa.io/en/latest/">virtualenv</a> project, and a simple version of
that approach is now available in the Python standard library as
<code class="docutils literal notranslate"><span class="pre">venv</span></code>. Installing a Python package into a virtualenv prevents it
from being visible to the unqualified <code class="docutils literal notranslate"><span class="pre">/usr/bin/python3</span></code> interpreter
and prevents breaking system software.</p>
<p>In some cases, however, its useful and intentional to install a
Python package from outside of the distro that influences the behavior
of distro-shipped commands. This is common in the case of software
like Sphinx or Ansible which have a mechanism for writing
Python-language extensions. A user may want to use their distros
version of the base software (for reasons of paid support or security
updates) but install a small extension from PyPI, and theyd want that
extension to be importable by the software in their base system.</p>
<p>While this continues to carry the risk of installing a newer version
of a dependency than the operating system expects or otherwise
negatively affecting the behavior of an application, it does not need
to carry the risk of removing files from the operating system. A tool
like pip should be able to install packages in some directory on the
default <code class="docutils literal notranslate"><span class="pre">sys.path</span></code>, if specifically requested, without deleting
files owned by the systems package manager.</p>
<p>Therefore, this PEP proposes two things.</p>
<p>First, it proposes <strong>a way for distributors of a Python interpreter to
mark that interpreter as having its packages managed by means external
to Python</strong>, such that Python-specific tools like pip should not
change the installed packages in the interpreters global <code class="docutils literal notranslate"><span class="pre">sys.path</span></code>
in any way (add, upgrade/downgrade, or remove) unless specifically
overridden. It also provides a means for the distributor to indicate
how to use a virtual environment as an alternative.</p>
<p>This is an opt-in mechanism: by default, the Python interpreter
compiled from upstream sources will not be so marked, and so running
<code class="docutils literal notranslate"><span class="pre">pip</span> <span class="pre">install</span></code> with a self-compiled interpreter, or with a distro
that has not explicitly marked its interpreter, will work as it always
has worked.</p>
<p>Second, it sets the rule that when installing packages to an
interpreters global context (either to an unmarked interpreter, or if
overriding the marking), <strong>Python-specific package managers should
modify or delete files only within the directories of the sysconfig
scheme in which they would create files</strong>. This permits a distributor
of a Python interpreter to set up two directories, one for its own
managed packages, and one for unmanaged packages installed by the end
user, and ensure that installing unmanaged packages will not delete
(or overwrite) files owned by the external package manager.</p>
</section>
<section id="rationale">
<h2><a class="toc-backref" href="#rationale" role="doc-backlink">Rationale</a></h2>
<p>As described in detail in the next section, the first behavior change
involves creating a marker file named <code class="docutils literal notranslate"><span class="pre">EXTERNALLY-MANAGED</span></code>, whose
presence indicates that non-virtual-environment package installations
are managed by some means external to Python, such as a distros
package manager. This file is specified to live in the <code class="docutils literal notranslate"><span class="pre">stdlib</span></code>
directory in the default <code class="docutils literal notranslate"><span class="pre">sysconfig</span></code> scheme, which marks the
interpreter / installation as a whole, not a particular location on
<code class="docutils literal notranslate"><span class="pre">sys.path</span></code>. The reason for this is that, as identified above, there
are two related problems that risk breaking an externally-managed
Python: you can install an incompatible new version of a package
system-wide (e.g., with <code class="docutils literal notranslate"><span class="pre">sudo</span> <span class="pre">pip</span> <span class="pre">install</span></code>), and you can install one
in your user account alone, but in a location that is on the standard
Python commands <code class="docutils literal notranslate"><span class="pre">sys.path</span></code> (e.g., with <code class="docutils literal notranslate"><span class="pre">pip</span> <span class="pre">install</span> <span class="pre">--user</span></code>). If
the marker file were in the system-wide <code class="docutils literal notranslate"><span class="pre">site-packages</span></code> directory,
it would not clearly apply to the second case. The <a class="reference internal" href="#alternatives">Alternatives</a>
section has further discussion of possible locations.</p>
<p>The second behavior change takes advantage of the existing
<code class="docutils literal notranslate"><span class="pre">sysconfig</span></code> setup in distros that have already encountered this
class of problem, and specifically addresses the problem of a
Python-specific package manager deleting or overwriting files that are
owned by an external package manager.</p>
<section id="use-cases">
<h3><a class="toc-backref" href="#use-cases" role="doc-backlink">Use cases</a></h3>
<p>The changed behavior in this PEP is intended to “do the right thing”
for as many use cases as possible. In this section, we consider the
changes specified by this PEP for several representative use cases /
contexts. Specifically, we ask about the two behaviors that could be
changed by this PEP:</p>
<ol class="arabic simple">
<li>Will a Python-specific installer tool like <code class="docutils literal notranslate"><span class="pre">pip</span> <span class="pre">install</span></code> permit
installations by default, after implementation of this PEP?</li>
<li>If you do run such a tool, should it be willing to delete packages
shipped by the external (non-Python-specific) package manager for
that context, such as a distro package manager?</li>
</ol>
<p>(For simplicity, this section discusses pip as the Python-specific
installer tool, though the analysis should apply equally to any other
Python-specific package management tool.)</p>
<p>This table summarizes the use cases discussed in detail below:</p>
<table class="docutils align-default">
<thead>
<tr class="row-odd"><th class="head">Case</th>
<th class="head">Description</th>
<th class="head"><code class="docutils literal notranslate"><span class="pre">pip</span> <span class="pre">install</span></code> permitted</th>
<th class="head">Deleting externally-installed packages permitted</th>
</tr>
</thead>
<tbody>
<tr class="row-even"><td>1</td>
<td>Unpatched CPython</td>
<td>Currently yes; stays yes</td>
<td>Currently yes; stays yes</td>
</tr>
<tr class="row-odd"><td>2</td>
<td>Distro <code class="docutils literal notranslate"><span class="pre">/usr/bin/python3</span></code></td>
<td>Currently yes; becomes no
(assuming the distro
adds a marker file)</td>
<td>Currently yes (except on Debian); becomes no</td>
</tr>
<tr class="row-even"><td>3</td>
<td>Distro Python in venv</td>
<td>Currently yes; stays yes</td>
<td>There are no externally-installed packages</td>
</tr>
<tr class="row-odd"><td>4</td>
<td>Distro Python in venv
with <code class="docutils literal notranslate"><span class="pre">--system-site-packages</span></code></td>
<td>Currently yes; stays yes</td>
<td>Currently no; stays no</td>
</tr>
<tr class="row-even"><td>5</td>
<td>Distro Python in Docker</td>
<td>Currently yes; becomes no
(assuming the distro
adds a marker file)</td>
<td>Currently yes; becomes no</td>
</tr>
<tr class="row-odd"><td>6</td>
<td>Conda environment</td>
<td>Currently yes; stays yes</td>
<td>Currently yes; stays yes</td>
</tr>
<tr class="row-even"><td>7</td>
<td>Dev-facing distro</td>
<td>Currently yes; becomes no
(assuming they add a
marker file)</td>
<td>Currently often yes; becomes no
(assuming they configure <code class="docutils literal notranslate"><span class="pre">sysconfig</span></code> as needed)</td>
</tr>
<tr class="row-odd"><td>8</td>
<td>Distro building packages</td>
<td>Currently yes; can stay yes</td>
<td>Currently yes; becomes no</td>
</tr>
<tr class="row-even"><td>9</td>
<td><code class="docutils literal notranslate"><span class="pre">PYTHONHOME</span></code> copied from
a distro Python stdlib</td>
<td>Currently yes; becomes no</td>
<td>Currently yes; becomes no</td>
</tr>
<tr class="row-odd"><td>10</td>
<td><code class="docutils literal notranslate"><span class="pre">PYTHONHOME</span></code> copied from
upstream Python stdlib</td>
<td>Currently yes; stays yes</td>
<td>Currently yes; stays yes</td>
</tr>
</tbody>
</table>
<p>In more detail, the use cases above are:</p>
<ol class="arabic">
<li>A standard unpatched CPython, without any special configuration of
or patches to <code class="docutils literal notranslate"><span class="pre">sysconfig</span></code> and without a marker file. This PEP
does not change its behavior.<p>Such a CPython should (regardless of this PEP) not be installed in
a way that overlaps any distro-installed Python on the same system.
For instance, on an OS that ships Python in <code class="docutils literal notranslate"><span class="pre">/usr/bin</span></code>, you
should not install a custom CPython built with <code class="docutils literal notranslate"><span class="pre">./configure</span>
<span class="pre">--prefix=/usr</span></code>, or it will overwrite some files from the distro
and the distro will eventually overwrite some files from your
installation. Instead, your installation should be in a separate
directory (perhaps <code class="docutils literal notranslate"><span class="pre">/usr/local</span></code>, <code class="docutils literal notranslate"><span class="pre">/opt</span></code>, or your home
directory).</p>
<p>Therefore, we can assume that such a CPython has its own <code class="docutils literal notranslate"><span class="pre">stdlib</span></code>
directory and its own <code class="docutils literal notranslate"><span class="pre">sysconfig</span></code> schemes that do not overlap any
distro-installed Python. So any OS-installed packages are not
visible or relevant here.</p>
<p>If there is a concept of “externally-installed” packages in this
case, its something outside the OS and generally managed by
whoever built and installed this CPython. Because the installer
chose not to add a marker file or modify <code class="docutils literal notranslate"><span class="pre">sysconfig</span></code> schemes,
theyre choosing the current behavior, and <code class="docutils literal notranslate"><span class="pre">pip</span> <span class="pre">install</span></code> can
remove any packages available in this CPython.</p>
</li>
<li>A distros <code class="docutils literal notranslate"><span class="pre">/usr/bin/python3</span></code>, either when running <code class="docutils literal notranslate"><span class="pre">pip</span>
<span class="pre">install</span></code> as root or <code class="docutils literal notranslate"><span class="pre">pip</span> <span class="pre">install</span> <span class="pre">--user</span></code>, following our
<a class="reference internal" href="#recommendations-for-distros">Recommendations for distros</a>.<p>These recommendations include shipping a marker file in the
<code class="docutils literal notranslate"><span class="pre">stdlib</span></code> directory, to prevent <code class="docutils literal notranslate"><span class="pre">pip</span> <span class="pre">install</span></code> by default, and
placing distro-shipped packages in a location other than the
default <code class="docutils literal notranslate"><span class="pre">sysconfig</span></code> scheme, so that <code class="docutils literal notranslate"><span class="pre">pip</span></code> as root does not
write to that location.</p>
<p>Many distros (including Debian, Fedora, and their derivatives) are
already doing the latter.</p>
<p>On Debian and derivatives, <code class="docutils literal notranslate"><span class="pre">pip</span> <span class="pre">install</span></code> does not currently
delete distro-installed packages, because Debian carries a <a class="reference external" href="https://sources.debian.org/src/python-pip/20.3.4-2/debian/patches/hands-off-system-packages.patch/">patch
to pip to prevent this</a>. So, for those distros, this PEP is not a
behavior change; it simply standardizes that behavior in a way that
is no longer Debian-specific and can be included into upstream pip.</p>
<p>(We have seen user reports of externally-installed packages being
deleted on Debian or a derivative. We suspect this is because the
user has previously run <code class="docutils literal notranslate"><span class="pre">sudo</span> <span class="pre">pip</span> <span class="pre">install</span> <span class="pre">--upgrade</span> <span class="pre">pip</span></code> and
therefore now has a version of <code class="docutils literal notranslate"><span class="pre">/usr/bin/pip</span></code> without the Debian
patch; standardizing this behavior in upstream package installers
would address this problem.)</p>
</li>
<li>A distro Python when used inside a virtual environment (either from
<code class="docutils literal notranslate"><span class="pre">venv</span></code> or <code class="docutils literal notranslate"><span class="pre">virtualenv</span></code>).<p>Inside a virtual environment, all packages are owned by that
environment. Even when <code class="docutils literal notranslate"><span class="pre">pip</span></code>, <code class="docutils literal notranslate"><span class="pre">setuptools</span></code>, etc. are installed
into the environment, they are and should be managed by tools
specific to that environment; they are not system-managed.</p>
</li>
<li>A distro Python when used inside a virtual environment with
<code class="docutils literal notranslate"><span class="pre">--system-site-packages</span></code>. This is like the previous case, but
worth calling out explicitly, because anything on the global
<code class="docutils literal notranslate"><span class="pre">sys.path</span></code> is visible.<p>Currently, the answer to “Will <code class="docutils literal notranslate"><span class="pre">pip</span></code> delete externally-installed
packages” is no, because pip has a special case for running in a
virtual environment and attempting to delete packages outside it.
After this PEP, the answer remains no, but the reasoning becomes
more general: system site packages will be outside any of the
<code class="docutils literal notranslate"><span class="pre">sysconfig</span></code> schemes used for package management in the
environment.</p>
</li>
<li>A distro Python when used in a single-application container image
(e.g., a Docker container). In this use case, the risk of breaking
system software is lower, since generally only a single application
runs in the container, and the impact is lower, since you can
rebuild the container and you dont have to struggle to recover a
running machine. There are also a large number of existing
Dockerfiles with an unqualified <code class="docutils literal notranslate"><span class="pre">RUN</span> <span class="pre">pip</span> <span class="pre">install</span> <span class="pre">...</span></code> statement,
etc., and it would be good not to break those. So, builders of base
container images may want to ensure that the marker file is not
present, even if the underlying OS ships one by default.<p>There is a small behavior change: currently, <code class="docutils literal notranslate"><span class="pre">pip</span></code> run as root
will delete externally-installed packages, but after this PEP it
will not. We dont propose a way to override this. However, since
the base image is generally minimal, there shouldnt be much of a
use case for simply uninstalling packages (especially without using
the distros own tools). The common case is when pip wants to
upgrade a package, which previously would have deleted the old
version (except on Debian). After this change, the old version will
still be on disk, but pip will still <em>shadow</em> externally-installed
packages, and we believe this to be sufficient for this not to be a
breaking change in practice - a Python <code class="docutils literal notranslate"><span class="pre">import</span></code> statement will
still get you the newly-installed package.</p>
<p>If it becomes necessary to have a way to do this, we suggest that
the distro should document a way for the installer tool to access
the <code class="docutils literal notranslate"><span class="pre">sysconfig</span></code> scheme used by the distro itself. See the
<a class="reference internal" href="#recommendations-for-distros">Recommendations for distros</a> section for more discussion.</p>
<p>It is the view of the authors of this PEP that its still a good
idea to use virtual environments with distro-installed Python
interpreters, even in single-application container images. Even
though they run a single <em>application</em>, that application may run
commands from the OS that are implemented in Python, and if youve
installed or upgraded the distro-shipped Python packages using
Python-specific tools, those commands may break.</p>
</li>
<li>Conda specifically supports the use of non-<code class="docutils literal notranslate"><span class="pre">conda</span></code> tools like pip
to install software not available in the Conda repositories. In
this context, Conda acts as the external package manager / distro
and pip as the Python-specific one.<p>In some sense, this is similar to the first case, since Conda
provides its own installation of the Python interpreter.</p>
<p>We dont believe this PEP requires any changes to Conda, and
versions of pip that have implemented the changes in this PEP will
continue to behave as they currently do inside Conda environments.
(That said, it may be worth considering whether to use separate
<code class="docutils literal notranslate"><span class="pre">sysconfig</span></code> schemes for pip-installed and Conda-installed
software, for the same reasons its a good idea for other distros.)</p>
</li>
<li>By a “developer-facing distro,” we mean a specific type of distro
where direct users of Python or other languages in the distro are
expected or encouraged to make changes to the distro itself if they
wish to add libraries. Common examples include private “monorepos”
at software development companies, where a single repository builds
both third-party and in-house software, and the direct users of the
distros Python interpreter are generally software developers
writing said in-house software. User-level package managers like
<a class="reference external" href="https://github.com/NixOS/nixpkgs">Nixpkgs</a> may also count, because they encourage users of Nix who
are Python developers to <a class="reference external" href="https://wiki.nixos.org/wiki/Python">package their software for Nix</a>.<p>In these cases, the distro may want to respond to an attempted
<code class="docutils literal notranslate"><span class="pre">pip</span> <span class="pre">install</span></code> with guidance encouraging use of the distros own
facilities for adding new packages, along with a link to
documentation.</p>
<p>If the distro supports/encourages creating a virtual environment
from the distros Python interpreter, there may also be custom
instructions for how to properly set up a virtual environment (as
for example Nixpkgs does).</p>
</li>
<li>When building distro Python packages for a distro Python (case 2),
it may be useful to have <code class="docutils literal notranslate"><span class="pre">pip</span> <span class="pre">install</span></code> be usable as part of the
distros package build process. (Consider, for instance, building a
<code class="docutils literal notranslate"><span class="pre">python-xyz</span></code> RPM by using <code class="docutils literal notranslate"><span class="pre">pip</span> <span class="pre">install</span> <span class="pre">.</span></code> inside an sdist /
source tarball for <code class="docutils literal notranslate"><span class="pre">xyz</span></code>.) The distro may also want to use a more
targeted but still Python-specific installation tool such as
<a class="reference external" href="https://installer.rtfd.io/">installer</a>.<p>For this case, the build process will need to find some way to
suppress the marker file to allow <code class="docutils literal notranslate"><span class="pre">pip</span> <span class="pre">install</span></code> to work, and will
probably need to point the Python-specific tool at the distros
<code class="docutils literal notranslate"><span class="pre">sysconfig</span></code> scheme instead of the shipped default. See the
<a class="reference internal" href="#recommendations-for-distros">Recommendations for distros</a> section for more discussion on how
to implement this.</p>
<p>As a result of this PEP, pip will no longer be able to remove
packages already on the system. However, this behavior change is
fine because a package build process should not (and generally
cannot) include instructions to delete some other files on the
system; it can only package up its own files.</p>
</li>
<li>A distro Python used with <code class="docutils literal notranslate"><span class="pre">PYTHONHOME</span></code> to set up an alternative
Python environment (as opposed to a virtual environment), where
<code class="docutils literal notranslate"><span class="pre">PYTHONHOME</span></code> is set to some directory copied directly from the
distro Python (e.g., <code class="docutils literal notranslate"><span class="pre">cp</span> <span class="pre">-a</span> <span class="pre">/usr/lib/python3.x</span> <span class="pre">pyhome/lib</span></code>).<p>Assuming there are no modifications, then the behavior is just like
the underlying distro Python (case 2). So there are behavior
changes - you can no longer <code class="docutils literal notranslate"><span class="pre">pip</span> <span class="pre">install</span></code> by default, and if you
override it, it will no longer delete externally-installed packages
(i.e., Python packages that were copied from the OS and live in the
OS-managed <code class="docutils literal notranslate"><span class="pre">sys.path</span></code> entry).</p>
<p>This behavior change seems to be defensible, in that if your
<code class="docutils literal notranslate"><span class="pre">PYTHONHOME</span></code> is a straight copy of the distros Python, it should
behave like the distros Python.</p>
</li>
<li>A distro Python (or any Python interpreter) used with a
<code class="docutils literal notranslate"><span class="pre">PYTHONHOME</span></code> taken from a compatible unmodified upstream Python.<p>Because the behavior changes in this PEP are keyed off of files in
the standard library (the marker file in <code class="docutils literal notranslate"><span class="pre">stdlib</span></code> and the
behavior of the <code class="docutils literal notranslate"><span class="pre">sysconfig</span></code> module), the behavior is just like
an unmodified upstream CPython (case 1).</p>
</li>
</ol>
</section>
</section>
<section id="specification">
<h2><a class="toc-backref" href="#specification" role="doc-backlink">Specification</a></h2>
<section id="marking-an-interpreter-as-using-an-external-package-manager">
<h3><a class="toc-backref" href="#marking-an-interpreter-as-using-an-external-package-manager" role="doc-backlink">Marking an interpreter as using an external package manager</a></h3>
<p>Before a Python-specific package installer (that is, a tool such as
pip - not an external tool such as apt) installs a package into a
certain Python context, it should make the following checks by
default:</p>
<ol class="arabic simple">
<li>Is it running outside of a virtual environment? It can determine
this by whether <code class="docutils literal notranslate"><span class="pre">sys.prefix</span> <span class="pre">==</span> <span class="pre">sys.base_prefix</span></code> (but see
<a class="reference internal" href="#backwards-compatibility">Backwards Compatibility</a>).</li>
<li>Is there an <code class="docutils literal notranslate"><span class="pre">EXTERNALLY-MANAGED</span></code> file in the directory identified
by <code class="docutils literal notranslate"><span class="pre">sysconfig.get_path(&quot;stdlib&quot;,</span>
<span class="pre">sysconfig.get_default_scheme())</span></code>?</li>
</ol>
<p>If both of these conditions are true, the installer should exit with
an error message indicating that package installation into this Python
interpreters directory are disabled outside of a virtual environment.</p>
<p>The installer should have a way for the user to override these rules,
such as a command-line flag <code class="docutils literal notranslate"><span class="pre">--break-system-packages</span></code>. This option
should not be enabled by default and should carry some connotation
that its use is risky.</p>
<p>The <code class="docutils literal notranslate"><span class="pre">EXTERNALLY-MANAGED</span></code> file is an INI-style metadata file intended
to be parsable by the standard library <a class="reference external" href="https://docs.python.org/3/library/configparser.html">configparser</a> module. If the
file can be parsed by
<code class="docutils literal notranslate"><span class="pre">configparser.ConfigParser(interpolation=None)</span></code> using the UTF-8
encoding, and it contains a section <code class="docutils literal notranslate"><span class="pre">[externally-managed]</span></code>, then the
installer should look for an error message specified in the file and
output it as part of its error. If the first element of the tuple
returned by <code class="docutils literal notranslate"><span class="pre">locale.getlocale(locale.LC_MESSAGES)</span></code>, i.e., the
language code, is not <code class="docutils literal notranslate"><span class="pre">None</span></code>, it should look for the error message
as the value of a key named <code class="docutils literal notranslate"><span class="pre">Error-</span></code> followed by the language code.
If that key does not exist, and if the language code contains
underscore or hyphen, it should look for a key named <code class="docutils literal notranslate"><span class="pre">Error-</span></code>
followed by the portion of the language code before the underscore or
hyphen. If it cannot find either of those, or if the language code is
<code class="docutils literal notranslate"><span class="pre">None</span></code>, it should look for a key simply named <code class="docutils literal notranslate"><span class="pre">Error</span></code>.</p>
<p>If the installer cannot find an error message in the file (either
because the file cannot be parsed or because no suitable error key
exists), then the installer should just use a pre-defined error
message of its own, which should suggest that the user create a
virtual environment to install packages.</p>
<p>Software distributors who have a non-Python-specific package manager
that manages libraries in the <code class="docutils literal notranslate"><span class="pre">sys.path</span></code> of their Python package
should, in general, ship a <code class="docutils literal notranslate"><span class="pre">EXTERNALLY-MANAGED</span></code> file in their
standard library directory. For instance, Debian may ship a file in
<code class="docutils literal notranslate"><span class="pre">/usr/lib/python3.9/EXTERNALLY-MANAGED</span></code> consisting of something like</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="p">[</span><span class="n">externally</span><span class="o">-</span><span class="n">managed</span><span class="p">]</span>
<span class="n">Error</span><span class="o">=</span><span class="n">To</span> <span class="n">install</span> <span class="n">Python</span> <span class="n">packages</span> <span class="n">system</span><span class="o">-</span><span class="n">wide</span><span class="p">,</span> <span class="k">try</span> <span class="n">apt</span> <span class="n">install</span>
<span class="n">python3</span><span class="o">-</span><span class="n">xyz</span><span class="p">,</span> <span class="n">where</span> <span class="n">xyz</span> <span class="ow">is</span> <span class="n">the</span> <span class="n">package</span> <span class="n">you</span> <span class="n">are</span> <span class="n">trying</span> <span class="n">to</span>
<span class="n">install</span><span class="o">.</span>
<span class="n">If</span> <span class="n">you</span> <span class="n">wish</span> <span class="n">to</span> <span class="n">install</span> <span class="n">a</span> <span class="n">non</span><span class="o">-</span><span class="n">Debian</span><span class="o">-</span><span class="n">packaged</span> <span class="n">Python</span> <span class="n">package</span><span class="p">,</span>
<span class="n">create</span> <span class="n">a</span> <span class="n">virtual</span> <span class="n">environment</span> <span class="n">using</span> <span class="n">python3</span> <span class="o">-</span><span class="n">m</span> <span class="n">venv</span> <span class="n">path</span><span class="o">/</span><span class="n">to</span><span class="o">/</span><span class="n">venv</span><span class="o">.</span>
<span class="n">Then</span> <span class="n">use</span> <span class="n">path</span><span class="o">/</span><span class="n">to</span><span class="o">/</span><span class="n">venv</span><span class="o">/</span><span class="nb">bin</span><span class="o">/</span><span class="n">python</span> <span class="ow">and</span> <span class="n">path</span><span class="o">/</span><span class="n">to</span><span class="o">/</span><span class="n">venv</span><span class="o">/</span><span class="nb">bin</span><span class="o">/</span><span class="n">pip</span><span class="o">.</span> <span class="n">Make</span>
<span class="n">sure</span> <span class="n">you</span> <span class="n">have</span> <span class="n">python3</span><span class="o">-</span><span class="n">full</span> <span class="n">installed</span><span class="o">.</span>
<span class="n">If</span> <span class="n">you</span> <span class="n">wish</span> <span class="n">to</span> <span class="n">install</span> <span class="n">a</span> <span class="n">non</span><span class="o">-</span><span class="n">Debian</span> <span class="n">packaged</span> <span class="n">Python</span> <span class="n">application</span><span class="p">,</span>
<span class="n">it</span> <span class="n">may</span> <span class="n">be</span> <span class="n">easiest</span> <span class="n">to</span> <span class="n">use</span> <span class="n">pipx</span> <span class="n">install</span> <span class="n">xyz</span><span class="p">,</span> <span class="n">which</span> <span class="n">will</span> <span class="n">manage</span> <span class="n">a</span>
<span class="n">virtual</span> <span class="n">environment</span> <span class="k">for</span> <span class="n">you</span><span class="o">.</span> <span class="n">Make</span> <span class="n">sure</span> <span class="n">you</span> <span class="n">have</span> <span class="n">pipx</span> <span class="n">installed</span><span class="o">.</span>
<span class="n">See</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">share</span><span class="o">/</span><span class="n">doc</span><span class="o">/</span><span class="n">python3</span><span class="mf">.9</span><span class="o">/</span><span class="n">README</span><span class="o">.</span><span class="n">venv</span> <span class="k">for</span> <span class="n">more</span> <span class="n">information</span><span class="o">.</span>
</pre></div>
</div>
<p>which provides useful and distro-relevant information
to a user trying to install a package. Optionally,
translations can be provided in the same file:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span>Error-de_DE=Wenn ist das Nunstück git und Slotermeyer?
Ja! Beiherhund das Oder die Virtualenvironment gersput!
</pre></div>
</div>
<p>In certain contexts, such as single-application container images that
arent updated after creation, a distributor may choose not to ship an
<code class="docutils literal notranslate"><span class="pre">EXTERNALLY-MANAGED</span></code> file, so that users can install whatever they
like (as they can today) without having to manually override this
rule.</p>
</section>
<section id="writing-to-only-the-target-sysconfig-scheme">
<h3><a class="toc-backref" href="#writing-to-only-the-target-sysconfig-scheme" role="doc-backlink">Writing to only the target <code class="docutils literal notranslate"><span class="pre">sysconfig</span></code> scheme</a></h3>
<p>Usually, a Python package installer installs to directories in a
scheme returned by the <code class="docutils literal notranslate"><span class="pre">sysconfig</span></code> standard library package.
Ordinarily, this is the scheme returned by
<code class="docutils literal notranslate"><span class="pre">sysconfig.get_default_scheme()</span></code>, but based on configuration (e.g.
<code class="docutils literal notranslate"><span class="pre">pip</span> <span class="pre">install</span> <span class="pre">--user</span></code>), it may use a different scheme.</p>
<p>Whenever the installer is installing to a <code class="docutils literal notranslate"><span class="pre">sysconfig</span></code> scheme, this
PEP specifies that the installer should never modify or delete files
outside of that scheme. For instance, if its upgrading a package, and
the package is already installed in a directory outside that scheme
(perhaps in a directory from another scheme), it should leave the
existing files alone.</p>
<p>If the installer does end up shadowing an existing installation during
an upgrade, we recommend that it produces a warning at the end of its
run.</p>
<p>If the installer is installing to a location outside of a
<code class="docutils literal notranslate"><span class="pre">sysconfig</span></code> scheme (e.g., <code class="docutils literal notranslate"><span class="pre">pip</span> <span class="pre">install</span> <span class="pre">--target</span></code>), then this
subsection does not apply.</p>
</section>
</section>
<section id="recommendations-for-distros">
<h2><a class="toc-backref" href="#recommendations-for-distros" role="doc-backlink">Recommendations for distros</a></h2>
<p>This section is non-normative. It provides best practices we believe
distros should follow unless they have a specific reason otherwise.</p>
<section id="mark-the-installation-as-externally-managed">
<h3><a class="toc-backref" href="#mark-the-installation-as-externally-managed" role="doc-backlink">Mark the installation as externally managed</a></h3>
<p>Distros should create an <code class="docutils literal notranslate"><span class="pre">EXTERNALLY-MANAGED</span></code> file in their
<code class="docutils literal notranslate"><span class="pre">stdlib</span></code> directory.</p>
</section>
<section id="guide-users-towards-virtual-environments">
<h3><a class="toc-backref" href="#guide-users-towards-virtual-environments" role="doc-backlink">Guide users towards virtual environments</a></h3>
<p>The file should contain a useful and distro-relevant error message
indicating both how to install system-wide packages via the distros
package manager and how to set up a virtual environment. If your
distro is often used by users in a state where the <code class="docutils literal notranslate"><span class="pre">python3</span></code> command
is available (and especially where <code class="docutils literal notranslate"><span class="pre">pip</span></code> or <code class="docutils literal notranslate"><span class="pre">get-pip</span></code> is
available) but <code class="docutils literal notranslate"><span class="pre">python3</span> <span class="pre">-m</span> <span class="pre">venv</span></code> does not work, the message should
indicate clearly how to make <code class="docutils literal notranslate"><span class="pre">python3</span> <span class="pre">-m</span> <span class="pre">venv</span></code> work properly.</p>
<p>Consider packaging <a class="reference external" href="https://github.com/pypa/pipx">pipx</a>, a tool for installing Python-language
applications, and suggesting it in the error. pipx automatically
creates a virtual environment for that application alone, which is a
much better default for end users who want to install some
Python-language software (which isnt available in the distro) but are
not themselves Python users. Packaging pipx in the distro avoids the
irony of instructing users to <code class="docutils literal notranslate"><span class="pre">pip</span> <span class="pre">install</span> <span class="pre">--user</span>
<span class="pre">--break-system-packages</span> <span class="pre">pipx</span></code> to <em>avoid</em> breaking system packages.
Consider arranging things so your distros package / environment for
Python for end users (e.g., <code class="docutils literal notranslate"><span class="pre">python3</span></code> on Fedora or <code class="docutils literal notranslate"><span class="pre">python3-full</span></code>
on Debian) depends on pipx.</p>
</section>
<section id="keep-the-marker-file-in-container-images">
<h3><a class="toc-backref" href="#keep-the-marker-file-in-container-images" role="doc-backlink">Keep the marker file in container images</a></h3>
<p>Distros that produce official images for single-application containers
(e.g., Docker container images) should keep the
<code class="docutils literal notranslate"><span class="pre">EXTERNALLY-MANAGED</span></code> file, preferably in a way that makes it not
go away if a user of that image installs package updates inside
their image (think <code class="docutils literal notranslate"><span class="pre">RUN</span> <span class="pre">apt-get</span> <span class="pre">dist-upgrade</span></code>).</p>
</section>
<section id="create-separate-distro-and-local-directories">
<h3><a class="toc-backref" href="#create-separate-distro-and-local-directories" role="doc-backlink">Create separate distro and local directories</a></h3>
<p>Distros should place two separate paths on the system interpreters
<code class="docutils literal notranslate"><span class="pre">sys.path</span></code>, one for distro-installed packages and one for packages
installed by the local system administrator, and configure
<code class="docutils literal notranslate"><span class="pre">sysconfig.get_default_scheme()</span></code> to point at the latter path. This
ensures that tools like pip will not modify distro-installed packages.
The path for the local system administrator should come before the
distro path on <code class="docutils literal notranslate"><span class="pre">sys.path</span></code> so that local installs take preference
over distro packages.</p>
<p>For example, Fedora and Debian (and their derivatives) both implement
this split by using <code class="docutils literal notranslate"><span class="pre">/usr/local</span></code> for locally-installed packages and
<code class="docutils literal notranslate"><span class="pre">/usr</span></code> for distro-installed packages. Fedora uses
<code class="docutils literal notranslate"><span class="pre">/usr/local/lib/python3.x/site-packages</span></code> vs.
<code class="docutils literal notranslate"><span class="pre">/usr/lib/python3.x/site-packages</span></code>. (Debian uses
<code class="docutils literal notranslate"><span class="pre">/usr/local/lib/python3/dist-packages</span></code> vs.
<code class="docutils literal notranslate"><span class="pre">/usr/lib/python3/dist-packages</span></code> as an additional layer of
separation from a locally-compiled Python interpreter: if you build
and install upstream CPython in <code class="docutils literal notranslate"><span class="pre">/usr/local/bin</span></code>, it will look at
<code class="docutils literal notranslate"><span class="pre">/usr/local/lib/python3/site-packages</span></code>, and Debian wishes to make
sure that packages installed via the locally-built interpreter dont
show up on <code class="docutils literal notranslate"><span class="pre">sys.path</span></code> for the distro interpreter.)</p>
<p>Note that the <code class="docutils literal notranslate"><span class="pre">/usr/local</span></code> vs. <code class="docutils literal notranslate"><span class="pre">/usr</span></code> split is analogous to how
the <code class="docutils literal notranslate"><span class="pre">PATH</span></code> environment variable typically includes
<code class="docutils literal notranslate"><span class="pre">/usr/local/bin:/usr/bin</span></code> and non-distro software installs to
<code class="docutils literal notranslate"><span class="pre">/usr/local</span></code> by default. This split is <a class="reference external" href="https://refspecs.linuxfoundation.org/FHS_3.0/fhs/ch04s09.html">recommended by the
Filesystem Hierarchy Standard</a>.</p>
<p>There are two ways you could do this. One is, if you are building and
packaging Python libraries directly (e.g., your packaging helpers
unpack a <a class="pep reference internal" href="../pep-0517/" title="PEP 517 A build-system independent format for source trees">PEP 517</a>-built wheel or call <code class="docutils literal notranslate"><span class="pre">setup.py</span> <span class="pre">install</span></code>), arrange
for those tools to use a directory that is not in a <code class="docutils literal notranslate"><span class="pre">sysconfig</span></code>
scheme but is still on <code class="docutils literal notranslate"><span class="pre">sys.path</span></code>.</p>
<p>The other is to arrange for the default <code class="docutils literal notranslate"><span class="pre">sysconfig</span></code> scheme to change
when running inside a package build versus when running on an
installed system. The <code class="docutils literal notranslate"><span class="pre">sysconfig</span></code> customization hooks from
<a class="reference external" href="https://bugs.python.org/issue43976">bpo-43976</a> should make this easy (once accepted and implemented):
make your packaging tool set an
environment variable or some other detectable configuration, and
define a <code class="docutils literal notranslate"><span class="pre">get_preferred_schemes</span></code> function to return a different
scheme when called from inside a package build. Then you can use <code class="docutils literal notranslate"><span class="pre">pip</span>
<span class="pre">install</span></code> as part of your distro packaging.</p>
<p>We propose adding a <code class="docutils literal notranslate"><span class="pre">--scheme=...</span></code> option to instruct pip to run
against a specific scheme. (See <a class="reference internal" href="#implementation-notes">Implementation Notes</a> below for how
pip currently determines schemes.) Once thats available, for local
testing and possibly for actual packaging, you would be able to run
something like <code class="docutils literal notranslate"><span class="pre">pip</span> <span class="pre">install</span> <span class="pre">--scheme=posix_distro</span></code> to explicitly
install a package into your distros location (bypassing
<code class="docutils literal notranslate"><span class="pre">get_preferred_schemes</span></code>). One could also, if absolutely needed, use
<code class="docutils literal notranslate"><span class="pre">pip</span> <span class="pre">uninstall</span> <span class="pre">--scheme=posix_distro</span></code> to use pip to remove packages
from the system-managed directory, which addresses the (hopefully
theoretical) regression in use case 5 in <a class="reference internal" href="#rationale">Rationale</a>.</p>
<p>To install packages with pip, you would also need to either suppress
the <code class="docutils literal notranslate"><span class="pre">EXTERNALLY-MANAGED</span></code> marker file to allow pip to run or to
override it on the command line. You may want to use the same means
for suppressing the marker file in build chroots as you do in
container images.</p>
<p>The advantage of setting these up to be automatic (suppressing the
marker file in your build environment and having
<code class="docutils literal notranslate"><span class="pre">get_preferred_schemes</span></code> automatically return your distros scheme)
is that an unadorned <code class="docutils literal notranslate"><span class="pre">pip</span> <span class="pre">install</span></code> will work inside a package build,
which generally means that an unmodified upstream build script that
happens to internally call <code class="docutils literal notranslate"><span class="pre">pip</span> <span class="pre">install</span></code> will do the right thing.
You can, of course, just ensure that your packaging process always
calls <code class="docutils literal notranslate"><span class="pre">pip</span> <span class="pre">install</span> <span class="pre">--scheme=posix_distro</span> <span class="pre">--break-system-packages</span></code>,
which would work too.</p>
<p>The best approach here depends a lot on your distros conventions and
mechanisms for packaging.</p>
<p>Similarly, the <code class="docutils literal notranslate"><span class="pre">sysconfig</span></code> paths that are not for importable Python
code - that is, <code class="docutils literal notranslate"><span class="pre">include</span></code>, <code class="docutils literal notranslate"><span class="pre">platinclude</span></code>, <code class="docutils literal notranslate"><span class="pre">scripts</span></code>, and
<code class="docutils literal notranslate"><span class="pre">data</span></code> - should also have two variants, one for use by
distro-packaged software and one for use for locally-installed
software, and the distro should be set up such that both are usable.
For instance, a typical FHS-compliant distro will use
<code class="docutils literal notranslate"><span class="pre">/usr/local/include</span></code> for the default schemes <code class="docutils literal notranslate"><span class="pre">include</span></code> and
<code class="docutils literal notranslate"><span class="pre">/usr/include</span></code> for distro-packaged headers and place both on the
compilers search path, and it will use <code class="docutils literal notranslate"><span class="pre">/usr/local/bin</span></code> for the
default schemes <code class="docutils literal notranslate"><span class="pre">scripts</span></code> and <code class="docutils literal notranslate"><span class="pre">/usr/bin</span></code> for distro-packaged
entry points and place both on <code class="docutils literal notranslate"><span class="pre">$PATH</span></code>.</p>
</section>
</section>
<section id="backwards-compatibility">
<h2><a class="toc-backref" href="#backwards-compatibility" role="doc-backlink">Backwards Compatibility</a></h2>
<p>All of these mechanisms are proposed for new distro releases and new
versions of tools like pip only.</p>
<p>In particular, we strongly recommend that distros with a concept of
major versions only add the marker file or change <code class="docutils literal notranslate"><span class="pre">sysconfig</span></code>
schemes in a new major version; otherwise there is a risk that, on an
existing system, software installed via a Python-specific package
manager now becomes unmanageable (without an override option). For a
rolling-release distro, if possible, only add the marker file or
change <code class="docutils literal notranslate"><span class="pre">sysconfig</span></code> schemes in a new Python minor version.</p>
<p>One particular backwards-compatibility difficulty for package
installation tools is likely to be managing environments created by
old versions of <code class="docutils literal notranslate"><span class="pre">virtualenv</span></code> which have the latest version of the
tool installed. A “virtual environment” now has a fairly precise
definition: it uses the <code class="docutils literal notranslate"><span class="pre">pyvenv.cfg</span></code> mechanism, which causes
<code class="docutils literal notranslate"><span class="pre">sys.base_prefix</span> <span class="pre">!=</span> <span class="pre">sys.prefix</span></code>. It is possible, however, that a
user may have an old virtual environment created by an older version
of <code class="docutils literal notranslate"><span class="pre">virtualenv</span></code>; as of this writing, pip supports Python 3.6
onwards, which is in turn supported by <code class="docutils literal notranslate"><span class="pre">virtualenv</span></code> 15.1.0 onwards,
so this scenario is possible. In older versions of <code class="docutils literal notranslate"><span class="pre">virtualenv</span></code>, the
mechanism is instead to set a new attribute, <code class="docutils literal notranslate"><span class="pre">sys.real_prefix</span></code>, and
it does not use the standard library support for virtual environments,
so <code class="docutils literal notranslate"><span class="pre">sys.base_prefix</span></code> is the same as <code class="docutils literal notranslate"><span class="pre">sys.prefix</span></code>. So the logic for
robustly detecting a virtual environment is something like:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">is_virtual_environment</span><span class="p">():</span>
<span class="k">return</span> <span class="n">sys</span><span class="o">.</span><span class="n">base_prefix</span> <span class="o">!=</span> <span class="n">sys</span><span class="o">.</span><span class="n">prefix</span> <span class="ow">or</span> <span class="nb">hasattr</span><span class="p">(</span><span class="n">sys</span><span class="p">,</span> <span class="s2">&quot;real_prefix&quot;</span><span class="p">)</span>
</pre></div>
</div>
</section>
<section id="security-implications">
<h2><a class="toc-backref" href="#security-implications" role="doc-backlink">Security Implications</a></h2>
<p>The purpose of this feature is not to implement a security boundary;
it is to discourage well-intended changes from unexpectedly breaking a
users environment. That is to say, the reason this PEP restricts
<code class="docutils literal notranslate"><span class="pre">pip</span> <span class="pre">install</span></code> outside a virtual environment is not that its a
security risk to be able to do so; its that “There should be one
and preferably only one obvious way to do it,” and that way should
be using a virtual environment. <code class="docutils literal notranslate"><span class="pre">pip</span> <span class="pre">install</span></code> outside a virtual
environment is rather too obvious for what is almost always the wrong
way to do it.</p>
<p>If there is a case where a user should not be able to <code class="docutils literal notranslate"><span class="pre">sudo</span> <span class="pre">pip</span>
<span class="pre">install</span></code> or <code class="docutils literal notranslate"><span class="pre">pip</span> <span class="pre">install</span> <span class="pre">--user</span></code> and add files to <code class="docutils literal notranslate"><span class="pre">sys.path</span></code> <em>for
security reasons</em>, that needs to be implemented either via access
control rules on what files the user can write to or an explicitly
secured <code class="docutils literal notranslate"><span class="pre">sys.path</span></code> for the program in question. Neither of the
mechanisms in this PEP should be interpreted as a way to address such
a scenario.</p>
<p>For those reasons, an attempted install with a marker file present is
not a security incident, and there is no need to raise an auditing
event for it. If the calling user legitimately has access to <code class="docutils literal notranslate"><span class="pre">sudo</span>
<span class="pre">pip</span> <span class="pre">install</span></code> or <code class="docutils literal notranslate"><span class="pre">pip</span> <span class="pre">install</span> <span class="pre">--user</span></code>, they can accomplish the same
installation entirely outside of Python; if they do not legitimately
have such access, thats a problem outside the scope of this PEP.</p>
<p>The marker file itself is located in the standard library directory,
which is a trusted location (i.e., anyone who can write to the marker
file used by a particular installer could, presumably, run arbitrary
code inside the installer). Therefore, there is generally no need to
filter out terminal escape sequences or other potentially-malicious
content in the error message.</p>
</section>
<section id="alternatives">
<h2><a class="toc-backref" href="#alternatives" role="doc-backlink">Alternatives</a></h2>
<p>There are a number of similar proposals we considered that this PEP
rejects or defers, largely to preserve the behavior in the
case-by-case analysis in <a class="reference internal" href="#rationale">Rationale</a>.</p>
<section id="marker-file">
<h3><a class="toc-backref" href="#marker-file" role="doc-backlink">Marker file</a></h3>
<p>Should the marker file be in <code class="docutils literal notranslate"><span class="pre">sys.path</span></code>, marking a particular
directory as not to be written to by a Python-specific package
manager? This would help with the second problem addressed by this PEP
(not overwriting deleting distro-owned files) but not the first
(incompatible installs). A directory-specific marker in
<code class="docutils literal notranslate"><span class="pre">/usr/lib/python3.x/site-packages</span></code> would not discourage
installations into either <code class="docutils literal notranslate"><span class="pre">/usr/local/lib/python3.x/site-packages</span></code>
or <code class="docutils literal notranslate"><span class="pre">~/.local/lib/python3.x/site-packages</span></code>, both of which are on
<code class="docutils literal notranslate"><span class="pre">sys.path</span></code> for <code class="docutils literal notranslate"><span class="pre">/usr/bin/python3</span></code>. In other words, the marker file
should not be interpreted as marking a single <em>directory</em> as
externally managed (even though it happens to be in a directory on
<code class="docutils literal notranslate"><span class="pre">sys.path</span></code>); it marks the entire <em>Python installation</em> as externally
managed.</p>
<p>Another variant of the above: should the marker file be in
<code class="docutils literal notranslate"><span class="pre">sys.path</span></code>, where if it can be found in any directory in
<code class="docutils literal notranslate"><span class="pre">sys.path</span></code>, it marks the installation as externally managed? An
apparent advantage of this approach is that it automatically disables
itself in virtual environments. Unfortunately, This has the wrong
behavior with a <code class="docutils literal notranslate"><span class="pre">--system-site-packages</span></code> virtual environment, where
the system-wide <code class="docutils literal notranslate"><span class="pre">sys.path</span></code> is visible but package installations are
allowed. (It could work if the rule of exempting virtual environments
is preserved, but that seems to have no advantage over the current
scheme.)</p>
<p>Should the marker just be a new attribute of a <code class="docutils literal notranslate"><span class="pre">sysconfig</span></code> scheme?
There is some conceptual cleanliness to this, except that its hard to
override. We want to make it easy for container images, package build
environments, etc. to suppress the marker file. A file that you can
remove is easy; code in <code class="docutils literal notranslate"><span class="pre">sysconfig</span></code> is much harder to modify.</p>
<p>Should the file be in <code class="docutils literal notranslate"><span class="pre">/etc</span></code>? No, because again, it refers to a
specific Python installation. A user who installs their own Python may
well want to install packages within the global context of that
interpreter.</p>
<p>Should the configuration setting be in <code class="docutils literal notranslate"><span class="pre">pip.conf</span></code> or
<code class="docutils literal notranslate"><span class="pre">distutils.cfg</span></code>? Apart from the above objections about marking an
installation, this mechanism isnt specific to either of those tools.
(It seems reasonable for pip to <em>also</em> implement a configuration flag
for users to prevent themselves from performing accidental
non-virtual-environment installs in any Python installation, but that
is outside the scope of this PEP.)</p>
<p>Should the file be TOML? TOML is gaining popularity for packaging (see
e.g. <a class="pep reference internal" href="../pep-0517/" title="PEP 517 A build-system independent format for source trees">PEP 517</a>) but does not yet have an implementation in the standard
library. Strictly speaking, this isnt a blocker - distros need only
write the file, not read it, so they dont need a TOML library (the
file will probably be written by hand, regardless of format), and
packaging tools likely have a TOML reader already. However, the INI
format is currently used for various other forms of packaging metadata
(e.g., <code class="docutils literal notranslate"><span class="pre">pydistutils.cfg</span></code> and <code class="docutils literal notranslate"><span class="pre">setup.cfg</span></code>), meets our needs, and is
parsable by the standard library, and the pip maintainers expressed a
preference to avoid using TOML for this yet.</p>
<p>Should the file be <code class="docutils literal notranslate"><span class="pre">email.message</span></code>-style? While this format is also
used for packaging metadata (e.g. sdist and wheel metadata) and is
also parsable by the standard library, it doesnt handle multi-line
entries quite as clearly, and that is our primary use case.</p>
<p>Should the marker file be executable Python code that evaluates
whether installation should be allowed or not? Apart from the concerns
above about having the file in <code class="docutils literal notranslate"><span class="pre">sys.path</span></code>, we have a concern that
making it executable is committing to too powerful of an API and risks
making behavior harder to understand. (Note that the
<code class="docutils literal notranslate"><span class="pre">get_default_scheme</span></code> hook of <a class="reference external" href="https://bugs.python.org/issue43976">bpo-43976</a> is in fact executable, but
that code needs to be supplied when the interpreter builds; it isnt
intended to be supplied post-build.)</p>
<p>When overriding the marker, should a Python-specific package manager
be disallowed from shadowing a package installed by the external
package manager (i.e., installing modules of the same name)? This
would minimize the risk of breaking system software, but its not
clear its worth the additional user experience complexity. There are
legitimate use cases for shadowing system packages, and an additional
command-line option to permit it would be more confusing. Meanwhile,
not passing that option wouldnt eliminate the risk of breaking system
software, which may be relying on a <code class="docutils literal notranslate"><span class="pre">try:</span> <span class="pre">import</span> <span class="pre">xyz</span></code> failing,
finding a limited set of entry points, etc. Communicating this
distinction seems difficult. We think its a good idea for
Python-specific package managers to print a warning if they shadow a
package, but we think its not worth disabling it by default.</p>
<p>Why not use the <code class="docutils literal notranslate"><span class="pre">INSTALLER</span></code> file from <a class="pep reference internal" href="../pep-0376/" title="PEP 376 Database of Installed Python Distributions">PEP 376</a> to determine who
installed a package and whether it can be removed? First, its
specific to a particular package (its in the packages <code class="docutils literal notranslate"><span class="pre">dist-info</span></code>
directory), so like some of the alternatives above, it doesnt provide
information on an entire environment and whether package installations
are permissible. <a class="pep reference internal" href="../pep-0627/" title="PEP 627 Recording installed projects">PEP 627</a> also updates <a class="pep reference internal" href="../pep-0376/" title="PEP 376 Database of Installed Python Distributions">PEP 376</a> to prevent programmatic
use of <code class="docutils literal notranslate"><span class="pre">INSTALLER</span></code>, specifying that the file is “to be used for
informational purposes only. […] Our goal is supporting
interoperating tools, and basing any action on which tool happened to
install a package runs counter to that goal.” Finally, as <a class="pep reference internal" href="../pep-0627/" title="PEP 627 Recording installed projects">PEP 627</a>
envisions, there are legitimate use cases for one tool knowing how to
handle packages installed by another tool; for instance, <code class="docutils literal notranslate"><span class="pre">conda</span></code> can
safely remove a package installed by <code class="docutils literal notranslate"><span class="pre">pip</span></code> into a Conda environment.</p>
<p>Why does the specification give no means for disabling package
installations inside a virtual environment? We cant see a
particularly strong use case for it (at least not one related to the
purposes of this PEP). If you need it, its simple enough to <code class="docutils literal notranslate"><span class="pre">pip</span>
<span class="pre">uninstall</span> <span class="pre">pip</span></code> inside that environment, which should discourage at
least unintentional changes to the environment (and this specification
makes no provision to disable <em>intentional</em> changes, since after all
the marker file can be easily removed).</p>
</section>
<section id="system-python">
<h3><a class="toc-backref" href="#system-python" role="doc-backlink">System Python</a></h3>
<p>Shouldnt distro software just run with the distro <code class="docutils literal notranslate"><span class="pre">site-packages</span></code>
directory alone on <code class="docutils literal notranslate"><span class="pre">sys.path</span></code> and ignore the local system
administrators <code class="docutils literal notranslate"><span class="pre">site-packages</span></code> as well as the user-specific one?
This is a worthwhile idea, and various versions of it have been
circulating for a while under the name of “system Python” or “platform
Python” (with a separate “user Python” for end users writing Python or
installing Python software separate from the system). However, its
much more involved of a change. First, it would be a
backwards-incompatible change. As mentioned in the <a class="reference internal" href="#motivation">Motivation</a>
section, there are valid use cases for running distro-installed Python
applications like Sphinx or Ansible with locally-installed Python
libraries available on their <code class="docutils literal notranslate"><span class="pre">sys.path</span></code>. A wholesale switch to
ignoring local packages would break these use cases, and a distro
would have to make a case-by-case analysis of whether an application
ought to see locally-installed libraries or not.</p>
<p>Furthermore, <a class="reference external" href="https://lists.fedoraproject.org/archives/list/devel&#64;lists.fedoraproject.org/thread/SEFUWW4XZBTVOAQ36XOJQ72PIICMFOSN/">Fedora attempted this change and reverted it</a>, finding,
ironically, that their implementation of the change <a class="reference external" href="https://bugzilla.redhat.com/show_bug.cgi?id=1483342">broke their
package manager</a>. Given that experience, there are clearly details to
be worked out before distros can reliably implement that approach, and
a PEP recommending it would be premature.</p>
<p>This PEP is intended to be a complete and self-contained change that
is independent of a distributors decision for or against “system
Python” or similar proposals. It is not incompatible with a distro
implementing “system Python” in the future, and even though both
proposals address the same class of problems, there are still
arguments in favor of implementing something like “system Python” even
after implementing this PEP. At the same time, though, this PEP
specifically tries to make a more targeted and minimal change, such
that it can be implemented by distributors who dont expect to adopt
“system Python” (or dont expect to implement it immediately). The
changes in this PEP stand on their own merits and are not an
intermediate step for some future proposal. This PEP reduces (but does
not eliminate) the risk of breaking system software while minimizing
(but not completely avoiding) breaking changes, which should therefore
be much easier to implement than the full “system Python” idea, which
comes with the downsides mentioned above.</p>
<p>We expect that the guidance in this PEP - that users should use
virtual environments whenever possible and that distros should have
separate <code class="docutils literal notranslate"><span class="pre">sys.path</span></code> directories for distro-managed and
locally-managed modules - should make further experiments easier in
the future. These may include distributing wholly separate “system”
and “user” Python interpreters, running system software out of a
distro-owned virtual environment or <code class="docutils literal notranslate"><span class="pre">PYTHONHOME</span></code> (but shipping a
single interpreter), or modifying the entry points for certain
software (such as the distros package manager) to use a <code class="docutils literal notranslate"><span class="pre">sys.path</span></code>
that only sees distro-managed directories. Those ideas themselves,
however, remain outside the scope of this PEP.</p>
</section>
</section>
<section id="implementation-notes">
<h2><a class="toc-backref" href="#implementation-notes" role="doc-backlink">Implementation Notes</a></h2>
<p>This section is non-normative and contains notes relevant to both the
specification and potential implementations.</p>
<p>Currently, pip does not directly expose a way to choose a target
<code class="docutils literal notranslate"><span class="pre">sysconfig</span></code> scheme, but it has three ways of looking up schemes when
installing:</p>
<dl class="simple">
<dt><code class="docutils literal notranslate"><span class="pre">pip</span> <span class="pre">install</span></code></dt><dd>Calls <code class="docutils literal notranslate"><span class="pre">sysconfig.get_default_scheme()</span></code>, which is usually (in
upstream CPython and most current distros) the same as
<code class="docutils literal notranslate"><span class="pre">get_preferred_scheme('prefix')</span></code>.</dd>
<dt><code class="docutils literal notranslate"><span class="pre">pip</span> <span class="pre">install</span> <span class="pre">--prefix=/some/path</span></code></dt><dd>Calls <code class="docutils literal notranslate"><span class="pre">sysconfig.get_preferred_scheme('prefix')</span></code>.</dd>
<dt><code class="docutils literal notranslate"><span class="pre">pip</span> <span class="pre">install</span> <span class="pre">--user</span></code></dt><dd>Calls <code class="docutils literal notranslate"><span class="pre">sysconfig.get_preferred_scheme('user')</span></code>.</dd>
</dl>
<p>Finally, <code class="docutils literal notranslate"><span class="pre">pip</span> <span class="pre">install</span> <span class="pre">--target=/some/path</span></code> writes directly to
<code class="docutils literal notranslate"><span class="pre">/some/path</span></code> without looking up any schemes.</p>
<p>Debian currently carries a <a class="reference external" href="https://sources.debian.org/src/python3.7/3.7.3-2+deb10u3/debian/patches/distutils-install-layout.diff/">patch to change the default install
location inside a virtual environment</a>, using a few heuristics
(including checking for the <code class="docutils literal notranslate"><span class="pre">VIRTUAL_ENV</span></code> environment variable),
largely so that the directory used in a virtual environment remains
<code class="docutils literal notranslate"><span class="pre">site-packages</span></code> and not <code class="docutils literal notranslate"><span class="pre">dist-packages</span></code>. This does not
particularly affect this proposal, because the implementation of that
patch does not actually change the default <code class="docutils literal notranslate"><span class="pre">sysconfig</span></code> scheme, and
notably does not change the result of
<code class="docutils literal notranslate"><span class="pre">sysconfig.get_path(&quot;stdlib&quot;)</span></code>.</p>
<p>Fedora currently carries a <a class="reference external" href="https://src.fedoraproject.org/rpms/python3.9/blob/f34/f/00251-change-user-install-location.patch">patch to change the default install
location when not running inside rpmbuild</a>, which they use to
implement the two-system-wide-directories approach. This is
conceptually the sort of hook envisioned by <a class="reference external" href="https://bugs.python.org/issue43976">bpo-43976</a>, except
implemented as a code patch to <code class="docutils literal notranslate"><span class="pre">distutils</span></code> instead of as a changed
<code class="docutils literal notranslate"><span class="pre">sysconfig</span></code> scheme.</p>
<p>The implementation of <code class="docutils literal notranslate"><span class="pre">is_virtual_environment</span></code> above, as well as the
logic to load the <code class="docutils literal notranslate"><span class="pre">EXTERNALLY-MANAGED</span></code> file and find the error
message from it, may as well get added to the standard library
(<code class="docutils literal notranslate"><span class="pre">sys</span></code> and <code class="docutils literal notranslate"><span class="pre">sysconfig</span></code>, respectively), to centralize their
implementations, but they dont need to be added yet.</p>
</section>
<section id="references">
<h2><a class="toc-backref" href="#references" role="doc-backlink">References</a></h2>
<p>For additional background on these problems and previous attempts to
solve them, see <a class="reference external" href="https://bugs.debian.org/771794">Debian bug 771794</a> “pip silently removes/updates
system provided python packages” from 2014, Fedoras 2018 article
<a class="reference external" href="https://fedoraproject.org/wiki/Changes/Making_sudo_pip_safe">Making sudo pip safe</a> about pointing <code class="docutils literal notranslate"><span class="pre">sudo</span> <span class="pre">pip</span></code> at /usr/local
(which acknowledges that the changes still do not make <code class="docutils literal notranslate"><span class="pre">sudo</span> <span class="pre">pip</span></code>
completely safe), pip issues <a class="reference external" href="https://github.com/pypa/pip/issues/5605">5605</a> (“Disable upgrades to existing
python modules which were not installed via pip”) and <a class="reference external" href="https://github.com/pypa/pip/issues/5722">5722</a> (“pip
should respect /usr/local”) from 2018, and the post-PyCon US 2019
discussion thread <a class="reference external" href="https://discuss.python.org/t/playing-nice-with-external-package-managers/1968">Playing nice with external package managers</a>.</p>
<aside class="footnote-list brackets">
<aside class="footnote brackets" id="pip" role="doc-footnote">
<dt class="label" id="pip">[<a href="#id1">1</a>]</dt>
<dd><a class="reference external" href="https://pip.pypa.io/en/stable/">https://pip.pypa.io/en/stable/</a></aside>
<aside class="footnote brackets" id="easy-install" role="doc-footnote">
<dt class="label" id="easy-install">[<a href="#id2">2</a>]</dt>
<dd><a class="reference external" href="https://setuptools.readthedocs.io/en/latest/deprecated/easy_install.html">https://setuptools.readthedocs.io/en/latest/deprecated/easy_install.html</a>
(Note that the <code class="docutils literal notranslate"><span class="pre">easy_install</span></code> command was removed in
setuptools version 52, released 23 January 2021.)</aside>
<aside class="footnote brackets" id="conda" role="doc-footnote">
<dt class="label" id="conda">[<a href="#id3">3</a>]</dt>
<dd><a class="reference external" href="https://conda.io">https://conda.io</a></aside>
</aside>
</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-0668.rst">https://github.com/python/peps/blob/main/peps/pep-0668.rst</a></p>
<p>Last modified: <a class="reference external" href="https://github.com/python/peps/commits/main/peps/pep-0668.rst">2024-05-17 01:32:43 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="#terminology">Terminology</a></li>
<li><a class="reference internal" href="#motivation">Motivation</a></li>
<li><a class="reference internal" href="#rationale">Rationale</a><ul>
<li><a class="reference internal" href="#use-cases">Use cases</a></li>
</ul>
</li>
<li><a class="reference internal" href="#specification">Specification</a><ul>
<li><a class="reference internal" href="#marking-an-interpreter-as-using-an-external-package-manager">Marking an interpreter as using an external package manager</a></li>
<li><a class="reference internal" href="#writing-to-only-the-target-sysconfig-scheme">Writing to only the target <code class="docutils literal notranslate"><span class="pre">sysconfig</span></code> scheme</a></li>
</ul>
</li>
<li><a class="reference internal" href="#recommendations-for-distros">Recommendations for distros</a><ul>
<li><a class="reference internal" href="#mark-the-installation-as-externally-managed">Mark the installation as externally managed</a></li>
<li><a class="reference internal" href="#guide-users-towards-virtual-environments">Guide users towards virtual environments</a></li>
<li><a class="reference internal" href="#keep-the-marker-file-in-container-images">Keep the marker file in container images</a></li>
<li><a class="reference internal" href="#create-separate-distro-and-local-directories">Create separate distro and local directories</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="#alternatives">Alternatives</a><ul>
<li><a class="reference internal" href="#marker-file">Marker file</a></li>
<li><a class="reference internal" href="#system-python">System Python</a></li>
</ul>
</li>
<li><a class="reference internal" href="#implementation-notes">Implementation Notes</a></li>
<li><a class="reference internal" href="#references">References</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-0668.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>