|
| 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)) |
0 commit comments