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

  1. Reorder the dotted component into sorted order: 'py3.py2' -> 'py2.py3'.
  2. If ordering is irrelevant to your use case, call parse_tag(tag) without validate_order to skip the check.
  3. 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

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


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