pipx: Once system python and pipx upgraded, cannot install new app if some previous old shared_venv existed

Describe the bug

On Manjaro/Arch, I was with python 3.7.4. To ensure stability of some apps (and as some apps are not fully working on 3.8 that I new was coming) I also had python 3.7.5 installed through pyenv. I use this python to install my apps with

pipx install --python ~/.pyenv/versions/3.7.5/bin/python flexget
pipx inject flexget transmissionrpc

Then my system got upgraded from 3.7.4 to 3.8 (Note that my 3.7.5 pyenv stayed) I reinstalled pipx on 3.8 on system. Then I had error thrown when installing new pipx no matter what versions I tried:

$ pipx install pycowsay --verbose
pipx > (run_pipx_command:134): Virtual Environment location is /home/hcooh/.local/pipx/venvs/pycowsay
pipx > (run:97): running /usr/bin/python -m venv --without-pip /home/hcooh/.local/pipx/venvs/pycowsay
pipx > (run:97): running /home/hcooh/.local/pipx/venvs/pycowsay/bin/python -m pip install pycowsay
/home/hcooh/.local/pipx/venvs/pycowsay/bin/python: No module named pip

pipx > (rmdir:16): removing directory /home/hcooh/.local/pipx/venvs/pycowsay
'/home/hcooh/.local/pipx/venvs/pycowsay/bin/python -m pip install pycowsay' failed

or even trying to use the pyenv:

pipx install --python ~/.pyenv/versions/3.7.5/bin/python pycowsay --verbose
pipx > (run_pipx_command:134): Virtual Environment location is /home/hcooh/.local/pipx/venvs/pycowsay
pipx > (run:97): running /home/hcooh/.pyenv/versions/3.7.5/bin/python -m venv --without-pip /home/hcooh/.local/pipx/venvs/pycowsay
pipx > (run:97): running /home/hcooh/.local/pipx/venvs/pycowsay/bin/python -m pip install pycowsay
/home/hcooh/.local/pipx/venvs/pycowsay/bin/python: No module named pip

pipx > (rmdir:16): removing directory /home/hcooh/.local/pipx/venvs/pycowsay
'/home/hcooh/.local/pipx/venvs/pycowsay/bin/python -m pip install pycowsay' failed

Running the already installed app (using the 3.7.5) would work fine and even pipx runpip xxx list I realised then that .local/pipx/shared/pyvenv.cfg mentioned python version 3.7.4. So I renamed the .local/pipx/shared folder differently. It recreated the shared lib for python3.8. I had to then symlink the pyenv folder of python3.7 with:

ln -s ../../../../.pyenv/versions/3.7.5/lib/python3.7/ ~/.local/pipx/shared/lib/python3.7

And then install run fine and the already installed app run fine too

How to reproduce

See above

Expected behavior

pipx Install install should work after a pyhton upgrade even if hte previous shared folder still exists. i think

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 6
  • Comments: 25 (11 by maintainers)

Most upvoted comments

Have you tried pipx reinstall-all? This command is supposed to help for situations when the system python changes, and has been known to work for that situation on the Mac.

If that doesn’t work, what does pipx list return? It should show a lit of packages with warnings that they are missing a valid python interpreter.

Did you uninstall the old jq before trying to install the new jq? That may be important.

The other thing I can think of that might possibly be affecting you is that you are installing pipx to the system as opposed to using python3 -m pip install --user pipx as is our recommendation.

Just had this issue again upgrading to fedora 35:

