Skip to content

Commit c6baa75

Browse files
committed
Add download_info to InstallRequirement
1 parent b69f505 commit c6baa75

File tree

3 files changed

+145
-1
lines changed

3 files changed

+145
-1
lines changed

src/pip/_internal/operations/prepare.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
)
2626
from pip._internal.index.package_finder import PackageFinder
2727
from pip._internal.metadata import BaseDistribution
28+
from pip._internal.models.direct_url import ArchiveInfo
2829
from pip._internal.models.link import Link
2930
from pip._internal.models.wheel import Wheel
3031
from pip._internal.network.download import BatchDownloader, Downloader
@@ -35,9 +36,18 @@
3536
from pip._internal.network.session import PipSession
3637
from pip._internal.operations.build.build_tracker import BuildTracker
3738
from pip._internal.req.req_install import InstallRequirement
39+
from pip._internal.utils.direct_url_helpers import (
40+
direct_url_for_editable,
41+
direct_url_from_link,
42+
)
3843
from pip._internal.utils.hashes import Hashes, MissingHashes
3944
from pip._internal.utils.logging import indent_log
40-
from pip._internal.utils.misc import display_path, hide_url, is_installable_dir
45+
from pip._internal.utils.misc import (
46+
display_path,
47+
hash_file,
48+
hide_url,
49+
is_installable_dir,
50+
)
4151
from pip._internal.utils.temp_dir import TempDirectory
4252
from pip._internal.utils.unpacking import unpack_file
4353
from pip._internal.vcs import vcs
@@ -489,10 +499,26 @@ def _prepare_linked_requirement(
489499
hashes.check_against_path(file_path)
490500
local_file = File(file_path, content_type=None)
491501

502+
# If download_info is set, we got it from the wheel cache.
503+
if req.download_info is None:
504+
# Editables don't go through this function (see
505+
# prepare_editable_requirement).
506+
assert not req.editable
507+
req.download_info = direct_url_from_link(link, req.source_dir)
508+
492509
# For use in later processing,
493510
# preserve the file path on the requirement.
494511
if local_file:
495512
req.local_file_path = local_file.path
513+
# Make sure we have a hash in download_info. If we got it as part of the
514+
# URL, it will have been verified and we can rely on it. Otherwise we
515+
# compute it from the downloaded file.
516+
if (
517+
isinstance(req.download_info.info, ArchiveInfo)
518+
and not req.download_info.info.hash
519+
):
520+
hash = hash_file(local_file.path)[0].hexdigest()
521+
req.download_info.info.hash = f"sha256={hash}"
496522

497523
dist = _get_prepared_distribution(
498524
req,
@@ -547,6 +573,8 @@ def prepare_editable_requirement(
547573
)
548574
req.ensure_has_source_dir(self.src_dir)
549575
req.update_editable()
576+
assert req.source_dir
577+
req.download_info = direct_url_for_editable(req.unpacked_source_directory)
550578

551579
dist = _get_prepared_distribution(
552580
req,

src/pip/_internal/req/req_install.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
get_default_environment,
2727
get_directory_distribution,
2828
)
29+
from pip._internal.models.direct_url import DirectUrl
2930
from pip._internal.models.link import Link
3031
from pip._internal.operations.build.metadata import generate_metadata
3132
from pip._internal.operations.build.metadata_editable import generate_editable_metadata
@@ -112,6 +113,10 @@ def __init__(
112113
self.link = self.original_link = link
113114
self.original_link_is_in_wheel_cache = False
114115

116+
# Information about the location of the artifact that was downloaded . This
117+
# property is guaranteed to be set in resolver results.
118+
self.download_info: Optional[DirectUrl] = None
119+
115120
# Path to any downloaded or already-existing package.
116121
self.local_file_path: Optional[str] = None
117122
if self.link and self.link.is_file:
@@ -762,6 +767,7 @@ def install(
762767
if self.is_wheel:
763768
assert self.local_file_path
764769
direct_url = None
770+
# TODO this can be refactored to direct_url = self.download_info
765771
if self.editable:
766772
direct_url = direct_url_for_editable(self.unpacked_source_directory)
767773
elif self.original_link:

tests/unit/test_req.py

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
)
2323
from pip._internal.index.package_finder import PackageFinder
2424
from pip._internal.metadata import select_backend
25+
from pip._internal.models.direct_url import ArchiveInfo, DirInfo, VcsInfo
2526
from pip._internal.network.session import PipSession
2627
from pip._internal.operations.build.build_tracker import get_build_tracker
2728
from pip._internal.operations.prepare import RequirementPreparer
@@ -342,6 +343,115 @@ def test_hashed_deps_on_require_hashes(self) -> None:
342343
)
343344
)
344345

346+
def test_download_info_find_links(self, data: TestData) -> None:
347+
"""Test that download_info is set for requirements via find_links."""
348+
finder = make_test_finder(find_links=[data.find_links])
349+
with self._basic_resolver(finder) as resolver:
350+
ireq = get_processed_req_from_line("simple")
351+
reqset = resolver.resolve([ireq], True)
352+
assert len(reqset.all_requirements) == 1
353+
req = reqset.all_requirements[0]
354+
assert req.download_info
355+
assert isinstance(req.download_info.info, ArchiveInfo)
356+
assert req.download_info.info.hash
357+
358+
@pytest.mark.network
359+
def test_download_info_index_url(self) -> None:
360+
"""Test that download_info is set for requirements via index."""
361+
finder = make_test_finder(index_urls=["https://pypi.org/simple"])
362+
with self._basic_resolver(finder) as resolver:
363+
ireq = get_processed_req_from_line("initools")
364+
reqset = resolver.resolve([ireq], True)
365+
assert len(reqset.all_requirements) == 1
366+
req = reqset.all_requirements[0]
367+
assert req.download_info
368+
assert isinstance(req.download_info.info, ArchiveInfo)
369+
assert req.download_info.info.hash
370+
371+
@pytest.mark.network
372+
def test_download_info_web_archive(self) -> None:
373+
"""Test that download_info is set for requirements from a web archive."""
374+
finder = make_test_finder()
375+
with self._basic_resolver(finder) as resolver:
376+
ireq = get_processed_req_from_line(
377+
"pip-test-package @ "
378+
"https://github.com/pypa/pip-test-package/tarball/0.1.1"
379+
)
380+
reqset = resolver.resolve([ireq], True)
381+
assert len(reqset.all_requirements) == 1
382+
req = reqset.all_requirements[0]
383+
assert req.download_info
384+
assert (
385+
req.download_info.url
386+
== "https://github.com/pypa/pip-test-package/tarball/0.1.1"
387+
)
388+
assert isinstance(req.download_info.info, ArchiveInfo)
389+
assert (
390+
req.download_info.info.hash == "sha256="
391+
"ad977496000576e1b6c41f6449a9897087ce9da6db4f15b603fe8372af4bf3c6"
392+
)
393+
394+
def test_download_info_local_wheel(self, data: TestData) -> None:
395+
"""Test that download_info is set for requirements from a local wheel."""
396+
finder = make_test_finder()
397+
with self._basic_resolver(finder) as resolver:
398+
ireq = get_processed_req_from_line(
399+
f"{data.packages}/simplewheel-1.0-py2.py3-none-any.whl"
400+
)
401+
reqset = resolver.resolve([ireq], True)
402+
assert len(reqset.all_requirements) == 1
403+
req = reqset.all_requirements[0]
404+
assert req.download_info
405+
assert req.download_info.url.startswith("file://")
406+
assert isinstance(req.download_info.info, ArchiveInfo)
407+
assert (
408+
req.download_info.info.hash == "sha256="
409+
"e63aa139caee941ec7f33f057a5b987708c2128238357cf905429846a2008718"
410+
)
411+
412+
def test_download_info_local_dir(self, data: TestData) -> None:
413+
"""Test that download_info is set for requirements from a local dir."""
414+
finder = make_test_finder()
415+
with self._basic_resolver(finder) as resolver:
416+
ireq_url = path_to_url(data.packages / "FSPkg")
417+
ireq = get_processed_req_from_line(f"FSPkg @ {ireq_url}")
418+
reqset = resolver.resolve([ireq], True)
419+
assert len(reqset.all_requirements) == 1
420+
req = reqset.all_requirements[0]
421+
assert req.download_info
422+
assert req.download_info.url.startswith("file://")
423+
assert isinstance(req.download_info.info, DirInfo)
424+
425+
def test_download_info_local_editable_dir(self, data: TestData) -> None:
426+
"""Test that download_info is set for requirements from a local editable dir."""
427+
finder = make_test_finder()
428+
with self._basic_resolver(finder) as resolver:
429+
ireq_url = path_to_url(data.packages / "FSPkg")
430+
ireq = get_processed_req_from_line(f"-e {ireq_url}#egg=FSPkg")
431+
reqset = resolver.resolve([ireq], True)
432+
assert len(reqset.all_requirements) == 1
433+
req = reqset.all_requirements[0]
434+
assert req.download_info
435+
assert req.download_info.url.startswith("file://")
436+
assert isinstance(req.download_info.info, DirInfo)
437+
assert req.download_info.info.editable
438+
439+
@pytest.mark.network
440+
def test_download_info_vcs(self) -> None:
441+
"""Test that download_info is set for requirements from git."""
442+
finder = make_test_finder()
443+
with self._basic_resolver(finder) as resolver:
444+
ireq = get_processed_req_from_line(
445+
"pip-test-package @ git+https://github.com/pypa/pip-test-package"
446+
)
447+
reqset = resolver.resolve([ireq], True)
448+
assert len(reqset.all_requirements) == 1
449+
req = reqset.all_requirements[0]
450+
assert req.download_info
451+
assert isinstance(req.download_info.info, VcsInfo)
452+
assert req.download_info.url == "https://github.com/pypa/pip-test-package"
453+
assert req.download_info.info.vcs == "git"
454+
345455

346456
class TestInstallRequirement:
347457
def setup(self) -> None:

0 commit comments

Comments
 (0)