Skip to content

Commit

Permalink
Reduce usage of pkg_resources (#3792)
Browse files Browse the repository at this point in the history
  • Loading branch information
abravalheri committed Feb 13, 2023
2 parents bc5f3c3 + 51bf756 commit 52c6055
Show file tree
Hide file tree
Showing 15 changed files with 299 additions and 140 deletions.
2 changes: 1 addition & 1 deletion pytest.ini
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ filterwarnings=
ignore:The Windows bytes API has been deprecated:DeprecationWarning

# https://github.com/pypa/setuptools/issues/2823
ignore:setuptools.installer is deprecated.
ignore:setuptools.installer and fetch_build_eggs are deprecated.

# https://github.com/pypa/setuptools/issues/917
ignore:setup.py install is deprecated.
Expand Down
117 changes: 117 additions & 0 deletions setuptools/_normalization.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
"""
Helpers for normalization as expected in wheel/sdist/module file names
and core metadata
"""
import re
import warnings
from inspect import cleandoc
from pathlib import Path
from typing import Union

from setuptools.extern import packaging

from ._deprecation_warning import SetuptoolsDeprecationWarning

_Path = Union[str, Path]

# https://packaging.python.org/en/latest/specifications/core-metadata/#name
_VALID_NAME = re.compile(r"^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$", re.I)
_UNSAFE_NAME_CHARS = re.compile(r"[^A-Z0-9.]+", re.I)


def safe_identifier(name: str) -> str:
"""Make a string safe to be used as Python identifier.
>>> safe_identifier("12abc")
'_12abc'
>>> safe_identifier("__editable__.myns.pkg-78.9.3_local")
'__editable___myns_pkg_78_9_3_local'
"""
safe = re.sub(r'\W|^(?=\d)', '_', name)
assert safe.isidentifier()
return safe


def safe_name(component: str) -> str:
"""Escape a component used as a project name according to Core Metadata.
>>> safe_name("hello world")
'hello-world'
>>> safe_name("hello?world")
'hello-world'
"""
# See pkg_resources.safe_name
return _UNSAFE_NAME_CHARS.sub("-", component)


def safe_version(version: str) -> str:
"""Convert an arbitrary string into a valid version string.
>>> safe_version("1988 12 25")
'1988.12.25'
>>> safe_version("v0.2.1")
'0.2.1'
>>> safe_version("v0.2?beta")
'0.2b0'
>>> safe_version("v0.2 beta")
'0.2b0'
>>> safe_version("ubuntu lts")
Traceback (most recent call last):
...
setuptools.extern.packaging.version.InvalidVersion: Invalid version: 'ubuntu.lts'
"""
v = version.replace(' ', '.')
try:
return str(packaging.version.Version(v))
except packaging.version.InvalidVersion:
attempt = _UNSAFE_NAME_CHARS.sub("-", v)
return str(packaging.version.Version(attempt))


def best_effort_version(version: str) -> str:
"""Convert an arbitrary string into a version-like string.
>>> best_effort_version("v0.2 beta")
'0.2b0'
>>> import warnings
>>> warnings.simplefilter("ignore", category=SetuptoolsDeprecationWarning)
>>> best_effort_version("ubuntu lts")
'ubuntu.lts'
"""
# See pkg_resources.safe_version
try:
return safe_version(version)
except packaging.version.InvalidVersion:
msg = f"""Invalid version: {version!r}.
!!\n\n
###################
# Invalid version #
###################
{version!r} is not valid according to PEP 440.\n
Please make sure specify a valid version for your package.
Also note that future releases of setuptools may halt the build process
if an invalid version is given.
\n\n!!
"""
warnings.warn(cleandoc(msg), SetuptoolsDeprecationWarning)
v = version.replace(' ', '.')
return safe_name(v)


def filename_component(value: str) -> str:
"""Normalize each component of a filename (e.g. distribution/version part of wheel)
Note: ``value`` needs to be already normalized.
>>> filename_component("my-pkg")
'my_pkg'
"""
return value.replace("-", "_").strip("_")


def safer_name(value: str) -> str:
"""Like ``safe_name`` but can be used as filename component for wheel"""
# See bdist_wheel.safer_name
return filename_component(safe_name(value))


def safer_best_effort_version(value: str) -> str:
"""Like ``best_effort_version`` but can be used as filename component for wheel"""
# See bdist_wheel.safer_verion
# TODO: Replace with only safe_version in the future (no need for best effort)
return filename_component(best_effort_version(value))
10 changes: 9 additions & 1 deletion setuptools/_path.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import os
import sys
from typing import Union

_Path = Union[str, os.PathLike]
Expand Down Expand Up @@ -26,4 +27,11 @@ def same_path(p1: _Path, p2: _Path) -> bool:
>>> same_path("a", "a/b")
False
"""
return os.path.normpath(p1) == os.path.normpath(p2)
return normpath(p1) == normpath(p2)


def normpath(filename: _Path) -> str:
"""Normalize a file/dir name for comparison purposes."""
# See pkg_resources.normalize_path for notes about cygwin
file = os.path.abspath(filename) if sys.platform == 'cygwin' else filename
return os.path.normcase(os.path.realpath(os.path.normpath(file)))
24 changes: 19 additions & 5 deletions setuptools/_reqs.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
from typing import Callable, Iterable, Iterator, TypeVar, Union, overload

import setuptools.extern.jaraco.text as text
from setuptools.extern.packaging.requirements import Requirement

from pkg_resources import Requirement
_T = TypeVar("_T")
_StrOrIter = Union[str, Iterable[str]]


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


def parse(strs):
@overload
def parse(strs: _StrOrIter) -> Iterator[Requirement]:
...


@overload
def parse(strs: _StrOrIter, parser: Callable[[str], _T]) -> Iterator[_T]:
...


def parse(strs, parser=Requirement):
"""
Deprecated drop-in replacement for pkg_resources.parse_requirements.
Replacement for ``pkg_resources.parse_requirements`` that uses ``packaging``.
"""
return map(Requirement, parse_strings(strs))
return map(parser, parse_strings(strs))
15 changes: 7 additions & 8 deletions setuptools/command/bdist_egg.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import textwrap
import marshal

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

if self.plat_name is None:
from pkg_resources import get_build_platform

self.plat_name = get_build_platform()

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

if self.egg_output is None:

# Compute filename of the output egg
basename = Distribution(
None, None, ei_cmd.egg_name, ei_cmd.egg_version,
get_python_version(),
self.distribution.has_ext_modules() and self.plat_name
).egg_name()
basename = ei_cmd._get_egg_basename(
py_version=get_python_version(),
platform=self.distribution.has_ext_modules() and self.plat_name,
)

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

Expand Down
17 changes: 9 additions & 8 deletions setuptools/command/develop.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
import glob
import io

import pkg_resources
from setuptools.command.easy_install import easy_install
from setuptools import _path
from setuptools import namespaces
import setuptools

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

def finalize_options(self):
import pkg_resources

ei = self.get_finalized_command("egg_info")
if ei.broken_egg_info:
template = "Please rename %r to %r before using 'develop'"
Expand All @@ -61,10 +63,8 @@ def finalize_options(self):
if self.egg_path is None:
self.egg_path = os.path.abspath(ei.egg_base)

target = pkg_resources.normalize_path(self.egg_base)
egg_path = pkg_resources.normalize_path(
os.path.join(self.install_dir, self.egg_path)
)
target = _path.normpath(self.egg_base)
egg_path = _path.normpath(os.path.join(self.install_dir, self.egg_path))
if egg_path != target:
raise DistutilsOptionError(
"--egg-path must be a relative path from the install"
Expand Down Expand Up @@ -94,15 +94,16 @@ def _resolve_setup_path(egg_base, install_dir, egg_path):
path_to_setup = egg_base.replace(os.sep, '/').rstrip('/')
if path_to_setup != os.curdir:
path_to_setup = '../' * (path_to_setup.count('/') + 1)
resolved = pkg_resources.normalize_path(
resolved = _path.normpath(
os.path.join(install_dir, egg_path, path_to_setup)
)
if resolved != pkg_resources.normalize_path(os.curdir):
curdir = _path.normpath(os.curdir)
if resolved != curdir:
raise DistutilsOptionError(
"Can't get a consistent path to setup script from"
" installation directory",
resolved,
pkg_resources.normalize_path(os.curdir),
curdir,
)
return path_to_setup

Expand Down
40 changes: 6 additions & 34 deletions setuptools/command/dist_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,16 @@
"""

import os
import re
import shutil
import sys
import warnings
from contextlib import contextmanager
from inspect import cleandoc
from distutils import log
from distutils.core import Command
from pathlib import Path

from distutils.core import Command
from distutils import log
from setuptools.extern import packaging
from setuptools._deprecation_warning import SetuptoolsDeprecationWarning
from .. import _normalization
from .._deprecation_warning import SetuptoolsDeprecationWarning


class dist_info(Command):
Expand Down Expand Up @@ -76,8 +74,8 @@ def finalize_options(self):
egg_info.finalize_options()
self.egg_info = egg_info

name = _safe(dist.get_name())
version = _version(dist.get_version())
name = _normalization.safer_name(dist.get_name())
version = _normalization.safer_best_effort_version(dist.get_version())
self.name = f"{name}-{version}"
self.dist_info_dir = os.path.join(self.output_dir, f"{self.name}.dist-info")

Expand Down Expand Up @@ -109,32 +107,6 @@ def run(self):
bdist_wheel.egg2dist(egg_info_dir, self.dist_info_dir)


def _safe(component: str) -> str:
"""Escape a component used to form a wheel name according to PEP 491"""
return re.sub(r"[^\w\d.]+", "_", component)


def _version(version: str) -> str:
"""Convert an arbitrary string to a version string."""
v = version.replace(' ', '.')
try:
return str(packaging.version.Version(v)).replace("-", "_")
except packaging.version.InvalidVersion:
msg = f"""Invalid version: {version!r}.
!!\n\n
###################
# Invalid version #
###################
{version!r} is not valid according to PEP 440.\n
Please make sure specify a valid version for your package.
Also note that future releases of setuptools may halt the build process
if an invalid version is given.
\n\n!!
"""
warnings.warn(cleandoc(msg))
return _safe(v).strip("_")


def _rm(dir_name, **opts):
if os.path.isdir(dir_name):
shutil.rmtree(dir_name, **opts)
Expand Down
Loading

0 comments on commit 52c6055

Please sign in to comment.