Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use Python Version and OS to Select the Appropriate Wheel (#944) {Against "master" Branch} #955

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 79 additions & 2 deletions poetry/repositories/pypi_repository.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import logging
import sys
import os
import platform
import tarfile
import zipfile

Expand Down Expand Up @@ -56,6 +58,14 @@ class PyPiRepository(Repository):

CACHE_VERSION = parse_constraint("0.12.0")

# Implementation name lookup specified by PEP425
IMP_NAME_LOOKUP = {
"cpython": "cp",
"ironpython": "ip",
"pypy": "pp",
"jython": "jy",
}

def __init__(self, url="https://pypi.org/", disable_cache=False, fallback=True):
self._name = "PyPI"
self._url = url
Expand All @@ -80,6 +90,20 @@ def __init__(self, url="https://pypi.org/", disable_cache=False, fallback=True):

super(PyPiRepository, self).__init__()

@property
def sys_info(self): # type: () -> Dict[str, str]
"""
Return dictionary describing the OS and Python configuration.
"""
if not hasattr(self, "_sys_info"):
self._sys_info = {
"plat": platform.system().lower(),
"is32bit": sys.maxsize <= 2 ** 32,
"imp_name": sys.implementation.name,
"pyver": platform.python_version_tuple(),
}
return self._sys_info

def find_packages(
self,
name, # type: str
Expand Down Expand Up @@ -421,11 +445,64 @@ def _get_info_from_urls(
return info

if platform_specific_wheels and "sdist" not in urls:
# Pick the first wheel available and hope for the best
return self._get_info_from_wheel(platform_specific_wheels[0])
# Attempt to select the best platform-specific wheel
best_wheel = self._pick_platform_specific_wheel(
platform_specific_wheels
)
return self._get_info_from_wheel(best_wheel)

return self._get_info_from_sdist(urls["sdist"][0])

def _pick_platform_specific_wheel(
self, platform_specific_wheels
): # type: (list) -> str
# Format information for checking the PEP425 "Platform Tag"
os_map = {"windows": "win", "darwin": "macosx"}
os_name = os_map.get(self.sys_info["plat"], self.sys_info["plat"])
bit_label = "32" if self.sys_info["is32bit"] else "64"
# Format information for checking the PEP425 "Python Tag"
imp_abbr = self.IMP_NAME_LOOKUP.get(self.sys_info["imp_name"].lower(), "py")
py_abbr = "".join(self.sys_info["pyver"][:2])
self._log(
"Attempting to determine best wheel file for: {}".format(self.sys_info),
level="debug",
)

platform_matches = []
for url in platform_specific_wheels:
m = wheel_file_re.match(Link(url).filename)
KyleKing marked this conversation as resolved.
Show resolved Hide resolved
plat = m.group("plat")
if os_name in plat:
# Check python version and the Python implementation or generic "py"
match_py = m.group("pyver") in [imp_abbr + py_abbr, "py" + py_abbr]
if match_py and (bit_label in plat or "x86_64" in plat):
KyleKing marked this conversation as resolved.
Show resolved Hide resolved
self._log(
"Selected best platform, bit, and Python version match: {}".format(
url
),
level="debug",
)
return url
elif match_py:
platform_matches.insert(0, url)
if len(platform_matches) > 0:
# Return first platform match as more specificity couldn't be determined
self._log(
"Selecting first wheel file for platform {}: {}".format(
os_name, platform_matches[0]
),
level="debug",
)
return platform_matches[0]
# Could not pick the best wheel, return the first available and hope for the best
self._log(
"Matching was unsuccessful, selecting first wheel file: {}".format(
platform_specific_wheels[0]
),
level="debug",
)
return platform_specific_wheels[0]

def _get_info_from_wheel(
self, url
): # type: (str) -> Dict[str, Union[str, List, None]]
Expand Down
96 changes: 95 additions & 1 deletion tests/repositories/test_pypi_repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,21 @@ class MockRepository(PyPiRepository):
JSON_FIXTURES = Path(__file__).parent / "fixtures" / "pypi.org" / "json"
DIST_FIXTURES = Path(__file__).parent / "fixtures" / "pypi.org" / "dists"

def __init__(self, fallback=False):
def __init__(
self, fallback=False, plat="Linux", is32bit=True, imp_name="py", pyver=(3, 7, 2)
):
super(MockRepository, self).__init__(
url="http://foo.bar", disable_cache=True, fallback=fallback
)

# Mock different hardware configurations
self._sys_info = {
"plat": plat.lower(),
"is32bit": is32bit,
"imp_name": imp_name,
"pyver": pyver,
}

def _get(self, url):
parts = url.split("/")[1:]
name = parts[0]
Expand Down Expand Up @@ -103,6 +113,90 @@ def test_fallback_on_downloading_packages():
]


# Mock platform specific wheels for testing
numpy_plat_spec_wheels = [
(
"numpy-1.16.2-cp27-cp27m-macosx_10_6_intel.macosx_10_9_intel."
"macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl"
),
"numpy-1.16.2-cp27-cp27m-manylinux1_i686.whl",
"numpy-1.16.2-cp27-cp27m-manylinux1_x86_64.whl",
"numpy-1.16.2-cp27-cp27mu-manylinux1_i686.whl",
"numpy-1.16.2-cp27-cp27mu-manylinux1_x86_64.whl",
"numpy-1.16.2-cp27-cp27m-win32.whl",
"numpy-1.16.2-cp27-cp27m-win_amd64.whl",
(
"numpy-1.16.2-cp35-cp35m-macosx_10_6_intel.macosx_10_9_intel."
"macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl"
),
"numpy-1.16.2-cp35-cp35m-manylinux1_i686.whl",
"numpy-1.16.2-cp35-cp35m-manylinux1_x86_64.whl",
"numpy-1.16.2-cp35-cp35m-win32.whl",
"numpy-1.16.2-cp35-cp35m-win_amd64.whl",
(
"numpy-1.16.2-cp36-cp36m-macosx_10_6_intel.macosx_10_9_intel."
"macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl"
),
"numpy-1.16.2-cp36-cp36m-manylinux1_i686.whl",
"numpy-1.16.2-cp36-cp36m-manylinux1_x86_64.whl",
"numpy-1.16.2-cp36-cp36m-win32.whl",
"numpy-1.16.2-cp36-cp36m-win_amd64.whl",
(
"numpy-1.16.2-cp37-cp37m-macosx_10_6_intel.macosx_10_9_intel."
"macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl"
),
"numpy-1.16.2-cp37-cp37m-manylinux1_i686.whl",
"numpy-1.16.2-cp37-cp37m-manylinux1_x86_64.whl",
"numpy-1.16.2-cp37-cp37m-win32.whl",
"numpy-1.16.2-cp37-cp37m-win_amd64.whl",
]


@pytest.mark.parametrize(
"plat,is32bit,imp_name,pyver,best_wheel",
[
(
"Linux",
False,
"CPython",
("3", "7", "2"),
"numpy-1.16.2-cp37-cp37m-manylinux1_x86_64.whl",
),
(
"Darwin",
False,
"CPython",
("2", "7", "3"),
(
"numpy-1.16.2-cp27-cp27m-macosx_10_6_intel.macosx_10_9_intel."
"macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl"
),
),
(
"Windows",
False,
"CPython",
("3", "6", "2"),
"numpy-1.16.2-cp36-cp36m-win_amd64.whl",
),
(
"Windows",
True,
"CPython",
("3", "5", "0a1"),
"numpy-1.16.2-cp35-cp35m-win32.whl",
),
],
)
def test_fallback_selects_correct_platform_wheel(
plat, is32bit, imp_name, pyver, best_wheel
):
repo = MockRepository(
fallback=True, plat=plat, is32bit=is32bit, imp_name=imp_name, pyver=pyver
)
assert best_wheel == repo._pick_platform_specific_wheel(numpy_plat_spec_wheels)


def test_fallback_inspects_sdist_first_if_no_matching_wheels_can_be_found():
repo = MockRepository(fallback=True)

Expand Down