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

Diagnostic error when processing package already installed with invalid requirement #12953

Merged
merged 9 commits into from
Oct 21, 2024
1 change: 1 addition & 0 deletions news/12953.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Display disagnostic error message when already installed package has an invalid requirement.
32 changes: 32 additions & 0 deletions src/pip/_internal/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -775,3 +777,33 @@ def __init__(self, *, distribution: "BaseDistribution") -> None:
),
hint_stmt=None,
)


class InvalidInstalledPackage(DiagnosticPipError):
reference = "invalid-installed-package"

def __init__(
self,
*,
dist: "BaseDistribution",
invalid_exc: Union[InvalidRequirement, InvalidVersion],
) -> None:
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 {dist} "
+ (f"in {installed_location!r} " if installed_location else "")
+ f"because it has an invalid {invalid_type}:\n{invalid_exc.args[0]}"
),
context=(
"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.",
)
9 changes: 7 additions & 2 deletions src/pip/_internal/resolution/resolvelib/candidates.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from pip._internal.exceptions import (
HashError,
InstallationSubprocessError,
InvalidInstalledPackage,
MetadataInconsistent,
MetadataInvalid,
)
Expand Down Expand Up @@ -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(dist=self.dist, invalid_exc=exc) from None

def get_install_requirement(self) -> Optional[InstallRequirement]:
return None
Expand Down
16 changes: 11 additions & 5 deletions src/pip/_internal/resolution/resolvelib/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
Loading