Skip to content
Open
1 change: 1 addition & 0 deletions news/13545.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Include user-supplied constraints in dependency conflict resolution logs for consistency with error reporting.
34 changes: 25 additions & 9 deletions src/pip/_internal/resolution/resolvelib/reporter.py
Original file line number Diff line number Diff line change
@@ -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: (
Expand All @@ -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)


Expand Down
3 changes: 2 additions & 1 deletion src/pip/_internal/resolution/resolvelib/resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading