mirror of https://github.com/pypa/pip
Add LinkEvaluator class.
This commit is contained in:
parent
a8510bc5e6
commit
979c8405d2
|
@ -7,7 +7,6 @@ import logging
|
||||||
import mimetypes
|
import mimetypes
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
from collections import namedtuple
|
|
||||||
|
|
||||||
from pip._vendor import html5lib, requests, six
|
from pip._vendor import html5lib, requests, six
|
||||||
from pip._vendor.distlib.compat import unescape
|
from pip._vendor.distlib.compat import unescape
|
||||||
|
@ -41,8 +40,8 @@ from pip._internal.wheel import Wheel
|
||||||
if MYPY_CHECK_RUNNING:
|
if MYPY_CHECK_RUNNING:
|
||||||
from logging import Logger
|
from logging import Logger
|
||||||
from typing import (
|
from typing import (
|
||||||
Any, Callable, Iterable, Iterator, List, MutableMapping, Optional,
|
Any, Callable, FrozenSet, Iterable, Iterator, List, MutableMapping,
|
||||||
Sequence, Set, Text, Tuple, Union,
|
Optional, Sequence, Set, Text, Tuple, Union,
|
||||||
)
|
)
|
||||||
import xml.etree.ElementTree
|
import xml.etree.ElementTree
|
||||||
from pip._vendor.packaging.version import _BaseVersion
|
from pip._vendor.packaging.version import _BaseVersion
|
||||||
|
@ -50,6 +49,7 @@ if MYPY_CHECK_RUNNING:
|
||||||
from pip._internal.models.search_scope import SearchScope
|
from pip._internal.models.search_scope import SearchScope
|
||||||
from pip._internal.req import InstallRequirement
|
from pip._internal.req import InstallRequirement
|
||||||
from pip._internal.download import PipSession
|
from pip._internal.download import PipSession
|
||||||
|
from pip._internal.pep425tags import Pep425Tag
|
||||||
|
|
||||||
BuildTag = Tuple[Any, ...] # either empty tuple or Tuple[int, str]
|
BuildTag = Tuple[Any, ...] # either empty tuple or Tuple[int, str]
|
||||||
CandidateSortingKey = (
|
CandidateSortingKey = (
|
||||||
|
@ -301,62 +301,58 @@ def _check_link_requires_python(
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
class CandidateEvaluator(object):
|
class LinkEvaluator(object):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Responsible for filtering and sorting candidates for installation based
|
Responsible for evaluating links for a particular project.
|
||||||
on what tags are valid.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
_py_version_re = re.compile(r'-py([123]\.?[0-9]?)$')
|
||||||
|
|
||||||
# Don't include an allow_yanked default value to make sure each call
|
# Don't include an allow_yanked default value to make sure each call
|
||||||
# site considers whether yanked releases are allowed. This also causes
|
# site considers whether yanked releases are allowed. This also causes
|
||||||
# that decision to be made explicit in the calling code, which helps
|
# that decision to be made explicit in the calling code, which helps
|
||||||
# people when reading the code.
|
# people when reading the code.
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
allow_yanked, # type: bool
|
project_name, # type: str
|
||||||
target_python=None, # type: Optional[TargetPython]
|
canonical_name, # type: str
|
||||||
prefer_binary=False, # type: bool
|
formats, # type: FrozenSet
|
||||||
allow_all_prereleases=False, # type: bool
|
target_python, # type: TargetPython
|
||||||
|
allow_yanked, # type: bool
|
||||||
ignore_requires_python=None, # type: Optional[bool]
|
ignore_requires_python=None, # type: Optional[bool]
|
||||||
):
|
):
|
||||||
# type: (...) -> None
|
# type: (...) -> None
|
||||||
"""
|
"""
|
||||||
|
:param project_name: The user supplied package name.
|
||||||
|
:param canonical_name: The canonical package name.
|
||||||
|
:param formats: The formats allowed for this package. Should be a set
|
||||||
|
with 'binary' or 'source' or both in it.
|
||||||
|
:param target_python: The target Python interpreter to use when
|
||||||
|
evaluating link compatibility. This is used, for example, to
|
||||||
|
check wheel compatibility, as well as when checking the Python
|
||||||
|
version, e.g. the Python version embedded in a link filename
|
||||||
|
(or egg fragment) and against an HTML link's optional PEP 503
|
||||||
|
"data-requires-python" attribute.
|
||||||
:param allow_yanked: Whether files marked as yanked (in the sense
|
:param allow_yanked: Whether files marked as yanked (in the sense
|
||||||
of PEP 592) are permitted to be candidates for install.
|
of PEP 592) are permitted to be candidates for install.
|
||||||
:param target_python: The target Python interpreter to use to check
|
|
||||||
both the Python version embedded in the filename and the package's
|
|
||||||
"Requires-Python" metadata. If None (the default), then a
|
|
||||||
TargetPython object will be constructed from the running Python.
|
|
||||||
:param allow_all_prereleases: Whether to allow all pre-releases.
|
|
||||||
:param ignore_requires_python: Whether to ignore incompatible
|
:param ignore_requires_python: Whether to ignore incompatible
|
||||||
"Requires-Python" values in links. Defaults to False.
|
PEP 503 "data-requires-python" values in HTML links. Defaults
|
||||||
|
to False.
|
||||||
"""
|
"""
|
||||||
if target_python is None:
|
|
||||||
target_python = TargetPython()
|
|
||||||
if ignore_requires_python is None:
|
if ignore_requires_python is None:
|
||||||
ignore_requires_python = False
|
ignore_requires_python = False
|
||||||
|
|
||||||
self._allow_yanked = allow_yanked
|
self._allow_yanked = allow_yanked
|
||||||
|
self._canonical_name = canonical_name
|
||||||
self._ignore_requires_python = ignore_requires_python
|
self._ignore_requires_python = ignore_requires_python
|
||||||
self._prefer_binary = prefer_binary
|
self._formats = formats
|
||||||
self._target_python = target_python
|
self._target_python = target_python
|
||||||
|
|
||||||
# We compile the regex here instead of as a class attribute so as
|
self.project_name = project_name
|
||||||
# not to impact pip start-up time. This is also okay because
|
|
||||||
# CandidateEvaluator is generally instantiated only once per pip
|
|
||||||
# invocation (when PackageFinder is instantiated).
|
|
||||||
self._py_version_re = re.compile(r'-py([123]\.?[0-9]?)$')
|
|
||||||
|
|
||||||
self.allow_all_prereleases = allow_all_prereleases
|
def evaluate_link(self, link):
|
||||||
|
# type: (Link) -> Tuple[bool, Optional[Text]]
|
||||||
def _is_wheel_supported(self, wheel):
|
|
||||||
# type: (Wheel) -> bool
|
|
||||||
valid_tags = self._target_python.get_tags()
|
|
||||||
return wheel.supported(valid_tags)
|
|
||||||
|
|
||||||
def evaluate_link(self, link, search):
|
|
||||||
# type: (Link, Search) -> Tuple[bool, Optional[Text]]
|
|
||||||
"""
|
"""
|
||||||
Determine whether a link is a candidate for installation.
|
Determine whether a link is a candidate for installation.
|
||||||
|
|
||||||
|
@ -382,8 +378,8 @@ class CandidateEvaluator(object):
|
||||||
return (False, 'not a file')
|
return (False, 'not a file')
|
||||||
if ext not in SUPPORTED_EXTENSIONS:
|
if ext not in SUPPORTED_EXTENSIONS:
|
||||||
return (False, 'unsupported archive format: %s' % ext)
|
return (False, 'unsupported archive format: %s' % ext)
|
||||||
if "binary" not in search.formats and ext == WHEEL_EXTENSION:
|
if "binary" not in self._formats and ext == WHEEL_EXTENSION:
|
||||||
reason = 'No binaries permitted for %s' % search.supplied
|
reason = 'No binaries permitted for %s' % self.project_name
|
||||||
return (False, reason)
|
return (False, reason)
|
||||||
if "macosx10" in link.path and ext == '.zip':
|
if "macosx10" in link.path and ext == '.zip':
|
||||||
return (False, 'macosx10 one')
|
return (False, 'macosx10 one')
|
||||||
|
@ -392,11 +388,12 @@ class CandidateEvaluator(object):
|
||||||
wheel = Wheel(link.filename)
|
wheel = Wheel(link.filename)
|
||||||
except InvalidWheelFilename:
|
except InvalidWheelFilename:
|
||||||
return (False, 'invalid wheel filename')
|
return (False, 'invalid wheel filename')
|
||||||
if canonicalize_name(wheel.name) != search.canonical:
|
if canonicalize_name(wheel.name) != self._canonical_name:
|
||||||
reason = 'wrong project name (not %s)' % search.supplied
|
reason = 'wrong project name (not %s)' % self.project_name
|
||||||
return (False, reason)
|
return (False, reason)
|
||||||
|
|
||||||
if not self._is_wheel_supported(wheel):
|
supported_tags = self._target_python.get_tags()
|
||||||
|
if not wheel.supported(supported_tags):
|
||||||
# Include the wheel's tags in the reason string to
|
# Include the wheel's tags in the reason string to
|
||||||
# simplify troubleshooting compatibility issues.
|
# simplify troubleshooting compatibility issues.
|
||||||
file_tags = wheel.get_formatted_file_tags()
|
file_tags = wheel.get_formatted_file_tags()
|
||||||
|
@ -409,16 +406,18 @@ class CandidateEvaluator(object):
|
||||||
|
|
||||||
version = wheel.version
|
version = wheel.version
|
||||||
|
|
||||||
# This should be up by the search.ok_binary check, but see issue 2700.
|
# This should be up by the self.ok_binary check, but see issue 2700.
|
||||||
if "source" not in search.formats and ext != WHEEL_EXTENSION:
|
if "source" not in self._formats and ext != WHEEL_EXTENSION:
|
||||||
return (False, 'No sources permitted for %s' % search.supplied)
|
return (False, 'No sources permitted for %s' % self.project_name)
|
||||||
|
|
||||||
if not version:
|
if not version:
|
||||||
version = _extract_version_from_fragment(
|
version = _extract_version_from_fragment(
|
||||||
egg_info, search.canonical,
|
egg_info, self._canonical_name,
|
||||||
)
|
)
|
||||||
if not version:
|
if not version:
|
||||||
return (False, 'Missing project version for %s' % search.supplied)
|
return (
|
||||||
|
False, 'Missing project version for %s' % self.project_name,
|
||||||
|
)
|
||||||
|
|
||||||
match = self._py_version_re.search(version)
|
match = self._py_version_re.search(version)
|
||||||
if match:
|
if match:
|
||||||
|
@ -440,6 +439,36 @@ class CandidateEvaluator(object):
|
||||||
|
|
||||||
return (True, version)
|
return (True, version)
|
||||||
|
|
||||||
|
|
||||||
|
class CandidateEvaluator(object):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Responsible for filtering and sorting candidates for installation based
|
||||||
|
on what tags are valid.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
supported_tags=None, # type: Optional[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.
|
||||||
|
"""
|
||||||
|
if supported_tags is None:
|
||||||
|
target_python = TargetPython()
|
||||||
|
supported_tags = target_python.get_tags()
|
||||||
|
|
||||||
|
self._prefer_binary = prefer_binary
|
||||||
|
self._supported_tags = supported_tags
|
||||||
|
|
||||||
|
self.allow_all_prereleases = allow_all_prereleases
|
||||||
|
|
||||||
def make_found_candidates(
|
def make_found_candidates(
|
||||||
self,
|
self,
|
||||||
candidates, # type: List[InstallationCandidate]
|
candidates, # type: List[InstallationCandidate]
|
||||||
|
@ -490,7 +519,7 @@ class CandidateEvaluator(object):
|
||||||
If not finding wheels, they are sorted by version only.
|
If not finding wheels, they are sorted by version only.
|
||||||
If finding wheels, then the sort order is by version, then:
|
If finding wheels, then the sort order is by version, then:
|
||||||
1. existing installs
|
1. existing installs
|
||||||
2. wheels ordered via Wheel.support_index_min(self._valid_tags)
|
2. wheels ordered via Wheel.support_index_min(self._supported_tags)
|
||||||
3. source archives
|
3. source archives
|
||||||
If prefer_binary was set, then all wheels are sorted above sources.
|
If prefer_binary was set, then all wheels are sorted above sources.
|
||||||
|
|
||||||
|
@ -498,7 +527,7 @@ class CandidateEvaluator(object):
|
||||||
comparison operators, but then different sdist links
|
comparison operators, but then different sdist links
|
||||||
with the same version, would have to be considered equal
|
with the same version, would have to be considered equal
|
||||||
"""
|
"""
|
||||||
valid_tags = self._target_python.get_tags()
|
valid_tags = self._supported_tags
|
||||||
support_num = len(valid_tags)
|
support_num = len(valid_tags)
|
||||||
build_tag = tuple() # type: BuildTag
|
build_tag = tuple() # type: BuildTag
|
||||||
binary_preference = 0
|
binary_preference = 0
|
||||||
|
@ -506,7 +535,7 @@ class CandidateEvaluator(object):
|
||||||
if link.is_wheel:
|
if link.is_wheel:
|
||||||
# can raise InvalidWheelFilename
|
# can raise InvalidWheelFilename
|
||||||
wheel = Wheel(link.filename)
|
wheel = Wheel(link.filename)
|
||||||
if not self._is_wheel_supported(wheel):
|
if not wheel.supported(valid_tags):
|
||||||
raise UnsupportedWheel(
|
raise UnsupportedWheel(
|
||||||
"%s is not a supported wheel for this platform. It "
|
"%s is not a supported wheel for this platform. It "
|
||||||
"can't be sorted." % wheel.filename
|
"can't be sorted." % wheel.filename
|
||||||
|
@ -616,8 +645,11 @@ class PackageFinder(object):
|
||||||
candidate_evaluator, # type: CandidateEvaluator
|
candidate_evaluator, # type: CandidateEvaluator
|
||||||
search_scope, # type: SearchScope
|
search_scope, # type: SearchScope
|
||||||
session, # type: PipSession
|
session, # type: PipSession
|
||||||
|
target_python, # type: TargetPython
|
||||||
|
allow_yanked, # type: bool
|
||||||
format_control=None, # type: Optional[FormatControl]
|
format_control=None, # type: Optional[FormatControl]
|
||||||
trusted_hosts=None, # type: Optional[List[str]]
|
trusted_hosts=None, # type: Optional[List[str]]
|
||||||
|
ignore_requires_python=None, # type: Optional[bool]
|
||||||
):
|
):
|
||||||
# type: (...) -> None
|
# type: (...) -> None
|
||||||
"""
|
"""
|
||||||
|
@ -635,6 +667,10 @@ class PackageFinder(object):
|
||||||
|
|
||||||
format_control = format_control or FormatControl(set(), set())
|
format_control = format_control or FormatControl(set(), set())
|
||||||
|
|
||||||
|
self._allow_yanked = allow_yanked
|
||||||
|
self._ignore_requires_python = ignore_requires_python
|
||||||
|
self._target_python = target_python
|
||||||
|
|
||||||
self.candidate_evaluator = candidate_evaluator
|
self.candidate_evaluator = candidate_evaluator
|
||||||
self.search_scope = search_scope
|
self.search_scope = search_scope
|
||||||
self.session = session
|
self.session = session
|
||||||
|
@ -665,28 +701,34 @@ class PackageFinder(object):
|
||||||
:param trusted_hosts: Domains not to emit warnings for when not using
|
:param trusted_hosts: Domains not to emit warnings for when not using
|
||||||
HTTPS.
|
HTTPS.
|
||||||
:param session: The Session to use to make requests.
|
:param session: The Session to use to make requests.
|
||||||
:param target_python: The target Python interpreter.
|
: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 session is None:
|
if session is None:
|
||||||
raise TypeError(
|
raise TypeError(
|
||||||
"PackageFinder.create() missing 1 required keyword argument: "
|
"PackageFinder.create() missing 1 required keyword argument: "
|
||||||
"'session'"
|
"'session'"
|
||||||
)
|
)
|
||||||
|
if target_python is None:
|
||||||
|
target_python = TargetPython()
|
||||||
|
|
||||||
|
supported_tags = target_python.get_tags()
|
||||||
candidate_evaluator = CandidateEvaluator(
|
candidate_evaluator = CandidateEvaluator(
|
||||||
allow_yanked=selection_prefs.allow_yanked,
|
supported_tags=supported_tags,
|
||||||
target_python=target_python,
|
|
||||||
prefer_binary=selection_prefs.prefer_binary,
|
prefer_binary=selection_prefs.prefer_binary,
|
||||||
allow_all_prereleases=selection_prefs.allow_all_prereleases,
|
allow_all_prereleases=selection_prefs.allow_all_prereleases,
|
||||||
ignore_requires_python=selection_prefs.ignore_requires_python,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return cls(
|
return cls(
|
||||||
candidate_evaluator=candidate_evaluator,
|
candidate_evaluator=candidate_evaluator,
|
||||||
search_scope=search_scope,
|
search_scope=search_scope,
|
||||||
session=session,
|
session=session,
|
||||||
|
target_python=target_python,
|
||||||
|
allow_yanked=selection_prefs.allow_yanked,
|
||||||
format_control=selection_prefs.format_control,
|
format_control=selection_prefs.format_control,
|
||||||
trusted_hosts=trusted_hosts,
|
trusted_hosts=trusted_hosts,
|
||||||
|
ignore_requires_python=selection_prefs.ignore_requires_python,
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -865,6 +907,20 @@ class PackageFinder(object):
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def make_link_evaluator(self, project_name):
|
||||||
|
# type: (str) -> LinkEvaluator
|
||||||
|
canonical_name = canonicalize_name(project_name)
|
||||||
|
formats = self.format_control.get_allowed_formats(canonical_name)
|
||||||
|
|
||||||
|
return LinkEvaluator(
|
||||||
|
project_name=project_name,
|
||||||
|
canonical_name=canonical_name,
|
||||||
|
formats=formats,
|
||||||
|
target_python=self._target_python,
|
||||||
|
allow_yanked=self._allow_yanked,
|
||||||
|
ignore_requires_python=self._ignore_requires_python,
|
||||||
|
)
|
||||||
|
|
||||||
def find_all_candidates(self, project_name):
|
def find_all_candidates(self, project_name):
|
||||||
# type: (str) -> List[InstallationCandidate]
|
# type: (str) -> List[InstallationCandidate]
|
||||||
"""Find all available InstallationCandidate for project_name
|
"""Find all available InstallationCandidate for project_name
|
||||||
|
@ -872,7 +928,7 @@ class PackageFinder(object):
|
||||||
This checks index_urls and find_links.
|
This checks index_urls and find_links.
|
||||||
All versions found are returned as an InstallationCandidate list.
|
All versions found are returned as an InstallationCandidate list.
|
||||||
|
|
||||||
See CandidateEvaluator.evaluate_link() for details on which files
|
See LinkEvaluator.evaluate_link() for details on which files
|
||||||
are accepted.
|
are accepted.
|
||||||
"""
|
"""
|
||||||
search_scope = self.search_scope
|
search_scope = self.search_scope
|
||||||
|
@ -903,13 +959,11 @@ class PackageFinder(object):
|
||||||
for location in url_locations:
|
for location in url_locations:
|
||||||
logger.debug('* %s', location)
|
logger.debug('* %s', location)
|
||||||
|
|
||||||
canonical_name = canonicalize_name(project_name)
|
link_evaluator = self.make_link_evaluator(project_name)
|
||||||
formats = self.format_control.get_allowed_formats(canonical_name)
|
|
||||||
search = Search(project_name, canonical_name, formats)
|
|
||||||
find_links_versions = self._package_versions(
|
find_links_versions = self._package_versions(
|
||||||
|
link_evaluator,
|
||||||
# We trust every directly linked archive in find_links
|
# We trust every directly linked archive in find_links
|
||||||
(Link(url, '-f') for url in self.find_links),
|
(Link(url, '-f') for url in self.find_links),
|
||||||
search
|
|
||||||
)
|
)
|
||||||
|
|
||||||
page_versions = []
|
page_versions = []
|
||||||
|
@ -917,10 +971,10 @@ class PackageFinder(object):
|
||||||
logger.debug('Analyzing links from page %s', page.url)
|
logger.debug('Analyzing links from page %s', page.url)
|
||||||
with indent_log():
|
with indent_log():
|
||||||
page_versions.extend(
|
page_versions.extend(
|
||||||
self._package_versions(page.iter_links(), search)
|
self._package_versions(link_evaluator, page.iter_links())
|
||||||
)
|
)
|
||||||
|
|
||||||
file_versions = self._package_versions(file_locations, search)
|
file_versions = self._package_versions(link_evaluator, file_locations)
|
||||||
if file_versions:
|
if file_versions:
|
||||||
file_versions.sort(reverse=True)
|
file_versions.sort(reverse=True)
|
||||||
logger.debug(
|
logger.debug(
|
||||||
|
@ -1075,35 +1129,31 @@ class PackageFinder(object):
|
||||||
logger.debug(u'Skipping link: %s: %s', reason, link)
|
logger.debug(u'Skipping link: %s: %s', reason, link)
|
||||||
self._logged_links.add(link)
|
self._logged_links.add(link)
|
||||||
|
|
||||||
def get_install_candidate(self, link, search):
|
def get_install_candidate(self, link_evaluator, link):
|
||||||
# type: (Link, Search) -> Optional[InstallationCandidate]
|
# type: (LinkEvaluator, Link) -> Optional[InstallationCandidate]
|
||||||
"""
|
"""
|
||||||
If the link is a candidate for install, convert it to an
|
If the link is a candidate for install, convert it to an
|
||||||
InstallationCandidate and return it. Otherwise, return None.
|
InstallationCandidate and return it. Otherwise, return None.
|
||||||
"""
|
"""
|
||||||
is_candidate, result = (
|
is_candidate, result = link_evaluator.evaluate_link(link)
|
||||||
self.candidate_evaluator.evaluate_link(link, search=search)
|
|
||||||
)
|
|
||||||
if not is_candidate:
|
if not is_candidate:
|
||||||
if result:
|
if result:
|
||||||
self._log_skipped_link(link, reason=result)
|
self._log_skipped_link(link, reason=result)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return InstallationCandidate(
|
return InstallationCandidate(
|
||||||
|
project=link_evaluator.project_name,
|
||||||
|
location=link,
|
||||||
# Convert the Text result to str since InstallationCandidate
|
# Convert the Text result to str since InstallationCandidate
|
||||||
# accepts str.
|
# accepts str.
|
||||||
search.supplied, location=link, version=str(result),
|
version=str(result),
|
||||||
)
|
)
|
||||||
|
|
||||||
def _package_versions(
|
def _package_versions(self, link_evaluator, links):
|
||||||
self,
|
# type: (LinkEvaluator, Iterable[Link]) -> List[InstallationCandidate]
|
||||||
links, # type: Iterable[Link]
|
|
||||||
search # type: Search
|
|
||||||
):
|
|
||||||
# type: (...) -> List[InstallationCandidate]
|
|
||||||
result = []
|
result = []
|
||||||
for link in self._sort_links(links):
|
for link in self._sort_links(links):
|
||||||
candidate = self.get_install_candidate(link, search=search)
|
candidate = self.get_install_candidate(link_evaluator, link)
|
||||||
if candidate is not None:
|
if candidate is not None:
|
||||||
result.append(candidate)
|
result.append(candidate)
|
||||||
return result
|
return result
|
||||||
|
@ -1272,13 +1322,3 @@ class HTMLPage(object):
|
||||||
if link is None:
|
if link is None:
|
||||||
continue
|
continue
|
||||||
yield link
|
yield link
|
||||||
|
|
||||||
|
|
||||||
Search = namedtuple('Search', 'supplied canonical formats')
|
|
||||||
"""Capture key aspects of a search.
|
|
||||||
|
|
||||||
:attribute supplied: The user supplied package.
|
|
||||||
:attribute canonical: The canonical package name.
|
|
||||||
:attribute formats: The formats allowed for this package. Should be a set
|
|
||||||
with 'binary' or 'source' or both in it.
|
|
||||||
"""
|
|
||||||
|
|
|
@ -82,7 +82,9 @@ class TargetPython(object):
|
||||||
def get_tags(self):
|
def get_tags(self):
|
||||||
# type: () -> List[Pep425Tag]
|
# type: () -> List[Pep425Tag]
|
||||||
"""
|
"""
|
||||||
Return the supported tags to check wheel candidates against.
|
Return the supported PEP 425 tags to check wheel candidates against.
|
||||||
|
|
||||||
|
The tags are returned in order of preference (most preferred first).
|
||||||
"""
|
"""
|
||||||
if self._valid_tags is None:
|
if self._valid_tags is None:
|
||||||
# Pass versions=None if no py_version_info was given since
|
# Pass versions=None if no py_version_info was given since
|
||||||
|
|
|
@ -3,7 +3,7 @@ import sys
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from mock import Mock, patch
|
from mock import Mock, patch
|
||||||
from pkg_resources import Distribution, parse_version
|
from pkg_resources import parse_version
|
||||||
|
|
||||||
import pip._internal.pep425tags
|
import pip._internal.pep425tags
|
||||||
import pip._internal.wheel
|
import pip._internal.wheel
|
||||||
|
@ -11,7 +11,7 @@ from pip._internal.exceptions import (
|
||||||
BestVersionAlreadyInstalled, DistributionNotFound,
|
BestVersionAlreadyInstalled, DistributionNotFound,
|
||||||
)
|
)
|
||||||
from pip._internal.index import (
|
from pip._internal.index import (
|
||||||
CandidateEvaluator, InstallationCandidate, Link, Search,
|
CandidateEvaluator, InstallationCandidate, Link, LinkEvaluator,
|
||||||
)
|
)
|
||||||
from pip._internal.models.target_python import TargetPython
|
from pip._internal.models.target_python import TargetPython
|
||||||
from pip._internal.req.constructors import install_req_from_line
|
from pip._internal.req.constructors import install_req_from_line
|
||||||
|
@ -216,12 +216,7 @@ class TestWheel:
|
||||||
('pyT', 'TEST', 'any'),
|
('pyT', 'TEST', 'any'),
|
||||||
('pyT', 'none', 'any'),
|
('pyT', 'none', 'any'),
|
||||||
]
|
]
|
||||||
target_python = TargetPython()
|
evaluator = CandidateEvaluator(supported_tags=valid_tags)
|
||||||
target_python._valid_tags = valid_tags
|
|
||||||
evaluator = CandidateEvaluator(
|
|
||||||
allow_yanked=True,
|
|
||||||
target_python=target_python,
|
|
||||||
)
|
|
||||||
sort_key = evaluator._sort_key
|
sort_key = evaluator._sort_key
|
||||||
results = sorted(links, key=sort_key, reverse=True)
|
results = sorted(links, key=sort_key, reverse=True)
|
||||||
results2 = sorted(reversed(links), key=sort_key, reverse=True)
|
results2 = sorted(reversed(links), key=sort_key, reverse=True)
|
||||||
|
@ -419,17 +414,17 @@ def test_finder_installs_pre_releases_with_version_spec():
|
||||||
assert link.url == "https://foo/bar-2.0b1.tar.gz"
|
assert link.url == "https://foo/bar-2.0b1.tar.gz"
|
||||||
|
|
||||||
|
|
||||||
class TestCandidateEvaluator(object):
|
class TestLinkEvaluator(object):
|
||||||
|
|
||||||
# patch this for travis which has distribute in its base env for now
|
def make_test_link_evaluator(self, formats):
|
||||||
@patch(
|
target_python = TargetPython()
|
||||||
'pip._internal.wheel.pkg_resources.get_distribution',
|
return LinkEvaluator(
|
||||||
lambda x: Distribution(project_name='setuptools', version='0.9')
|
project_name='pytest',
|
||||||
)
|
canonical_name='pytest',
|
||||||
def setup(self):
|
formats=formats,
|
||||||
self.search_name = 'pytest'
|
target_python=target_python,
|
||||||
self.canonical_name = 'pytest'
|
allow_yanked=True,
|
||||||
self.evaluator = CandidateEvaluator(allow_yanked=True)
|
)
|
||||||
|
|
||||||
@pytest.mark.parametrize('url, expected_version', [
|
@pytest.mark.parametrize('url, expected_version', [
|
||||||
('http:/yo/pytest-1.0.tar.gz', '1.0'),
|
('http:/yo/pytest-1.0.tar.gz', '1.0'),
|
||||||
|
@ -438,12 +433,8 @@ class TestCandidateEvaluator(object):
|
||||||
def test_evaluate_link__match(self, url, expected_version):
|
def test_evaluate_link__match(self, url, expected_version):
|
||||||
"""Test that 'pytest' archives match for 'pytest'"""
|
"""Test that 'pytest' archives match for 'pytest'"""
|
||||||
link = Link(url)
|
link = Link(url)
|
||||||
search = Search(
|
evaluator = self.make_test_link_evaluator(formats=['source', 'binary'])
|
||||||
supplied=self.search_name,
|
actual = evaluator.evaluate_link(link)
|
||||||
canonical=self.canonical_name,
|
|
||||||
formats=['source', 'binary'],
|
|
||||||
)
|
|
||||||
actual = self.evaluator.evaluate_link(link, search)
|
|
||||||
assert actual == (True, expected_version)
|
assert actual == (True, expected_version)
|
||||||
|
|
||||||
@pytest.mark.parametrize('url, expected_msg', [
|
@pytest.mark.parametrize('url, expected_msg', [
|
||||||
|
@ -457,12 +448,8 @@ class TestCandidateEvaluator(object):
|
||||||
def test_evaluate_link__substring_fails(self, url, expected_msg):
|
def test_evaluate_link__substring_fails(self, url, expected_msg):
|
||||||
"""Test that 'pytest<something> archives won't match for 'pytest'."""
|
"""Test that 'pytest<something> archives won't match for 'pytest'."""
|
||||||
link = Link(url)
|
link = Link(url)
|
||||||
search = Search(
|
evaluator = self.make_test_link_evaluator(formats=['source', 'binary'])
|
||||||
supplied=self.search_name,
|
actual = evaluator.evaluate_link(link)
|
||||||
canonical=self.canonical_name,
|
|
||||||
formats=['source', 'binary'],
|
|
||||||
)
|
|
||||||
actual = self.evaluator.evaluate_link(link, search)
|
|
||||||
assert actual == (False, expected_msg)
|
assert actual == (False, expected_msg)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -7,9 +7,10 @@ from pip._vendor import html5lib, requests
|
||||||
|
|
||||||
from pip._internal.download import PipSession
|
from pip._internal.download import PipSession
|
||||||
from pip._internal.index import (
|
from pip._internal.index import (
|
||||||
CandidateEvaluator, HTMLPage, Link, PackageFinder, Search,
|
CandidateEvaluator, FormatControl, HTMLPage, Link, LinkEvaluator,
|
||||||
_check_link_requires_python, _clean_link, _determine_base_url,
|
PackageFinder, _check_link_requires_python, _clean_link,
|
||||||
_extract_version_from_fragment, _find_name_version_sep, _get_html_page,
|
_determine_base_url, _extract_version_from_fragment,
|
||||||
|
_find_name_version_sep, _get_html_page,
|
||||||
)
|
)
|
||||||
from pip._internal.models.candidate import InstallationCandidate
|
from pip._internal.models.candidate import InstallationCandidate
|
||||||
from pip._internal.models.search_scope import SearchScope
|
from pip._internal.models.search_scope import SearchScope
|
||||||
|
@ -84,32 +85,7 @@ def test_check_link_requires_python__invalid_requires(caplog):
|
||||||
check_caplog(caplog, 'DEBUG', expected_message)
|
check_caplog(caplog, 'DEBUG', expected_message)
|
||||||
|
|
||||||
|
|
||||||
class TestCandidateEvaluator:
|
class TestLinkEvaluator:
|
||||||
|
|
||||||
def test_init__target_python(self):
|
|
||||||
"""
|
|
||||||
Test the target_python argument.
|
|
||||||
"""
|
|
||||||
target_python = TargetPython(py_version_info=(3, 7, 3))
|
|
||||||
evaluator = CandidateEvaluator(
|
|
||||||
allow_yanked=True,
|
|
||||||
target_python=target_python,
|
|
||||||
)
|
|
||||||
# The target_python attribute should be set as is.
|
|
||||||
assert evaluator._target_python is target_python
|
|
||||||
|
|
||||||
def test_init__target_python_none(self):
|
|
||||||
"""
|
|
||||||
Test passing None for the target_python argument.
|
|
||||||
"""
|
|
||||||
evaluator = CandidateEvaluator(
|
|
||||||
allow_yanked=True,
|
|
||||||
target_python=None,
|
|
||||||
)
|
|
||||||
# Spot-check the default TargetPython object.
|
|
||||||
actual_target_python = evaluator._target_python
|
|
||||||
assert actual_target_python._given_py_version_info is None
|
|
||||||
assert actual_target_python.py_version_info == CURRENT_PY_VERSION_INFO
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
'py_version_info,ignore_requires_python,expected', [
|
'py_version_info,ignore_requires_python,expected', [
|
||||||
|
@ -124,19 +100,19 @@ class TestCandidateEvaluator:
|
||||||
self, py_version_info, ignore_requires_python, expected,
|
self, py_version_info, ignore_requires_python, expected,
|
||||||
):
|
):
|
||||||
target_python = TargetPython(py_version_info=py_version_info)
|
target_python = TargetPython(py_version_info=py_version_info)
|
||||||
evaluator = CandidateEvaluator(
|
evaluator = LinkEvaluator(
|
||||||
allow_yanked=True,
|
project_name='twine',
|
||||||
|
canonical_name='twine',
|
||||||
|
formats={'source'},
|
||||||
target_python=target_python,
|
target_python=target_python,
|
||||||
|
allow_yanked=True,
|
||||||
ignore_requires_python=ignore_requires_python,
|
ignore_requires_python=ignore_requires_python,
|
||||||
)
|
)
|
||||||
link = Link(
|
link = Link(
|
||||||
'https://example.com/#egg=twine-1.12',
|
'https://example.com/#egg=twine-1.12',
|
||||||
requires_python='== 3.6.5',
|
requires_python='== 3.6.5',
|
||||||
)
|
)
|
||||||
search = Search(
|
actual = evaluator.evaluate_link(link)
|
||||||
supplied='twine', canonical='twine', formats=['source'],
|
|
||||||
)
|
|
||||||
actual = evaluator.evaluate_link(link, search=search)
|
|
||||||
assert actual == expected
|
assert actual == expected
|
||||||
|
|
||||||
@pytest.mark.parametrize('yanked_reason, allow_yanked, expected', [
|
@pytest.mark.parametrize('yanked_reason, allow_yanked, expected', [
|
||||||
|
@ -155,15 +131,19 @@ class TestCandidateEvaluator:
|
||||||
def test_evaluate_link__allow_yanked(
|
def test_evaluate_link__allow_yanked(
|
||||||
self, yanked_reason, allow_yanked, expected,
|
self, yanked_reason, allow_yanked, expected,
|
||||||
):
|
):
|
||||||
evaluator = CandidateEvaluator(allow_yanked=allow_yanked)
|
target_python = TargetPython(py_version_info=(3, 6, 4))
|
||||||
|
evaluator = LinkEvaluator(
|
||||||
|
project_name='twine',
|
||||||
|
canonical_name='twine',
|
||||||
|
formats={'source'},
|
||||||
|
target_python=target_python,
|
||||||
|
allow_yanked=allow_yanked,
|
||||||
|
)
|
||||||
link = Link(
|
link = Link(
|
||||||
'https://example.com/#egg=twine-1.12',
|
'https://example.com/#egg=twine-1.12',
|
||||||
yanked_reason=yanked_reason,
|
yanked_reason=yanked_reason,
|
||||||
)
|
)
|
||||||
search = Search(
|
actual = evaluator.evaluate_link(link)
|
||||||
supplied='twine', canonical='twine', formats=['source'],
|
|
||||||
)
|
|
||||||
actual = evaluator.evaluate_link(link, search=search)
|
|
||||||
assert actual == expected
|
assert actual == expected
|
||||||
|
|
||||||
def test_evaluate_link__incompatible_wheel(self):
|
def test_evaluate_link__incompatible_wheel(self):
|
||||||
|
@ -173,20 +153,23 @@ class TestCandidateEvaluator:
|
||||||
target_python = TargetPython(py_version_info=(3, 6, 4))
|
target_python = TargetPython(py_version_info=(3, 6, 4))
|
||||||
# Set the valid tags to an empty list to make sure nothing matches.
|
# Set the valid tags to an empty list to make sure nothing matches.
|
||||||
target_python._valid_tags = []
|
target_python._valid_tags = []
|
||||||
evaluator = CandidateEvaluator(
|
evaluator = LinkEvaluator(
|
||||||
allow_yanked=True,
|
project_name='sample',
|
||||||
|
canonical_name='sample',
|
||||||
|
formats={'binary'},
|
||||||
target_python=target_python,
|
target_python=target_python,
|
||||||
|
allow_yanked=True,
|
||||||
)
|
)
|
||||||
link = Link('https://example.com/sample-1.0-py2.py3-none-any.whl')
|
link = Link('https://example.com/sample-1.0-py2.py3-none-any.whl')
|
||||||
search = Search(
|
actual = evaluator.evaluate_link(link)
|
||||||
supplied='sample', canonical='sample', formats=['binary'],
|
|
||||||
)
|
|
||||||
actual = evaluator.evaluate_link(link, search=search)
|
|
||||||
expected = (
|
expected = (
|
||||||
False, "none of the wheel's tags match: py2-none-any, py3-none-any"
|
False, "none of the wheel's tags match: py2-none-any, py3-none-any"
|
||||||
)
|
)
|
||||||
assert actual == expected
|
assert actual == expected
|
||||||
|
|
||||||
|
|
||||||
|
class TestCandidateEvaluator:
|
||||||
|
|
||||||
@pytest.mark.parametrize('yanked_reason, expected', [
|
@pytest.mark.parametrize('yanked_reason, expected', [
|
||||||
# Test a non-yanked file.
|
# Test a non-yanked file.
|
||||||
(None, 0),
|
(None, 0),
|
||||||
|
@ -201,7 +184,7 @@ class TestCandidateEvaluator:
|
||||||
link = Link(url, yanked_reason=yanked_reason)
|
link = Link(url, yanked_reason=yanked_reason)
|
||||||
candidate = InstallationCandidate('mypackage', '1.0', link)
|
candidate = InstallationCandidate('mypackage', '1.0', link)
|
||||||
|
|
||||||
evaluator = CandidateEvaluator(allow_yanked=True)
|
evaluator = CandidateEvaluator()
|
||||||
sort_value = evaluator._sort_key(candidate)
|
sort_value = evaluator._sort_key(candidate)
|
||||||
# Yanked / non-yanked is reflected in the first element of the tuple.
|
# Yanked / non-yanked is reflected in the first element of the tuple.
|
||||||
actual = sort_value[0]
|
actual = sort_value[0]
|
||||||
|
@ -218,7 +201,7 @@ class TestCandidateEvaluator:
|
||||||
"""
|
"""
|
||||||
Test passing an empty list.
|
Test passing an empty list.
|
||||||
"""
|
"""
|
||||||
evaluator = CandidateEvaluator(allow_yanked=True)
|
evaluator = CandidateEvaluator()
|
||||||
actual = evaluator.get_best_candidate([])
|
actual = evaluator.get_best_candidate([])
|
||||||
assert actual is None
|
assert actual is None
|
||||||
|
|
||||||
|
@ -233,7 +216,7 @@ class TestCandidateEvaluator:
|
||||||
self.make_mock_candidate('2.0', yanked_reason='bad metadata #2'),
|
self.make_mock_candidate('2.0', yanked_reason='bad metadata #2'),
|
||||||
]
|
]
|
||||||
expected_best = candidates[1]
|
expected_best = candidates[1]
|
||||||
evaluator = CandidateEvaluator(allow_yanked=True)
|
evaluator = CandidateEvaluator()
|
||||||
actual = evaluator.get_best_candidate(candidates)
|
actual = evaluator.get_best_candidate(candidates)
|
||||||
assert actual is expected_best
|
assert actual is expected_best
|
||||||
assert str(actual.version) == '3.0'
|
assert str(actual.version) == '3.0'
|
||||||
|
@ -264,7 +247,7 @@ class TestCandidateEvaluator:
|
||||||
candidates = [
|
candidates = [
|
||||||
self.make_mock_candidate('1.0', yanked_reason=yanked_reason),
|
self.make_mock_candidate('1.0', yanked_reason=yanked_reason),
|
||||||
]
|
]
|
||||||
evaluator = CandidateEvaluator(allow_yanked=True)
|
evaluator = CandidateEvaluator()
|
||||||
actual = evaluator.get_best_candidate(candidates)
|
actual = evaluator.get_best_candidate(candidates)
|
||||||
assert str(actual.version) == '1.0'
|
assert str(actual.version) == '1.0'
|
||||||
|
|
||||||
|
@ -291,7 +274,7 @@ class TestCandidateEvaluator:
|
||||||
self.make_mock_candidate('1.0'),
|
self.make_mock_candidate('1.0'),
|
||||||
]
|
]
|
||||||
expected_best = candidates[1]
|
expected_best = candidates[1]
|
||||||
evaluator = CandidateEvaluator(allow_yanked=True)
|
evaluator = CandidateEvaluator()
|
||||||
actual = evaluator.get_best_candidate(candidates)
|
actual = evaluator.get_best_candidate(candidates)
|
||||||
assert actual is expected_best
|
assert actual is expected_best
|
||||||
assert str(actual.version) == '2.0'
|
assert str(actual.version) == '2.0'
|
||||||
|
@ -302,43 +285,116 @@ class TestCandidateEvaluator:
|
||||||
|
|
||||||
class TestPackageFinder:
|
class TestPackageFinder:
|
||||||
|
|
||||||
@pytest.mark.parametrize('allow_yanked', [False, True])
|
@pytest.mark.parametrize('allow_all_prereleases, prefer_binary', [
|
||||||
def test_create__allow_yanked(self, allow_yanked):
|
(False, False),
|
||||||
|
(False, True),
|
||||||
|
(True, False),
|
||||||
|
(True, True),
|
||||||
|
])
|
||||||
|
def test_create__candidate_evaluator(
|
||||||
|
self, allow_all_prereleases, prefer_binary,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Test that allow_yanked is passed to CandidateEvaluator.
|
Test that the candidate_evaluator attribute is set correctly.
|
||||||
"""
|
"""
|
||||||
search_scope = SearchScope([], [])
|
|
||||||
selection_prefs = SelectionPreferences(
|
|
||||||
allow_yanked=allow_yanked,
|
|
||||||
)
|
|
||||||
finder = PackageFinder.create(
|
|
||||||
search_scope=search_scope,
|
|
||||||
selection_prefs=selection_prefs,
|
|
||||||
session=object(),
|
|
||||||
)
|
|
||||||
evaluator = finder.candidate_evaluator
|
|
||||||
assert evaluator._allow_yanked == allow_yanked
|
|
||||||
|
|
||||||
def test_create__target_python(self):
|
|
||||||
"""
|
|
||||||
Test that target_python is passed to CandidateEvaluator as is.
|
|
||||||
"""
|
|
||||||
search_scope = SearchScope([], [])
|
|
||||||
selection_prefs = SelectionPreferences(
|
selection_prefs = SelectionPreferences(
|
||||||
allow_yanked=True,
|
allow_yanked=True,
|
||||||
|
allow_all_prereleases=allow_all_prereleases,
|
||||||
|
prefer_binary=prefer_binary,
|
||||||
)
|
)
|
||||||
target_python = TargetPython(py_version_info=(3, 7, 3))
|
target_python = TargetPython(py_version_info=(3, 7, 3))
|
||||||
|
target_python._valid_tags = ['tag1', 'tag2']
|
||||||
finder = PackageFinder.create(
|
finder = PackageFinder.create(
|
||||||
search_scope=search_scope,
|
search_scope=SearchScope([], []),
|
||||||
selection_prefs=selection_prefs,
|
selection_prefs=selection_prefs,
|
||||||
session=object(),
|
session=PipSession(),
|
||||||
target_python=target_python,
|
target_python=target_python,
|
||||||
)
|
)
|
||||||
evaluator = finder.candidate_evaluator
|
evaluator = finder.candidate_evaluator
|
||||||
actual_target_python = evaluator._target_python
|
assert evaluator.allow_all_prereleases == allow_all_prereleases
|
||||||
|
assert evaluator._prefer_binary == prefer_binary
|
||||||
|
assert evaluator._supported_tags == ['tag1', 'tag2']
|
||||||
|
|
||||||
|
def test_create__target_python(self):
|
||||||
|
"""
|
||||||
|
Test that the _target_python attribute is set correctly.
|
||||||
|
"""
|
||||||
|
target_python = TargetPython(py_version_info=(3, 7, 3))
|
||||||
|
finder = PackageFinder.create(
|
||||||
|
search_scope=SearchScope([], []),
|
||||||
|
selection_prefs=SelectionPreferences(allow_yanked=True),
|
||||||
|
session=PipSession(),
|
||||||
|
target_python=target_python,
|
||||||
|
)
|
||||||
|
actual_target_python = finder._target_python
|
||||||
|
# The target_python attribute should be set as is.
|
||||||
assert actual_target_python is target_python
|
assert actual_target_python is target_python
|
||||||
|
# Check that the attributes weren't reset.
|
||||||
assert actual_target_python.py_version_info == (3, 7, 3)
|
assert actual_target_python.py_version_info == (3, 7, 3)
|
||||||
|
|
||||||
|
def test_create__target_python_none(self):
|
||||||
|
"""
|
||||||
|
Test passing target_python=None.
|
||||||
|
"""
|
||||||
|
finder = PackageFinder.create(
|
||||||
|
search_scope=SearchScope([], []),
|
||||||
|
selection_prefs=SelectionPreferences(allow_yanked=True),
|
||||||
|
session=PipSession(),
|
||||||
|
target_python=None,
|
||||||
|
)
|
||||||
|
# Spot-check the default TargetPython object.
|
||||||
|
actual_target_python = finder._target_python
|
||||||
|
assert actual_target_python._given_py_version_info is None
|
||||||
|
assert actual_target_python.py_version_info == CURRENT_PY_VERSION_INFO
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('allow_yanked', [False, True])
|
||||||
|
def test_create__allow_yanked(self, allow_yanked):
|
||||||
|
"""
|
||||||
|
Test that the _allow_yanked attribute is set correctly.
|
||||||
|
"""
|
||||||
|
selection_prefs = SelectionPreferences(allow_yanked=allow_yanked)
|
||||||
|
finder = PackageFinder.create(
|
||||||
|
search_scope=SearchScope([], []),
|
||||||
|
selection_prefs=selection_prefs,
|
||||||
|
session=PipSession(),
|
||||||
|
)
|
||||||
|
assert finder._allow_yanked == allow_yanked
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('ignore_requires_python', [False, True])
|
||||||
|
def test_create__ignore_requires_python(self, ignore_requires_python):
|
||||||
|
"""
|
||||||
|
Test that the _ignore_requires_python attribute is set correctly.
|
||||||
|
"""
|
||||||
|
selection_prefs = SelectionPreferences(
|
||||||
|
allow_yanked=True,
|
||||||
|
ignore_requires_python=ignore_requires_python,
|
||||||
|
)
|
||||||
|
finder = PackageFinder.create(
|
||||||
|
search_scope=SearchScope([], []),
|
||||||
|
selection_prefs=selection_prefs,
|
||||||
|
session=PipSession(),
|
||||||
|
)
|
||||||
|
assert finder._ignore_requires_python == ignore_requires_python
|
||||||
|
|
||||||
|
def test_create__format_control(self):
|
||||||
|
"""
|
||||||
|
Test that the format_control attribute is set correctly.
|
||||||
|
"""
|
||||||
|
format_control = FormatControl(set(), {':all:'})
|
||||||
|
selection_prefs = SelectionPreferences(
|
||||||
|
allow_yanked=True,
|
||||||
|
format_control=format_control,
|
||||||
|
)
|
||||||
|
finder = PackageFinder.create(
|
||||||
|
search_scope=SearchScope([], []),
|
||||||
|
selection_prefs=selection_prefs,
|
||||||
|
session=PipSession(),
|
||||||
|
)
|
||||||
|
actual_format_control = finder.format_control
|
||||||
|
assert actual_format_control is format_control
|
||||||
|
# Check that the attributes weren't reset.
|
||||||
|
assert actual_format_control.only_binary == {':all:'}
|
||||||
|
|
||||||
def test_add_trusted_host(self):
|
def test_add_trusted_host(self):
|
||||||
# Leave a gap to test how the ordering is affected.
|
# Leave a gap to test how the ordering is affected.
|
||||||
trusted_hosts = ['host1', 'host3']
|
trusted_hosts = ['host1', 'host3']
|
||||||
|
@ -429,6 +485,52 @@ class TestPackageFinder:
|
||||||
# Spot-check that SECURE_ORIGINS is included.
|
# Spot-check that SECURE_ORIGINS is included.
|
||||||
assert actual[0] == ('https', '*', '*')
|
assert actual[0] == ('https', '*', '*')
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'allow_yanked, ignore_requires_python, only_binary, expected_formats',
|
||||||
|
[
|
||||||
|
(False, False, {}, frozenset({'binary', 'source'})),
|
||||||
|
# Test allow_yanked=True.
|
||||||
|
(True, False, {}, frozenset({'binary', 'source'})),
|
||||||
|
# Test ignore_requires_python=True.
|
||||||
|
(False, True, {}, frozenset({'binary', 'source'})),
|
||||||
|
# Test a non-trivial only_binary.
|
||||||
|
(False, False, {'twine'}, frozenset({'binary'})),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
def test_make_link_evaluator(
|
||||||
|
self, allow_yanked, ignore_requires_python, only_binary,
|
||||||
|
expected_formats,
|
||||||
|
):
|
||||||
|
# Create a test TargetPython that we can check for.
|
||||||
|
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,
|
||||||
|
allow_yanked=allow_yanked,
|
||||||
|
format_control=format_control,
|
||||||
|
ignore_requires_python=ignore_requires_python,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Pass a project_name that will be different from canonical_name.
|
||||||
|
link_evaluator = finder.make_link_evaluator('Twine')
|
||||||
|
|
||||||
|
assert link_evaluator.project_name == 'Twine'
|
||||||
|
assert link_evaluator._canonical_name == 'twine'
|
||||||
|
assert link_evaluator._allow_yanked == allow_yanked
|
||||||
|
assert link_evaluator._ignore_requires_python == ignore_requires_python
|
||||||
|
assert link_evaluator._formats == expected_formats
|
||||||
|
|
||||||
|
# Test the _target_python attribute.
|
||||||
|
actual_target_python = link_evaluator._target_python
|
||||||
|
# The target_python attribute should be set as is.
|
||||||
|
assert actual_target_python is target_python
|
||||||
|
# For good measure, check that the attributes weren't reset.
|
||||||
|
assert actual_target_python._given_py_version_info == (3, 7)
|
||||||
|
assert actual_target_python.py_version_info == (3, 7, 0)
|
||||||
|
|
||||||
|
|
||||||
def test_sort_locations_file_expand_dir(data):
|
def test_sort_locations_file_expand_dir(data):
|
||||||
"""
|
"""
|
||||||
|
|
Loading…
Reference in New Issue