From 95171c881fa8bc98b9dcd2f5c0c34cd7fce95904 Mon Sep 17 00:00:00 2001 From: Pradyun Gedam Date: Tue, 20 Oct 2020 07:00:15 +0530 Subject: [PATCH 1/7] Display messages when backtracking on a package --- .../resolution/resolvelib/reporter.py | 35 +++++++++++++++ .../resolution/resolvelib/resolver.py | 5 ++- tests/functional/test_new_resolver.py | 45 +++++++++++++++++++ 3 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 src/pip/_internal/resolution/resolvelib/reporter.py diff --git a/src/pip/_internal/resolution/resolvelib/reporter.py b/src/pip/_internal/resolution/resolvelib/reporter.py new file mode 100644 index 000000000..56e805975 --- /dev/null +++ b/src/pip/_internal/resolution/resolvelib/reporter.py @@ -0,0 +1,35 @@ +from collections import defaultdict +from logging import getLogger + +from pip._vendor.resolvelib.reporters import BaseReporter + +logger = getLogger(__name__) + + +class PipReporter(BaseReporter): + + def __init__(self): + self.backtracks_by_package = defaultdict(int) + + self._messages_at_backtrack = { + 8: ( + "pip is looking at multiple versions of this package to determine " + "which version is compatible with other requirements. " + "This could take a while." + ), + 13: ( + "This is taking longer than usual. You might need to provide the " + "dependency resolver with stricter constraints to reduce runtime." + "If you want to abort this run, you can press Ctrl + C to do so." + ) + } + + def backtracking(self, candidate): + self.backtracks_by_package[candidate.name] += 1 + + count = self.backtracks_by_package[candidate.name] + if count not in self._messages_at_backtrack: + return + + message = self._messages_at_backtrack[count] + logger.info(message) diff --git a/src/pip/_internal/resolution/resolvelib/resolver.py b/src/pip/_internal/resolution/resolvelib/resolver.py index cb7d1ae8a..acb7cfeda 100644 --- a/src/pip/_internal/resolution/resolvelib/resolver.py +++ b/src/pip/_internal/resolution/resolvelib/resolver.py @@ -3,7 +3,7 @@ import logging from pip._vendor import six from pip._vendor.packaging.utils import canonicalize_name -from pip._vendor.resolvelib import BaseReporter, ResolutionImpossible +from pip._vendor.resolvelib import ResolutionImpossible from pip._vendor.resolvelib import Resolver as RLResolver from pip._internal.exceptions import InstallationError @@ -11,6 +11,7 @@ from pip._internal.req.req_install import check_invalid_constraint_type from pip._internal.req.req_set import RequirementSet from pip._internal.resolution.base import BaseResolver from pip._internal.resolution.resolvelib.provider import PipProvider +from pip._internal.resolution.resolvelib.reporter import PipReporter from pip._internal.utils.misc import dist_is_editable from pip._internal.utils.typing import MYPY_CHECK_RUNNING @@ -103,7 +104,7 @@ class Resolver(BaseResolver): upgrade_strategy=self.upgrade_strategy, user_requested=user_requested, ) - reporter = BaseReporter() + reporter = PipReporter() resolver = RLResolver(provider, reporter) try: diff --git a/tests/functional/test_new_resolver.py b/tests/functional/test_new_resolver.py index 1718ab8a8..aa1744cc8 100644 --- a/tests/functional/test_new_resolver.py +++ b/tests/functional/test_new_resolver.py @@ -1046,3 +1046,48 @@ def test_new_resolver_prefers_installed_in_upgrade_if_latest(script): "pkg", ) assert_installed(script, pkg="2") + + +@pytest.mark.parametrize("N", [10, 20]) +def test_new_resolver_presents_messages_when_backtracking_a_lot(script, N): + # Generate a set of wheels that will definitely cause backtracking. + for index in range(1, N+1): + A_version = "{index}.0.0".format(index=index) + B_version = "{index}.0.0".format(index=index) + C_version = "{index_minus_one}.0.0".format(index_minus_one=index - 1) + + depends = ["B == " + B_version] + if index != 1: + depends.append("C == " + C_version) + + print("A", A_version, "B", B_version, "C", C_version) + create_basic_wheel_for_package(script, "A", A_version, depends=depends) + + for index in range(1, N+1): + B_version = "{index}.0.0".format(index=index) + C_version = "{index}.0.0".format(index=index) + depends = ["C == " + C_version] + + print("B", B_version, "C", C_version) + create_basic_wheel_for_package(script, "B", B_version, depends=depends) + + for index in range(1, N+1): + C_version = "{index}.0.0".format(index=index) + print("C", C_version) + create_basic_wheel_for_package(script, "C", C_version) + + # Install A + result = script.pip( + "install", + "--use-feature=2020-resolver", + "--no-cache-dir", + "--no-index", + "--find-links", script.scratch_path, + "A" + ) + + assert_installed(script, A="1.0.0", B="1.0.0", C="1.0.0") + if N >= 8: # this number is hard-coded in the code too. + assert "This could take a while." in result.stdout + if N >= 13: # this number is hard-coded in the code too. + assert "press Ctrl + C" in result.stdout From daa003bd9bcfeeb15181af40d5e73ebb4cfa52ad Mon Sep 17 00:00:00 2001 From: Pradyun Gedam Date: Tue, 20 Oct 2020 07:07:14 +0530 Subject: [PATCH 2/7] Make mypy happy --- src/pip/_internal/resolution/resolvelib/reporter.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/pip/_internal/resolution/resolvelib/reporter.py b/src/pip/_internal/resolution/resolvelib/reporter.py index 56e805975..b0da83c7e 100644 --- a/src/pip/_internal/resolution/resolvelib/reporter.py +++ b/src/pip/_internal/resolution/resolvelib/reporter.py @@ -3,13 +3,22 @@ from logging import getLogger from pip._vendor.resolvelib.reporters import BaseReporter +from pip._internal.utils.typing import MYPY_CHECK_RUNNING + +if MYPY_CHECK_RUNNING: + from typing import DefaultDict + + from .base import Candidate + + logger = getLogger(__name__) class PipReporter(BaseReporter): def __init__(self): - self.backtracks_by_package = defaultdict(int) + # type: () -> None + self.backtracks_by_package = defaultdict(int) # type: DefaultDict[str, int] self._messages_at_backtrack = { 8: ( @@ -25,6 +34,7 @@ class PipReporter(BaseReporter): } def backtracking(self, candidate): + # type: (Candidate) -> None self.backtracks_by_package[candidate.name] += 1 count = self.backtracks_by_package[candidate.name] From 9a1f790951332dea5d6811d5dd243e6f97571922 Mon Sep 17 00:00:00 2001 From: Pradyun Gedam Date: Tue, 20 Oct 2020 15:40:17 +0530 Subject: [PATCH 3/7] :newspaper: --- news/8975.feature.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 news/8975.feature.rst diff --git a/news/8975.feature.rst b/news/8975.feature.rst new file mode 100644 index 000000000..082612505 --- /dev/null +++ b/news/8975.feature.rst @@ -0,0 +1 @@ +Log an informational message when backtracking takes multiple rounds on a specific package. From f3307a5103b603d582da673d3a3b21c108ecf443 Mon Sep 17 00:00:00 2001 From: Pradyun Gedam Date: Mon, 26 Oct 2020 16:37:10 +0530 Subject: [PATCH 4/7] Present a message upon first backtrack --- src/pip/_internal/resolution/resolvelib/reporter.py | 5 +++++ tests/functional/test_new_resolver.py | 9 ++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/pip/_internal/resolution/resolvelib/reporter.py b/src/pip/_internal/resolution/resolvelib/reporter.py index b0da83c7e..1d0dd9798 100644 --- a/src/pip/_internal/resolution/resolvelib/reporter.py +++ b/src/pip/_internal/resolution/resolvelib/reporter.py @@ -21,6 +21,11 @@ class PipReporter(BaseReporter): self.backtracks_by_package = defaultdict(int) # type: DefaultDict[str, int] self._messages_at_backtrack = { + 1: ( + "pip is looking at multiple versions of this package to determine " + "which version is compatible with other requirements. " + "This could take a while." + ), 8: ( "pip is looking at multiple versions of this package to determine " "which version is compatible with other requirements. " diff --git a/tests/functional/test_new_resolver.py b/tests/functional/test_new_resolver.py index aa1744cc8..91befb7f5 100644 --- a/tests/functional/test_new_resolver.py +++ b/tests/functional/test_new_resolver.py @@ -1048,7 +1048,7 @@ def test_new_resolver_prefers_installed_in_upgrade_if_latest(script): assert_installed(script, pkg="2") -@pytest.mark.parametrize("N", [10, 20]) +@pytest.mark.parametrize("N", [2, 10, 20]) def test_new_resolver_presents_messages_when_backtracking_a_lot(script, N): # Generate a set of wheels that will definitely cause backtracking. for index in range(1, N+1): @@ -1087,7 +1087,10 @@ def test_new_resolver_presents_messages_when_backtracking_a_lot(script, N): ) assert_installed(script, A="1.0.0", B="1.0.0", C="1.0.0") - if N >= 8: # this number is hard-coded in the code too. + # These numbers are hard-coded in the code. + if N >= 1: assert "This could take a while." in result.stdout - if N >= 13: # this number is hard-coded in the code too. + if N >= 8: + assert result.stdout.count("This could take a while.") >= 2 + if N >= 13: assert "press Ctrl + C" in result.stdout From 1acca46aa326cd8e5e594b416bf98e7d1a1d1f8c Mon Sep 17 00:00:00 2001 From: Pradyun Gedam Date: Mon, 26 Oct 2020 16:41:59 +0530 Subject: [PATCH 5/7] Prefix backtracking message with "INFO: " --- src/pip/_internal/resolution/resolvelib/reporter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pip/_internal/resolution/resolvelib/reporter.py b/src/pip/_internal/resolution/resolvelib/reporter.py index 1d0dd9798..dc7bc5ad4 100644 --- a/src/pip/_internal/resolution/resolvelib/reporter.py +++ b/src/pip/_internal/resolution/resolvelib/reporter.py @@ -47,4 +47,4 @@ class PipReporter(BaseReporter): return message = self._messages_at_backtrack[count] - logger.info(message) + logger.info("INFO: %s", message) From 55e316a45256d054d19425015ef13868a84c5ff1 Mon Sep 17 00:00:00 2001 From: Pradyun Gedam Date: Tue, 27 Oct 2020 01:22:13 +0530 Subject: [PATCH 6/7] Add the last line to the info message --- src/pip/_internal/resolution/resolvelib/reporter.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pip/_internal/resolution/resolvelib/reporter.py b/src/pip/_internal/resolution/resolvelib/reporter.py index dc7bc5ad4..e150c4948 100644 --- a/src/pip/_internal/resolution/resolvelib/reporter.py +++ b/src/pip/_internal/resolution/resolvelib/reporter.py @@ -35,6 +35,8 @@ class PipReporter(BaseReporter): "This is taking longer than usual. You might need to provide the " "dependency resolver with stricter constraints to reduce runtime." "If you want to abort this run, you can press Ctrl + C to do so." + "To improve how pip performs, tell us that this happened here: " + "https://pip.pypa.io/surveys/backtracking" ) } From 741b80ac085c810e79008743ff03090b6dc41aa1 Mon Sep 17 00:00:00 2001 From: Pradyun Gedam <3275593+pradyunsg@users.noreply.github.com> Date: Wed, 28 Oct 2020 18:25:12 +0530 Subject: [PATCH 7/7] Update wording in informational message --- src/pip/_internal/resolution/resolvelib/reporter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pip/_internal/resolution/resolvelib/reporter.py b/src/pip/_internal/resolution/resolvelib/reporter.py index e150c4948..65c437373 100644 --- a/src/pip/_internal/resolution/resolvelib/reporter.py +++ b/src/pip/_internal/resolution/resolvelib/reporter.py @@ -35,7 +35,7 @@ class PipReporter(BaseReporter): "This is taking longer than usual. You might need to provide the " "dependency resolver with stricter constraints to reduce runtime." "If you want to abort this run, you can press Ctrl + C to do so." - "To improve how pip performs, tell us that this happened here: " + "To improve how pip performs, tell us what happened here: " "https://pip.pypa.io/surveys/backtracking" ) }