From 0a91572ac1d0175558aa47a07e85fe6a998ced7b Mon Sep 17 00:00:00 2001 From: sepehrrasooli Date: Thu, 20 Nov 2025 12:27:02 +0330 Subject: [PATCH 01/11] Remove extras from pip error message when installing packages --- src/pip/_internal/metadata/importlib/_dists.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pip/_internal/metadata/importlib/_dists.py b/src/pip/_internal/metadata/importlib/_dists.py index 89364b8b7a..7768fea43c 100644 --- a/src/pip/_internal/metadata/importlib/_dists.py +++ b/src/pip/_internal/metadata/importlib/_dists.py @@ -226,4 +226,5 @@ def iter_dependencies(self, extras: Collection[str] = ()) -> Iterable[Requiremen elif not extras and req.marker.evaluate({"extra": ""}): yield req elif any(req.marker.evaluate(context) for context in contexts): + req.marker = None yield req From d63cc78da573ff8cd82c1511e8aa2daaf7ed644b Mon Sep 17 00:00:00 2001 From: sepehrrasooli Date: Thu, 20 Nov 2025 12:34:53 +0330 Subject: [PATCH 02/11] Add news entry --- news/13618.bugfix.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 news/13618.bugfix.rst diff --git a/news/13618.bugfix.rst b/news/13618.bugfix.rst new file mode 100644 index 0000000000..cfe788aa70 --- /dev/null +++ b/news/13618.bugfix.rst @@ -0,0 +1,2 @@ +When installing packages, pip will no longer show a misleading +error message that included extra markers such as `; extra == "..."`. From a771dde0035861359728e5450f4a5f5fcaa9442b Mon Sep 17 00:00:00 2001 From: Sepehr Rasouli Date: Thu, 20 Nov 2025 12:43:35 +0330 Subject: [PATCH 03/11] Update 13618.bugfix.rst --- news/13618.bugfix.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/news/13618.bugfix.rst b/news/13618.bugfix.rst index cfe788aa70..6e3d6cae78 100644 --- a/news/13618.bugfix.rst +++ b/news/13618.bugfix.rst @@ -1,2 +1,2 @@ When installing packages, pip will no longer show a misleading -error message that included extra markers such as `; extra == "..."`. +error message that included extra markers such as ``; extra == "..."``. From 2c261c38de8f80864dd64b1c1d3e769ae1794a5d Mon Sep 17 00:00:00 2001 From: sepehrrasooli Date: Thu, 27 Nov 2025 11:28:01 +0330 Subject: [PATCH 04/11] Move removal of extra down the stack --- src/pip/_internal/metadata/importlib/_dists.py | 1 - src/pip/_internal/resolution/resolvelib/factory.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/pip/_internal/metadata/importlib/_dists.py b/src/pip/_internal/metadata/importlib/_dists.py index 7768fea43c..89364b8b7a 100644 --- a/src/pip/_internal/metadata/importlib/_dists.py +++ b/src/pip/_internal/metadata/importlib/_dists.py @@ -226,5 +226,4 @@ def iter_dependencies(self, extras: Collection[str] = ()) -> Iterable[Requiremen elif not extras and req.marker.evaluate({"extra": ""}): yield req elif any(req.marker.evaluate(context) for context in contexts): - req.marker = None yield req diff --git a/src/pip/_internal/resolution/resolvelib/factory.py b/src/pip/_internal/resolution/resolvelib/factory.py index 07be5693d5..7109e56413 100644 --- a/src/pip/_internal/resolution/resolvelib/factory.py +++ b/src/pip/_internal/resolution/resolvelib/factory.py @@ -708,7 +708,7 @@ def _report_single_requirement_conflict( "using the '-r' flag to install the packages listed in " "requirements.txt" ) - + req = str(req).partition(';')[0].strip() return DistributionNotFound(f"No matching distribution found for {req}") def _has_any_candidates(self, project_name: str) -> bool: From 5de2b1ad38d42ac807818f0baaf21464d78d7a5d Mon Sep 17 00:00:00 2001 From: sepehrrasooli Date: Thu, 27 Nov 2025 12:30:26 +0330 Subject: [PATCH 05/11] Fix linting issue --- src/pip/_internal/resolution/resolvelib/factory.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pip/_internal/resolution/resolvelib/factory.py b/src/pip/_internal/resolution/resolvelib/factory.py index 7109e56413..cea07888cf 100644 --- a/src/pip/_internal/resolution/resolvelib/factory.py +++ b/src/pip/_internal/resolution/resolvelib/factory.py @@ -708,8 +708,8 @@ def _report_single_requirement_conflict( "using the '-r' flag to install the packages listed in " "requirements.txt" ) - req = str(req).partition(';')[0].strip() - return DistributionNotFound(f"No matching distribution found for {req}") + req_str = str(req).partition(";")[0].strip() + return DistributionNotFound(f"No matching distribution found for {req_str}") def _has_any_candidates(self, project_name: str) -> bool: """ From c958efcd9e3f8875477191e4695ebcd7c589924a Mon Sep 17 00:00:00 2001 From: sepehrrasooli Date: Wed, 11 Feb 2026 12:21:04 +0330 Subject: [PATCH 06/11] Update tests and coverage --- src/pip/_internal/index/package_finder.py | 6 +++--- src/pip/_internal/resolution/resolvelib/factory.py | 5 +++-- tests/functional/test_install_extras.py | 4 ++++ 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/pip/_internal/index/package_finder.py b/src/pip/_internal/index/package_finder.py index aa7c2ebd48..1dd6d8475e 100644 --- a/src/pip/_internal/index/package_finder.py +++ b/src/pip/_internal/index/package_finder.py @@ -1028,16 +1028,16 @@ def _format_versions(cand_iter: Iterable[InstallationCandidate]) -> str: ) if allows_pre is False: version_type = "final version" - + req_str = re.sub(r';\s*extra\s*==\s*".*?"', "", str(req)).strip() logger.critical( "Could not find a %s that satisfies the requirement %s " "(from versions: %s)", version_type, - req, + req_str, _format_versions(best_candidate_result.all_candidates), ) - raise DistributionNotFound(f"No matching distribution found for {req}") + raise DistributionNotFound(f"No matching distribution found for {req_str}") def _should_install_candidate( candidate: InstallationCandidate | None, diff --git a/src/pip/_internal/resolution/resolvelib/factory.py b/src/pip/_internal/resolution/resolvelib/factory.py index 817f79d3c8..9215600197 100644 --- a/src/pip/_internal/resolution/resolvelib/factory.py +++ b/src/pip/_internal/resolution/resolvelib/factory.py @@ -3,6 +3,7 @@ import contextlib import functools import logging +import re from collections.abc import Iterable, Iterator, Mapping, Sequence from typing import ( TYPE_CHECKING, @@ -666,7 +667,8 @@ def _report_single_requirement_conflict( req_disp = str(req) else: req_disp = f"{req} (from {parent.name})" - + req_str = re.sub(r';\s*extra\s*==\s*".*?"', "", str(req)).strip() + req_disp = req_str cands = self._finder.find_all_candidates(req.project_name) skipped_by_requires_python = self._finder.requires_python_skipped_reasons() @@ -719,7 +721,6 @@ def _report_single_requirement_conflict( "using the '-r' flag to install the packages listed in " "requirements.txt" ) - req_str = str(req).partition(";")[0].strip() return DistributionNotFound(f"No matching distribution found for {req_str}") def _has_any_candidates(self, project_name: str) -> bool: diff --git a/tests/functional/test_install_extras.py b/tests/functional/test_install_extras.py index 5a0ebc23ce..7d62677639 100644 --- a/tests/functional/test_install_extras.py +++ b/tests/functional/test_install_extras.py @@ -189,6 +189,10 @@ def test_install_special_extra( assert ( "Could not find a version that satisfies the requirement missing_pkg" ) in result.stderr, str(result) + assert "No matching distribution found for missing_pkg" in result.stderr, str( + result + ) + assert "extra ==" not in result.stderr, str(result) @pytest.mark.network From 9d0df206b9270b8b3fcf9e9b4c75a6296343cd8a Mon Sep 17 00:00:00 2001 From: sepehrrasooli Date: Wed, 18 Feb 2026 13:42:33 +0330 Subject: [PATCH 07/11] Update code to address reviews --- src/pip/_internal/index/package_finder.py | 3 +++ src/pip/_internal/resolution/resolvelib/factory.py | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/pip/_internal/index/package_finder.py b/src/pip/_internal/index/package_finder.py index 1dd6d8475e..a95cba7ce1 100644 --- a/src/pip/_internal/index/package_finder.py +++ b/src/pip/_internal/index/package_finder.py @@ -1028,6 +1028,9 @@ def _format_versions(cand_iter: Iterable[InstallationCandidate]) -> str: ) if allows_pre is False: version_type = "final version" + # pip sometimes includes environment markers like `;extra == "foo"` + # in requirement strings, which is confusing in error messages. + # Strip them so the user sees only the actual missing requirement. req_str = re.sub(r';\s*extra\s*==\s*".*?"', "", str(req)).strip() logger.critical( "Could not find a %s that satisfies the requirement %s " diff --git a/src/pip/_internal/resolution/resolvelib/factory.py b/src/pip/_internal/resolution/resolvelib/factory.py index 9215600197..a54d2e05e3 100644 --- a/src/pip/_internal/resolution/resolvelib/factory.py +++ b/src/pip/_internal/resolution/resolvelib/factory.py @@ -667,8 +667,8 @@ def _report_single_requirement_conflict( req_disp = str(req) else: req_disp = f"{req} (from {parent.name})" - req_str = re.sub(r';\s*extra\s*==\s*".*?"', "", str(req)).strip() - req_disp = req_str + req_disp = re.sub(r';\s*extra\s*==\s*".*?"', "", str(req)).strip() + req_str = req_disp cands = self._finder.find_all_candidates(req.project_name) skipped_by_requires_python = self._finder.requires_python_skipped_reasons() From 392b9b0b5bded0280b3bf85648b95b8f41b19d34 Mon Sep 17 00:00:00 2001 From: Sepehr Rasouli Date: Sun, 19 Apr 2026 08:25:59 +0330 Subject: [PATCH 08/11] Update factory.py --- src/pip/_internal/resolution/resolvelib/factory.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/pip/_internal/resolution/resolvelib/factory.py b/src/pip/_internal/resolution/resolvelib/factory.py index a9c487d104..5db22bea67 100644 --- a/src/pip/_internal/resolution/resolvelib/factory.py +++ b/src/pip/_internal/resolution/resolvelib/factory.py @@ -693,7 +693,6 @@ def _report_single_requirement_conflict( else: req_disp = f"{req} (from {parent.name})" req_disp = re.sub(r';\s*extra\s*==\s*".*?"', "", str(req)).strip() - req_str = req_disp cands = self._finder.find_all_candidates(req.project_name) skipped_by_requires_python = self._finder.requires_python_skipped_reasons() @@ -746,7 +745,7 @@ def _report_single_requirement_conflict( "using the '-r' flag to install the packages listed in " "requirements.txt" ) - return DistributionNotFound(f"No matching distribution found for {req_str}") + return DistributionNotFound(f"No matching distribution found for {req_disp}") def _has_any_candidates(self, project_name: str) -> bool: """ From 546106ea740f66de24059a08985411d60793b007 Mon Sep 17 00:00:00 2001 From: Sepehr Rasouli Date: Fri, 24 Apr 2026 16:31:37 +0330 Subject: [PATCH 09/11] Update src/pip/_internal/resolution/resolvelib/factory.py Co-authored-by: Daniel Hollas --- src/pip/_internal/resolution/resolvelib/factory.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pip/_internal/resolution/resolvelib/factory.py b/src/pip/_internal/resolution/resolvelib/factory.py index 5642e2aa60..7d57ba02cd 100644 --- a/src/pip/_internal/resolution/resolvelib/factory.py +++ b/src/pip/_internal/resolution/resolvelib/factory.py @@ -694,7 +694,7 @@ def _report_single_requirement_conflict( req_disp = str(req) else: req_disp = f"{req} (from {parent.name})" - req_disp = re.sub(r';\s*extra\s*==\s*".*?"', "", str(req)).strip() + req_disp = re.sub(r';\s*extra\s*==\s*".*?"', "", str(req_disp)).strip() cands = self._finder.find_all_candidates(req.project_name) skipped_by_requires_python = self._finder.requires_python_skipped_reasons() From 08c9cf2faec77158993e9688d288a15a79a4e75f Mon Sep 17 00:00:00 2001 From: Sepehr Rasouli Date: Fri, 24 Apr 2026 16:33:12 +0330 Subject: [PATCH 10/11] Update package_finder.py --- src/pip/_internal/index/package_finder.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pip/_internal/index/package_finder.py b/src/pip/_internal/index/package_finder.py index 5b9c05b9da..4df6c0562b 100644 --- a/src/pip/_internal/index/package_finder.py +++ b/src/pip/_internal/index/package_finder.py @@ -1019,16 +1019,16 @@ def _format_versions(cand_iter: Iterable[InstallationCandidate]) -> str: # pip sometimes includes environment markers like `;extra == "foo"` # in requirement strings, which is confusing in error messages. # Strip them so the user sees only the actual missing requirement. - req_str = re.sub(r';\s*extra\s*==\s*".*?"', "", str(req)).strip() + req_disp = re.sub(r';\s*extra\s*==\s*".*?"', "", str(req)).strip() logger.critical( "Could not find a %s that satisfies the requirement %s " "(from versions: %s)", version_type, - req_str, + req_disp, _format_versions(best_candidate_result.all_candidates), ) - raise DistributionNotFound(f"No matching distribution found for {req_str}") + raise DistributionNotFound(f"No matching distribution found for {req_disp}") def _should_install_candidate( candidate: InstallationCandidate | None, From 352991160bb604eb0fb34a55ac73007a421d2fe7 Mon Sep 17 00:00:00 2001 From: Sepehr Rasouli Date: Sun, 26 Apr 2026 08:51:18 +0330 Subject: [PATCH 11/11] Update src/pip/_internal/resolution/resolvelib/factory.py Co-authored-by: Daniel Hollas --- src/pip/_internal/resolution/resolvelib/factory.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pip/_internal/resolution/resolvelib/factory.py b/src/pip/_internal/resolution/resolvelib/factory.py index 7d57ba02cd..0d2783ec6c 100644 --- a/src/pip/_internal/resolution/resolvelib/factory.py +++ b/src/pip/_internal/resolution/resolvelib/factory.py @@ -694,7 +694,7 @@ def _report_single_requirement_conflict( req_disp = str(req) else: req_disp = f"{req} (from {parent.name})" - req_disp = re.sub(r';\s*extra\s*==\s*".*?"', "", str(req_disp)).strip() + req_disp = re.sub(r';\s*extra\s*==\s*".*?"', "", req_disp).strip() cands = self._finder.find_all_candidates(req.project_name) skipped_by_requires_python = self._finder.requires_python_skipped_reasons()