Skip to content

Commit

Permalink
Fix #6185
Browse files Browse the repository at this point in the history
  • Loading branch information
Naofal-Helal committed Sep 22, 2024
1 parent f046425 commit 146be17
Show file tree
Hide file tree
Showing 4 changed files with 41 additions and 20 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.
39 changes: 29 additions & 10 deletions pipenv/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import site
import sys
import typing
from collections import namedtuple
from functools import cached_property
from pathlib import Path
from sysconfig import get_paths, get_python_version, get_scheme_names
Expand All @@ -19,6 +20,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 +74,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 +99,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 e:
if e.ok:
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 +709,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 @@ -761,6 +768,8 @@ def run_activate_this(self):
code = compile(f.read(), activate_this, "exec")
exec(code, {"__file__": activate_this})

EnvironmentActivationResult = namedtuple("EnvironmentActivationResult", ["ok"])

@contextlib.contextmanager
def activated(self):
"""Helper context manager to activate the environment.
Expand All @@ -781,6 +790,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 self.EnvironmentActivationResult(ok=False)
return

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

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.dependencies import (
expansive_install_req_from_line,
Expand All @@ -18,10 +19,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 e:
if not e.ok:
return False

click.secho(f"Uninstalling {package}...", fg="green", bold=True)
cmd = [
project_python(project, system=system),
get_runnable_pip(),
Expand All @@ -38,7 +42,7 @@ def _uninstall_from_environment(project, package, system=False):


def do_uninstall(
project,
project: Project,
packages=None,
editable_packages=None,
python=False,
Expand Down
9 changes: 3 additions & 6 deletions tests/integration/test_uninstall.py
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ def test_category_not_sorted_without_directive(pipenv_instance_private_pypi):


@pytest.mark.uninstall
def test_uninstall_whithout_venv(pipenv_instance_private_pypi):
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 = """
Expand All @@ -354,8 +354,5 @@ def test_uninstall_whithout_venv(pipenv_instance_private_pypi):

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

0 comments on commit 146be17

Please sign in to comment.