Skip to content

Commit ee4371c

Browse files
authored
Merge pull request #8685 from chrahunt/use-preparer-for-lazy-wheels
2 parents 370322e + 8b838eb commit ee4371c

File tree

7 files changed

+92
-51
lines changed

7 files changed

+92
-51
lines changed

src/pip/_internal/cli/req_command.py

+15-1
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,19 @@ def make_requirement_preparer(
218218
temp_build_dir_path = temp_build_dir.path
219219
assert temp_build_dir_path is not None
220220

221+
if '2020-resolver' in options.features_enabled:
222+
lazy_wheel = 'fast-deps' in options.features_enabled
223+
if lazy_wheel:
224+
logger.warning(
225+
'pip is using lazily downloaded wheels using HTTP '
226+
'range requests to obtain dependency information. '
227+
'This experimental feature is enabled through '
228+
'--use-feature=fast-deps and it is not ready for '
229+
'production.'
230+
)
231+
else:
232+
lazy_wheel = False
233+
221234
return RequirementPreparer(
222235
build_dir=temp_build_dir_path,
223236
src_dir=options.src_dir,
@@ -229,6 +242,7 @@ def make_requirement_preparer(
229242
finder=finder,
230243
require_hashes=options.require_hashes,
231244
use_user_site=use_user_site,
245+
lazy_wheel=lazy_wheel,
232246
)
233247

234248
@staticmethod
@@ -259,6 +273,7 @@ def make_resolver(
259273
# "Resolver" class being redefined.
260274
if '2020-resolver' in options.features_enabled:
261275
import pip._internal.resolution.resolvelib.resolver
276+
262277
return pip._internal.resolution.resolvelib.resolver.Resolver(
263278
preparer=preparer,
264279
finder=finder,
@@ -271,7 +286,6 @@ def make_resolver(
271286
force_reinstall=force_reinstall,
272287
upgrade_strategy=upgrade_strategy,
273288
py_version_info=py_version_info,
274-
lazy_wheel='fast-deps' in options.features_enabled,
275289
)
276290
import pip._internal.resolution.legacy.resolver
277291
return pip._internal.resolution.legacy.resolver.Resolver(

src/pip/_internal/operations/prepare.py

+47
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
import os
1010
import shutil
1111

12+
from pip._vendor.contextlib2 import suppress
13+
from pip._vendor.packaging.utils import canonicalize_name
1214
from pip._vendor.six import PY2
1315

1416
from pip._internal.distributions import (
@@ -24,6 +26,11 @@
2426
PreviousBuildDirError,
2527
VcsHashUnsupported,
2628
)
29+
from pip._internal.models.wheel import Wheel
30+
from pip._internal.network.lazy_wheel import (
31+
HTTPRangeRequestUnsupported,
32+
dist_from_wheel_url,
33+
)
2734
from pip._internal.utils.filesystem import copy2_fixed
2835
from pip._internal.utils.hashes import MissingHashes
2936
from pip._internal.utils.logging import indent_log
@@ -329,6 +336,7 @@ def __init__(
329336
finder, # type: PackageFinder
330337
require_hashes, # type: bool
331338
use_user_site, # type: bool
339+
lazy_wheel, # type: bool
332340
):
333341
# type: (...) -> None
334342
super(RequirementPreparer, self).__init__()
@@ -362,6 +370,9 @@ def __init__(
362370
# Should install in user site-packages?
363371
self.use_user_site = use_user_site
364372

373+
# Should wheels be downloaded lazily?
374+
self.use_lazy_wheel = lazy_wheel
375+
365376
@property
366377
def _download_should_save(self):
367378
# type: () -> bool
@@ -448,12 +459,48 @@ def _get_linked_req_hashes(self, req):
448459
# showing the user what the hash should be.
449460
return req.hashes(trust_internet=False) or MissingHashes()
450461

462+
def _fetch_metadata(preparer, link):
463+
# type: (Link) -> Optional[Distribution]
464+
"""Fetch metadata, using lazy wheel if possible."""
465+
use_lazy_wheel = preparer.use_lazy_wheel
466+
remote_wheel = link.is_wheel and not link.is_file
467+
if use_lazy_wheel and remote_wheel and not preparer.require_hashes:
468+
wheel = Wheel(link.filename)
469+
name = canonicalize_name(wheel.name)
470+
# If HTTPRangeRequestUnsupported is raised, fallback silently.
471+
with indent_log(), suppress(HTTPRangeRequestUnsupported):
472+
logger.info(
473+
'Obtaining dependency information from %s %s',
474+
name, wheel.version,
475+
)
476+
url = link.url.split('#', 1)[0]
477+
session = preparer.downloader._session
478+
return dist_from_wheel_url(name, url, session)
479+
return None
480+
451481
def prepare_linked_requirement(self, req, parallel_builds=False):
452482
# type: (InstallRequirement, bool) -> Distribution
453483
"""Prepare a requirement to be obtained from req.link."""
454484
assert req.link
455485
link = req.link
456486
self._log_preparing_link(req)
487+
wheel_dist = self._fetch_metadata(link)
488+
if wheel_dist is not None:
489+
req.needs_more_preparation = True
490+
return wheel_dist
491+
return self._prepare_linked_requirement(req, parallel_builds)
492+
493+
def prepare_linked_requirement_more(self, req, parallel_builds=False):
494+
# type: (InstallRequirement, bool) -> None
495+
"""Prepare a linked requirement more, if needed."""
496+
if not req.needs_more_preparation:
497+
return
498+
self._prepare_linked_requirement(req, parallel_builds)
499+
500+
def _prepare_linked_requirement(self, req, parallel_builds):
501+
# type: (InstallRequirement, bool) -> Distribution
502+
assert req.link
503+
link = req.link
457504
if link.is_wheel and self.wheel_download_dir:
458505
# Download wheels to a dedicated dir when doing `pip wheel`.
459506
download_dir = self.wheel_download_dir

src/pip/_internal/req/req_install.py

+3
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,9 @@ def __init__(
213213
# but after loading this flag should be treated as read only.
214214
self.use_pep517 = use_pep517
215215

216+
# This requirement needs more preparation before it can be built
217+
self.needs_more_preparation = False
218+
216219
def __str__(self):
217220
# type: () -> str
218221
if self.req:

src/pip/_internal/resolution/resolvelib/candidates.py

+23-38
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,17 @@
11
import logging
22
import sys
33

4-
from pip._vendor.contextlib2 import suppress
54
from pip._vendor.packaging.specifiers import InvalidSpecifier, SpecifierSet
65
from pip._vendor.packaging.utils import canonicalize_name
76
from pip._vendor.packaging.version import Version
87

98
from pip._internal.exceptions import HashError, MetadataInconsistent
10-
from pip._internal.network.lazy_wheel import (
11-
HTTPRangeRequestUnsupported,
12-
dist_from_wheel_url,
13-
)
9+
from pip._internal.models.wheel import Wheel
1410
from pip._internal.req.constructors import (
1511
install_req_from_editable,
1612
install_req_from_line,
1713
)
1814
from pip._internal.req.req_install import InstallRequirement
19-
from pip._internal.utils.logging import indent_log
2015
from pip._internal.utils.misc import dist_is_editable, normalize_version_info
2116
from pip._internal.utils.packaging import get_requires_python
2217
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
@@ -147,7 +142,6 @@ def __init__(
147142
self._name = name
148143
self._version = version
149144
self._dist = None # type: Optional[Distribution]
150-
self._prepared = False
151145

152146
def __repr__(self):
153147
# type: () -> str
@@ -203,12 +197,11 @@ def _prepare_distribution(self):
203197
# type: () -> Distribution
204198
raise NotImplementedError("Override in subclass")
205199

206-
def _check_metadata_consistency(self):
207-
# type: () -> None
200+
def _check_metadata_consistency(self, dist):
201+
# type: (Distribution) -> None
208202
"""Check for consistency of project name and version of dist."""
209203
# TODO: (Longer term) Rather than abort, reject this candidate
210204
# and backtrack. This would need resolvelib support.
211-
dist = self._dist # type: Distribution
212205
name = canonicalize_name(dist.project_name)
213206
if self._name is not None and self._name != name:
214207
raise MetadataInconsistent(self._ireq, "name", dist.project_name)
@@ -218,45 +211,23 @@ def _check_metadata_consistency(self):
218211

219212
def _prepare(self):
220213
# type: () -> None
221-
if self._prepared:
214+
if self._dist is not None:
222215
return
223216
try:
224-
self._dist = self._prepare_distribution()
217+
dist = self._prepare_distribution()
225218
except HashError as e:
226219
e.req = self._ireq
227220
raise
228221

229-
assert self._dist is not None, "Distribution already installed"
230-
self._check_metadata_consistency()
231-
self._prepared = True
232-
233-
def _fetch_metadata(self):
234-
# type: () -> None
235-
"""Fetch metadata, using lazy wheel if possible."""
236-
preparer = self._factory.preparer
237-
use_lazy_wheel = self._factory.use_lazy_wheel
238-
remote_wheel = self._link.is_wheel and not self._link.is_file
239-
if use_lazy_wheel and remote_wheel and not preparer.require_hashes:
240-
assert self._name is not None
241-
logger.info('Collecting %s', self._ireq.req or self._ireq)
242-
# If HTTPRangeRequestUnsupported is raised, fallback silently.
243-
with indent_log(), suppress(HTTPRangeRequestUnsupported):
244-
logger.info(
245-
'Obtaining dependency information from %s %s',
246-
self._name, self._version,
247-
)
248-
url = self._link.url.split('#', 1)[0]
249-
session = preparer.downloader._session
250-
self._dist = dist_from_wheel_url(self._name, url, session)
251-
self._check_metadata_consistency()
252-
if self._dist is None:
253-
self._prepare()
222+
assert dist is not None, "Distribution already installed"
223+
self._check_metadata_consistency(dist)
224+
self._dist = dist
254225

255226
@property
256227
def dist(self):
257228
# type: () -> Distribution
258229
if self._dist is None:
259-
self._fetch_metadata()
230+
self._prepare()
260231
return self._dist
261232

262233
def _get_requires_python_specifier(self):
@@ -309,6 +280,20 @@ def __init__(
309280
logger.debug("Using cached wheel link: %s", cache_entry.link)
310281
link = cache_entry.link
311282
ireq = make_install_req_from_link(link, template)
283+
assert ireq.link == link
284+
if ireq.link.is_wheel and not ireq.link.is_file:
285+
wheel = Wheel(ireq.link.filename)
286+
wheel_name = canonicalize_name(wheel.name)
287+
assert name == wheel_name, (
288+
"{!r} != {!r} for wheel".format(name, wheel_name)
289+
)
290+
# Version may not be present for PEP 508 direct URLs
291+
if version is not None:
292+
assert str(version) == wheel.version, (
293+
"{!r} != {!r} for wheel {}".format(
294+
version, wheel.version, name
295+
)
296+
)
312297

313298
if (cache_entry is not None and
314299
cache_entry.persistent and

src/pip/_internal/resolution/resolvelib/factory.py

-2
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,6 @@ def __init__(
8282
ignore_installed, # type: bool
8383
ignore_requires_python, # type: bool
8484
py_version_info=None, # type: Optional[Tuple[int, ...]]
85-
lazy_wheel=False, # type: bool
8685
):
8786
# type: (...) -> None
8887
self._finder = finder
@@ -93,7 +92,6 @@ def __init__(
9392
self._use_user_site = use_user_site
9493
self._force_reinstall = force_reinstall
9594
self._ignore_requires_python = ignore_requires_python
96-
self.use_lazy_wheel = lazy_wheel
9795

9896
self._link_candidate_cache = {} # type: Cache[LinkCandidate]
9997
self._editable_candidate_cache = {} # type: Cache[EditableCandidate]

src/pip/_internal/resolution/resolvelib/resolver.py

+3-10
Original file line numberDiff line numberDiff line change
@@ -49,17 +49,8 @@ def __init__(
4949
force_reinstall, # type: bool
5050
upgrade_strategy, # type: str
5151
py_version_info=None, # type: Optional[Tuple[int, ...]]
52-
lazy_wheel=False, # type: bool
5352
):
5453
super(Resolver, self).__init__()
55-
if lazy_wheel:
56-
logger.warning(
57-
'pip is using lazily downloaded wheels using HTTP '
58-
'range requests to obtain dependency information. '
59-
'This experimental feature is enabled through '
60-
'--use-feature=fast-deps and it is not ready for production.'
61-
)
62-
6354
assert upgrade_strategy in self._allowed_strategies
6455

6556
self.factory = Factory(
@@ -72,7 +63,6 @@ def __init__(
7263
ignore_installed=ignore_installed,
7364
ignore_requires_python=ignore_requires_python,
7465
py_version_info=py_version_info,
75-
lazy_wheel=lazy_wheel,
7666
)
7767
self.ignore_dependencies = ignore_dependencies
7868
self.upgrade_strategy = upgrade_strategy
@@ -169,6 +159,9 @@ def resolve(self, root_reqs, check_supported_wheels):
169159

170160
req_set.add_named_requirement(ireq)
171161

162+
for actual_req in req_set.all_requirements:
163+
self.factory.preparer.prepare_linked_requirement_more(actual_req)
164+
172165
return req_set
173166

174167
def get_installation_order(self, req_set):

tests/unit/test_req.py

+1
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ def _basic_resolver(self, finder, require_hashes=False):
8989
finder=finder,
9090
require_hashes=require_hashes,
9191
use_user_site=False,
92+
lazy_wheel=False,
9293
)
9394
yield Resolver(
9495
preparer=preparer,

0 commit comments

Comments
 (0)