Skip to content

Commit 5a329fe

Browse files
cache "concrete" dists by Distribution instead of InstallRequirement
1 parent 203780b commit 5a329fe

File tree

15 files changed

+234
-73
lines changed

15 files changed

+234
-73
lines changed

news/12863.trivial.rst

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Cache "concrete" dists by ``Distribution`` instead of ``InstallRequirement``.

src/pip/_internal/distributions/__init__.py

+5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from pip._internal.distributions.base import AbstractDistribution
2+
from pip._internal.distributions.installed import InstalledDistribution
23
from pip._internal.distributions.sdist import SourceDistribution
34
from pip._internal.distributions.wheel import WheelDistribution
45
from pip._internal.req.req_install import InstallRequirement
@@ -8,6 +9,10 @@ def make_distribution_for_install_requirement(
89
install_req: InstallRequirement,
910
) -> AbstractDistribution:
1011
"""Returns a Distribution for the given InstallRequirement"""
12+
# Only pre-installed requirements will have a .satisfied_by dist.
13+
if install_req.satisfied_by:
14+
return InstalledDistribution(install_req)
15+
1116
# Editable requirements will always be source distributions. They use the
1217
# legacy logic until we create a modern standard for them.
1318
if install_req.editable:

src/pip/_internal/distributions/base.py

+16-3
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,17 @@ def build_tracker_id(self) -> Optional[str]:
3737
3838
If None, then this dist has no work to do in the build tracker, and
3939
``.prepare_distribution_metadata()`` will not be called."""
40-
raise NotImplementedError()
40+
...
4141

4242
@abc.abstractmethod
4343
def get_metadata_distribution(self) -> BaseDistribution:
44-
raise NotImplementedError()
44+
"""Generate a concrete ``BaseDistribution`` instance for this artifact.
45+
46+
The implementation should also cache the result with
47+
``self.req.cache_concrete_dist()`` so the distribution is available to other
48+
users of the ``InstallRequirement``. This method is not called within the build
49+
tracker context, so it should not identify any new setup requirements."""
50+
...
4551

4652
@abc.abstractmethod
4753
def prepare_distribution_metadata(
@@ -50,4 +56,11 @@ def prepare_distribution_metadata(
5056
build_isolation: bool,
5157
check_build_deps: bool,
5258
) -> None:
53-
raise NotImplementedError()
59+
"""Generate the information necessary to extract metadata from the artifact.
60+
61+
This method will be executed within the context of ``BuildTracker#track()``, so
62+
it needs to fully identify any setup requirements so they can be added to the
63+
same active set of tracked builds, while ``.get_metadata_distribution()`` takes
64+
care of generating and caching the ``BaseDistribution`` to expose to the rest of
65+
the resolve."""
66+
...

src/pip/_internal/distributions/installed.py

+9-5
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
from typing import Optional
1+
from typing import TYPE_CHECKING, Optional
22

33
from pip._internal.distributions.base import AbstractDistribution
4-
from pip._internal.index.package_finder import PackageFinder
54
from pip._internal.metadata import BaseDistribution
65

6+
if TYPE_CHECKING:
7+
from pip._internal.index.package_finder import PackageFinder
8+
79

810
class InstalledDistribution(AbstractDistribution):
911
"""Represents an installed package.
@@ -17,12 +19,14 @@ def build_tracker_id(self) -> Optional[str]:
1719
return None
1820

1921
def get_metadata_distribution(self) -> BaseDistribution:
20-
assert self.req.satisfied_by is not None, "not actually installed"
21-
return self.req.satisfied_by
22+
dist = self.req.satisfied_by
23+
assert dist is not None, "not actually installed"
24+
self.req.cache_concrete_dist(dist)
25+
return dist
2226

2327
def prepare_distribution_metadata(
2428
self,
25-
finder: PackageFinder,
29+
finder: "PackageFinder",
2630
build_isolation: bool,
2731
check_build_deps: bool,
2832
) -> None:

