summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--setuptools/_normalization.py99
-rw-r--r--setuptools/command/dist_info.py40
-rw-r--r--setuptools/command/editable_wheel.py36
3 files changed, 113 insertions, 62 deletions
diff --git a/setuptools/_normalization.py b/setuptools/_normalization.py
new file mode 100644
index 00000000..d6343ca5
--- /dev/null
+++ b/setuptools/_normalization.py
@@ -0,0 +1,99 @@
+import os
+import re
+import sys
+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 path(filename: _Path) -> str:
+ """Normalize a file/dir name for comparison purposes."""
+ # See pkg_resources.normalize_path
+ file = os.path.abspath(filename) if sys.platform == 'cygwin' else filename
+ return os.path.normcase(os.path.realpath(os.path.normpath(file)))
+
+
+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'
+ """
+ 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).strip("_")
diff --git a/setuptools/command/dist_info.py b/setuptools/command/dist_info.py
index 0685c945..d5344471 100644
--- a/setuptools/command/dist_info.py
+++ b/setuptools/command/dist_info.py
@@ -4,18 +4,16 @@ As defined in the wheel specification
"""
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):
@@ -72,8 +70,8 @@ class dist_info(Command):
egg_info.finalize_options()
self.egg_info = egg_info
- name = _safe(dist.get_name())
- version = _version(dist.get_version())
+ name = _normalization.safe_name(dist.get_name()).replace(".", "_")
+ version = _normalization.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")
@@ -105,32 +103,6 @@ class dist_info(Command):
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)
diff --git a/setuptools/command/editable_wheel.py b/setuptools/command/editable_wheel.py
index d60cfbeb..1875641f 100644
--- a/setuptools/command/editable_wheel.py
+++ b/setuptools/command/editable_wheel.py
@@ -12,7 +12,6 @@ Create a wheel that, when installed, will make the source package 'editable'
import logging
import os
-import re
import shutil
import sys
import traceback
@@ -36,10 +35,10 @@ from typing import (
Union,
)
-from setuptools import Command, SetuptoolsDeprecationWarning, errors, namespaces
-from setuptools.command.build_py import build_py as build_py_cls
-from setuptools.discovery import find_package_path
-from setuptools.dist import Distribution
+from .. import Command, SetuptoolsDeprecationWarning, errors, namespaces, _normalization
+from ..discovery import find_package_path
+from ..dist import Distribution
+from .build_py import build_py as build_py_cls
if TYPE_CHECKING:
from wheel.wheelfile import WheelFile # noqa
@@ -490,7 +489,7 @@ class _TopLevelFinder:
))
name = f"__editable__.{self.name}.finder"
- finder = _make_identifier(name)
+ finder = _normalization.safe_identifier(name)
content = bytes(_finder_template(name, roots, namespaces_), "utf-8")
wheel.writestr(f"{finder}.py", content)
@@ -569,7 +568,7 @@ def _simple_layout(
return set(package_dir) in ({}, {""})
parent = os.path.commonpath([_parent_path(k, v) for k, v in layout.items()])
return all(
- _normalize_path(Path(parent, *key.split('.'))) == _normalize_path(value)
+ _normalization.path(Path(parent, *key.split('.'))) == _normalization.path(value)
for key, value in layout.items()
)
@@ -698,21 +697,14 @@ def _is_nested(pkg: str, pkg_path: str, parent: str, parent_path: str) -> bool:
>>> _is_nested("b.a", "path/b/a", "a", "path/a")
False
"""
- norm_pkg_path = _normalize_path(pkg_path)
+ norm_pkg_path = _normalization.path(pkg_path)
rest = pkg.replace(parent, "", 1).strip(".").split(".")
return (
pkg.startswith(parent)
- and norm_pkg_path == _normalize_path(Path(parent_path, *rest))
+ and norm_pkg_path == _normalization.path(Path(parent_path, *rest))
)
-def _normalize_path(filename: _Path) -> str:
- """Normalize a file/dir name for comparison purposes"""
- # See pkg_resources.normalize_path
- file = os.path.abspath(filename) if sys.platform == 'cygwin' else filename
- return os.path.normcase(os.path.realpath(os.path.normpath(file)))
-
-
def _empty_dir(dir_: _P) -> _P:
"""Create a directory ensured to be empty. Existing files may be removed."""
shutil.rmtree(dir_, ignore_errors=True)
@@ -720,18 +712,6 @@ def _empty_dir(dir_: _P) -> _P:
return dir_
-def _make_identifier(name: str) -> str:
- """Make a string safe to be used as Python identifier.
- >>> _make_identifier("12abc")
- '_12abc'
- >>> _make_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
-
-
class _NamespaceInstaller(namespaces.Installer):
def __init__(self, distribution, installation_dir, editable_name, src_root):
self.distribution = distribution