Repository navigation
fix(utils): reject empty tag components in parse_wheel_filename/parse_tag (#577) - #1234
Merged
Merged
Conversation
henryiii
approved these changes
Jun 8, 2026
Contributor
There was a problem hiding this comment.
I've got a potential (hand-written) follow-up to reduce the number of times this is parsed. I also asked Claude for a review after I reviewed it, it was happy with it too.
Dumping followup here so I don't lose the stash
diff --git a/src/packaging/tags.py b/src/packaging/tags.py
index 086d42a..8bd4ec1 100644
--- a/src/packaging/tags.py
+++ b/src/packaging/tags.py
@@ -234,23 +234,24 @@ def parse_tag(tag: str, *, validate_order: bool = False) -> frozenset[Tag]:
.. versionadded:: 26.3
Raises :class:`InvalidTag` on empty tag components.
"""
- tags = set()
- interpreters, abis, platforms = tag.split("-")
- for component in (interpreters, abis, platforms):
- if "" in component.split("."):
+ component_parts = [component.split(".") for component in tag.split("-")]
+ for parts in component_parts:
+ if "" in parts:
+ component = ".".join(parts)
raise InvalidTag(f"Tag {tag!r} has an empty component: {component!r}")
- if validate_order:
- for component in (interpreters, abis, platforms):
- parts = component.split(".")
- if parts != sorted(parts):
- raise UnsortedTagsError(
- f"Tag component {component!r} is not in sorted order per PEP 425"
- )
- for interpreter in interpreters.split("."):
- for abi in abis.split("."):
- for platform_ in platforms.split("."):
- tags.add(Tag(interpreter, abi, platform_))
- return frozenset(tags)
+ if validate_order and parts != sorted(parts):
+ component = ".".join(parts)
+ raise UnsortedTagsError(
+ f"Tag component {component!r} is not in sorted order per PEP 425"
+ )
+
+ interpreters, abis, platforms = component_parts
+ return frozenset(
+ Tag(interpreter, abi, platform_)
+ for interpreter in interpreters
+ for abi in abis
+ for platform_ in platforms
+ )
def _get_config_var(name: str, warn: bool = False) -> int | str | None:…_tag
parse_wheel_filename/parse_tag silently built nonsense Tag('', 'none',
'any') instances from filenames with an empty interpreter/ABI/platform
component. An empty component can never match a real sys_tags() entry, so
accepting it masks upstream filename corruption.
Raise a new InvalidTag(ValueError) from parse_tag and wrap it as
InvalidWheelFilename, mirroring the UnsortedTagsError handling from pypa#1150.
Narrow on purpose: rejects only empty components, not the broader
interpreter-format grammar left unresolved in pypa#577.
henryiii
force-pushed
the
fix/parse-tag-empty-components
branch
from
June 8, 2026 14:06
0ba3ef2 to
bf6494c
Compare
This was referenced Jun 8, 2026
r266-tech
added a commit
to r266-tech/packaging
that referenced
this pull request
Jun 19, 2026
parse_wheel_filename gained empty-tag-component rejection in 26.3 (pypa#1234) but only parse_tag got the matching annotation; add .. versionchanged:: 26.3. parse_sdist_filename's :raises list omitted the invalid-version case (the Version() call is wrapped as InvalidSdistFilename); add it. Both from pypa#1239.
henryiii
pushed a commit
that referenced
this pull request
Jun 19, 2026
#1273) parse_wheel_filename gained empty-tag-component rejection in 26.3 (#1234) but only parse_tag got the matching annotation; add .. versionchanged:: 26.3. parse_sdist_filename's :raises list omitted the invalid-version case (the Version() call is wrapped as InvalidSdistFilename); add it. Both from #1239.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes part of #577.
parse_wheel_filename/parse_tagsilently build nonsenseTag('', 'none', 'any')instances from filenames with an empty interpreter/ABI/platform component, e.g.:foo-1.0--none-any.whl(empty interpreter)foo-1.0-py3-none-.whl(empty platform)foo-1.0-py3.-none-any.whl(empty member of a compressed tag set)An empty component can never match a real
sys_tags()entry, so accepting it silently masks upstream filename corruption/truncation instead of surfacing it.This raises a new
InvalidTag(ValueError)fromparse_tagand wraps it asInvalidWheelFilename, mirroring theUnsortedTagsErrorhandling added in #1150.Deliberately narrow: it only rejects empty components — the slice that is illegal under every spec reading. It does not add the broader interpreter-format grammar check discussed in #577 (the
\w+\d+/ shortname-grammar variants), which was left unresolved feature-vs-bug in the original thread.