Skip to content

Commit 135faab

Browse files
authored
Remove direct pkg_resource usages from resolver and preparer
1 parent e4e334a commit 135faab

File tree

22 files changed

+300
-234
lines changed

22 files changed

+300
-234
lines changed

src/pip/_internal/distributions/base.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
import abc
2-
from typing import Optional
3-
4-
from pip._vendor.pkg_resources import Distribution
52

63
from pip._internal.index.package_finder import PackageFinder
4+
from pip._internal.metadata.base import BaseDistribution
75
from pip._internal.req import InstallRequirement
86

97

@@ -28,7 +26,7 @@ def __init__(self, req: InstallRequirement) -> None:
2826
self.req = req
2927

3028
@abc.abstractmethod
31-
def get_pkg_resources_distribution(self) -> Optional[Distribution]:
29+
def get_metadata_distribution(self) -> BaseDistribution:
3230
raise NotImplementedError()
3331

3432
@abc.abstractmethod

src/pip/_internal/distributions/installed.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
1-
from typing import Optional
2-
3-
from pip._vendor.pkg_resources import Distribution
4-
51
from pip._internal.distributions.base import AbstractDistribution
62
from pip._internal.index.package_finder import PackageFinder
3+
from pip._internal.metadata import BaseDistribution
74

85

96
class InstalledDistribution(AbstractDistribution):
@@ -13,7 +10,8 @@ class InstalledDistribution(AbstractDistribution):
1310
been computed.
1411
"""
1512

16-
def get_pkg_resources_distribution(self) -> Optional[Distribution]:
13+
def get_metadata_distribution(self) -> BaseDistribution:
14+
assert self.req.satisfied_by is not None, "not actually installed"
1715
return self.req.satisfied_by
1816

1917
def prepare_distribution_metadata(

src/pip/_internal/distributions/sdist.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
import logging
22
from typing import Set, Tuple
33

4-
from pip._vendor.pkg_resources import Distribution
5-
64
from pip._internal.build_env import BuildEnvironment
75
from pip._internal.distributions.base import AbstractDistribution
86
from pip._internal.exceptions import InstallationError
97
from pip._internal.index.package_finder import PackageFinder
8+
from pip._internal.metadata import BaseDistribution
109
from pip._internal.utils.subprocess import runner_with_spinner_message
1110

1211
logger = logging.getLogger(__name__)
@@ -19,8 +18,10 @@ class SourceDistribution(AbstractDistribution):
1918
generated, either using PEP 517 or using the legacy `setup.py egg_info`.
2019
"""
2120

22-
def get_pkg_resources_distribution(self) -> Distribution:
23-
return self.req.get_dist()
21+
def get_metadata_distribution(self) -> BaseDistribution:
22+
from pip._internal.metadata.pkg_resources import Distribution as _Dist
23+
24+
return _Dist(self.req.get_dist())
2425

