Skip to content

Commit

Permalink
Merge pull request #6250 from Naofal-Helal/fix-uninstall
Browse files Browse the repository at this point in the history
Fix uninstall --all fails when venv is deleted
  • Loading branch information
matteius authored Sep 23, 2024
2 parents f7058e8 + 3cabf79 commit fbecd8e
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 14 deletions.
1 change: 1 addition & 0 deletions news/6185.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed ``pipenv uninstall --all`` failing when the virtual environment no longer exists.
36 changes: 26 additions & 10 deletions pipenv/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from pipenv.patched.pip._vendor.packaging.specifiers import SpecifierSet
from pipenv.patched.pip._vendor.packaging.utils import canonicalize_name
from pipenv.patched.pip._vendor.packaging.version import parse as parse_version
from pipenv.patched.pip._vendor.typing_extensions import Iterable
from pipenv.utils import console
from pipenv.utils.fileutils import normalize_path, temp_path
from pipenv.utils.funktools import chunked, unnest
Expand Down Expand Up @@ -72,8 +73,9 @@ def __init__(
pipfile = project.parsed_pipfile
self.pipfile = pipfile
self.extra_dists = []
prefix = prefix if prefix else sys.prefix
self.prefix = Path(prefix)
if self.is_venv and prefix is not None and not Path(prefix).exists():
return
self.prefix = Path(prefix if prefix else sys.prefix)
self._base_paths = {}
if self.is_venv:
self._base_paths = self.get_paths()
Expand All @@ -96,11 +98,14 @@ def safe_import(self, name: str) -> ModuleType:
return module

@cached_property
def python_version(self) -> str:
with self.activated():
sysconfig = self.safe_import("sysconfig")
py_version = sysconfig.get_python_version()
return py_version
def python_version(self) -> str | None:
with self.activated() as active:
if active:
sysconfig = self.safe_import("sysconfig")
py_version = sysconfig.get_python_version()
return py_version
else:
return None

@property
def python_info(self) -> dict[str, str]:
Expand Down Expand Up @@ -703,9 +708,10 @@ def reverse_dependencies(self):
}
return rdeps

def get_working_set(self):
def get_working_set(self) -> Iterable:
"""Retrieve the working set of installed packages for the environment."""

if not hasattr(self, "sys_path"):
return []
return importlib_metadata.distributions(path=self.sys_path)

def is_installed(self, pkgname):
Expand Down Expand Up @@ -781,6 +787,16 @@ def activated(self):
to `os.environ["PATH"]` to ensure that calls to `~Environment.run()` use the
environment's path preferentially.
"""

# Fail if the virtualenv is needed but cannot be found
if self.is_venv and (
hasattr(self, "prefix")
and not self.prefix.exists()
or not hasattr(self, "prefix")
):
yield False
return

original_path = sys.path
original_prefix = sys.prefix
prefix = self.prefix.as_posix()
Expand All @@ -806,7 +822,7 @@ def activated(self):
sys.path = self.sys_path
sys.prefix = self.sys_prefix
try:
yield
yield True
finally:
sys.path = original_path
sys.prefix = original_prefix
13 changes: 9 additions & 4 deletions pipenv/routines/uninstall.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@

from pipenv import exceptions
from pipenv.patched.pip._internal.build_env import get_runnable_pip
from pipenv.project import Project
from pipenv.routines.lock import do_lock
from pipenv.utils import console
from pipenv.utils.dependencies import (
expansive_install_req_from_line,
get_lockfile_section_using_pipfile_category,
Expand All @@ -18,10 +20,13 @@
from pipenv.vendor.importlib_metadata.compat.py39 import normalized_name


def _uninstall_from_environment(project, package, system=False):
def _uninstall_from_environment(project: Project, package, system=False):
# Execute the uninstall command for the package
click.secho(f"Uninstalling {package}...", fg="green", bold=True)
with project.environment.activated():
with project.environment.activated() as is_active:
if not is_active:
return False

console.print(f"Uninstalling {package}...", style="bold green")
cmd = [
project_python(project, system=system),
get_runnable_pip(),
Expand All @@ -38,7 +43,7 @@ def _uninstall_from_environment(project, package, system=False):


def do_uninstall(
project,
project: Project,
packages=None,
editable_packages=None,
python=False,
Expand Down
20 changes: 20 additions & 0 deletions tests/integration/test_uninstall.py
Original file line number Diff line number Diff line change
Expand Up @@ -336,3 +336,23 @@ def test_category_not_sorted_without_directive(pipenv_instance_private_pypi):
"colorama",
"atomicwrites",
]


@pytest.mark.uninstall
def test_uninstall_without_venv(pipenv_instance_private_pypi):
with pipenv_instance_private_pypi() as p:
with open(p.pipfile_path, "w") as f:
contents = """
[packages]
colorama = "*"
atomicwrites = "*"
""".strip()
f.write(contents)

c = p.pipenv("install")
assert c.returncode == 0

c = p.pipenv("uninstall --all")
assert c.returncode == 0
# uninstall --all shold not remove packages from Pipfile
assert list(p.pipfile["packages"].keys()) == ["colorama", "atomicwrites"]

0 comments on commit fbecd8e

Please sign in to comment.