pypa/pip · warning · UnsortedTagsError

Tag component is not in sorted order per PEP 425

Error message

Tag component {component!r} is not in sorted order per PEP 425

What it means

Raised as UnsortedTagsError (ValueError subclass) by parse_tag (tags.py:282-286) only when validate_order=True and a compressed component's '.'-separated parts are not in ascending sorted order. PEP 425 requires compressed tag components to be sorted so index lookups can short-circuit; this check enforces it. The comparison is lexicographic string sort (parts != sorted(parts)).

Solutions

  1. Sort each '.'-separated component lexicographically before emitting the tag string.
  2. If the order is intentional for your tool, omit validate_order (it defaults to False).
  3. Use parse_tag without validate_order for tolerant parsing, and apply ordering only when emitting canonical tags.
  4. Catch UnsortedTagsError and re-sort the component automatically, then re-parse.

Example fix

# before
parse_tag('py3.py2-none-any', validate_order=True)  # UnsortedTagsError

# after - emit components in sorted order
comp = '.'.join(sorted(['py3', 'py2']))  # 'py2.py3'
parse_tag(f'{comp}-none-any', validate_order=True)
Defensive patterns

Strategy: validation

Validate before calling

def sorted_component(comp: str) -> str:
    return '.'.join(sorted(comp.split('.')))

# then build the tag with each component pre-sorted

Type guard

def components_sorted(tag: str) -> bool:
    return all(p.split('.') == sorted(p.split('.')) for p in tag.split('-'))

Try / catch

from packaging.tags import UnsortedTagsError
try:
    parse_tag(tag, validate_order=True)
except UnsortedTagsError:
    tag = '-'.join(sorted_component(c) for c in tag.split('-'))
    parse_tag(tag, validate_order=True)

Prevention

When it happens

Trigger: parse_tag('py3.py2-none-any', validate_order=True) (['py3','py2'] != sorted ['py2','py3']); parse_tag('cp310.cp39-...', validate_order=True); any compressed tag where a higher version precedes a lower one within a component.

Common situations: A wheel index/mirror tool enforcing PEP 425 ordering on submitted compressed tags; a build tool generating py2.py3 tags in the wrong order; validating user-uploaded wheel filenames for spec compliance.

Related errors


AI-assisted analysis of pypa/pip@f399c37189 (2026-08-08). Data as JSON: /api/errors/e974a5d4898040a6. Report an issue: GitHub.

Appendix: source

Thrown at src/pip/_vendor/packaging/tags.py:284

    .. versionadded:: 26.3
       Raises :class:`InvalidTag` when an interpreter component is not an
       identifier, a tag component is empty, or a tag does not have exactly
       three components.
       Added the *limit* parameter. Raises :class:`TooManyTagsError` if the compressed
       tag set would generate more than *limit* tags.
    """

    if limit is not None and limit < 0:
        raise ValueError("limit must be non-negative")

    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 and parts != sorted(parts):
            component = ".".join(parts)
            raise UnsortedTagsError(
                f"Tag component {component!r} is not in sorted order per PEP 425"
            )

    tag_count = 1
    for parts in component_parts:
        tag_count *= len(parts)

    if limit is not None and tag_count > limit:
        raise TooManyTagsError(
            f"Compressed tag set would generate {tag_count} tags, exceeding "
            f"limit {limit}"
        )

    try:
        interpreters, abis, platforms = component_parts
    except ValueError as exc:
        raise InvalidTag(f"Tag {tag!r} must have exactly three components") from exc
    for interpreter in interpreters:

View on GitHub (pinned to f399c37189)