From 80e11bf61110cb28a3985faf070f885fd096f2f7 Mon Sep 17 00:00:00 2001 From: Damian Shaw Date: Tue, 3 Sep 2024 20:16:06 -0400 Subject: [PATCH 1/7] Diagnostic error on pip uninstall of invalid package --- src/pip/_internal/exceptions.py | 20 +++++++++++++++++++ .../resolution/resolvelib/candidates.py | 9 +++++++-- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/pip/_internal/exceptions.py b/src/pip/_internal/exceptions.py index 2587740f73a..66b13476a48 100644 --- a/src/pip/_internal/exceptions.py +++ b/src/pip/_internal/exceptions.py @@ -22,10 +22,12 @@ if TYPE_CHECKING: from hashlib import _Hash + from pip._vendor.packaging.requirements import InvalidRequirement from pip._vendor.requests.models import Request, Response from pip._internal.metadata import BaseDistribution from pip._internal.req.req_install import InstallRequirement + from pip._internal.resolution.resolvelib.candidates import AlreadyInstalledCandidate logger = logging.getLogger(__name__) @@ -775,3 +777,21 @@ def __init__(self, *, distribution: "BaseDistribution") -> None: ), hint_stmt=None, ) + +class InvalidInstalledPackage(DiagnosticPipError): + reference = "invalid-installed-package" + + def __init__( + self, + *, + package: "AlreadyInstalledCandidate", + invalid_req_exc: "InvalidRequirement", + ) -> None: + super().__init__( + message=Text( + f"Cannot uninstall {package} because it has an invalid requirement:\n" + f"{invalid_req_exc.args[0]}." + ), + context="Since pip 24.1+ invalid requirements can not be read by pip.", + hint_stmt="Please use 'pip<24.1' if you need to uninstall this package.", + ) diff --git a/src/pip/_internal/resolution/resolvelib/candidates.py b/src/pip/_internal/resolution/resolvelib/candidates.py index d30d477be68..c8a0f6c91a3 100644 --- a/src/pip/_internal/resolution/resolvelib/candidates.py +++ b/src/pip/_internal/resolution/resolvelib/candidates.py @@ -9,6 +9,7 @@ from pip._internal.exceptions import ( HashError, InstallationSubprocessError, + InvalidInstalledPackage, MetadataInconsistent, MetadataInvalid, ) @@ -398,8 +399,12 @@ def format_for_error(self) -> str: def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requirement]]: if not with_requires: return - for r in self.dist.iter_dependencies(): - yield from self._factory.make_requirements_from_spec(str(r), self._ireq) + + try: + for r in self.dist.iter_dependencies(): + yield from self._factory.make_requirements_from_spec(str(r), self._ireq) + except InvalidRequirement as exc: + raise InvalidInstalledPackage(package=self, invalid_req_exc=exc) from None def get_install_requirement(self) -> Optional[InstallRequirement]: return None From 98fe9915ccd175aaa9be4632163fb7431d15b4ab Mon Sep 17 00:00:00 2001 From: Damian Shaw Date: Tue, 3 Sep 2024 20:19:23 -0400 Subject: [PATCH 2/7] Formatting --- src/pip/_internal/exceptions.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/pip/_internal/exceptions.py b/src/pip/_internal/exceptions.py index 66b13476a48..b2fd081c7f2 100644 --- a/src/pip/_internal/exceptions.py +++ b/src/pip/_internal/exceptions.py @@ -778,20 +778,21 @@ def __init__(self, *, distribution: "BaseDistribution") -> None: hint_stmt=None, ) + class InvalidInstalledPackage(DiagnosticPipError): reference = "invalid-installed-package" def __init__( - self, - *, - package: "AlreadyInstalledCandidate", - invalid_req_exc: "InvalidRequirement", - ) -> None: + self, + *, + package: "AlreadyInstalledCandidate", + invalid_req_exc: "InvalidRequirement", + ) -> None: super().__init__( message=Text( f"Cannot uninstall {package} because it has an invalid requirement:\n" f"{invalid_req_exc.args[0]}." - ), + ), context="Since pip 24.1+ invalid requirements can not be read by pip.", hint_stmt="Please use 'pip<24.1' if you need to uninstall this package.", ) From 8b3f784ab668e33d05b08916b52dec0de8c8c8ed Mon Sep 17 00:00:00 2001 From: Damian Shaw Date: Sun, 8 Sep 2024 15:22:12 -0400 Subject: [PATCH 3/7] Update message --- src/pip/_internal/exceptions.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/pip/_internal/exceptions.py b/src/pip/_internal/exceptions.py index b2fd081c7f2..bdca1426d54 100644 --- a/src/pip/_internal/exceptions.py +++ b/src/pip/_internal/exceptions.py @@ -788,11 +788,19 @@ def __init__( package: "AlreadyInstalledCandidate", invalid_req_exc: "InvalidRequirement", ) -> None: + installed_location = package.dist.installed_location super().__init__( message=Text( - f"Cannot uninstall {package} because it has an invalid requirement:\n" - f"{invalid_req_exc.args[0]}." + f"Cannot process installed package {package} " + + (f"in {installed_location!r} " if installed_location else "") + + f"because it has an invalid requirement:\n{invalid_req_exc.args[0]}" + ), + context=( + "Starting with pip 24.1, packages with invalid " + "requirements can not be processed." + ), + hint_stmt=( + "To proceed this package must be uninstalled using 'pip<24.1', " + "some other Python package tool, or manually deleted." ), - context="Since pip 24.1+ invalid requirements can not be read by pip.", - hint_stmt="Please use 'pip<24.1' if you need to uninstall this package.", ) From 968a190f8d75b660732b1074c8f2a3741d95002d Mon Sep 17 00:00:00 2001 From: Damian Shaw Date: Sun, 8 Sep 2024 15:23:27 -0400 Subject: [PATCH 4/7] NEWS ENTRY. --- news/12953.bugfix.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 news/12953.bugfix.rst diff --git a/news/12953.bugfix.rst b/news/12953.bugfix.rst new file mode 100644 index 00000000000..f2d0521b20e --- /dev/null +++ b/news/12953.bugfix.rst @@ -0,0 +1 @@ +Display disagnostic error message when already installed package has an invalid requirement. From 22cc32d654be7e33be19724f7e27d02a187c4cc9 Mon Sep 17 00:00:00 2001 From: Damian Shaw Date: Fri, 27 Sep 2024 21:32:30 -0400 Subject: [PATCH 5/7] Also handle `InvalidVersion` --- src/pip/_internal/exceptions.py | 22 ++++++++++++------- .../resolution/resolvelib/candidates.py | 2 +- .../resolution/resolvelib/factory.py | 16 +++++++++----- 3 files changed, 26 insertions(+), 14 deletions(-) diff --git a/src/pip/_internal/exceptions.py b/src/pip/_internal/exceptions.py index bdca1426d54..52a18b14a18 100644 --- a/src/pip/_internal/exceptions.py +++ b/src/pip/_internal/exceptions.py @@ -15,6 +15,8 @@ from itertools import chain, groupby, repeat from typing import TYPE_CHECKING, Dict, Iterator, List, Literal, Optional, Union +from pip._vendor.packaging.requirements import InvalidRequirement +from pip._vendor.packaging.version import InvalidVersion from pip._vendor.rich.console import Console, ConsoleOptions, RenderResult from pip._vendor.rich.markup import escape from pip._vendor.rich.text import Text @@ -22,12 +24,10 @@ if TYPE_CHECKING: from hashlib import _Hash - from pip._vendor.packaging.requirements import InvalidRequirement from pip._vendor.requests.models import Request, Response from pip._internal.metadata import BaseDistribution from pip._internal.req.req_install import InstallRequirement - from pip._internal.resolution.resolvelib.candidates import AlreadyInstalledCandidate logger = logging.getLogger(__name__) @@ -785,19 +785,25 @@ class InvalidInstalledPackage(DiagnosticPipError): def __init__( self, *, - package: "AlreadyInstalledCandidate", - invalid_req_exc: "InvalidRequirement", + dist: "BaseDistribution", + invalid_exc: Union[InvalidRequirement, InvalidVersion], ) -> None: - installed_location = package.dist.installed_location + installed_location = dist.installed_location + + if isinstance(invalid_exc, InvalidRequirement): + invalid_type = "requirement" + else: + invalid_type = "version" + super().__init__( message=Text( - f"Cannot process installed package {package} " + f"Cannot process installed package {dist} " + (f"in {installed_location!r} " if installed_location else "") - + f"because it has an invalid requirement:\n{invalid_req_exc.args[0]}" + + f"because it has an invalid {invalid_type}:\n{invalid_exc.args[0]}" ), context=( "Starting with pip 24.1, packages with invalid " - "requirements can not be processed." + f"{invalid_type}s can not be processed." ), hint_stmt=( "To proceed this package must be uninstalled using 'pip<24.1', " diff --git a/src/pip/_internal/resolution/resolvelib/candidates.py b/src/pip/_internal/resolution/resolvelib/candidates.py index c8a0f6c91a3..6617644fe53 100644 --- a/src/pip/_internal/resolution/resolvelib/candidates.py +++ b/src/pip/_internal/resolution/resolvelib/candidates.py @@ -404,7 +404,7 @@ def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requiremen for r in self.dist.iter_dependencies(): yield from self._factory.make_requirements_from_spec(str(r), self._ireq) except InvalidRequirement as exc: - raise InvalidInstalledPackage(package=self, invalid_req_exc=exc) from None + raise InvalidInstalledPackage(dist=self.dist, invalid_exc=exc) from None def get_install_requirement(self) -> Optional[InstallRequirement]: return None diff --git a/src/pip/_internal/resolution/resolvelib/factory.py b/src/pip/_internal/resolution/resolvelib/factory.py index 145bdbf71a1..dc6e2e12e1f 100644 --- a/src/pip/_internal/resolution/resolvelib/factory.py +++ b/src/pip/_internal/resolution/resolvelib/factory.py @@ -23,13 +23,14 @@ from pip._vendor.packaging.requirements import InvalidRequirement from pip._vendor.packaging.specifiers import SpecifierSet from pip._vendor.packaging.utils import NormalizedName, canonicalize_name -from pip._vendor.packaging.version import Version +from pip._vendor.packaging.version import InvalidVersion, Version from pip._vendor.resolvelib import ResolutionImpossible from pip._internal.cache import CacheEntry, WheelCache from pip._internal.exceptions import ( DistributionNotFound, InstallationError, + InvalidInstalledPackage, MetadataInconsistent, MetadataInvalid, UnsupportedPythonVersion, @@ -283,10 +284,15 @@ def _get_installed_candidate() -> Optional[Candidate]: installed_dist = self._installed_dists[name] except KeyError: return None - # Don't use the installed distribution if its version does not fit - # the current dependency graph. - if not specifier.contains(installed_dist.version, prereleases=True): - return None + + try: + # Don't use the installed distribution if its version + # does not fit the current dependency graph. + if not specifier.contains(installed_dist.version, prereleases=True): + return None + except InvalidVersion as e: + raise InvalidInstalledPackage(dist=installed_dist, invalid_exc=e) + candidate = self._make_candidate_from_dist( dist=installed_dist, extras=extras, From faf02acaf0dd372d42fd82040f6382cd4c9b228e Mon Sep 17 00:00:00 2001 From: Damian Shaw Date: Sun, 20 Oct 2024 14:20:12 -0400 Subject: [PATCH 6/7] Simplify hint message in invalid install error --- src/pip/_internal/exceptions.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/pip/_internal/exceptions.py b/src/pip/_internal/exceptions.py index ee248396d8f..e977e5253ad 100644 --- a/src/pip/_internal/exceptions.py +++ b/src/pip/_internal/exceptions.py @@ -806,7 +806,6 @@ def __init__( f"{invalid_type}s can not be processed." ), hint_stmt=( - "To proceed this package must be uninstalled using 'pip<24.1', " - "some other Python package tool, or manually deleted." + "To proceed this package must be uninstalled." ), ) From c558a4965708e1d7cdd6ca481182db2d677e455c Mon Sep 17 00:00:00 2001 From: Damian Shaw Date: Sun, 20 Oct 2024 14:24:10 -0400 Subject: [PATCH 7/7] Fix formatting --- src/pip/_internal/exceptions.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/pip/_internal/exceptions.py b/src/pip/_internal/exceptions.py index e977e5253ad..45a876a850d 100644 --- a/src/pip/_internal/exceptions.py +++ b/src/pip/_internal/exceptions.py @@ -805,7 +805,5 @@ def __init__( "Starting with pip 24.1, packages with invalid " f"{invalid_type}s can not be processed." ), - hint_stmt=( - "To proceed this package must be uninstalled." - ), + hint_stmt="To proceed this package must be uninstalled.", )