2526
def prepare_distribution_metadata(
2627
self, finder: PackageFinder, build_isolation: bool

src/pip/_internal/distributions/wheel.py

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
from zipfile import ZipFile
2-
3-
from pip._vendor.pkg_resources import Distribution
1+
from pip._vendor.packaging.utils import canonicalize_name
42

53
from pip._internal.distributions.base import AbstractDistribution
64
from pip._internal.index.package_finder import PackageFinder
7-
from pip._internal.utils.wheel import pkg_resources_distribution_for_wheel
5+
from pip._internal.metadata import (
6+
BaseDistribution,
7+
FilesystemWheel,
8+
get_wheel_distribution,
9+
)
810

911

1012
class WheelDistribution(AbstractDistribution):
@@ -13,20 +15,15 @@ class WheelDistribution(AbstractDistribution):
1315
This does not need any preparation as wheels can be directly unpacked.
1416
"""
1517

16-
def get_pkg_resources_distribution(self) -> Distribution:
18+
def get_metadata_distribution(self) -> BaseDistribution:
1719
"""Loads the metadata from the wheel file into memory and returns a
1820
Distribution that uses it, not relying on the wheel file or
1921
requirement.
2022
"""
21-
# Set as part of preparation during download.
22-
assert self.req.local_file_path
23-
# Wheels are never unnamed.
24-
assert self.req.name
25-
26-
with ZipFile(self.req.local_file_path, allowZip64=True) as z:
27-
return pkg_resources_distribution_for_wheel(
28-
z, self.req.name, self.req.local_file_path
29-
)
23+
assert self.req.local_file_path, "Set as part of preparation during download"
24+
assert self.req.name, "Wheels are never unnamed"
25+
wheel = FilesystemWheel(self.req.local_file_path)
26+
return get_wheel_distribution(wheel, canonicalize_name(self.req.name))
3027

3128
def prepare_distribution_metadata(
3229
self, finder: PackageFinder, build_isolation: bool

src/pip/_internal/exceptions.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@
22

33
import configparser
44
from itertools import chain, groupby, repeat
5-
from typing import TYPE_CHECKING, Dict, List, Optional
5+
from typing import TYPE_CHECKING, Dict, List, Optional, Union
66

77
from pip._vendor.pkg_resources import Distribution
88
from pip._vendor.requests.models import Request, Response
99

1010
if TYPE_CHECKING:
1111
from hashlib import _Hash
1212

13+
from pip._internal.metadata import BaseDistribution
1314
from pip._internal.req.req_install import InstallRequirement
1415

1516

@@ -38,7 +39,11 @@ class NoneMetadataError(PipError):
3839
"PKG-INFO").
3940
"""
4041

41-
def __init__(self, dist: Distribution, metadata_name: str) -> None:
42+
def __init__(
43+
self,
44+
dist: Union[Distribution, "BaseDistribution"],
45+
metadata_name: str,
46+
) -> None:
4247
"""
4348
:param dist: A Distribution object.
4449
:param metadata_name: The name of the metadata being accessed

src/pip/_internal/metadata/__init__.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
from typing import List, Optional
22

3-
from .base import BaseDistribution, BaseEnvironment
3+
from .base import BaseDistribution, BaseEnvironment, FilesystemWheel, MemoryWheel, Wheel
44

55
__all__ = [
66
"BaseDistribution",
77
"BaseEnvironment",
8+
"FilesystemWheel",
9+
"MemoryWheel",
10+
"Wheel",
811
"get_default_environment",
912
"get_environment",
1013
"get_wheel_distribution",
@@ -35,7 +38,7 @@ def get_environment(paths: Optional[List[str]]) -> BaseEnvironment:
3538
return Environment.from_paths(paths)
3639

3740

38-
def get_wheel_distribution(wheel_path: str, canonical_name: str) -> BaseDistribution:
41+
def get_wheel_distribution(wheel: Wheel, canonical_name: str) -> BaseDistribution:
3942
"""Get the representation of the specified wheel's distribution metadata.
4043
4144
This returns a Distribution instance from the chosen backend based on
@@ -45,4 +48,4 @@ def get_wheel_distribution(wheel_path: str, canonical_name: str) -> BaseDistribu
4548
"""
4649
from .pkg_resources import Distribution
4750

48-
return Distribution.from_wheel(wheel_path, canonical_name)
51+
return Distribution.from_wheel(wheel, canonical_name)

src/pip/_internal/metadata/base.py

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
import json
33
import logging
44
import re
5+
import zipfile
56
from typing import (
7+
IO,
68
TYPE_CHECKING,
79
Collection,
810
Container,
@@ -14,6 +16,7 @@
1416
)
1517

1618
from pip._vendor.packaging.requirements import Requirement
19+
from pip._vendor.packaging.specifiers import InvalidSpecifier, SpecifierSet
1720
from pip._vendor.packaging.version import LegacyVersion, Version
1821

1922
from pip._internal.models.direct_url import (
@@ -50,6 +53,12 @@ def group(self) -> str:
5053

5154

5255
class BaseDistribution(Protocol):
56+
def __repr__(self) -> str:
57+
return f"{self.raw_name} {self.version} ({self.location})"
58+
59+
def __str__(self) -> str:
60+
return f"{self.raw_name} {self.version}"
61+
5362
@property
5463
def location(self) -> Optional[str]:
5564
"""Where the distribution is loaded from.
@@ -159,10 +168,42 @@ def metadata_version(self) -> Optional[str]:
159168
def raw_name(self) -> str:
160169
"""Value of "Name:" in distribution metadata."""
161170
# The metadata should NEVER be missing the Name: key, but if it somehow
162-
# does not, fall back to the known canonical name.
171+
# does, fall back to the known canonical name.
163172
return self.metadata.get("Name", self.canonical_name)
164173

174+
@property
175+
def requires_python(self) -> SpecifierSet:
176+
"""Value of "Requires-Python:" in distribution metadata.
177+
178+
If the key does not exist or contains an invalid value, an empty
179+
SpecifierSet should be returned.
180+
"""
181+
value = self.metadata.get("Requires-Python")
182+
if value is None:
183+
return SpecifierSet()
184+
try:
185+
# Convert to str to satisfy the type checker; this can be a Header object.
186+
spec = SpecifierSet(str(value))
187+
except InvalidSpecifier as e:
188+
message = "Package %r has an invalid Requires-Python: %s"
189+
logger.warning(message, self.raw_name, e)
190+
return SpecifierSet()
191+
return spec
192+
165193
def iter_dependencies(self, extras: Collection[str] = ()) -> Iterable[Requirement]:
194+
"""Dependencies of this distribution.
195+
196+
For modern .dist-info distributions, this is the collection of
197+
"Requires-Dist:" entries in distribution metadata.
198+
"""
199+
raise NotImplementedError()
200+
201+
def iter_provided_extras(self) -> Iterable[str]:
202+
"""Extras provided by this distribution.
203+
204+
For modern .dist-info distributions, this is the collection of
205+
"Provides-Extra:" entries in distribution metadata.
206+
"""
166207
raise NotImplementedError()
167208

168209

@@ -240,3 +281,27 @@ def iter_installed_distributions(
240281
if user_only:
241282
it = (d for d in it if d.in_usersite)
242283
return (d for d in it if d.canonical_name not in skip)
284+
285+
286+
class Wheel(Protocol):
287+
location: str
288+
289+
def as_zipfile(self) -> zipfile.ZipFile:
290+
raise NotImplementedError()
291+
292+
293+
class FilesystemWheel(Wheel):
294+
def __init__(self, location: str) -> None:
295+
self.location = location
296+
297+
def as_zipfile(self) -> zipfile.ZipFile:
298+
return zipfile.ZipFile(self.location, allowZip64=True)
299+
300+
301+
class MemoryWheel(Wheel):
302+
def __init__(self, location: str, stream: IO[bytes]) -> None:
303+
self.location = location
304+
self.stream = stream
305+
306+
def as_zipfile(self) -> zipfile.ZipFile:
307+
return zipfile.ZipFile(self.stream, allowZip64=True)

src/pip/_internal/metadata/pkg_resources.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import email.message
22
import logging
3-
import zipfile
43
from typing import (
54
TYPE_CHECKING,
65
Collection,
@@ -20,7 +19,13 @@
2019
from pip._internal.utils.packaging import get_installer, get_metadata
2120
from pip._internal.utils.wheel import pkg_resources_distribution_for_wheel
2221

23-
from .base import BaseDistribution, BaseEntryPoint, BaseEnvironment, DistributionVersion
22+
from .base import (
23+
BaseDistribution,
24+
BaseEntryPoint,
25+
BaseEnvironment,
26+
DistributionVersion,
27+
Wheel,
28+
)
2429

2530
if TYPE_CHECKING:
2631
from pip._vendor.packaging.utils import NormalizedName
@@ -39,9 +44,9 @@ def __init__(self, dist: pkg_resources.Distribution) -> None:
3944
self._dist = dist
4045

4146
@classmethod
42-
def from_wheel(cls, path: str, name: str) -> "Distribution":
43-
with zipfile.ZipFile(path, allowZip64=True) as zf:
44-
dist = pkg_resources_distribution_for_wheel(zf, name, path)
47+
def from_wheel(cls, wheel: Wheel, name: str) -> "Distribution":
48+
with wheel.as_zipfile() as zf:
49+
dist = pkg_resources_distribution_for_wheel(zf, name, wheel.location)
4550
return cls(dist)
4651

4752
@property
@@ -100,6 +105,9 @@ def iter_dependencies(self, extras: Collection[str] = ()) -> Iterable[Requiremen
100105
extras = frozenset(extras).intersection(self._dist.extras)
101106
return self._dist.requires(extras)
102107

108+
def iter_provided_extras(self) -> Iterable[str]:
109+
return self._dist.extras
110+
103111

104112
class Environment(BaseEnvironment):
105113
def __init__(self, ws: pkg_resources.WorkingSet) -> None:

src/pip/_internal/network/lazy_wheel.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,33 +8,33 @@
88
from typing import Any, Dict, Iterator, List, Optional, Tuple
99
from zipfile import BadZipfile, ZipFile
1010

11-
from pip._vendor.pkg_resources import Distribution
11+
from pip._vendor.packaging.utils import canonicalize_name
1212
from pip._vendor.requests.models import CONTENT_CHUNK_SIZE, Response
1313

14+
from pip._internal.metadata import BaseDistribution, MemoryWheel, get_wheel_distribution
1415
from pip._internal.network.session import PipSession
1516
from pip._internal.network.utils import HEADERS, raise_for_status, response_chunks
16-
from pip._internal.utils.wheel import pkg_resources_distribution_for_wheel
1717

1818

1919
class HTTPRangeRequestUnsupported(Exception):
2020
pass
2121

2222

23-
def dist_from_wheel_url(name: str, url: str, session: PipSession) -> Distribution:
24-
"""Return a pkg_resources.Distribution from the given wheel URL.
23+
def dist_from_wheel_url(name: str, url: str, session: PipSession) -> BaseDistribution:
24+
"""Return a distribution object from the given wheel URL.
2525
2626
This uses HTTP range requests to only fetch the potion of the wheel
2727
containing metadata, just enough for the object to be constructed.
2828
If such requests are not supported, HTTPRangeRequestUnsupported
2929
is raised.
3030
"""
31-
with LazyZipOverHTTP(url, session) as wheel:
31+
with LazyZipOverHTTP(url, session) as zf:
3232
# For read-only ZIP files, ZipFile only needs methods read,
3333
# seek, seekable and tell, not the whole IO protocol.
34-
zip_file = ZipFile(wheel) # type: ignore
34+
wheel = MemoryWheel(zf.name, zf) # type: ignore
3535
# After context manager exit, wheel.name
3636
# is an invalid file by intention.
37-
return pkg_resources_distribution_for_wheel(zip_file, name, wheel.name)
37+
return get_wheel_distribution(wheel, canonicalize_name(name))
3838

3939

4040
class LazyZipOverHTTP:

src/pip/_internal/operations/check.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -126,11 +126,9 @@ def _simulate_installation_of(
126126
# Modify it as installing requirement_set would (assuming no errors)
127127
for inst_req in to_install:
128128
abstract_dist = make_distribution_for_install_requirement(inst_req)
129-
dist = abstract_dist.get_pkg_resources_distribution()
130-
131-
assert dist is not None
132-
name = canonicalize_name(dist.project_name)
133-
package_set[name] = PackageDetails(dist.parsed_version, dist.requires())
129+
dist = abstract_dist.get_metadata_distribution()
130+
name = dist.canonical_name
131+
package_set[name] = PackageDetails(dist.version, list(dist.iter_dependencies()))
134132

135133
installed.add(name)
136134

0 commit comments

Comments
 (0)