Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added packaging.utils.create_wheel_filename and create_sdist_filename functions #409

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Changelog
*unreleased*
~~~~~~~~~~~~

* Added ``packaging.utils.create_wheel_filename()`` and ``create_sdist_filename()`` (:issue:`408`)
* ``Marker.evaluate`` will now assume evaluation environment with empty ``extra``.
Evaluating markers like ``"extra == 'xyz'"`` without passing any extra in the
``environment`` will no longer raise an exception.
Expand Down
41 changes: 41 additions & 0 deletions docs/utils.rst
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,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,
Expand Down Expand Up @@ -81,6 +108,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
Expand Down
31 changes: 30 additions & 1 deletion packaging/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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+)(.*)")
Expand Down Expand Up @@ -83,6 +84,30 @@ def canonicalize_version(
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]]:
Expand Down Expand Up @@ -119,6 +144,10 @@ def parse_wheel_filename(
return (name, version, build, tags)


def create_sdist_filename(name: str, version: Version) -> str:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parse -> create feels... asymmetric. My instinctive answer for an alternative to create is compose and a Google search got me to https://stackoverflow.com/questions/148857/what-is-the-opposite-of-parse as the first result, which makes me feel validated. :)

Let's change to this naming scheme:

Suggested change
def create_sdist_filename(name: str, version: Version) -> str:
def compose_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 filename.endswith(".tar.gz"):
file_stem = filename[: -len(".tar.gz")]
Expand Down
51 changes: 50 additions & 1 deletion tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
InvalidWheelFilename,
canonicalize_name,
canonicalize_version,
create_sdist_filename,
create_wheel_filename,
parse_sdist_filename,
parse_wheel_filename,
)
Expand Down Expand Up @@ -94,6 +96,43 @@ def test_canonicalize_version_no_strip_trailing_zero(version):
(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")},
),
],
Comment on lines +105 to +106
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a test putting numpy-1.23.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl through parse_..., and putting the result through create_...?

)
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"),
[
(
"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")},
),
],
)
def test_parse_wheel_filename(filename, name, version, build, tags):
Expand All @@ -119,7 +158,17 @@ def test_parse_wheel_invalid_filename(filename):

@pytest.mark.parametrize(
("filename", "name", "version"),
[("foo-1.0.tar.gz", "foo", Version("1.0")), ("foo-1.0.zip", "foo", Version("1.0"))],
[
("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"))]
)
def test_parse_sdist_filename(filename, name, version):
assert parse_sdist_filename(filename) == (name, version)
Expand Down