Skip to content
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

uv venv's runtime import patching hooks are unexpected #6426

Open
edmorley opened this issue Aug 22, 2024 · 5 comments
Open

uv venv's runtime import patching hooks are unexpected #6426

edmorley opened this issue Aug 22, 2024 · 5 comments
Labels
virtualenv Related to virtual environments

Comments

@edmorley
Copy link
Contributor

edmorley commented Aug 22, 2024

Hi

When a virtual environment is created using uv venv, it adds run-time import hooks in the created site-packages, which patch distutils.dist and/or setuptools.dist:

$ uv --version
uv 0.3.1 (Homebrew 2024-08-21)

$ uv venv
Using Python 3.12.5 interpreter at: /opt/homebrew/opt/python@3.12/bin/python3.12
Creating virtualenv at: .venv
Activate with: source .venv/bin/activate

$ ls -1 .venv/lib/python3.12/site-packages/
_virtualenv.pth
_virtualenv.py

See:
https://github.com/astral-sh/uv/blob/0.3.1/crates/uv-virtualenv/src/virtualenv.rs#L401-L403
https://github.com/astral-sh/uv/blob/0.3.1/crates/uv-virtualenv/src/_virtualenv.py

It turns out these hooks are copied from the virtualenv project:
https://github.com/pypa/virtualenv/blob/20.26.3/src/virtualenv/create/via_global_ref/_virtualenv.py

