mirror of https://github.com/pypa/pip
Add CandidatePreferences and PackageFinder.make_candidate_evaluator().
This commit is contained in:
parent
9311049de2
commit
b2389bf8c7
|
@ -189,7 +189,7 @@ class ListCommand(Command):
|
|||
all_candidates = [candidate for candidate in all_candidates
|
||||
if not candidate.version.is_prerelease]
|
||||
|
||||
evaluator = finder.candidate_evaluator
|
||||
evaluator = finder.make_candidate_evaluator()
|
||||
best_candidate = evaluator.get_best_candidate(all_candidates)
|
||||
if best_candidate is None:
|
||||
continue
|
||||
|
|
|
@ -440,6 +440,26 @@ class LinkEvaluator(object):
|
|||
return (True, version)
|
||||
|
||||
|
||||
class CandidatePreferences(object):
|
||||
|
||||
"""
|
||||
Encapsulates some of the preferences for filtering and sorting
|
||||
InstallationCandidate objects.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
prefer_binary=False, # type: bool
|
||||
allow_all_prereleases=False, # type: bool
|
||||
):
|
||||
# type: (...) -> None
|
||||
"""
|
||||
:param allow_all_prereleases: Whether to allow all pre-releases.
|
||||
"""
|
||||
self.allow_all_prereleases = allow_all_prereleases
|
||||
self.prefer_binary = prefer_binary
|
||||
|
||||
|
||||
class CandidateEvaluator(object):
|
||||
|
||||
"""
|
||||
|
@ -447,28 +467,46 @@ class CandidateEvaluator(object):
|
|||
on what tags are valid.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def create(
|
||||
cls,
|
||||
target_python=None, # type: Optional[TargetPython]
|
||||
prefer_binary=False, # type: bool
|
||||
allow_all_prereleases=False, # type: bool
|
||||
):
|
||||
# type: (...) -> CandidateEvaluator
|
||||
"""Create a CandidateEvaluator object.
|
||||
|
||||
:param target_python: The target Python interpreter to use when
|
||||
checking compatibility. If None (the default), a TargetPython
|
||||
object will be constructed from the running Python.
|
||||
"""
|
||||
if target_python is None:
|
||||
target_python = TargetPython()
|
||||
|
||||
supported_tags = target_python.get_tags()
|
||||
|
||||
return cls(
|
||||
supported_tags=supported_tags,
|
||||
prefer_binary=prefer_binary,
|
||||
allow_all_prereleases=allow_all_prereleases,
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
supported_tags=None, # type: Optional[List[Pep425Tag]]
|
||||
supported_tags, # type: List[Pep425Tag]
|
||||
prefer_binary=False, # type: bool
|
||||
allow_all_prereleases=False, # type: bool
|
||||
):
|
||||
# type: (...) -> None
|
||||
"""
|
||||
:param supported_tags: The PEP 425 tags supported by the target
|
||||
Python in order of preference (most preferred first). If None,
|
||||
then the list will be generated from the running Python.
|
||||
:param allow_all_prereleases: Whether to allow all pre-releases.
|
||||
Python in order of preference (most preferred first).
|
||||
"""
|
||||
if supported_tags is None:
|
||||
target_python = TargetPython()
|
||||
supported_tags = target_python.get_tags()
|
||||
|
||||
self._allow_all_prereleases = allow_all_prereleases
|
||||
self._prefer_binary = prefer_binary
|
||||
self._supported_tags = supported_tags
|
||||
|
||||
self.allow_all_prereleases = allow_all_prereleases
|
||||
|
||||
def make_found_candidates(
|
||||
self,
|
||||
candidates, # type: List[InstallationCandidate]
|
||||
|
@ -486,7 +524,7 @@ class CandidateEvaluator(object):
|
|||
specifier = specifiers.SpecifierSet()
|
||||
|
||||
# Using None infers from the specifier instead.
|
||||
allow_prereleases = self.allow_all_prereleases or None
|
||||
allow_prereleases = self._allow_all_prereleases or None
|
||||
versions = {
|
||||
str(v) for v in specifier.filter(
|
||||
# We turn the version object into a str here because otherwise
|
||||
|
@ -649,13 +687,13 @@ class PackageFinder(object):
|
|||
|
||||
def __init__(
|
||||
self,
|
||||
candidate_evaluator, # type: CandidateEvaluator
|
||||
search_scope, # type: SearchScope
|
||||
session, # type: PipSession
|
||||
target_python, # type: TargetPython
|
||||
allow_yanked, # type: bool
|
||||
format_control=None, # type: Optional[FormatControl]
|
||||
trusted_hosts=None, # type: Optional[List[str]]
|
||||
candidate_prefs=None, # type: CandidatePreferences
|
||||
ignore_requires_python=None, # type: Optional[bool]
|
||||
):
|
||||
# type: (...) -> None
|
||||
|
@ -663,22 +701,25 @@ class PackageFinder(object):
|
|||
This constructor is primarily meant to be used by the create() class
|
||||
method and from tests.
|
||||
|
||||
:param candidate_evaluator: A CandidateEvaluator object.
|
||||
:param session: The Session to use to make requests.
|
||||
:param format_control: A FormatControl object, used to control
|
||||
the selection of source packages / binary packages when consulting
|
||||
the index and links.
|
||||
:param candidate_prefs: Options to use when creating a
|
||||
CandidateEvaluator object.
|
||||
"""
|
||||
if trusted_hosts is None:
|
||||
trusted_hosts = []
|
||||
if candidate_prefs is None:
|
||||
candidate_prefs = CandidatePreferences()
|
||||
|
||||
format_control = format_control or FormatControl(set(), set())
|
||||
|
||||
self._allow_yanked = allow_yanked
|
||||
self._candidate_prefs = candidate_prefs
|
||||
self._ignore_requires_python = ignore_requires_python
|
||||
self._target_python = target_python
|
||||
|
||||
self.candidate_evaluator = candidate_evaluator
|
||||
self.search_scope = search_scope
|
||||
self.session = session
|
||||
self.format_control = format_control
|
||||
|
@ -720,15 +761,13 @@ class PackageFinder(object):
|
|||
if target_python is None:
|
||||
target_python = TargetPython()
|
||||
|
||||
supported_tags = target_python.get_tags()
|
||||
candidate_evaluator = CandidateEvaluator(
|
||||
supported_tags=supported_tags,
|
||||
candidate_prefs = CandidatePreferences(
|
||||
prefer_binary=selection_prefs.prefer_binary,
|
||||
allow_all_prereleases=selection_prefs.allow_all_prereleases,
|
||||
)
|
||||
|
||||
return cls(
|
||||
candidate_evaluator=candidate_evaluator,
|
||||
candidate_prefs=candidate_prefs,
|
||||
search_scope=search_scope,
|
||||
session=session,
|
||||
target_python=target_python,
|
||||
|
@ -751,11 +790,11 @@ class PackageFinder(object):
|
|||
@property
|
||||
def allow_all_prereleases(self):
|
||||
# type: () -> bool
|
||||
return self.candidate_evaluator.allow_all_prereleases
|
||||
return self._candidate_prefs.allow_all_prereleases
|
||||
|
||||
def set_allow_all_prereleases(self):
|
||||
# type: () -> None
|
||||
self.candidate_evaluator.allow_all_prereleases = True
|
||||
self._candidate_prefs.allow_all_prereleases = True
|
||||
|
||||
def add_trusted_host(self, host, source=None):
|
||||
# type: (str, Optional[str]) -> None
|
||||
|
@ -995,6 +1034,17 @@ class PackageFinder(object):
|
|||
# This is an intentional priority ordering
|
||||
return file_versions + find_links_versions + page_versions
|
||||
|
||||
def make_candidate_evaluator(self):
|
||||
# type: (...) -> CandidateEvaluator
|
||||
"""Create a CandidateEvaluator object to use.
|
||||
"""
|
||||
candidate_prefs = self._candidate_prefs
|
||||
return CandidateEvaluator.create(
|
||||
target_python=self._target_python,
|
||||
prefer_binary=candidate_prefs.prefer_binary,
|
||||
allow_all_prereleases=candidate_prefs.allow_all_prereleases,
|
||||
)
|
||||
|
||||
def find_candidates(
|
||||
self,
|
||||
project_name, # type: str
|
||||
|
@ -1010,7 +1060,8 @@ class PackageFinder(object):
|
|||
:return: A `FoundCandidates` instance.
|
||||
"""
|
||||
candidates = self.find_all_candidates(project_name)
|
||||
return self.candidate_evaluator.make_found_candidates(
|
||||
candidate_evaluator = self.make_candidate_evaluator()
|
||||
return candidate_evaluator.make_found_candidates(
|
||||
candidates, specifier=specifier,
|
||||
)
|
||||
|
||||
|
|
|
@ -242,8 +242,8 @@ class TestWheel:
|
|||
Link("simplewheel-1.0-py2.py3-none-any.whl"),
|
||||
),
|
||||
]
|
||||
finder = make_test_finder()
|
||||
sort_key = finder.candidate_evaluator._sort_key
|
||||
candidate_evaluator = CandidateEvaluator.create()
|
||||
sort_key = candidate_evaluator._sort_key
|
||||
results = sorted(links, key=sort_key, reverse=True)
|
||||
results2 = sorted(reversed(links), key=sort_key, reverse=True)
|
||||
assert links == results == results2, results2
|
||||
|
|
|
@ -8,8 +8,8 @@ from pip._vendor.packaging.specifiers import SpecifierSet
|
|||
|
||||
from pip._internal.download import PipSession
|
||||
from pip._internal.index import (
|
||||
CandidateEvaluator, FormatControl, HTMLPage, Link, LinkEvaluator,
|
||||
PackageFinder, _check_link_requires_python, _clean_link,
|
||||
CandidateEvaluator, CandidatePreferences, FormatControl, HTMLPage, Link,
|
||||
LinkEvaluator, PackageFinder, _check_link_requires_python, _clean_link,
|
||||
_determine_base_url, _extract_version_from_fragment,
|
||||
_find_name_version_sep, _get_html_page,
|
||||
)
|
||||
|
@ -17,6 +17,7 @@ from pip._internal.models.candidate import InstallationCandidate
|
|||
from pip._internal.models.search_scope import SearchScope
|
||||
from pip._internal.models.selection_prefs import SelectionPreferences
|
||||
from pip._internal.models.target_python import TargetPython
|
||||
from pip._internal.pep425tags import get_supported
|
||||
from tests.lib import CURRENT_PY_VERSION_INFO, make_test_finder
|
||||
|
||||
|
||||
|
@ -171,6 +172,32 @@ class TestLinkEvaluator:
|
|||
|
||||
class TestCandidateEvaluator:
|
||||
|
||||
@pytest.mark.parametrize('allow_all_prereleases, prefer_binary', [
|
||||
(False, False),
|
||||
(False, True),
|
||||
(True, False),
|
||||
(True, True),
|
||||
])
|
||||
def test_create(self, allow_all_prereleases, prefer_binary):
|
||||
target_python = TargetPython()
|
||||
target_python._valid_tags = [('py36', 'none', 'any')]
|
||||
evaluator = CandidateEvaluator.create(
|
||||
target_python=target_python,
|
||||
allow_all_prereleases=allow_all_prereleases,
|
||||
prefer_binary=prefer_binary,
|
||||
)
|
||||
assert evaluator._allow_all_prereleases == allow_all_prereleases
|
||||
assert evaluator._prefer_binary == prefer_binary
|
||||
assert evaluator._supported_tags == [('py36', 'none', 'any')]
|
||||
|
||||
def test_create__target_python_none(self):
|
||||
"""
|
||||
Test passing target_python=None.
|
||||
"""
|
||||
evaluator = CandidateEvaluator.create()
|
||||
expected_tags = get_supported()
|
||||
assert evaluator._supported_tags == expected_tags
|
||||
|
||||
def make_mock_candidate(self, version, yanked_reason=None):
|
||||
url = 'https://example.com/pkg-{}.tar.gz'.format(version)
|
||||
link = Link(url, yanked_reason=yanked_reason)
|
||||
|
@ -184,7 +211,7 @@ class TestCandidateEvaluator:
|
|||
candidates = [
|
||||
self.make_mock_candidate(version) for version in versions
|
||||
]
|
||||
evaluator = CandidateEvaluator()
|
||||
evaluator = CandidateEvaluator.create()
|
||||
found_candidates = evaluator.make_found_candidates(
|
||||
candidates, specifier=specifier,
|
||||
)
|
||||
|
@ -212,7 +239,7 @@ class TestCandidateEvaluator:
|
|||
link = Link(url, yanked_reason=yanked_reason)
|
||||
candidate = InstallationCandidate('mypackage', '1.0', link)
|
||||
|
||||
evaluator = CandidateEvaluator()
|
||||
evaluator = CandidateEvaluator.create()
|
||||
sort_value = evaluator._sort_key(candidate)
|
||||
# Yanked / non-yanked is reflected in the first element of the tuple.
|
||||
actual = sort_value[0]
|
||||
|
@ -222,7 +249,7 @@ class TestCandidateEvaluator:
|
|||
"""
|
||||
Test passing an empty list.
|
||||
"""
|
||||
evaluator = CandidateEvaluator()
|
||||
evaluator = CandidateEvaluator.create()
|
||||
actual = evaluator.get_best_candidate([])
|
||||
assert actual is None
|
||||
|
||||
|
@ -237,7 +264,7 @@ class TestCandidateEvaluator:
|
|||
self.make_mock_candidate('2.0', yanked_reason='bad metadata #2'),
|
||||
]
|
||||
expected_best = candidates[1]
|
||||
evaluator = CandidateEvaluator()
|
||||
evaluator = CandidateEvaluator.create()
|
||||
actual = evaluator.get_best_candidate(candidates)
|
||||
assert actual is expected_best
|
||||
assert str(actual.version) == '3.0'
|
||||
|
@ -268,7 +295,7 @@ class TestCandidateEvaluator:
|
|||
candidates = [
|
||||
self.make_mock_candidate('1.0', yanked_reason=yanked_reason),
|
||||
]
|
||||
evaluator = CandidateEvaluator()
|
||||
evaluator = CandidateEvaluator.create()
|
||||
actual = evaluator.get_best_candidate(candidates)
|
||||
assert str(actual.version) == '1.0'
|
||||
|
||||
|
@ -295,7 +322,7 @@ class TestCandidateEvaluator:
|
|||
self.make_mock_candidate('1.0'),
|
||||
]
|
||||
expected_best = candidates[1]
|
||||
evaluator = CandidateEvaluator()
|
||||
evaluator = CandidateEvaluator.create()
|
||||
actual = evaluator.get_best_candidate(candidates)
|
||||
assert actual is expected_best
|
||||
assert str(actual.version) == '2.0'
|
||||
|
@ -312,29 +339,25 @@ class TestPackageFinder:
|
|||
(True, False),
|
||||
(True, True),
|
||||
])
|
||||
def test_create__candidate_evaluator(
|
||||
def test_create__candidate_prefs(
|
||||
self, allow_all_prereleases, prefer_binary,
|
||||
):
|
||||
"""
|
||||
Test that the candidate_evaluator attribute is set correctly.
|
||||
Test that the _candidate_prefs attribute is set correctly.
|
||||
"""
|
||||
selection_prefs = SelectionPreferences(
|
||||
allow_yanked=True,
|
||||
allow_all_prereleases=allow_all_prereleases,
|
||||
prefer_binary=prefer_binary,
|
||||
)
|
||||
target_python = TargetPython(py_version_info=(3, 7, 3))
|
||||
target_python._valid_tags = ['tag1', 'tag2']
|
||||
finder = PackageFinder.create(
|
||||
search_scope=SearchScope([], []),
|
||||
selection_prefs=selection_prefs,
|
||||
session=PipSession(),
|
||||
target_python=target_python,
|
||||
)
|
||||
evaluator = finder.candidate_evaluator
|
||||
assert evaluator.allow_all_prereleases == allow_all_prereleases
|
||||
assert evaluator._prefer_binary == prefer_binary
|
||||
assert evaluator._supported_tags == ['tag1', 'tag2']
|
||||
candidate_prefs = finder._candidate_prefs
|
||||
assert candidate_prefs.allow_all_prereleases == allow_all_prereleases
|
||||
assert candidate_prefs.prefer_binary == prefer_binary
|
||||
|
||||
def test_create__target_python(self):
|
||||
"""
|
||||
|
@ -526,7 +549,6 @@ class TestPackageFinder:
|
|||
target_python = TargetPython(py_version_info=(3, 7))
|
||||
format_control = FormatControl(set(), only_binary)
|
||||
finder = PackageFinder(
|
||||
candidate_evaluator=CandidateEvaluator(),
|
||||
search_scope=SearchScope([], []),
|
||||
session=PipSession(),
|
||||
target_python=target_python,
|
||||
|
@ -552,6 +574,34 @@ class TestPackageFinder:
|
|||
assert actual_target_python._given_py_version_info == (3, 7)
|
||||
assert actual_target_python.py_version_info == (3, 7, 0)
|
||||
|
||||
@pytest.mark.parametrize('allow_all_prereleases, prefer_binary', [
|
||||
(False, False),
|
||||
(False, True),
|
||||
(True, False),
|
||||
(True, True),
|
||||
])
|
||||
def test_make_candidate_evaluator(
|
||||
self, allow_all_prereleases, prefer_binary,
|
||||
):
|
||||
target_python = TargetPython()
|
||||
target_python._valid_tags = [('py36', 'none', 'any')]
|
||||
candidate_prefs = CandidatePreferences(
|
||||
prefer_binary=prefer_binary,
|
||||
allow_all_prereleases=allow_all_prereleases,
|
||||
)
|
||||
finder = PackageFinder(
|
||||
search_scope=SearchScope([], []),
|
||||
session=PipSession(),
|
||||
target_python=target_python,
|
||||
allow_yanked=True,
|
||||
candidate_prefs=candidate_prefs,
|
||||
)
|
||||
|
||||
evaluator = finder.make_candidate_evaluator()
|
||||
assert evaluator._allow_all_prereleases == allow_all_prereleases
|
||||
assert evaluator._prefer_binary == prefer_binary
|
||||
assert evaluator._supported_tags == [('py36', 'none', 'any')]
|
||||
|
||||
|
||||
def test_sort_locations_file_expand_dir(data):
|
||||
"""
|
||||
|
|
Loading…
Reference in New Issue