diff --git a/news/13545.bugfix.rst b/news/13545.bugfix.rst new file mode 100644 index 00000000000..64ed6d57139 --- /dev/null +++ b/news/13545.bugfix.rst @@ -0,0 +1 @@ +Include user-supplied constraints in dependency conflict resolution logs for consistency with error reporting. diff --git a/src/pip/_internal/resolution/resolvelib/reporter.py b/src/pip/_internal/resolution/resolvelib/reporter.py index e694132ba5e..3e6552f2ce5 100644 --- a/src/pip/_internal/resolution/resolvelib/reporter.py +++ b/src/pip/_internal/resolution/resolvelib/reporter.py @@ -1,19 +1,24 @@ from __future__ import annotations from collections import defaultdict +from collections.abc import Mapping from logging import getLogger -from typing import Any +from typing import TYPE_CHECKING, Any from pip._vendor.resolvelib.reporters import BaseReporter -from .base import Candidate, Requirement +from .base import Candidate, Constraint, Requirement + +if TYPE_CHECKING: + pass logger = getLogger(__name__) class PipReporter(BaseReporter[Requirement, Candidate, str]): - def __init__(self) -> None: + def __init__(self, constraints: Mapping[str, Constraint] | None = None) -> None: self.reject_count_by_package: defaultdict[str, int] = defaultdict(int) + self._constraints = constraints or {} self._messages_at_reject_count = { 1: ( @@ -35,25 +40,36 @@ def __init__(self) -> None: } def rejecting_candidate(self, criterion: Any, candidate: Candidate) -> None: + """Report a candidate being rejected. + + Logs both the rejection count message (if applicable) and details about + the requirements and constraints that caused the rejection. + """ self.reject_count_by_package[candidate.name] += 1 count = self.reject_count_by_package[candidate.name] - if count not in self._messages_at_reject_count: - return - - message = self._messages_at_reject_count[count] - logger.info("INFO: %s", message.format(package_name=candidate.name)) + if count in self._messages_at_reject_count: + message = self._messages_at_reject_count[count] + logger.info("INFO: %s", message.format(package_name=candidate.name)) msg = "Will try a different candidate, due to conflict:" for req_info in criterion.information: req, parent = req_info.requirement, req_info.parent - # Inspired by Factory.get_installation_error msg += "\n " if parent: msg += f"{parent.name} {parent.version} depends on " else: msg += "The user requested " msg += req.format_for_error() + + # Add any relevant constraints + if self._constraints: + name = candidate.name + constraint = self._constraints.get(name) + if constraint and constraint.specifier: + constraint_text = f"{name}{constraint.specifier}" + msg += f"\n The user requested (constraint) {constraint_text}" + logger.debug(msg) diff --git a/src/pip/_internal/resolution/resolvelib/resolver.py b/src/pip/_internal/resolution/resolvelib/resolver.py index 1ba70c2b39e..38385150275 100644 --- a/src/pip/_internal/resolution/resolvelib/resolver.py +++ b/src/pip/_internal/resolution/resolvelib/resolver.py @@ -87,7 +87,8 @@ def resolve( if "PIP_RESOLVER_DEBUG" in os.environ: reporter: BaseReporter[Requirement, Candidate, str] = PipDebuggingReporter() else: - reporter = PipReporter() + reporter = PipReporter(constraints=provider._constraints) + resolver: RLResolver[Requirement, Candidate, str] = RLResolver( provider, reporter,