This was surprising to me, since:

  • I had expected uv venv to behave more like python -m venv (which doesn't add these hooks) than the virtualenv project, given (a) the venv subcommand name, (b) the virtualenv project to me is more commonly associated with older Python versions (prior to the venv module existing) or niche use-cases for seeding or API usage.
  • There's no mention of these hooks on https://docs.astral.sh/uv/reference/cli/#uv-venv or https://docs.astral.sh/uv/pip/environments/#creating-a-virtual-environment
  • Native support for virtual environments has existed since Python 3.3 (see PEP 405), so at first glance it seems unlikely that any issues wouldn't have already been fixed upstream by now, making these patches redundant? Sadly there's no link to an upstream distutils issues in _virtualenv.py with history or steps to reproduce.
  • Distuils has been removed in Python 3.12, and whilst a copy of it lives on in setuptools:
    • this isn't really mentioned in code comments in _virtualenv.py, requiring people to have that knowledge,
    • for Python 3.12 setuptools is no longer installed by default by ensurepip or venv, and so it feels odd to be installing pre-emptive hooks for it.
  • I generally have a negative association with packages that inject custom import hooks into site-packages, having seen performance and compatibility issues with them over the years (eg some of setuptools' hooks).

Some thoughts:

  1. Do the issues that these hooks aim to fix still exist? If they really were still a widespread problem, then surely the stdlib's venv feature would include these hooks too?
  2. Could the fixes be upstreamed to distutils instead? (If only for setuptool's copy of distutils, where it would be easier to have changes be accepted, and would at least mean the hooks could be dropped for Python 3.12+)
  3. Should uv venv provide an option to opt-out of the hooks?
  4. Should uv venv skip installing these hooks on Python 3.12+, given that setuptools is more likely to not be installed there (due to the upstream ensurepip and venv changes)?
  5. More generally, should uv venv be looking to align itself with the behaviour of the stdlib's venv module, or the virtualenv project? Which would be least surprising for end-users? (Personally, I would expect to be able to substitute a python -m venv .venv && uv ... with uv venv && uv ... and for there to be no change in functionality. I haven't used the separate virtualenv project to directly create an environment for years.)
@charliermarsh
Copy link
Member

@konstin -- Do you know if we can remove these?

@charliermarsh charliermarsh added the virtualenv Related to virtual environments label Aug 22, 2024
@konstin
Copy link
Member

konstin commented Aug 22, 2024

I frankly don't know, i initially copied this file from https://github.com/gaborbernat/virtualenv/blob/main/src/virtualenv/create/via_global_ref/_virtualenv.py to create identical venvs to pypa/virtualenv (an initial goal in gourgeist), but i can't tell if they have any relevance today still.

@edmorley
Copy link
Contributor Author

I found some upstream history for the hooks in:

Of note I found this comment:
pypa/virtualenv#1663 (comment)

...which said:

So, MacOS Catalina 10.15.3 came with a stock /usr/bin/python3 (version 3.7.3) - It also came with what looks like a "light weight" virtual env:
<gives example of python -m venv usage
...
This virtualenv doesn't have this issue.

...which suggests at least one of the bugs being worked around by these hooks only occurred with virtualenv's virtual environments, and not the venv module's.

@edmorley
Copy link
Contributor Author

edmorley commented Aug 22, 2024

Hmm so the distutils.cfg venv issue was supposedly fixed upstream in distutils in Python 3.3:

And that fix is still there in Python 3.11:
https://github.com/python/cpython/blob/795f2597a4be988e2bb19b69ff9958e981cb894e/Lib/distutils/dist.py#L384-L392

Plus in setuptools' adopted copy of distutils:
https://github.com/pypa/setuptools/blob/c11767170a6cc1097b3ba5046d82dd2a9d25290c/setuptools/_distutils/dist.py#L369-L387

The fix involves identify a venv using sys.prefix != sys.base_prefix - which will be the case for PEP-405 compliant venvs.

My first thought was that perhaps the environments created by virtualenv weren't PEP-405 compliant back then, but digging around it seems they switched to creating pyvenv.cfg files in the big rewrite for virtualenv 20 :
pypa/virtualenv@d47ab61

And pypa/virtualenv#1663 reports using virtualenv 20.

That said, there was a slight deviation from the PEP-405 spec until it was fixed in virtualenv v20.16.7 (Nov 2022):
pypa/virtualenv#2441

@garthk
Copy link
Contributor

garthk commented Sep 3, 2024

I'd like to delete the patch, as it breaks Python 3.6.
I'd like to delete the one line of the patch that breaks Python 3.6:

from __future__ import annotations

I appreciate the Python team no longer support Python 3.6, but RedHat-style Linux distributions will support Python 3.6 until releasever 8 expires in 2029. I'm happy for the community to move on, in general, but find it frustrating when key tools break due to a lack of care or a surplus of spite rather than necessity.

Update: it occurs to me Python 3.6 projects might be benefiting from some of the other lines in the VIRTUALENV_PATCH. I'd like to keep those lines until either 2029 or the discovery or provision of some means to patch the environment ourselves. I regret we can't just simply create the file between the environment's creation and use, as they're both happening during uv pip install . as invoked by Hatch.

charliermarsh pushed a commit that referenced this issue Sep 4, 2024
`_virtualenv.py` doesn't need to import `__future__.annotations`, as it
has none.

Removing the import:

* Restores the action of the VIRTUALENV_PATCH on Python 3.6

* Eliminates 24 lines of error messages displayed by Python 3.6 when it
starts in an environment created by uv:

```plaintext
Error processing line 1 of /tmp/tmp.ENwqZ0oeyb/lib/python3.6/site-packages/_virtualenv.pth:

  Traceback (most recent call last):
    File "~/.pyenv/versions/3.6.15/lib/python3.6/site.py", line 168, in addpackage
      exec(line)
    File "<string>", line 1, in <module>
    File "/tmp/tmp.ENwqZ0oeyb/lib/python3.6/site-packages/_virtualenv.py", line 3
      from __future__ import annotations
                                       ^
  SyntaxError: future feature annotations is not defined

Remainder of file ignored
```

(Python displays the errors above twice.)

I appreciate the Python team no longer support Python 3.6, but
RedHat-style Linux distributions will support Python 3.6 in their
`/usr/libexec/platform-python` until [releasever 8 expires in
2029](https://access.redhat.com/support/policy/updates/errata#RHEL8_Planning_Guide).
I'm happy for the community to move on, in general, but don't see the
harm in helping those who can't.

I'm not yet sure what in the “remainder of file ignored” is necessary
for my project's build, as I haven't yet finished digging that from
under Hatch. I'll follow up on #6426 when I do, so we can concentrate on
getting to the happy cow.

## Test Plan

```sh
( set -eu
  export VIRTUAL_ENV="$(mktemp -d)"
  ./target/release/uv venv "$VIRTUAL_ENV" --python=python3.6
  ./target/release/uv pip install cowsay        
  $VIRTUAL_ENV/bin/python -m cowsay --text 'Look, a talking cow!' )
  ```
  
Happy output:

```plaintext
Using Python 3.6.15 interpreter at: ~/.local/bin/python3.6
Creating virtualenv at: /tmp/tmp.VHl4XNi3oI
Activate with: source /tmp//tmp.VHl4XNi3oI/bin/activate
Resolved 1 package in 929ms
Installed 1 package in 17ms
 + cowsay==6.0
  ____________________
| Look, a talking cow! |
  ====================
                    \
                     \
                       ^__^
                       (oo)\_______
                       (__)\       )\/\
                           ||----w |
                           ||     ||
```

---------

Co-authored-by: Zanie Blue <contact@zanie.dev>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
virtualenv Related to virtual environments
Projects
None yet
Development

No branches or pull requests

4 participants