-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
PEP 796: relative virtual environments, initial draft #4476
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 16 commits
ceeaa3b
d0a9289
41d2ea8
65a415c
4175b20
bbc85b7
a0c2529
8e6e21e
37202ca
b81c50f
55a5e28
e09b00d
c7a6a9b
bc1ffbd
2301d2a
e14b01f
175f378
31cb756
cd46f4d
b532f5c
99434fc
862142a
1c8fb2d
c542890
3240227
557cb71
0579623
c9388b3
b959f24
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,287 @@ | ||||||||||||||||||
PEP: 796 | ||||||||||||||||||
Title: Relative Virtual Environments | ||||||||||||||||||
Author: Richard Levasseur <richardlev@gmail.com> | ||||||||||||||||||
Discussions-To: Pending | ||||||||||||||||||
Status: Draft | ||||||||||||||||||
Type: Standards Track | ||||||||||||||||||
Topic: Packaging | ||||||||||||||||||
Created: 06-26-2025 | ||||||||||||||||||
Python-Version: 3.15 | ||||||||||||||||||
Sponsor: Pending | ||||||||||||||||||
Post-History: `DD-MM-YYYY <https://url-to-pep-discussion>`__ | ||||||||||||||||||
|
||||||||||||||||||
.. highlight:: rst | ||||||||||||||||||
|
||||||||||||||||||
Comment on lines
+11
to
+12
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's remove this, it's the syntax highlighter used for code blocks (default Python) and there aren't any code blocks here.
Suggested change
|
||||||||||||||||||
|
||||||||||||||||||
Abstract | ||||||||||||||||||
======== | ||||||||||||||||||
|
||||||||||||||||||
This PEP describes how a Python virtual environment can be configured | ||||||||||||||||||
to allow copying it as-is to different hosts and still have a functioning | ||||||||||||||||||
virtual environment. Making this work consists of two main pieces: | ||||||||||||||||||
|
||||||||||||||||||
* Using a relative path for ``home`` in ``pyvenv.cfg``, which Python canonicalizes | ||||||||||||||||||
at runtime. | ||||||||||||||||||
* Having package installers recognize a relative venv and generate | ||||||||||||||||||
appropriate outputs, e.g. script wrappers. | ||||||||||||||||||
|
||||||||||||||||||
|
||||||||||||||||||
Motivation | ||||||||||||||||||
========== | ||||||||||||||||||
|
||||||||||||||||||
There are two main motivations for allowing relative paths in ``pyvenv.cfg`` | ||||||||||||||||||
and thus enabling host-relocatable virtual environments. | ||||||||||||||||||
|
||||||||||||||||||
First, it is currently proscribed that the ``home`` value in ``pyvenv.cfg`` be | ||||||||||||||||||
an absolute path. The behavior of relative paths is unspecified. While | ||||||||||||||||||
techniques exist to work around this for every other sub-part of a virtual | ||||||||||||||||||
environment, the one remaining part without a teneable solution is how the | ||||||||||||||||||
Python runtime itself finds ``PYTHONHOME``. This is because, currently, the | ||||||||||||||||||
startup process requires absolute paths be used for the ``home`` key in | ||||||||||||||||||
``pyvenv.cfg``. If a relative path is used, behavior is unspecified (currently, | ||||||||||||||||||
it is relative to the process's current working directory, making it untenable | ||||||||||||||||||
to use). | ||||||||||||||||||
|
||||||||||||||||||
This requirement is overly proscriptive and restrictive because, given a known | ||||||||||||||||||
anchor point, it's easy to tranform a relative path to an absolute path and | ||||||||||||||||||
still retain predictable and reliable behavior. Thus, the absolute path | ||||||||||||||||||
requirement should be relaxed and relative path behavior allowed and defined. | ||||||||||||||||||
|
||||||||||||||||||
Second, virtual environments are the standard way for running Python | ||||||||||||||||||
applications. This means ... | ||||||||||||||||||
|
||||||||||||||||||
* The closer the dev environment is to the non-dev environment, the more | ||||||||||||||||||
reliable software one can get. | ||||||||||||||||||
* The closer the dev and non-dev envs are, the easier it is to reproduce | ||||||||||||||||||
issues. | ||||||||||||||||||
* The simpler the process is to re-create the environment, the more reliable | ||||||||||||||||||
software one can get. | ||||||||||||||||||
* The simpler the process is to re-create the environment, the faster the | ||||||||||||||||||
process can be. | ||||||||||||||||||
|
||||||||||||||||||
By making it trivial to copy a virtual environment from one host to another, it | ||||||||||||||||||
avoids these categories of problems. Additionally, the development tools to | ||||||||||||||||||
create a virtual environment and install its dependencies aren't needed on the | ||||||||||||||||||
host that intends to run the program. | ||||||||||||||||||
|
||||||||||||||||||
Because the virtual environment doesn't require modifications to be usable, it | ||||||||||||||||||
also allows more advanced deployment mechansisms, e.g. remote mounting and | ||||||||||||||||||
caching of artifacts. | ||||||||||||||||||
|
||||||||||||||||||
Rationale | ||||||||||||||||||
========= | ||||||||||||||||||
|
||||||||||||||||||
The reason support for relative virtual environments needs to be | ||||||||||||||||||
in the interpreter itself is because locating ``PYTHONHOME`` happens | ||||||||||||||||||
very early in the interpreter startup process, which limits the options for | ||||||||||||||||||
customizing how it's computed. Without the ability to specify where the | ||||||||||||||||||
supporting Python runtime files are, the interpreter can't finish startup, | ||||||||||||||||||
so other hook points (e.g. ``site`` initialization) never trigger. | ||||||||||||||||||
|
||||||||||||||||||
rickeylev marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||
Specification | ||||||||||||||||||
============= | ||||||||||||||||||
|
||||||||||||||||||
The ``home`` value in ``pyvenv.cfg`` is permitted to use a relative path value. | ||||||||||||||||||
These may contain up-references outside of the virtual environnment root | ||||||||||||||||||
directory. Examples: | ||||||||||||||||||
|
||||||||||||||||||
* ``subdir/whatever/bin`` (a directory within the virtual environment). | ||||||||||||||||||
* ``./subdir/whatever/bin`` (same as above) | ||||||||||||||||||
* ``../../../../../elsewhere/runtime/bin`` (a directory outside the virtual | ||||||||||||||||||
environment). | ||||||||||||||||||
Comment on lines
+90
to
+93
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||
|
||||||||||||||||||
Relative paths are relative to the directory containing ``pyvenv.cfg``. During | ||||||||||||||||||
interpreter startup (i.e. ``getpath.py``), the relative path is joined to the | ||||||||||||||||||
directory to form an absolute path. Up-references (``../``) and current | ||||||||||||||||||
directory references (``./``) are resolved syntactically (i.e. not resolving | ||||||||||||||||||
symlinks). Symlinks are *not* resolved prior to construction of the absolute | ||||||||||||||||||
path to ensure semantics between a relative path and absolute path remain the | ||||||||||||||||||
same. | ||||||||||||||||||
|
||||||||||||||||||
For example, given | ||||||||||||||||||
``/home/user/venv/bin/pyvenv.cfg`` with | ||||||||||||||||||
``home = ../../runtime/./bin``, the result is ``home = /home/user/runtime/bin``, | ||||||||||||||||||
i.e. it's equivalent to using that value verbatim in ``pyvenv.cfg``. | ||||||||||||||||||
|
||||||||||||||||||
|
||||||||||||||||||
Python Runtime Changes | ||||||||||||||||||
rickeylev marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||
====================== | ||||||||||||||||||
|
||||||||||||||||||
The Python runtime itself *almost* already supports host-relocating virtual | ||||||||||||||||||
environment and the only change needed is to define how it resolves relative | ||||||||||||||||||
paths for ``home`` in ``pyvenv.cfg``. | ||||||||||||||||||
|
||||||||||||||||||
Today, relative paths resolve relative to the process's current working | ||||||||||||||||||
directory. Because CWD isn't knowable in advance, it makes relative paths today | ||||||||||||||||||
effective impossible. | ||||||||||||||||||
|
||||||||||||||||||
Instead, the paths should be relative to the location of the ``pyvenv.cfg`` | ||||||||||||||||||
file. This file is chosen as the anchor point because the tool that creates the | ||||||||||||||||||
file also has to know where the Python runtime is, so can easily calculate the | ||||||||||||||||||
correct relative path. For tools that read the ``pyvenv.cfg``, it is also easy | ||||||||||||||||||
to simple join the directory name of where ``pyvenv.cfg`` was found with the | ||||||||||||||||||
path in the config file. When a person reads the config file, they can do | ||||||||||||||||||
similar, which is a lower cognitive burden and helps avoids the question of | ||||||||||||||||||
"relative to what?" | ||||||||||||||||||
|
||||||||||||||||||
This change is only a couple of lines in the start up code. Specifically, when | ||||||||||||||||||
parsing the ``pyvenv.cfg`` file and finding the ``home`` value, it just needs | ||||||||||||||||||
to be checked if it's already absolute. If not, then join to the directory name | ||||||||||||||||||
of the ``pyvenv.cfg`` file. The code alredy knows the directory and has helpers | ||||||||||||||||||
already present for checking if a path is absolute and joining two paths. | ||||||||||||||||||
|
||||||||||||||||||
A proof-of-concept of this is implemented in | ||||||||||||||||||
`rickeylev/feat.relative.pyvenv.home <https://github.com/python/cpython/compare/main...rickeylev:cpython:feat.relative.pyvenv.home>`__ | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||
|
||||||||||||||||||
Virtual Environment Tool Changes | ||||||||||||||||||
================================ | ||||||||||||||||||
|
||||||||||||||||||
Virtual environment management tools should support a mechanism to generate a | ||||||||||||||||||
``pyvenv.cfg`` file with a ``home`` key that is a relative path to the relevant | ||||||||||||||||||
Python installation. | ||||||||||||||||||
|
||||||||||||||||||
For the standard library's ``venv`` module, the flag ``--relative`` will be | ||||||||||||||||||
added to trigger this behavior. | ||||||||||||||||||
|
||||||||||||||||||
Package Installer Changes | ||||||||||||||||||
========================= | ||||||||||||||||||
|
||||||||||||||||||
Package installers must be aware of when a relative virtual environment has | ||||||||||||||||||
been defined so they can avoid using absolute paths. The two main places | ||||||||||||||||||
absolute paths occur today are: | ||||||||||||||||||
|
||||||||||||||||||
1. ``bin/`` scripts, where shebangs are rewritten. | ||||||||||||||||||
2. ``site-packages`` symlinks. | ||||||||||||||||||
|
||||||||||||||||||
For symlinks, they should create relative symlinks pointing to the actual | ||||||||||||||||||
installation directory for packages. | ||||||||||||||||||
|
||||||||||||||||||
For ``bin/`` scripts, installers should generate executables that are able to | ||||||||||||||||||
locate the venv's interpreter at runtime. How to do this is implementation | ||||||||||||||||||
defined. Typically it means having shell code use ``$0`` to locate the | ||||||||||||||||||
appropriate ``bin/python3`` executable, but installers are free to use whatever | ||||||||||||||||||
mechanism they want (e.g. a native polyglot executable). | ||||||||||||||||||
|
||||||||||||||||||
Copying a Venv to Another Host | ||||||||||||||||||
================================= | ||||||||||||||||||
|
||||||||||||||||||
Copying the venv to another host is simple: copy the venv directory itself, and | ||||||||||||||||||
any directories outside the venv it needs, while preserving the relative | ||||||||||||||||||
directory structures. | ||||||||||||||||||
|
||||||||||||||||||
How to do this, exactly, is implementation-defined. | ||||||||||||||||||
|
||||||||||||||||||
In practice, this typically means copying a parent directory of the virtual | ||||||||||||||||||
environment, which contains the runtime, one or more venvs, installations | ||||||||||||||||||
of dependencies, and any other supporting files. | ||||||||||||||||||
|
||||||||||||||||||
Backwards Compatibility | ||||||||||||||||||
======================= | ||||||||||||||||||
|
||||||||||||||||||
Because relative paths for ``home`` aren't usable in practice, and their | ||||||||||||||||||
behavior undocumented and unspecified, there shouldn't be any backward | ||||||||||||||||||
compatibility concerns. | ||||||||||||||||||
|
||||||||||||||||||
|
||||||||||||||||||
How to Teach This | ||||||||||||||||||
================= | ||||||||||||||||||
|
||||||||||||||||||
Teaching this should be simple: if you use a relative path in ``pyvenv.cfg``, | ||||||||||||||||||
then it's relative to the directory containing the ``pyvenv.cfg`` file. This | ||||||||||||||||||
is simple to explain and understand. | ||||||||||||||||||
rickeylev marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||
|
||||||||||||||||||
|
||||||||||||||||||
Reference Implementation | ||||||||||||||||||
======================== | ||||||||||||||||||
|
||||||||||||||||||
A reference implementation is available by using the combination of: | ||||||||||||||||||
|
||||||||||||||||||
* Python runtime from `rickeylev/feat.relative.pyvenv.home <https://github.com/python/cpython/compare/main...rickeylev:cpython:feat.relative.pyvenv.home>`__ | ||||||||||||||||||
* rules_python with (todo: link to branch that uses above) | ||||||||||||||||||
|
||||||||||||||||||
Open Issues | ||||||||||||||||||
=========== | ||||||||||||||||||
|
||||||||||||||||||
(todo: placeholder section to be updated after discussions) | ||||||||||||||||||
|
||||||||||||||||||
Footnotes | ||||||||||||||||||
rickeylev marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||
========= | ||||||||||||||||||
|
||||||||||||||||||
* `rules_python <https://github.com/bazel-contrib/rules_python>`__: implements | ||||||||||||||||||
host-relocatable virtual environments. | ||||||||||||||||||
* `rules_py <https://github.com/aspect-build/rules_py>`__: implements | ||||||||||||||||||
host-relocatable virtual environments. | ||||||||||||||||||
* `uv venv | ||||||||||||||||||
relocatable | ||||||||||||||||||
<https://docs.astral.sh/uv/reference/cli/#uv-venv--relocatable>`__: | ||||||||||||||||||
implements same-host relocatable virtual environments. | ||||||||||||||||||
* `python-build-standalone <https://github.com/astral-sh/python-build-standalone>`__: | ||||||||||||||||||
rickeylev marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Put something after the colon? Or delete the colon?
Suggested change
|
||||||||||||||||||
A relocatable Python runtime. | ||||||||||||||||||
* `PoC for relative home in Python startup <https://github.com/python/cpython/compare/main...rickeylev:cpython:feat.relative.pyvenv.home>`__ | ||||||||||||||||||
* `Python Ideas "Making venvs relocatable friendly" discussion <https://discuss.python.org/t/making-venvs-relocatable-friendly/96177>`__ | ||||||||||||||||||
* `GH-136051: relative pyvenv.cfg home <https://github.com/python/cpython/issues/136051>`__ | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Convention is lowercase for issue, uppercase for PR:
Suggested change
|
||||||||||||||||||
|
||||||||||||||||||
Rejected Ideas | ||||||||||||||||||
===================== | ||||||||||||||||||
|
||||||||||||||||||
Relative to virtual env root | ||||||||||||||||||
---------------------------- | ||||||||||||||||||
|
||||||||||||||||||
Having the ``home`` value in ``pyvenv.cfg`` relative to the virtual environments | ||||||||||||||||||
root directory would work just as well, but this idea is rejected because it | ||||||||||||||||||
requires additional effort to compute the virtual env root. | ||||||||||||||||||
|
||||||||||||||||||
Unspecified home means to dynamically compute home | ||||||||||||||||||
---------------------------------------------------- | ||||||||||||||||||
|
||||||||||||||||||
Today, if a ``pyvenv.cfg`` file doesn't set ``home``, the runtime will try to | ||||||||||||||||||
dynamically compute it by checking if the current executable (which is typicall | ||||||||||||||||||
the venv's ``bin/python3`` symlink) is a symlink and, if so, use where that | ||||||||||||||||||
points as ``PYTHONHOME``. | ||||||||||||||||||
|
||||||||||||||||||
This behavior is undesirable for a couple reasons: | ||||||||||||||||||
rickeylev marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||
|
||||||||||||||||||
1. It presents platform-specific issues, namely with Windows. Windows does | ||||||||||||||||||
support symlinks, but not by default, and it can require special | ||||||||||||||||||
permissions to do so. | ||||||||||||||||||
2. It *requires* that a symlink be used, which precludes using otherwise | ||||||||||||||||||
equivalent mechanisms for creating an executable (e.g. a wrapper script, | ||||||||||||||||||
hard links, etc). | ||||||||||||||||||
|
||||||||||||||||||
In general, symlinks work best when they aren't special cased by consumers. | ||||||||||||||||||
|
||||||||||||||||||
Using the term "relocatable" | ||||||||||||||||||
---------------------------- | ||||||||||||||||||
|
||||||||||||||||||
Discussions pointed out the the term "relocatable" is somewhat ambigious and | ||||||||||||||||||
misleading for a couple reasons. | ||||||||||||||||||
|
||||||||||||||||||
First, absolute paths makes a venv arbitrarily relocatable *within* a host, but | ||||||||||||||||||
not between hosts, so "relocatable" requires *some* qualification for | ||||||||||||||||||
clarity. | ||||||||||||||||||
|
||||||||||||||||||
Second, when using relative paths that point outside the venv, the venv is only | ||||||||||||||||||
relocatable insofar as those external artifacts are also relocated. This is an | ||||||||||||||||||
additional nuance that requires qualification of the term. | ||||||||||||||||||
|
||||||||||||||||||
To better avoid this confusion, "relative" is chosen, which more naturally | ||||||||||||||||||
invites the question *"Relative to what?"*. | ||||||||||||||||||
|
||||||||||||||||||
|
||||||||||||||||||
Using PYTHONHOME at runtime to specify home | ||||||||||||||||||
------------------------------------------- | ||||||||||||||||||
|
||||||||||||||||||
Using the ``PYTHONHOME`` environment variable (or any environment variable) is | ||||||||||||||||||
problematic because it's difficult to know and control when an environment | ||||||||||||||||||
variable should or shouldn't be inherited by subprocesses. In some cases, it's | ||||||||||||||||||
not feasible because of how layers of programs calling programs interact. | ||||||||||||||||||
|
||||||||||||||||||
Code generally assumes that any virtual environment will be | ||||||||||||||||||
automatically detected and activated by the presence of ``pyvenv.cfg``, so | ||||||||||||||||||
things work better when alterations to the environemtn aren't a concern. | ||||||||||||||||||
|
||||||||||||||||||
Copyright | ||||||||||||||||||
========= | ||||||||||||||||||
|
||||||||||||||||||
This document is placed in the public domain or under the | ||||||||||||||||||
CC0-1.0-Universal license, whichever is more permissive. |
Uh oh!
There was an error while loading. Please reload this page.