From 394cd4ecccfe0f58a07ca63b56c39da66923289b Mon Sep 17 00:00:00 2001 From: Eldon Allred Date: Fri, 5 Mar 2021 23:30:01 -0500 Subject: [PATCH] Added packaging.utils.create_wheel_filename and create_sdist_filename functions --- CHANGELOG.rst | 1 + docs/utils.rst | 41 +++++++++++++++++++++++++++++++++++++ packaging/utils.py | 31 +++++++++++++++++++++++++++- tests/test_utils.py | 50 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 122 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 675a0d0a..399a02c9 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -5,6 +5,7 @@ Changelog ~~~~~~~~~~~~ * `packaging` is now only compatible with Python 3.6 and above. +* Added ``packaging.utils.create_wheel_filename()`` and ``create_sdist_filename()`` (:issue:`408`) 20.9 - 2021-01-29 ~~~~~~~~~~~~~~~~~ diff --git a/docs/utils.rst b/docs/utils.rst index 8fbb0250..97f986d7 100644 --- a/docs/utils.rst +++ b/docs/utils.rst @@ -41,6 +41,33 @@ Reference >>> canonicalize_version('1.4.0.0.0') '1.4' +.. function:: create_wheel_filename(name, version, build, tags) + + Combines a project name, version, build tag, and tag set + to make a properly formatted wheel filename. + + The project name is normalized such that the non-alphanumeric + characters are replaced with ``_``. The version is an instance of + :class:`~packaging.version.Version`. The build tag can be None, + an empty tuple or a two-item tuple of an integer and a string. + The tags is set of tags that will be compressed into a wheel + tag string. + + :param str name: The project name + :param ~packaging.version.Version version: The project version + :param Optional[(),(int,str)] build: An optional two-item tuple of an integer and string + :param set[~packaging.tags.Tag] tags: The set of tags that apply to the wheel + + .. doctest:: + + >>> from packaging.utils import create_wheel_filename + >>> from packaging.tags import Tag + >>> from packaging.version import Version + >>> version = Version("1.0") + >>> tags = {Tag("py3", "none", "any")} + >>> "foo_bar-1.0-py3-none-any.whl" == create_wheel_filename("foo-bar", version, None, tags) + True + .. function:: parse_wheel_filename(filename) This function takes the filename of a wheel file, and parses it, @@ -70,6 +97,20 @@ Reference >>> not build True +.. function:: create_sdist_filename(name, version) + + Combines the project name and a version to make a valid sdist filename. + + :param str name: The project name + :param ~packaging.version.Version version: The project version + + .. doctest:: + + >>> from packaging.utils import create_sdist_filename + >>> from packaging.version import Version + >>> "foo_bar-1.0.tar.gz" == create_sdist_filename("foo-bar", Version("1.0")) + True + .. function:: parse_sdist_filename(filename) This function takes the filename of a sdist file (as specified diff --git a/packaging/utils.py b/packaging/utils.py index f9424023..3c4e8e51 100644 --- a/packaging/utils.py +++ b/packaging/utils.py @@ -3,7 +3,7 @@ # for complete details. import re -from typing import FrozenSet, NewType, Tuple, Union, cast +from typing import AbstractSet, FrozenSet, NewType, Optional, Tuple, Union, cast from .tags import Tag, parse_tag from .version import InvalidVersion, Version @@ -24,6 +24,7 @@ class InvalidSdistFilename(ValueError): """ +_distribution_regex = re.compile(r"[^\w\d.]+") _canonicalize_regex = re.compile(r"[-_.]+") # PEP 427: The build number must start with a digit. _build_tag_regex = re.compile(r"(\d+)(.*)") @@ -78,6 +79,30 @@ def canonicalize_version(version: Union[Version, str]) -> str: return "".join(parts) +def _join_tag_attr(tags: AbstractSet[Tag], field: str) -> str: + return ".".join(sorted({getattr(tag, field) for tag in tags})) + + +def _compress_tag_set(tags: AbstractSet[Tag]) -> str: + return "-".join(_join_tag_attr(tags, x) for x in ("interpreter", "abi", "platform")) + + +def create_wheel_filename( + name: str, version: Version, build: Optional[BuildTag], tags: AbstractSet[Tag] +) -> str: + norm_name = _distribution_regex.sub("_", name) + compressed_tag = _compress_tag_set(tags) + + parts: Tuple[str, ...] + + if build: + parts = norm_name, str(version), "".join(map(str, build)), compressed_tag + else: + parts = norm_name, str(version), compressed_tag + + return "-".join(parts) + ".whl" + + def parse_wheel_filename( filename: str ) -> Tuple[NormalizedName, Version, BuildTag, FrozenSet[Tag]]: @@ -114,6 +139,10 @@ def parse_wheel_filename( return (name, version, build, tags) +def create_sdist_filename(name: str, version: Version) -> str: + return f"{_distribution_regex.sub('_', name)}-{version}.tar.gz" + + def parse_sdist_filename(filename: str) -> Tuple[NormalizedName, Version]: if not filename.endswith(".tar.gz"): raise InvalidSdistFilename( diff --git a/tests/test_utils.py b/tests/test_utils.py index 755fd65f..a1f52250 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -10,6 +10,8 @@ InvalidWheelFilename, canonicalize_name, canonicalize_version, + create_sdist_filename, + create_wheel_filename, parse_sdist_filename, parse_wheel_filename, ) @@ -56,6 +58,43 @@ def test_canonicalize_version(version, expected): assert canonicalize_version(version) == expected +@pytest.mark.parametrize( + ("filename", "name", "version", "build", "tags"), + [ + ( + "foo-1.0-py3-none-any.whl", + "foo", + Version("1.0"), + (), + {Tag("py3", "none", "any")}, + ), + ( + "foo-1.0-1000-py3-none-any.whl", + "foo", + Version("1.0"), + (1000, ""), + {Tag("py3", "none", "any")}, + ), + ( + "foo-1.0-1000abc-py3-none-any.whl", + "foo", + Version("1.0"), + (1000, "abc"), + {Tag("py3", "none", "any")}, + ), + ( + "foo_bar-1.0-42-py2.py3-none-any.whl", + "foo-bar", + Version("1.0"), + (42, ""), + {Tag("py2", "none", "any"), Tag("py3", "none", "any")}, + ), + ], +) +def test_create_wheel_filename(filename, name, version, build, tags): + assert create_wheel_filename(name, version, build, tags) == filename + + @pytest.mark.parametrize( ("filename", "name", "version", "build", "tags"), [ @@ -103,6 +142,17 @@ def test_parse_wheel_invalid_filename(filename): parse_wheel_filename(filename) +@pytest.mark.parametrize( + ("filename", "name", "version"), + [ + ("foo-1.0.tar.gz", "foo", Version("1.0")), + ("foo_bar-1.0.tar.gz", "foo-bar", Version("1.0")), + ], +) +def test_create_sdist_filename(filename, name, version): + assert create_sdist_filename(name, version) == filename + + @pytest.mark.parametrize( ("filename", "name", "version"), [("foo-1.0.tar.gz", "foo", Version("1.0"))] )