src/pip/_internal/distributions/sdist.py

+15-5
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import logging
2-
from typing import TYPE_CHECKING, Iterable, Optional, Set, Tuple
2+
from typing import TYPE_CHECKING, Iterable, Set, Tuple
33

44
from pip._internal.build_env import BuildEnvironment
55
from pip._internal.distributions.base import AbstractDistribution
66
from pip._internal.exceptions import InstallationError
7-
from pip._internal.metadata import BaseDistribution
7+
from pip._internal.metadata import BaseDistribution, get_directory_distribution
88
from pip._internal.utils.subprocess import runner_with_spinner_message
99

1010
if TYPE_CHECKING:
@@ -21,13 +21,19 @@ class SourceDistribution(AbstractDistribution):
2121
"""
2222

2323
@property
24-
def build_tracker_id(self) -> Optional[str]:
24+
def build_tracker_id(self) -> str:
2525
"""Identify this requirement uniquely by its link."""
2626
assert self.req.link
2727
return self.req.link.url_without_fragment
2828

2929
def get_metadata_distribution(self) -> BaseDistribution:
30-
return self.req.get_dist()
30+
assert (
31+
self.req.metadata_directory
32+
), "Set as part of .prepare_distribution_metadata()"
33+
dist = get_directory_distribution(self.req.metadata_directory)
34+
self.req.cache_concrete_dist(dist)
35+
self.req.validate_sdist_metadata()
36+
return dist
3137

3238
def prepare_distribution_metadata(
3339
self,
@@ -66,7 +72,11 @@ def prepare_distribution_metadata(
6672
self._raise_conflicts("the backend dependencies", conflicting)
6773
if missing:
6874
self._raise_missing_reqs(missing)
69-
self.req.prepare_metadata()
75+
76+
# NB: we must still call .cache_concrete_dist() and .validate_sdist_metadata()
77+
# before the InstallRequirement itself has been updated with the metadata from
78+
# this directory!
79+
self.req.prepare_metadata_directory()
7080

7181
def _prepare_build_backend(self, finder: "PackageFinder") -> None:
7282
# Isolate in a BuildEnvironment and install the build-time

src/pip/_internal/distributions/wheel.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@ def get_metadata_distribution(self) -> BaseDistribution:
3131
assert self.req.local_file_path, "Set as part of preparation during download"
3232
assert self.req.name, "Wheels are never unnamed"
3333
wheel = FilesystemWheel(self.req.local_file_path)
34-
return get_wheel_distribution(wheel, canonicalize_name(self.req.name))
34+
dist = get_wheel_distribution(wheel, canonicalize_name(self.req.name))
35+
self.req.cache_concrete_dist(dist)
36+
return dist
3537

3638
def prepare_distribution_metadata(
3739
self,

src/pip/_internal/metadata/base.py

+21
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,15 @@ class RequiresEntry(NamedTuple):
9797

9898

9999
class BaseDistribution(Protocol):
100+
@property
101+
def is_concrete(self) -> bool:
102+
"""Whether the distribution really exists somewhere on disk.
103+
104+
If this is false, it has been synthesized from metadata, e.g. via
105+
``.from_metadata_file_contents()``, or ``.from_wheel()`` against
106+
a ``MemoryWheel``."""
107+
raise NotImplementedError()
108+
100109
@classmethod
101110
def from_directory(cls, directory: str) -> "BaseDistribution":
102111
"""Load the distribution from a metadata directory.
@@ -667,6 +676,10 @@ def iter_installed_distributions(
667676
class Wheel(Protocol):
668677
location: str
669678

679+
@property
680+
def is_concrete(self) -> bool:
681+
raise NotImplementedError()
682+
670683
def as_zipfile(self) -> zipfile.ZipFile:
671684
raise NotImplementedError()
672685

@@ -675,6 +688,10 @@ class FilesystemWheel(Wheel):
675688
def __init__(self, location: str) -> None:
676689
self.location = location
677690

691+
@property
692+
def is_concrete(self) -> bool:
693+
return True
694+
678695
def as_zipfile(self) -> zipfile.ZipFile:
679696
return zipfile.ZipFile(self.location, allowZip64=True)
680697

@@ -684,5 +701,9 @@ def __init__(self, location: str, stream: IO[bytes]) -> None:
684701
self.location = location
685702
self.stream = stream
686703

704+
@property
705+
def is_concrete(self) -> bool:
706+
return False
707+
687708
def as_zipfile(self) -> zipfile.ZipFile:
688709
return zipfile.ZipFile(self.stream, allowZip64=True)

src/pip/_internal/metadata/importlib/_dists.py

+16-3
Original file line numberDiff line numberDiff line change
@@ -102,16 +102,22 @@ def __init__(
102102
dist: importlib.metadata.Distribution,
103103
info_location: Optional[BasePath],
104104
installed_location: Optional[BasePath],
105+
concrete: bool,
105106
) -> None:
106107
self._dist = dist
107108
self._info_location = info_location
108109
self._installed_location = installed_location
110+
self._concrete = concrete
111+
112+
@property
113+
def is_concrete(self) -> bool:
114+
return self._concrete
109115

110116
@classmethod
111117
def from_directory(cls, directory: str) -> BaseDistribution:
112118
info_location = pathlib.Path(directory)
113119
dist = importlib.metadata.Distribution.at(info_location)
114-
return cls(dist, info_location, info_location.parent)
120+
return cls(dist, info_location, info_location.parent, concrete=True)
115121

116122
@classmethod
117123
def from_metadata_file_contents(
@@ -128,7 +134,7 @@ def from_metadata_file_contents(
128134
metadata_path.write_bytes(metadata_contents)
129135
# Construct dist pointing to the newly created directory.
130136
dist = importlib.metadata.Distribution.at(metadata_path.parent)
131-
return cls(dist, metadata_path.parent, None)
137+
return cls(dist, metadata_path.parent, None, concrete=False)
132138

133139
@classmethod
134140
def from_wheel(cls, wheel: Wheel, name: str) -> BaseDistribution:
@@ -137,7 +143,14 @@ def from_wheel(cls, wheel: Wheel, name: str) -> BaseDistribution:
137143
dist = WheelDistribution.from_zipfile(zf, name, wheel.location)
138144
except zipfile.BadZipFile as e:
139145
raise InvalidWheel(wheel.location, name) from e
140-
return cls(dist, dist.info_location, pathlib.PurePosixPath(wheel.location))
146+
except UnsupportedWheel as e:
147+
raise UnsupportedWheel(f"{name} has an invalid wheel, {e}")
148+
return cls(
149+
dist,
150+
dist.info_location,
151+
pathlib.PurePosixPath(wheel.location),
152+
concrete=wheel.is_concrete,
153+
)
141154

142155
@property
143156
def location(self) -> Optional[str]:

src/pip/_internal/metadata/importlib/_envs.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ def find(self, location: str) -> Iterator[BaseDistribution]:
8080
installed_location: Optional[BasePath] = None
8181
else:
8282
installed_location = info_location.parent
83-
yield Distribution(dist, info_location, installed_location)
83+
yield Distribution(dist, info_location, installed_location, concrete=True)
8484

8585
def find_linked(self, location: str) -> Iterator[BaseDistribution]:
8686
"""Read location in egg-link files and return distributions in there.
@@ -104,7 +104,7 @@ def find_linked(self, location: str) -> Iterator[BaseDistribution]:
104104
continue
105105
target_location = str(path.joinpath(target_rel))
106106
for dist, info_location in self._find_impl(target_location):
107-
yield Distribution(dist, info_location, path)
107+
yield Distribution(dist, info_location, path, concrete=True)
108108

109109
def _find_eggs_in_dir(self, location: str) -> Iterator[BaseDistribution]:
110110
from pip._vendor.pkg_resources import find_distributions
@@ -116,7 +116,7 @@ def _find_eggs_in_dir(self, location: str) -> Iterator[BaseDistribution]:
116116
if not entry.name.endswith(".egg"):
117117
continue
118118
for dist in find_distributions(entry.path):
119-
yield legacy.Distribution(dist)
119+
yield legacy.Distribution(dist, concrete=True)
120120

121121
def _find_eggs_in_zip(self, location: str) -> Iterator[BaseDistribution]:
122122
from pip._vendor.pkg_resources import find_eggs_in_zip
@@ -128,7 +128,7 @@ def _find_eggs_in_zip(self, location: str) -> Iterator[BaseDistribution]:
128128
except zipimport.ZipImportError:
129129
return
130130
for dist in find_eggs_in_zip(importer, location):
131-
yield legacy.Distribution(dist)
131+
yield legacy.Distribution(dist, concrete=True)
132132

133133
def find_eggs(self, location: str) -> Iterator[BaseDistribution]:
134134
"""Find eggs in a location.

src/pip/_internal/metadata/pkg_resources.py

+10-5
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,9 @@ def run_script(self, script_name: str, namespace: str) -> None:
8181

8282

8383
class Distribution(BaseDistribution):
84-
def __init__(self, dist: pkg_resources.Distribution) -> None:
84+
def __init__(self, dist: pkg_resources.Distribution, concrete: bool) -> None:
8585
self._dist = dist
86+
self._concrete = concrete
8687
# This is populated lazily, to avoid loading metadata for all possible
8788
# distributions eagerly.
8889
self.__extra_mapping: Optional[Mapping[NormalizedName, str]] = None
@@ -96,6 +97,10 @@ def _extra_mapping(self) -> Mapping[NormalizedName, str]:
9697

9798
return self.__extra_mapping
9899

100+
@property
101+
def is_concrete(self) -> bool:
102+
return self._concrete
103+
99104
@classmethod
100105
def from_directory(cls, directory: str) -> BaseDistribution:
101106
dist_dir = directory.rstrip(os.sep)
@@ -114,7 +119,7 @@ def from_directory(cls, directory: str) -> BaseDistribution:
114119
dist_name = os.path.splitext(dist_dir_name)[0].split("-")[0]
115120

116121
dist = dist_cls(base_dir, project_name=dist_name, metadata=metadata)
117-
return cls(dist)
122+
return cls(dist, concrete=True)
118123

119124
@classmethod
120125
def from_metadata_file_contents(
@@ -131,7 +136,7 @@ def from_metadata_file_contents(
131136
metadata=InMemoryMetadata(metadata_dict, filename),
132137
project_name=project_name,
133138
)
134-
return cls(dist)
139+
return cls(dist, concrete=False)
135140

136141
@classmethod
137142
def from_wheel(cls, wheel: Wheel, name: str) -> BaseDistribution:
@@ -152,7 +157,7 @@ def from_wheel(cls, wheel: Wheel, name: str) -> BaseDistribution:
152157
metadata=InMemoryMetadata(metadata_dict, wheel.location),
153158
project_name=name,
154159
)
155-
return cls(dist)
160+
return cls(dist, concrete=wheel.is_concrete)
156161

157162
@property
158163
def location(self) -> Optional[str]:
@@ -264,7 +269,7 @@ def from_paths(cls, paths: Optional[List[str]]) -> BaseEnvironment:
264269

265270
def _iter_distributions(self) -> Iterator[BaseDistribution]:
266271
for dist in self._ws:
267-
yield Distribution(dist)
272+
yield Distribution(dist, concrete=True)
268273

269274
def _search_distribution(self, name: str) -> Optional[BaseDistribution]:
270275
"""Find a distribution matching the ``name`` in the environment.

0 commit comments

Comments
 (0)