/home/nota/.local/pipx/shared/bin/python: No module named pip
Failed to upgrade shared libraries
Traceback (most recent call last):
  File "/usr/lib/python3.10/site-packages/pipx/shared_libs.py", line 113, in upgrade
    subprocess_post_check(upgrade_process)
  File "/usr/lib/python3.10/site-packages/pipx/util.py", line 203, in subprocess_post_check
    raise PipxError(
pipx.util.PipxError: '/home/nota/.local/pipx/shared/bin/python -m pip --disable-pip-version-check install -q --upgrade pip setuptools wheel' failed
/home/nota/.local/pipx/venvs/pyupgrade/bin/python: No module named pip
Error encountered when upgrading pyupgrade:
'/home/nota/.local/pipx/venvs/pyupgrade/bin/python -m pip install --upgrade pyupgrade -q' failed

I fixed it by running:

rm -r ~/.local/pipx/shared/

and then running a pipx operation to make it recreate the shared env with the new python version.

Summary

Judging from https://github.com/pypa/pipx/issues/294#issuecomment-992699160 and https://github.com/pypa/pipx/issues/294#issuecomment-597792241, there’s at least 3 versions in a pipx setup:

  • The Python interpreter version used to install pipx
  • The shared env’s version
  • Each app’s venv version

Each of these should match the system Python interpreter, but might desync (the system Python interpreter’s path is still present, but belongs to a different Python version) when updating Python in-place on Linux.

Ideas

Not a pipx contributor so far, but I could try changing reinstall-all to always recreate the shared environment, and change more pipx operations (reinstall, upgrade[-all], install, etc.) to automatically recreate the shared environment if the Python interpreter version has changed. (Perhaps it should also reinstall all packages? It’s necessary to fix broken packages, but unexpected by the user. Maybe do nothing in shell scripts and prompt the user if a TTY is detected?) You’ll either have to save the install-time Python version in a config file, or detect the install-time Python version from the shared/app venv (I’m not sure how, and looking for a .../lib/python3.x folder can mistakenly indicate a venv was installed on newer Python than it actually was; on current pipx I managed to end up with a shared env with an empty python3.10 folder, and an app’s venv with a python3.10 folder containing Python 3.10 __pycache__ but 3.9 C extensions).

My other idea is to add a config file/database containing a list of venvs and each venv’s install options and Python versions. This complicates the architecture since the config file can desync with the actual folders present (eg. if a user adds/removes venv folders by hand), but is useful since if a failed reinstall deletes a venv, it’s still present in the config/database and a subsequent reinstall-all knows to install them. This config file should also store the shared environment’s Python version and/or interpreter path, so pipx can compute when to regenerate the shared env and all app venvs (or you can skip this and use the version stored in ~/.local/pipx/shared/pyvenv.cfg).

In theory you could also store the Python version/path of each app’s venv (though this is already tracked in ~/.local/pipx/venvs/*/pyvenv.cfg. Using this to support different venvs with different Python versions/interpreters is too complex IMO. It might be useful to use this to track which venvs need to be reinstalled. I kinda prefer not supporting partially upgrading venvs, but instead block all pipx operations until the shared env and all venvs are upgraded in a single operation. But old pipx versions (combined with users copying commands to try to fix pipx) can generate mixed Python versions. If we don’t check per-venv Python versions, we can update the docs and tell users to “reinstall-all to fix things”; if we do, we can detect and address this situation.

I think it’s still a good idea for reinstall-all to regenerate the shared env, even if it’s supposedly up-to-date according to the “install-time Python version” metadata. This allows the command to fix errors tied to the shared env, rather than any app’s venv. (These errors may exist, I’ve never encountered any other than a Python version mismatch.) This also fixes cases where the Python version has actually changed even though the metadata wrongly indicates the install-time version matches the current version.

Is it possible for each app’s shim binary to automatically reinstall the app (and shared env, maybe other apps) if necessary? This makes the reinstallation process transparent to users and hopefully “just work”, but I’m not sure if users want their apps to magically change behavior in edge cases (though they already break right now). And this might slow-down happy-path app startup time (though you can get pretty close to zero overhead by only reinstalling when you catch ModuleNotFoundError) (though I don’t know if this fixes both “outdated shared env and app” and “updated shared env but outdated app”). And this doesn’t catch cases where pipx install --system-site-packages and pipx upgrade results in a working app which uses C libraries from the system rather than the venv.

Or perhaps pipx itself should catch ModuleNotFoundError: No module named 'pipx' and reinstall the shared env and all packages. (I don’t think I’ve seen this particular error since I installed pipx from the Arch repositories rather than pip install --user.) This fixes another user-facing failure mode of pipx, but can’t replace my previous suggestions since it fails to catch cases where pipx is updated but the shared env is out-of-date and unusable.

I didn’t look into handling the system Python interpreter being uninstalled, and a new one being installed to a different location. This case is more common on Windows where Python is installed to C:\Python3xx\bin\python rather than /usr/bin/python. This may break the pipx binary, and require the user to reinstall pipx by hand, and then ideally pipx updates the shared and app envs from there.

Testing

I think it will be difficult to come to a consensus on an optimal design (probably manual testing will help in designing an intuitive interface), and difficult to manually or automatically test updating Python to a new major version (which only happens once per year per Linux installation). Perhaps pyenv will help to test switching Python interpreters quickly. There’s also “build a Docker image with an outdated Python”, but I’m less familiar with that than pyenv, and setting up Docker/Podman requires more upfront setup and invasive system changes (cgroups etc.) than pyenv IMO.

OK, I finally had a chance to run your very complete docker instructions. Thank you for that!

I can reproduce what you’re talking about. And it doesn’t seem to help if you install pipx using pip install --user.

First problem, yq stops working after system python upgrade

What seems to be happening is that in ~/.local/pipx/venv/yq/bin all the pythons are being linked back only to /usr/bin/python3. This is not what happens for me on my Mac: there each venv’s python is linked to a very particular version of python on my system (e.g. /usr/local/Cellar/python/3.7.7/python3.7.). So it doesn’t come up empty looking for a non-existent python (which we handle), but the python it finds has a different minor version than before. This causes site packages to be in the wrong place (they are in ~/.local/pipx/venvs/yq/lib/python3.7/ but the new python3.8 is looking for ~/.local/pipx/venvs/yq/lib/python3.8/ which doesn’t exist)

I see the same behavior on my Ubuntu system. The python interpreter link in all the venvs points to /usr/bin/python3, not a specific version like /usr/bin/python3.7.

The workaround when you first install yq would be this pipx command:

pipx install --python /usr/bin/python3.7 yq

Then after upgrading the system python yq will still work. (I tried it and it works for me.)

My suggestion to the pipx developers is that when we determine the system python, maybe we should resolve the link back to the original binary filepath which would get a specific python sub-version. (e.g. in utils.py we should use DEFAULT_PYTHON = Path(sys.executable).resolve()). I’m not sure if that’s the best solution (it would cause sub-minor python upgrades to break pipx venvs) but it’s a solution.

Second problem, reinstall of pipx will not install after system upgrade

I think this is similar to the venv problem described above. The shared libs have their own idea of a python binary, and on linux this seems to be linked to a generic binary without a sub-version (i.e. /usr/bin/python3) which then screws up the shared lib venv when the python minor version number changes. It appears to pipx that at least the python binary is valid, but the shared_lib venv has no site-packages under the the new python3.8 only under the old python3.7.

shared_libs.py, _SharedLibs.is_valid() only checks if we have files of the python binary and the pip binary, but it doesn’t check that the library pip is still accessible for the current python. When the python the shared_libs is linked to gets changed from 3.7 to 3.8, then we no longer have the package pip under the right directory in the shared_libs venv.

I reinstalled pipx using pip install pipx after upgrading to Python 3.10.

When I ran pipx reinstall-all, it printed a lot of errors and removed all my virtualenvs, it failed to reinstall these.

https://gist.github.com/Exagone313/99b7aa35b8aec098f431e702d9416f35

After that, running the command again gave an error about the pip package being not found in pipx virtualenv:

/home/emartinet/.local/pipx/shared/bin/python: No module named pip
Failed to upgrade shared libraries
Traceback (most recent call last):
  File "/home/emartinet/.local/lib/python3.10/site-packages/pipx/shared_libs.py", line 113, in upgrade
    subprocess_post_check(upgrade_process)
  File "/home/emartinet/.local/lib/python3.10/site-packages/pipx/util.py", line 165, in subprocess_post_check
    raise PipxError(
pipx.util.PipxError: '/home/emartinet/.local/pipx/shared/bin/python -m pip --disable-pip-version-check install -q --upgrade pip setuptools wheel' failed

I solved it by moving the ~/.local/pipx directory: mv ~/.local/pipx{,.old}, then I reinstalled all my packages manually.


I think the reinstallation should be protective and keep the list of installed virtualenvs at some place to deal with installation errors. At least I had the log.

Arch Linux, pipx 0.16.4, just upgraded from Python 3.9 to 3.10, and as per tradition all my pipx packages broke.

Old venvs break

running a pipx operation to make it recreate the shared env with the new python version.

Even after recreating the shared env, some of my existing envs don’t load, since they only have ~/.local/pipx/venvs/PACKAGE/lib/python3.9/:

> tg
Traceback (most recent call last):
  File "/home/nyanpasu64/.local/bin/tg", line 5, in <module>
    from tg.__main__ import main
ModuleNotFoundError: No module named 'tg'
> aws
Traceback (most recent call last):
  File "/home/nyanpasu64/.local/bin/aws", line 19, in <module>
    import awscli.clidriver
ModuleNotFoundError: No module named 'awscli'
> scan-build
Traceback (most recent call last):
  File "/home/nyanpasu64/.local/bin/scan-build", line 5, in <module>
    from libscanbuild.analyze import scan_build
ModuleNotFoundError: No module named 'libscanbuild'

Interestingly, maestral and corrscope (which I installed with --system-site-packages so they could use system PyQt5) work without having to reinstall. It seems pipx upgrade-all somehow produced a corrscope/lib/python3.10 with very recent modification dates (11:02:06 PM), and maestral/lib/python3.10 (11:02:00 PM). For context, I updated python at 10:57 PM, tried and failed to run pipx upgrade-all at 11:01:57 PM, and recreated my pipx shared env later at 11:02:39 PM. Interestingly, corrscope/bin and maestral/bin still have python3.9 binaries, but they now point to /usr/bin/python3.10 (possibly because /usr/bin/python links to /usr/bin/python3.10).

However, corrscope (which I installed with now fails to run when I uninstall system matplotlib (prints ModuleNotFoundError: No module named 'matplotlib'), because the pipx-venv matplotlib contains CPython 3.9 extensions and can no longer be loaded by 3.10.

Reinstalling works-ish

pipx reinstall awscli worked. I tried reinstalling this using fd -d 1 . ~/.local/pipx/venvs/ --exec pipx reinstall '{/}' (because I didn’t realize that reinstall-all existed). The uninstalls succeeded, but the installs failed:

Traceback (most recent call last):
  File "/usr/lib/python3.10/runpy.py", line 196, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "/usr/lib/python3.10/runpy.py", line 86, in _run_code
    exec(code, run_globals)
  File "/home/nyanpasu64/.local/pipx/shared/lib/python3.10/site-packages/pip/__main__.py", line 9, in <module>
    if sys.path[0] in ("", os.getcwd()):
FileNotFoundError: [Errno 2] No such file or directory

I think that fd doesn’t change the current working directory, implying that pipx reinstall failed because I ran fd in a folder deleted by one of the reinstall commands. Do you consider it a bug that pipx fails when run in a folder deleted by pip install, and do you plan to cd $HOME after deleting the venv? Is it a bug that a failed pipx reinstall deletes the venv folder and makes pipx act like the package never existed?

Suggestions

  • Change pipx reinstall-all to first delete (or rename) and recreate the shared venv, before reinstalling all packages from the original spec. (Before I realized that reinstall-all existed, I wrote: ideally if reinstalling any package fails, the old environment is left in place, and/or the failing packages are printed.) IDK if this is implemented or necessary.
  • Make pipx reinstall not fail if your current working directory is deleted by the uninstall process.
  • Somehow make pipx reinstall better handle the case where reinstallation fails (perhaps leave the old folder in place? perhaps rename the old folder instead of deleting it?).

We don’t plan to support this for now. On python upgrades I recommend reinstall-all.

@michjnich , what happens with Python 3.9 if you just try the following on your command-line:

pip --version

If that’s missing, then you might need to use apt to reinstall for Python 3.9 the necessary modules for Ubuntu that ensure venv and pip are available: https://pipxproject.github.io/pipx/troubleshooting/#debian-ubuntu-issues

sudo apt install python3-venv python3-pip

I’m not sure if this is related, but I’ve had very similar issues. After much un/re-installing with manual clear downs of the shared dirs I’ve got rid od f the “can’t find pip” issue, but am now having the following when trying to install something:

$  pipx install flake8
Error: Command '['/home/mike/.local/pipx/shared/bin/python', '-Im', 'ensurepip', '--upgrade', '--default-pip']' returned non-zero exit status 1.

'/usr/bin/python -m venv --clear /home/mike/.local/pipx/shared' failed

I can uninstall pipx, remove the .local/pipx tree completely, then reinstall and try it, and it’s the same problems.

This following an upgrade from 3.8 => 3.9 in Ubuntu on WSL2, using update-alternatives to manage the python version.

If I flip the python version back to 3.8 via update-alternatives, install pipx for that, then it all works just fine and I can pipx install as expected.

Then going back to 3.9 requires installing pipx again. After that pipx listworks, but nothing else - then I get “No module named pip”.

I think I encountered a similar problem after upgrading my python install from the brew. The solution that worked for me is pipx reinstall-all.

To illustrate the problem and how it gets resolved, the following are the commands I did.

 me@MacBook-Pro  ~  python --version
python 3.8.3

 me@MacBook-Pro  ~ brew upgrade
...
all done!
 me@MacBook-Pro  ~ python --version 
python 3.8.5
 me@MacBook-Pro  ~  pipx list
venvs are in /Users/me/.local/pipx/venvs
apps are exposed on your $PATH at /Users/me/.local/bin
   package autopep8 has invalid interpreter /usr/local/Cellar/python@3.8/3.8.3/bin/python3.8
   package black has invalid interpreter /usr/local/Cellar/python@3.8/3.8.3/bin/python3.8
   package flake8 has invalid interpreter /usr/local/Cellar/python@3.8/3.8.3/bin/python3.8
   package jupyterlab has invalid interpreter /usr/local/Cellar/python@3.8/3.8.3/bin/python3.8
   package pycowsay has invalid interpreter /usr/local/Cellar/python@3.8/3.8.3/bin/python3.8
   package pylint has invalid interpreter /usr/local/Cellar/python@3.8/3.8.3/bin/python3.8
   package virtualenv has invalid interpreter /usr/local/Cellar/python@3.8/3.8.3/bin/python3.8

 me@MacBook-Pro  ~  pipx reinstall-all
...
 me@MacBook-Pro  ~  pipx list
venvs are in /Users/me/.local/pipx/venvs
apps are exposed on your $PATH at /Users/me/.local/bin
   package autopep8 1.5.4, Python 3.8.5
    - autopep8
   package black 20.8b1, Python 3.8.5
    - black
    - black-primer
    - blackd
   package flake8 3.8.4, Python 3.8.5
    - flake8
   package jupyterlab 2.2.8, Python 3.8.5
    - ...
   package pycowsay 0.0.0.1, Python 3.8.5
    - pycowsay
   package pylint 2.6.0, Python 3.8.5
    - ...
   package virtualenv 20.0.33, Python 3.8.5
    - virtualenv

btw the workaround after an upgrade of the system python to get pipx working (after reinstalling pipx):

rm ~/.local/pipx/shared/bin/python3
ln -s /usr/bin/python3.7 ~/.local/pipx/shared/bin/python3

Or at least I assume that should be the workaround, but when I tried it, it found pip but then failed spectacularly trying to build a wheel for jq.