Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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 ``pip lock`` or ``pip install --dry-run``.
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 @@ -414,6 +414,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 @@ -531,6 +531,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