Split, simplify, and fix egg_info_matches

This commit is contained in:
Tzu-ping Chung 2018-10-13 03:16:38 +08:00
parent 83b879b1ec
commit 3368819156
5 changed files with 51 additions and 37 deletions

1
news/5870.bugfix Normal file
View File

@ -0,0 +1 @@
Canonicalize sdist file names so they can be matched to a canonicalized package name passed to ``pip install``.

View File

@ -815,7 +815,7 @@ class PackageFinder(object):
return
if not version:
version = egg_info_matches(egg_info, search.supplied, link)
version = _egg_info_matches(egg_info, search.canonical, link)
if version is None:
self._log_skipped_link(
link, 'Missing project version for %s' % search.supplied)
@ -846,33 +846,20 @@ class PackageFinder(object):
return InstallationCandidate(search.supplied, version, link)
def egg_info_matches(
egg_info, search_name, link,
_egg_info_re=re.compile(r'([a-z0-9_.]+)-([a-z0-9_.!+-]+)', re.I)):
def _egg_info_matches(egg_info, canonical_name, link):
"""Pull the version part out of a string.
:param egg_info: The string to parse. E.g. foo-2.1
:param search_name: The name of the package this belongs to. None to
infer the name. Note that this cannot unambiguously parse strings
like foo-2-2 which might be foo, 2-2 or foo-2, 2.
:param canonical_name: The canonicalized name of the package this
belongs to.
:param link: The link the string came from, for logging on failure.
"""
match = _egg_info_re.search(egg_info)
if not match:
logger.debug('Could not parse version from link: %s', link)
if not canonicalize_name(egg_info).startswith(canonical_name):
return None
if search_name is None:
full_match = match.group(0)
return full_match.split('-', 1)[-1]
name = match.group(0).lower()
# To match the "safe" name that pkg_resources creates:
name = name.replace('_', '-')
# project name and version must be separated by a dash
look_for = search_name.lower() + "-"
if name.startswith(look_for):
return match.group(0)[len(look_for):]
else:
# Project name and version must be separated by a dash.
if egg_info[len(canonical_name)] != "-":
return None
return egg_info[(len(canonical_name) + 1):]
def _determine_base_url(document, page_url):

View File

@ -620,6 +620,15 @@ class Wheel(object):
return bool(set(tags).intersection(self.file_tags))
def _contains_egg_info(
s, _egg_info_re=re.compile(r'([a-z0-9_.]+)-([a-z0-9_.!+-]+)', re.I)):
"""Determine whether the string looks like an egg_info.
:param s: The string to parse. E.g. foo-2.1
"""
return bool(_egg_info_re.search(s))
class WheelBuilder(object):
"""Build wheels from a RequirementSet."""
@ -712,7 +721,6 @@ class WheelBuilder(object):
newly built wheel, in preparation for installation.
:return: True if all the wheels built correctly.
"""
from pip._internal import index
from pip._internal.models.link import Link
building_is_possible = self._wheel_dir or (
@ -742,7 +750,7 @@ class WheelBuilder(object):
if autobuilding:
link = req.link
base, ext = link.splitext()
if index.egg_info_matches(base, None, link) is None:
if not _contains_egg_info(base):
# E.g. local directory. Build wheel just for this run.
ephem_cache = True
if "binary" not in format_control.get_allowed_formats(

View File

@ -7,7 +7,8 @@ from pip._vendor import html5lib, requests
from pip._internal.download import PipSession
from pip._internal.index import (
Link, PackageFinder, _determine_base_url, _get_html_page, egg_info_matches,
Link, PackageFinder, _determine_base_url, _egg_info_matches,
_get_html_page,
)
@ -165,32 +166,29 @@ def test_get_formatted_locations_basic_auth():
@pytest.mark.parametrize(
("egg_info", "search_name", "expected"),
("egg_info", "canonical_name", "expected"),
[
# Trivial.
("pip-18.0", "pip", "18.0"),
("pip-18.0", None, "18.0"),
("zope-interface-4.5.0", "zope-interface", "4.5.0"),
# Non-canonical names.
# Canonicalized name match non-canonicalized egg info. (pypa/pip#5870)
("Jinja2-2.10", "jinja2", "2.10"),
("jinja2-2.10", "Jinja2", "2.10"),
("zope.interface-4.5.0", "zope-interface", "4.5.0"),
("zope_interface-4.5.0", "zope-interface", "4.5.0"),
# Ambiguous names. Should be smart enough if the package name is
# provided, otherwise make a guess.
# Should be smart enough to parse ambiguous names from the provided
# package name.
("foo-2-2", "foo", "2-2"),
("foo-2-2", "foo-2", "2"),
("foo-2-2", None, "2-2"),
("im-valid", None, "valid"),
# Invalid names.
("invalid", None, None),
("im_invalid", None, None),
# Invalid.
("the-package-name-8.19", "does-not-match", None),
],
)
def test_egg_info_matches(egg_info, search_name, expected):
def test_egg_info_matches(egg_info, canonical_name, expected):
link = None # Only used for reporting.
version = egg_info_matches(egg_info, search_name, link)
version = _egg_info_matches(egg_info, canonical_name, link)
assert version == expected

View File

@ -13,6 +13,26 @@ from pip._internal.utils.misc import unpack_file
from tests.lib import DATA_DIR
@pytest.mark.parametrize(
"s, expected",
[
# Trivial.
("pip-18.0", True),
# Ambiguous.
("foo-2-2", True),
("im-valid", True),
# Invalid.
("invalid", False),
("im_invalid", False),
],
)
def test_contains_egg_info(s, expected):
result = wheel._contains_egg_info(s)
assert result == expected
@pytest.mark.parametrize("console_scripts",
["pip = pip._internal.main:pip",
"pip:pip = pip._internal.main:pip"])