Skip to content

Commit 52c6055

Browse files
committed
Reduce usage of pkg_resources (#3792)
2 parents bc5f3c3 + 51bf756 commit 52c6055

15 files changed

+299
-140
lines changed

pytest.ini

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ filterwarnings=
4848
ignore:The Windows bytes API has been deprecated:DeprecationWarning
4949

5050
# https://github.com/pypa/setuptools/issues/2823
51-
ignore:setuptools.installer is deprecated.
51+
ignore:setuptools.installer and fetch_build_eggs are deprecated.
5252

5353
# https://github.com/pypa/setuptools/issues/917
5454
ignore:setup.py install is deprecated.

setuptools/_normalization.py

+117
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
"""
2+
Helpers for normalization as expected in wheel/sdist/module file names
3+
and core metadata
4+
"""
5+
import re
6+
import warnings
7+
from inspect import cleandoc
8+
from pathlib import Path
9+
from typing import Union
10+
11+
from setuptools.extern import packaging
12+
13+
from ._deprecation_warning import SetuptoolsDeprecationWarning
14+
15+
_Path = Union[str, Path]
16+
17+
# https://packaging.python.org/en/latest/specifications/core-metadata/#name
18+
_VALID_NAME = re.compile(r"^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$", re.I)
19+
_UNSAFE_NAME_CHARS = re.compile(r"[^A-Z0-9.]+", re.I)
20+
21+
22+
def safe_identifier(name: str) -> str:
23+
"""Make a string safe to be used as Python identifier.
24+
>>> safe_identifier("12abc")
25+
'_12abc'
26+
>>> safe_identifier("__editable__.myns.pkg-78.9.3_local")
27+
'__editable___myns_pkg_78_9_3_local'
28+
"""
29+
safe = re.sub(r'\W|^(?=\d)', '_', name)
30+
assert safe.isidentifier()
31+
return safe
32+
33+
34+
def safe_name(component: str) -> str:
35+
"""Escape a component used as a project name according to Core Metadata.
36+
>>> safe_name("hello world")
37+
'hello-world'
38+
>>> safe_name("hello?world")
39+
'hello-world'
40+
"""
41+
# See pkg_resources.safe_name
42+
return _UNSAFE_NAME_CHARS.sub("-", component)
43+
44+
45+
def safe_version(version: str) -> str:
46+
"""Convert an arbitrary string into a valid version string.
47+
>>> safe_version("1988 12 25")
48+
'1988.12.25'
49+
>>> safe_version("v0.2.1")
50+
'0.2.1'
51+
>>> safe_version("v0.2?beta")
52+
'0.2b0'
53+
>>> safe_version("v0.2 beta")
54+
'0.2b0'
55+
>>> safe_version("ubuntu lts")
56+
Traceback (most recent call last):
57+
...
58+
setuptools.extern.packaging.version.InvalidVersion: Invalid version: 'ubuntu.lts'
59+
"""
60+
v = version.replace(' ', '.')
61+
try:
62+
return str(packaging.version.Version(v))
63+
except packaging.version.InvalidVersion:
64+
attempt = _UNSAFE_NAME_CHARS.sub("-", v)
65+
return str(packaging.version.Version(attempt))
66+
67+
68+
def best_effort_version(version: str) -> str:
69+
"""Convert an arbitrary string into a version-like string.
70+
>>> best_effort_version("v0.2 beta")
71+
'0.2b0'
72+
73+
>>> import warnings
74+
>>> warnings.simplefilter("ignore", category=SetuptoolsDeprecationWarning)
75+
>>> best_effort_version("ubuntu lts")
76+
'ubuntu.lts'
77+
"""
78+
# See pkg_resources.safe_version
79+
try:
80+
return safe_version(version)
81+
except packaging.version.InvalidVersion:
82+
msg = f"""Invalid version: {version!r}.
83+
!!\n\n
84+
###################
85+
# Invalid version #
86+
###################
87+
{version!r} is not valid according to PEP 440.\n
88+
Please make sure specify a valid version for your package.
89+
Also note that future releases of setuptools may halt the build process
90+
if an invalid version is given.
91+
\n\n!!
92+
"""
93+
warnings.warn(cleandoc(msg), SetuptoolsDeprecationWarning)
94+
v = version.replace(' ', '.')
95+
return safe_name(v)
96+
97+
98+
def filename_component(value: str) -> str:
99+
"""Normalize each component of a filename (e.g. distribution/version part of wheel)
100+
Note: ``value`` needs to be already normalized.
101+
>>> filename_component("my-pkg")
102+
'my_pkg'
103+
"""
104+
return value.replace("-", "_").strip("_")
105+
106+
107+
def safer_name(value: str) -> str:
108+
"""Like ``safe_name`` but can be used as filename component for wheel"""
109+
# See bdist_wheel.safer_name
110+
return filename_component(safe_name(value))
111+
112+
113+
def safer_best_effort_version(value: str) -> str:
114+
"""Like ``best_effort_version`` but can be used as filename component for wheel"""
115+
# See bdist_wheel.safer_verion
116+
# TODO: Replace with only safe_version in the future (no need for best effort)
117+
return filename_component(best_effort_version(value))

setuptools/_path.py

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import os
2+
import sys
23
from typing import Union
34

45
_Path = Union[str, os.PathLike]
@@ -26,4 +27,11 @@ def same_path(p1: _Path, p2: _Path) -> bool:
2627
>>> same_path("a", "a/b")
2728
False
2829
"""
29-
return os.path.normpath(p1) == os.path.normpath(p2)
30+
return normpath(p1) == normpath(p2)
31+
32+
33+
def normpath(filename: _Path) -> str:
34+
"""Normalize a file/dir name for comparison purposes."""
35+
# See pkg_resources.normalize_path for notes about cygwin
36+
file = os.path.abspath(filename) if sys.platform == 'cygwin' else filename
37+
return os.path.normcase(os.path.realpath(os.path.normpath(file)))

setuptools/_reqs.py

+19-5
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1+
from typing import Callable, Iterable, Iterator, TypeVar, Union, overload
2+
13
import setuptools.extern.jaraco.text as text
4+
from setuptools.extern.packaging.requirements import Requirement
25

3-
from pkg_resources import Requirement
6+
_T = TypeVar("_T")
7+
_StrOrIter = Union[str, Iterable[str]]
48

59

6-
def parse_strings(strs):
10+
def parse_strings(strs: _StrOrIter) -> Iterator[str]:
711
"""
812
Yield requirement strings for each specification in `strs`.
913
@@ -12,8 +16,18 @@ def parse_strings(strs):
1216
return text.join_continuation(map(text.drop_comment, text.yield_lines(strs)))
1317

1418

15-
def parse(strs):
19+
@overload
20+
def parse(strs: _StrOrIter) -> Iterator[Requirement]:
21+
...
22+
23+
24+
@overload
25+
def parse(strs: _StrOrIter, parser: Callable[[str], _T]) -> Iterator[_T]:
26+
...
27+
28+
29+
def parse(strs, parser=Requirement):
1630
"""
17-
Deprecated drop-in replacement for pkg_resources.parse_requirements.
31+
Replacement for ``pkg_resources.parse_requirements`` that uses ``packaging``.
1832
"""
19-
return map(Requirement, parse_strings(strs))
33+
return map(parser, parse_strings(strs))

setuptools/command/bdist_egg.py

+7-8
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
import textwrap
1212
import marshal
1313

14-
from pkg_resources import get_build_platform, Distribution
1514
from setuptools.extension import Library
1615
from setuptools import Command
1716
from .._path import ensure_directory
@@ -64,7 +63,7 @@ class bdist_egg(Command):
6463
('bdist-dir=', 'b',
6564
"temporary directory for creating the distribution"),
6665
('plat-name=', 'p', "platform name to embed in generated filenames "
67-
"(default: %s)" % get_build_platform()),
66+
"(by default uses `pkg_resources.get_build_platform()`)"),
6867
('exclude-source-files', None,
6968
"remove all .py files from the generated egg"),
7069
('keep-temp', 'k',
@@ -98,18 +97,18 @@ def finalize_options(self):
9897
self.bdist_dir = os.path.join(bdist_base, 'egg')
9998

10099
if self.plat_name is None:
100+
from pkg_resources import get_build_platform
101+
101102
self.plat_name = get_build_platform()
102103

103104
self.set_undefined_options('bdist', ('dist_dir', 'dist_dir'))
104105

105106
if self.egg_output is None:
106-
107107
# Compute filename of the output egg
108-
basename = Distribution(
109-
None, None, ei_cmd.egg_name, ei_cmd.egg_version,
110-
get_python_version(),
111-
self.distribution.has_ext_modules() and self.plat_name
112-
).egg_name()
108+
basename = ei_cmd._get_egg_basename(
109+
py_version=get_python_version(),
110+
platform=self.distribution.has_ext_modules() and self.plat_name,
111+
)
113112

114113
self.egg_output = os.path.join(self.dist_dir, basename + '.egg')
115114

setuptools/command/develop.py

+9-8
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
import glob
66
import io
77

8-
import pkg_resources
98
from setuptools.command.easy_install import easy_install
9+
from setuptools import _path
1010
from setuptools import namespaces
1111
import setuptools
1212

@@ -42,6 +42,8 @@ def initialize_options(self):
4242
self.always_copy_from = '.' # always copy eggs installed in curdir
4343

4444
def finalize_options(self):
45+
import pkg_resources
46+
4547
ei = self.get_finalized_command("egg_info")
4648
if ei.broken_egg_info:
4749
template = "Please rename %r to %r before using 'develop'"
@@ -61,10 +63,8 @@ def finalize_options(self):
6163
if self.egg_path is None:
6264
self.egg_path = os.path.abspath(ei.egg_base)
6365

64-
target = pkg_resources.normalize_path(self.egg_base)
65-
egg_path = pkg_resources.normalize_path(
66-
os.path.join(self.install_dir, self.egg_path)
67-
)
66+
target = _path.normpath(self.egg_base)
67+
egg_path = _path.normpath(os.path.join(self.install_dir, self.egg_path))
6868
if egg_path != target:
6969
raise DistutilsOptionError(
7070
"--egg-path must be a relative path from the install"
@@ -94,15 +94,16 @@ def _resolve_setup_path(egg_base, install_dir, egg_path):
9494
path_to_setup = egg_base.replace(os.sep, '/').rstrip('/')
9595
if path_to_setup != os.curdir:
9696
path_to_setup = '../' * (path_to_setup.count('/') + 1)
97-
resolved = pkg_resources.normalize_path(
97+
resolved = _path.normpath(
9898
os.path.join(install_dir, egg_path, path_to_setup)
9999
)
100-
if resolved != pkg_resources.normalize_path(os.curdir):
100+
curdir = _path.normpath(os.curdir)
101+
if resolved != curdir:
101102
raise DistutilsOptionError(
102103
"Can't get a consistent path to setup script from"
103104
" installation directory",
104105
resolved,
105-
pkg_resources.normalize_path(os.curdir),
106+
curdir,
106107
)
107108
return path_to_setup
108109

setuptools/command/dist_info.py

+6-34
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,16 @@
44
"""
55

66
import os
7-
import re
87
import shutil
98
import sys
109
import warnings
1110
from contextlib import contextmanager
12-
from inspect import cleandoc
11+
from distutils import log
12+
from distutils.core import Command
1313
from pathlib import Path
1414

15-
from distutils.core import Command
16-
from distutils import log
17-
from setuptools.extern import packaging
18-
from setuptools._deprecation_warning import SetuptoolsDeprecationWarning
15+
from .. import _normalization
16+
from .._deprecation_warning import SetuptoolsDeprecationWarning
1917

2018

2119
class dist_info(Command):
@@ -76,8 +74,8 @@ def finalize_options(self):
7674
egg_info.finalize_options()
7775
self.egg_info = egg_info
7876

79-
name = _safe(dist.get_name())
80-
version = _version(dist.get_version())
77+
name = _normalization.safer_name(dist.get_name())
78+
version = _normalization.safer_best_effort_version(dist.get_version())
8179
self.name = f"{name}-{version}"
8280
self.dist_info_dir = os.path.join(self.output_dir, f"{self.name}.dist-info")
8381

@@ -109,32 +107,6 @@ def run(self):
109107
bdist_wheel.egg2dist(egg_info_dir, self.dist_info_dir)
110108

111109

112-
def _safe(component: str) -> str:
113-
"""Escape a component used to form a wheel name according to PEP 491"""
114-
return re.sub(r"[^\w\d.]+", "_", component)
115-
116-
117-
def _version(version: str) -> str:
118-
"""Convert an arbitrary string to a version string."""
119-
v = version.replace(' ', '.')
120-
try:
121-
return str(packaging.version.Version(v)).replace("-", "_")
122-
except packaging.version.InvalidVersion:
123-
msg = f"""Invalid version: {version!r}.
124-
!!\n\n
125-
###################
126-
# Invalid version #
127-
###################
128-
{version!r} is not valid according to PEP 440.\n
129-
Please make sure specify a valid version for your package.
130-
Also note that future releases of setuptools may halt the build process
131-
if an invalid version is given.
132-
\n\n!!
133-
"""
134-
warnings.warn(cleandoc(msg))
135-
return _safe(v).strip("_")
136-
137-
138110
def _rm(dir_name, **opts):
139111
if os.path.isdir(dir_name):
140112
shutil.rmtree(dir_name, **opts)

0 commit comments

Comments
 (0)