Skip to content

Avoid pip install --dry-run downloading full wheels #13482

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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions news/12603.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
When PEP-658 metadata is available, full distribution download no longer occurs when using dry-run mode on install.
4 changes: 2 additions & 2 deletions src/pip/_internal/commands/download.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,15 +130,15 @@ def run(self, options: Values, args: list[str]) -> int:

requirement_set = resolver.resolve(reqs, check_supported_wheels=True)

preparer.prepare_linked_requirements_more(requirement_set.requirements.values())

downloaded: list[str] = []
for req in requirement_set.requirements.values():
if req.satisfied_by is None:
assert req.name is not None
preparer.save_linked_requirement(req)
downloaded.append(req.name)

preparer.prepare_linked_requirements_more(requirement_set.requirements.values())

if downloaded:
write_output("Successfully downloaded %s", " ".join(downloaded))

Expand Down
7 changes: 7 additions & 0 deletions src/pip/_internal/commands/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,13 @@ def run(self, options: Values, args: list[str]) -> int:
)
return SUCCESS

# If there is any more preparation to do for the actual installation, do
# so now. This includes actually downloading the files in the case that
# we have been using PEP-658 metadata so far.
preparer.prepare_linked_requirements_more(
requirement_set.requirements.values()
)

try:
pip_req = requirement_set.get_requirement("pip")
except KeyError:
Expand Down
4 changes: 2 additions & 2 deletions src/pip/_internal/commands/wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,15 +145,15 @@ def run(self, options: Values, args: list[str]) -> int:

requirement_set = resolver.resolve(reqs, check_supported_wheels=True)

preparer.prepare_linked_requirements_more(requirement_set.requirements.values())

reqs_to_build: list[InstallRequirement] = []
for req in requirement_set.requirements.values():
if req.is_wheel:
preparer.save_linked_requirement(req)
else:
reqs_to_build.append(req)

preparer.prepare_linked_requirements_more(requirement_set.requirements.values())

# build wheels
build_successes, build_failures = build(
reqs_to_build,
Expand Down
6 changes: 6 additions & 0 deletions src/pip/_internal/operations/prepare.py
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,12 @@ def prepare_linked_requirement(
metadata_dist = self._fetch_metadata_only(req)
if metadata_dist is not None:
req.needs_more_preparation = True
req.set_dist(metadata_dist)
# Ensure download_info is available even in dry-run mode
if req.download_info is None:
req.download_info = direct_url_from_link(
req.link, req.source_dir
)
return metadata_dist

# None of the optimizations worked, fully prepare the requirement
Expand Down
11 changes: 10 additions & 1 deletion src/pip/_internal/req/req_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,10 @@ def __init__(
# details).
self.metadata_directory: str | None = None

# The cached metadata distribution that this requirement represents.
# See get_dist / set_dist.
self._distribution: BaseDistribution | None = None

# The static build requirements (from pyproject.toml)
self.pyproject_requires: list[str] | None = None

Expand Down Expand Up @@ -604,8 +608,13 @@ def metadata(self) -> Any:

return self._metadata

def set_dist(self, distribution: BaseDistribution) -> None:
self._distribution = distribution

def get_dist(self) -> BaseDistribution:
if self.metadata_directory:
if self._distribution is not None:
return self._distribution
elif self.metadata_directory:
return get_directory_distribution(self.metadata_directory)
elif self.local_file_path and self.is_wheel:
assert self.req is not None
Expand Down
5 changes: 0 additions & 5 deletions src/pip/_internal/resolution/resolvelib/resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,11 +180,6 @@ def resolve(

req_set.add_named_requirement(ireq)

reqs = req_set.all_requirements
self.factory.preparer.prepare_linked_requirements_more(reqs)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is the key change - the resolver no longer triggers additional preparation. Instead, we move this responsibility to consumers of the resolve result.

Technically, I could have made the preparer aware of the fact that we don't want to download anything, but the preparer is already a grab-bag of flags, and it felt cleaner to do it this way.

for req in reqs:
req.prepared = True
req.needs_more_preparation = False
return req_set

def get_installation_order(
Expand Down
Loading