pypa/pip · warning · UnsortedTagsError
Tag component {component!r} is not in sorted order per PEP 4
Error message
Tag component {component!r} is not in sorted order per PEP 425 What it means
Raised as UnsortedTagsError (tags.py:230), a ValueError subclass, by parse_tag(tag, validate_order=True) when any of the three hyphen-separated components of a compressed tag set is not in sorted order per PEP 425. PEP 425 requires compressed-tag components (joined by '.') to list their parts in ascending lexical order so tag sets are canonical.
Source
Thrown at src/pip/_vendor/packaging/tags.py:230
If **validate_order** is true, compressed tag set components are checked
to be in sorted order as required by PEP 425.
:param str tag: The tag to parse, e.g. ``"py3-none-any"``.
:param bool validate_order: Check whether compressed tag set components
are in sorted order.
:raises UnsortedTagsError: If **validate_order** is true and any compressed tag
set component is not in sorted order.
.. versionadded:: 26.1
The *validate_order* parameter.
"""
tags = set()
interpreters, abis, platforms = tag.split("-")
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)
def _get_config_var(name: str, warn: bool = False) -> int | str | None:
value: int | str | None = sysconfig.get_config_var(name)
if value is None and warn:
logger.debug(
"Config variable '%s' is unset, Python ABI tag may be incorrect", name
)
return value
View on GitHub (pinned to d7d0d0a394)
Solutions
- Reorder the dotted component into sorted order: 'py3.py2' -> 'py2.py3'.
- If ordering is irrelevant to your use case, call parse_tag(tag) without validate_order to skip the check.
- Normalize authoring by always emitting sorted(part) before joining with '.'.
Example fix
# before
from pip._vendor.packaging.tags import parse_tag
tags = parse_tag("cp312.cp311-none-any", validate_order=True) # raises
# after
tags = parse_tag("cp311.cp312-none-any", validate_order=True) Defensive patterns
Strategy: validation
Validate before calling
def tag_components_sorted(tag):
interpreter, abi, platform = tag.split('-')
return all(part.split('.') == sorted(part.split('.'))
for part in (interpreter, abi, platform)) Type guard
null
Try / catch
from pip._vendor.packaging.tags import parse_tag, UnsortedTagsError
try:
tags = parse_tag(tag_str, validate_order=True)
except UnsortedTagsError as e:
parts = tag_str.split('-')
tag_str = '.'.join(sorted(parts[0].split('.'))) + '-' + \
'.'.join(sorted(parts[1].split('.'))) + '-' + \
'.'.join(sorted(parts[2].split('.')))
tags = parse_tag(tag_str, validate_order=True) Prevention
- Always emit '.'.join(sorted(parts)) when authoring compressed tag components.
- Only enable validate_order when you genuinely need PEP 425 canonical ordering.
When it happens
Trigger: parse_tag('py2.py3-none-any', validate_order=True) is fine (['py2','py3'] is sorted), but parse_tag('py3.py2-none-any', validate_order=True) raises because ['py3','py2'] != sorted(['py3','py2']). validate_order defaults to False, so this only fires when the caller explicitly opts in.
Common situations: Index generators and wheel-filename validators that enforce PEP 425 canonical ordering call parse_tag with validate_order=True. A hand-written tag string, or a buggy tool concatenating tags, can produce out-of-order components. Authoring a compressed interpreter/abi/platform list backwards.
Related errors
- Invalid wheel filename (compressed tag set components must b
- Invalid requirement: {req_as_string!r}: {exc}
- Invalid requirement: {req_string!r}: {exc}
- invalid version: %s
- invalid constraint: %s
AI-assisted analysis of pypa/pip@d7d0d0a394 (2026-08-04).
Data as JSON: /data/errors/e974a5d4898040a6.json.
Report an issue: GitHub.