From 528f3c8a46ba44bdb201bb73f8e25527b7d3baa0 Mon Sep 17 00:00:00 2001 From: atalman Date: Thu, 20 Mar 2025 10:43:20 -0700 Subject: [PATCH] Draft index_priority --- src/pip/_internal/cli/cmdoptions.py | 12 ++++++ src/pip/_internal/index/collector.py | 9 ++++- src/pip/_internal/index/package_finder.py | 48 +++++++++++++++++++++-- src/pip/_internal/models/search_scope.py | 5 ++- src/pip/_internal/req/req_file.py | 1 + 5 files changed, 70 insertions(+), 5 deletions(-) diff --git a/src/pip/_internal/cli/cmdoptions.py b/src/pip/_internal/cli/cmdoptions.py index 93969be93b9..b0043837849 100644 --- a/src/pip/_internal/cli/cmdoptions.py +++ b/src/pip/_internal/cli/cmdoptions.py @@ -364,6 +364,17 @@ def extra_index_url() -> Option: "--index-url.", ) +def index_groups() -> Option: + return Option( + "--index-groups", + dest="index_groups", + metavar="URL", + action="append", + default=[], + help="Index Groups of package indexes to use in addition to " + "--index-url. Should follow the similar rules as but applies for groups." + "--index-url.", + ) no_index: Callable[..., Option] = partial( Option, @@ -1118,6 +1129,7 @@ def check_list_path_option(options: Values) -> None: "options": [ index_url, extra_index_url, + index_groups, no_index, find_links, ], diff --git a/src/pip/_internal/index/collector.py b/src/pip/_internal/index/collector.py index 5f8fdee3d46..96ac5736927 100644 --- a/src/pip/_internal/index/collector.py +++ b/src/pip/_internal/index/collector.py @@ -32,7 +32,7 @@ from pip._vendor.requests import Response from pip._vendor.requests.exceptions import RetryError, SSLError -from pip._internal.exceptions import NetworkConnectionError +from pip._internal.exceptions import NetworkConnectionError, CommandError from pip._internal.models.link import Link from pip._internal.models.search_scope import SearchScope from pip._internal.network.session import PipSession @@ -416,6 +416,12 @@ def create( when constructing the SearchScope object. """ index_urls = [options.index_url] + options.extra_index_urls + index_priority = False + if (options.index_groups is not None and len(options.index_groups) > 0): + print(options.index_groups[0]) + index_urls = options.index_groups[0].split(",") + index_priority = True + if options.no_index and not suppress_no_index: logger.debug( "Ignoring indexes: %s", @@ -430,6 +436,7 @@ def create( find_links=find_links, index_urls=index_urls, no_index=options.no_index, + index_priority=index_priority, ) link_collector = LinkCollector( session=session, diff --git a/src/pip/_internal/index/package_finder.py b/src/pip/_internal/index/package_finder.py index ce0842b07e6..41a96b8e000 100644 --- a/src/pip/_internal/index/package_finder.py +++ b/src/pip/_internal/index/package_finder.py @@ -125,6 +125,8 @@ def __init__( target_python: TargetPython, allow_yanked: bool, ignore_requires_python: Optional[bool] = None, + index_priority: bool = False, + index_urls: Optional[List[str]] = None, ) -> None: """ :param project_name: The user supplied package name. @@ -151,6 +153,8 @@ def __init__( self._ignore_requires_python = ignore_requires_python self._formats = formats self._target_python = target_python + self._index_priority = index_priority + self._index_urls = index_urls self.project_name = project_name @@ -375,6 +379,8 @@ def create( allow_all_prereleases: bool = False, specifier: Optional[specifiers.BaseSpecifier] = None, hashes: Optional[Hashes] = None, + index_priority: bool = False, + index_urls: Optional[List[str]] = None, ) -> "CandidateEvaluator": """Create a CandidateEvaluator object. @@ -400,6 +406,8 @@ def create( prefer_binary=prefer_binary, allow_all_prereleases=allow_all_prereleases, hashes=hashes, + index_priority=index_priority, + index_urls=index_urls, ) def __init__( @@ -410,6 +418,8 @@ def __init__( prefer_binary: bool = False, allow_all_prereleases: bool = False, hashes: Optional[Hashes] = None, + index_priority: bool = False, + index_urls: Optional[List[str]] = None, ) -> None: """ :param supported_tags: The PEP 425 tags supported by the target @@ -421,6 +431,8 @@ def __init__( self._project_name = project_name self._specifier = specifier self._supported_tags = supported_tags + self._index_priority = index_priority + self._index_urls = index_urls # Since the index of the tag in the _supported_tags list is used # as a priority, precompute a map from tag to index/priority to be # used in wheel.find_most_preferred_tag. @@ -548,10 +560,31 @@ def compute_best_candidate( """ Compute and return a `BestCandidateResult` instance. """ - applicable_candidates = self.get_applicable_candidates(candidates) - - best_candidate = self.sort_best_candidate(applicable_candidates) + # Apply priority filtering to the list of candidates + found_candidate = False + if(self._index_priority and self._index_urls is not None): + for index_url in self._index_urls: + index_priority_candidates = list() + for candidate in candidates: + # filter out candidates by current priority index + if candidate.link.comes_from.startswith(index_url): + index_priority_candidates.append(candidate) + + if(len(index_priority_candidates) > 0): + applicable_candidates = self.get_applicable_candidates(index_priority_candidates) + best_candidate = self.sort_best_candidate(applicable_candidates) + found_candidate = True + break + + + # fall back on default behavior + if not found_candidate: + applicable_candidates = self.get_applicable_candidates(candidates) + best_candidate = self.sort_best_candidate(applicable_candidates) + + logger.debug(f"compute_best_candidate: {best_candidate}") + logger.debug(f"out of: {candidates}") return BestCandidateResult( candidates, applicable_candidates=applicable_candidates, @@ -657,6 +690,10 @@ def find_links(self) -> List[str]: def index_urls(self) -> List[str]: return self.search_scope.index_urls + @property + def index_priority(self) -> bool: + return self.search_scope.index_priority + @property def proxy(self) -> Optional[str]: return self._link_collector.session.pip_proxy @@ -713,6 +750,8 @@ def make_link_evaluator(self, project_name: str) -> LinkEvaluator: target_python=self._target_python, allow_yanked=self._allow_yanked, ignore_requires_python=self._ignore_requires_python, + index_priority=self.index_priority, + index_urls=self.index_urls, ) def _sort_links(self, links: Iterable[Link]) -> List[Link]: @@ -868,6 +907,8 @@ def make_candidate_evaluator( allow_all_prereleases=candidate_prefs.allow_all_prereleases, specifier=specifier, hashes=hashes, + index_priority=self.index_priority, + index_urls=self.index_urls, ) @functools.lru_cache(maxsize=None) @@ -909,6 +950,7 @@ def find_requirement( hashes=hashes, ) best_candidate = best_candidate_result.best_candidate + logger.debug(f"***best_candidate: {best_candidate}") installed_version: Optional[_BaseVersion] = None if req.satisfied_by is not None: diff --git a/src/pip/_internal/models/search_scope.py b/src/pip/_internal/models/search_scope.py index ee7bc86229a..79234964be5 100644 --- a/src/pip/_internal/models/search_scope.py +++ b/src/pip/_internal/models/search_scope.py @@ -21,11 +21,12 @@ class SearchScope: Encapsulates the locations that pip is configured to search. """ - __slots__ = ["find_links", "index_urls", "no_index"] + __slots__ = ["find_links", "index_urls", "no_index", "index_priority"] find_links: List[str] index_urls: List[str] no_index: bool + index_priority: bool @classmethod def create( @@ -33,6 +34,7 @@ def create( find_links: List[str], index_urls: List[str], no_index: bool, + index_priority: bool = False, ) -> "SearchScope": """ Create a SearchScope object after normalizing the `find_links`. @@ -67,6 +69,7 @@ def create( find_links=built_find_links, index_urls=index_urls, no_index=no_index, + index_priority=index_priority, ) def get_formatted_locations(self) -> str: diff --git a/src/pip/_internal/req/req_file.py b/src/pip/_internal/req/req_file.py index f6ba70fe7f6..cf2ec4fcf63 100644 --- a/src/pip/_internal/req/req_file.py +++ b/src/pip/_internal/req/req_file.py @@ -52,6 +52,7 @@ SUPPORTED_OPTIONS: List[Callable[..., optparse.Option]] = [ cmdoptions.index_url, cmdoptions.extra_index_url, + cmdoptions.index_groups, cmdoptions.no_index, cmdoptions.constraints, cmdoptions.requirements,