Skip to content

Commit f2748d1

Browse files
authored
Merge pull request #4997 from pypa/feature/remove-more-pkg_resources
Remove pkg_resources usage from installer (and more)
2 parents 1089223 + 7cb4c76 commit f2748d1

File tree

7 files changed

+45
-45
lines changed

7 files changed

+45
-45
lines changed

newsfragments/4997.feature.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Removed usage of pkg_resources from installer. Set an official deadline on the installer deprecation to 2025-10-31.

setuptools/_normalization.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ def safe_name(component: str) -> str:
3636
>>> safe_name("hello_world")
3737
'hello_world'
3838
"""
39-
# See pkg_resources.safe_name
4039
return _UNSAFE_NAME_CHARS.sub("-", component)
4140

4241

@@ -81,7 +80,6 @@ def best_effort_version(version: str) -> str:
8180
>>> best_effort_version("42.+?1")
8281
'42.dev0+sanitized.1'
8382
"""
84-
# See pkg_resources._forgiving_version
8583
try:
8684
return safe_version(version)
8785
except packaging.version.InvalidVersion:

setuptools/_path.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,20 @@ def same_path(p1: StrPath, p2: StrPath) -> bool:
3939
return normpath(p1) == normpath(p2)
4040

4141

42+
def _cygwin_patch(filename: StrPath): # pragma: nocover
43+
"""
44+
Contrary to POSIX 2008, on Cygwin, getcwd (3) contains
45+
symlink components. Using
46+
os.path.abspath() works around this limitation. A fix in os.getcwd()
47+
would probably better, in Cygwin even more so, except
48+
that this seems to be by design...
49+
"""
50+
return os.path.abspath(filename) if sys.platform == 'cygwin' else filename
51+
52+
4253
def normpath(filename: StrPath) -> str:
4354
"""Normalize a file/dir name for comparison purposes."""
44-
# See pkg_resources.normalize_path for notes about cygwin
45-
file = os.path.abspath(filename) if sys.platform == 'cygwin' else filename
46-
return os.path.normcase(os.path.realpath(os.path.normpath(file)))
55+
return os.path.normcase(os.path.realpath(os.path.normpath(_cygwin_patch(filename))))
4756

4857

4958
@contextlib.contextmanager

setuptools/_reqs.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,6 @@ def parse(strs: _StrOrIter) -> Iterator[Requirement]: ...
3737
def parse(strs: _StrOrIter, parser: Callable[[str], _T]) -> Iterator[_T]: ...
3838
def parse(strs: _StrOrIter, parser: Callable[[str], _T] = parse_req) -> Iterator[_T]: # type: ignore[assignment]
3939
"""
40-
Replacement for ``pkg_resources.parse_requirements`` that uses ``packaging``.
40+
Parse requirements.
4141
"""
4242
return map(parser, parse_strings(strs))

setuptools/dist.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,6 @@
4646
if TYPE_CHECKING:
4747
from typing_extensions import TypeAlias
4848

49-
from pkg_resources import Distribution as _pkg_resources_Distribution
50-
5149

5250
__all__ = ['Distribution']
5351

