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

Re-install PEP 508 wheel dependencies when the version changes #6402

Closed
wants to merge 17 commits into from
Closed
1 change: 1 addition & 0 deletions news/5780.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Re-install PEP 508 dependencies when the URL changes.
1 change: 1 addition & 0 deletions news/6402.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Re-install PEP 508 dependencies when the URL changes.
15 changes: 15 additions & 0 deletions src/pip/_internal/req/constructors.py
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,21 @@ def install_req_from_req_string(
"{} depends on {} ".format(comes_from.name, req)
)

if req.url:
rouge8 marked this conversation as resolved.
Show resolved Hide resolved
# Create an InstallRequirement for a wheel-like PEP 508 URL with the
# same behavior as 'pip install req.url'
parts = parse_req_from_line(req.url, None)
link = Link(req.url)
if link.is_wheel:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dropped support for directories and non-wheel URLs in 2e082f9 because that was triggering an assertion error in the resolver. Hopefully this PR can be a stopgap until #7678 can be addressed in the new resolver?

return InstallRequirement(
parts.requirement,
comes_from=comes_from,
link=parts.link,
markers=parts.markers,
use_pep517=use_pep517,
isolated=isolated,
extras=req.extras,
)
return InstallRequirement(
req, comes_from, isolated=isolated, use_pep517=use_pep517
)
Expand Down
31 changes: 31 additions & 0 deletions tests/functional/test_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -1592,6 +1592,37 @@ def test_install_pep508_with_url_in_install_requires(script):
assert "Successfully installed packaging-15.3" in str(res), str(res)


def test_install_pep508_with_url_in_install_requires_url_change_wheel(script):
dep_v1_path = create_basic_wheel_for_package(
script, name='dep', version='1.0',
)
dep_v2_path = create_basic_wheel_for_package(
script, name='dep', version='2.0',
)

pkga_path = create_basic_wheel_for_package(
script, name='pkga', version='1.0',
depends=['dep@' + path_to_url(dep_v1_path)],
)
res = script.pip('install', pkga_path)
assert "Successfully installed dep-1.0" in str(res), str(res)

pkga_path.unlink()

# Updating the URL to the dependency installs the updated dependency
pkga_path = create_basic_wheel_for_package(
script, name='pkga', version='2.0',
depends=['dep@' + path_to_url(dep_v2_path)],
)
res = script.pip('install', pkga_path)
assert "Successfully installed dep-2.0" in str(res), str(res)

res = script.pip('install', pkga_path)
# pip can determine the version from a wheel's filename, so the
# dependency is not reinstalled if the URL doesn't change
assert "Requirement already satisfied: dep==2.0" in str(res), str(res)


@pytest.mark.network
@pytest.mark.parametrize('index', (PyPI.simple_url, TestPyPI.simple_url))
def test_install_from_test_pypi_with_ext_url_dep_is_blocked(script, index):
Expand Down
46 changes: 44 additions & 2 deletions tests/unit/test_req_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ def test_install_req_from_string_without_comes_from(self):

assert isinstance(install_req, InstallRequirement)
assert install_req.link.url == wheel_url
assert install_req.req.url == wheel_url
assert install_req.req.url is None
rouge8 marked this conversation as resolved.
Show resolved Hide resolved
assert install_req.comes_from is None
assert install_req.is_wheel

Expand Down Expand Up @@ -106,5 +106,47 @@ def test_install_req_from_string_with_comes_from_without_link(self):
assert isinstance(install_req, InstallRequirement)
assert install_req.comes_from.link is None
assert install_req.link.url == wheel_url
assert install_req.req.url == wheel_url
assert install_req.req.url is None
assert install_req.is_wheel

@pytest.mark.parametrize("use_pep517", [None, True, False])
def test_install_req_from_string_pep508_url_wheel(self, use_pep517):
"""
install_req_from_string parses the version from PEP 508 URLs that point
to wheels so that updating the URL reinstalls the package.
"""
wheel_url = ("https://download.pytorch.org/whl/cu90/"
"torch-1.0.0-cp36-cp36m-win_amd64.whl")
install_str = "torch@ " + wheel_url
install_req = install_req_from_req_string(
install_str, use_pep517=use_pep517
)

assert isinstance(install_req, InstallRequirement)
assert str(install_req.req) == "torch==1.0.0"
assert install_req.link.url == wheel_url
assert install_req.is_wheel
assert install_req.use_pep517 == use_pep517

@pytest.mark.parametrize("use_pep517", [None, True, False])
def test_install_req_from_string_pep508_url_wheel_extras(self, use_pep517):
"""
install_req_from_string parses the version and extras from PEP 508 URLs
that point to wheels so that updating the URL reinstalls the package.
"""
wheel_url = (
"https://files.pythonhosted.org/packages/51/bd/"
"23c926cd341ea6b7dd0b2a00aba99ae0f828be89d72b2190f27c11d4b7fb/"
"requests-2.22.0-py2.py3-none-any.whl"
)
install_str = "requests[security] @ " + wheel_url
install_req = install_req_from_req_string(
install_str, use_pep517=use_pep517
)

assert isinstance(install_req, InstallRequirement)
assert str(install_req.req) == "requests==2.22.0"
assert install_req.link.url == wheel_url
assert install_req.is_wheel
assert install_req.use_pep517 == use_pep517
assert install_req.extras == {"security"}