diff --git a/src/pip/_internal/exceptions.py b/src/pip/_internal/exceptions.py index 8ceb818a35d..d5b1523a82e 100644 --- a/src/pip/_internal/exceptions.py +++ b/src/pip/_internal/exceptions.py @@ -775,3 +775,26 @@ def __init__(self, *, distribution: "BaseDistribution") -> None: ), hint_stmt=None, ) + + +class UnavailableExtra(InstallationError): + """A requested extra is not available.""" + + def __init__( + self, + base: str, + version: str, + extra: str, + available_extras: List[str], + ): + self.base = base + self.version = version + self.extra = extra + self.available_extras = available_extras + + def __str__(self) -> str: + nice_available = " ".join( + '"{}", '.format(e) + for e in self.available_extras + )[:-2] + return f"{self.base} {self.version} does not provide the extra '{self.extra}'\n{self.base} provides extras: {nice_available}" diff --git a/src/pip/_internal/resolution/resolvelib/candidates.py b/src/pip/_internal/resolution/resolvelib/candidates.py index d30d477be68..142290cd651 100644 --- a/src/pip/_internal/resolution/resolvelib/candidates.py +++ b/src/pip/_internal/resolution/resolvelib/candidates.py @@ -11,6 +11,7 @@ InstallationSubprocessError, MetadataInconsistent, MetadataInvalid, + UnavailableExtra, ) from pip._internal.metadata import BaseDistribution from pip._internal.models.link import Link, links_equivalent @@ -508,11 +509,11 @@ def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requiremen valid_extras = self.extras.intersection(self.base.dist.iter_provided_extras()) invalid_extras = self.extras.difference(self.base.dist.iter_provided_extras()) for extra in sorted(invalid_extras): - logger.warning( - "%s %s does not provide the extra '%s'", - self.base.name, - self.version, - extra, + raise UnavailableExtra( + base=self.base.name, + version=self.version, + extra=extra, + available_extras=self.base.dist.iter_provided_extras(), ) for r in self.base.dist.iter_dependencies(valid_extras):