@@ -761,9 +759,7 @@ def parse_config_files(
761759
self._finalize_license_expression()
762760
self._finalize_license_files()
763761

764-
def fetch_build_eggs(
765-
self, requires: _StrOrIter
766-
) -> list[_pkg_resources_Distribution]:
762+
def fetch_build_eggs(self, requires: _StrOrIter) -> list[metadata.Distribution]:
767763
"""Resolve pre-setup requirements"""
768764
from .installer import _fetch_build_eggs
769765

setuptools/installer.py

Lines changed: 29 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,18 @@
55
import subprocess
66
import sys
77
import tempfile
8-
from functools import partial
9-
from typing import TYPE_CHECKING
8+
9+
import packaging.requirements
10+
import packaging.utils
1011

1112
from . import _reqs
12-
from ._reqs import _StrOrIter
13+
from ._importlib import metadata
1314
from .warnings import SetuptoolsDeprecationWarning
1415
from .wheel import Wheel
1516

1617
from distutils import log
1718
from distutils.errors import DistutilsError
1819

19-
if TYPE_CHECKING:
20-
from pkg_resources import Distribution
21-
2220

2321
def _fixup_find_links(find_links):
2422
"""Ensure find-links option end-up being a list of strings."""
@@ -37,25 +35,30 @@ def fetch_build_egg(dist, req):
3735
return _fetch_build_egg_no_warn(dist, req)
3836

3937

40-
def _fetch_build_eggs(dist, requires: _StrOrIter) -> list[Distribution]:
41-
import pkg_resources # Delay import to avoid unnecessary side-effects
42-
38+
def _fetch_build_eggs(dist, requires: _reqs._StrOrIter) -> list[metadata.Distribution]:
4339
_DeprecatedInstaller.emit(stacklevel=3)
4440
_warn_wheel_not_available(dist)
4541

46-
resolved_dists = pkg_resources.working_set.resolve(
47-
_reqs.parse(requires, pkg_resources.Requirement), # required for compatibility
48-
installer=partial(_fetch_build_egg_no_warn, dist), # avoid warning twice
49-
replace_conflicting=True,
42+
needed_reqs = (
43+
req for req in _reqs.parse(requires) if not req.marker or req.marker.evaluate()
5044
)
45+
resolved_dists = [_fetch_build_egg_no_warn(dist, req) for req in needed_reqs]
5146
for dist in resolved_dists:
52-
pkg_resources.working_set.add(dist, replace=True)
47+
# dist.locate_file('') is the directory containing EGG-INFO, where the importabl
48+
# contents can be found.
49+
sys.path.insert(0, str(dist.locate_file('')))
5350
return resolved_dists
5451

5552

56-
def _fetch_build_egg_no_warn(dist, req): # noqa: C901 # is too complex (16) # FIXME
57-
import pkg_resources # Delay import to avoid unnecessary side-effects
53+
def _dist_matches_req(egg_dist, req):
54+
return (
55+
packaging.utils.canonicalize_name(egg_dist.name)
56+
== packaging.utils.canonicalize_name(req.name)
57+
and egg_dist.version in req.specifier
58+
)
59+
5860

61+
def _fetch_build_egg_no_warn(dist, req): # noqa: C901 # is too complex (16) # FIXME
5962
# Ignore environment markers; if supplied, it is required.
6063
req = strip_marker(req)
6164
# Take easy_install options into account, but do not override relevant
@@ -80,9 +83,11 @@ def _fetch_build_egg_no_warn(dist, req): # noqa: C901 # is too complex (16) #
8083
if dist.dependency_links:
8184
find_links.extend(dist.dependency_links)
8285
eggs_dir = os.path.realpath(dist.get_egg_cache_dir())
83-
environment = pkg_resources.Environment()
84-
for egg_dist in pkg_resources.find_distributions(eggs_dir):
85-
if egg_dist in req and environment.can_add(egg_dist):
86+
cached_dists = metadata.Distribution.discover(
87+
path=glob.glob(f'{eggs_dir}/*.egg/EGG-INFO')
88+
)
89+
for egg_dist in cached_dists:
90+
if _dist_matches_req(egg_dist, req):
8691
return egg_dist
8792
with tempfile.TemporaryDirectory() as tmpdir:
8893
cmd = [
@@ -112,12 +117,7 @@ def _fetch_build_egg_no_warn(dist, req): # noqa: C901 # is too complex (16) #
112117
wheel = Wheel(glob.glob(os.path.join(tmpdir, '*.whl'))[0])
113118
dist_location = os.path.join(eggs_dir, wheel.egg_name())
114119
wheel.install_as_egg(dist_location)
115-
dist_metadata = pkg_resources.PathMetadata(
116-
dist_location, os.path.join(dist_location, 'EGG-INFO')
117-
)
118-
return pkg_resources.Distribution.from_filename(
119-
dist_location, metadata=dist_metadata
120-
)
120+
return metadata.Distribution.at(dist_location + '/EGG-INFO')
121121

122122

123123
def strip_marker(req):
@@ -126,20 +126,16 @@ def strip_marker(req):
126126
calling pip with something like `babel; extra == "i18n"`, which
127127
would always be ignored.
128128
"""
129-
import pkg_resources # Delay import to avoid unnecessary side-effects
130-
131129
# create a copy to avoid mutating the input
132-
req = pkg_resources.Requirement.parse(str(req))
130+
req = packaging.requirements.Requirement(str(req))
133131
req.marker = None
134132
return req
135133

136134

137135
def _warn_wheel_not_available(dist):
138-
import pkg_resources # Delay import to avoid unnecessary side-effects
139-
140136
try:
141-
pkg_resources.get_distribution('wheel')
142-
except pkg_resources.DistributionNotFound:
137+
metadata.distribution('wheel')
138+
except metadata.PackageNotFoundError:
143139
dist.announce('WARNING: The wheel package is not available.', log.WARN)
144140

145141

@@ -149,4 +145,4 @@ class _DeprecatedInstaller(SetuptoolsDeprecationWarning):
149145
Requirements should be satisfied by a PEP 517 installer.
150146
If you are using pip, you can try `pip install --use-pep517`.
151147
"""
152-
# _DUE_DATE not decided yet
148+
_DUE_DATE = 2025, 10, 31

setuptools/tests/test_dist.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ def sdist_with_index(distname, version):
5656
dist = Distribution()
5757
dist.parse_config_files()
5858
resolved_dists = [dist.fetch_build_egg(r) for r in reqs]
59-
assert [dist.key for dist in resolved_dists if dist] == reqs
59+
assert [dist.name for dist in resolved_dists if dist] == reqs
6060

6161

6262
EXAMPLE_BASE_INFO = dict(

0 commit comments

Comments
 (0)