pypa/pip · error · IDNAError

Invalid ASCII in A-label

Error message

Invalid ASCII in A-label

What it means

Raised by decode() (core.py:617) when the input is not a str and cannot be decoded as ASCII — i.e. the caller passed bytes/bytearray containing non-ASCII octets. decode() expects a str (or pure-ASCII bytes); non-ASCII bytes are not valid in an A-label, which is by definition ASCII, so the UnicodeDecodeError/TypeError is wrapped as IDNAError('Invalid ASCII in A-label').

Source

Thrown at src/pip/_vendor/idna/core.py:617

    :param uts46: If ``True``, apply UTS #46 mapping before decoding.
    :param std3_rules: Forwarded to :func:`uts46_remap` when ``uts46`` is
        ``True``.
    :param display: If ``True``, any ``xn--`` label that fails IDNA
        validation is passed through unchanged (lowercased) rather than
        aborting the whole call. Intended for "decode for display"
        consumers (e.g. URL libraries, HTTP clients) that want to show
        the user the label as it appears on the wire when it cannot be
        rendered as Unicode. Matches the per-label recovery prescribed
        by UTS #46 §4 and the WHATWG URL "domain to Unicode" algorithm.
    :returns: The decoded domain as a Unicode string.
    :raises IDNAError: If the input is not valid ASCII, contains an
        invalid label, or is empty.
    """
    if not isinstance(s, str):
        try:
            s = str(s, "ascii")
        except (UnicodeDecodeError, TypeError) as err:
            raise IDNAError("Invalid ASCII in A-label") from err
    if len(s) > _max_input_length:
        raise IDNAError("Domain too long")
    if uts46:
        s = uts46_remap(s, std3_rules, False)
    # Reject inputs that exceed the maximum DNS domain length up-front
    # to avoid expensive computation on long inputs.
    if not valid_string_length(s, trailing_dot=True):
        raise IDNAError("Domain too long")
    trailing_dot = False
    result = []
    labels = s.split(".") if strict else _unicode_dots_re.split(s)
    if not labels or labels == [""]:
        raise IDNAError("Empty domain")
    if not labels[-1]:
        del labels[-1]
        trailing_dot = True
    for label in labels:
        try:

View on GitHub (pinned to d7d0d0a394)

Solutions

  1. Decode the bytes as ASCII (A-labels are ASCII by definition): idna.decode(s.decode('ascii')).
  2. If the source is UTF-8 text, decode it to str first and validate it is ASCII-range before calling decode().
  3. Make the caller-side type explicit: accept only str at the boundary and coerce/validate early.

Example fix

// before
idna.decode(b'xn--mnchen-3ya\xc3\xa4')  # non-ASCII byte -> IDNAError

// after
idna.decode('xn--mnchen-3ya')           # pass an ASCII str
# or, coming from bytes that should be ASCII:
idna.decode(raw.decode('ascii'))
Defensive patterns

Strategy: type-guard

Validate before calling

def to_ascii_domain(s):
    if isinstance(s, (bytes, bytearray)):
        try:
            return s.decode('ascii')
        except UnicodeDecodeError as err:
            raise ValueError('A-label must be pure ASCII') from err
    if not isinstance(s, str):
        raise TypeError(f'expected str or bytes, got {type(s).__name__}')
    return s

decoded = idna.decode(to_ascii_domain(raw))

Type guard

def is_str(s) -> bool:
    return isinstance(s, str)

Try / catch

import idna

try:
    decoded = idna.decode(raw)
except idna.IDNAError as err:
    if 'Invalid ASCII' in str(err):
        # raw was non-ASCII bytes; decode the source as UTF-8 and retry if appropriate
        decoded = idna.decode(raw.decode('utf-8'))
    else:
        raise

Prevention

When it happens

Trigger: Calling idna.decode() with bytes that contain non-ASCII octets, e.g. idna.decode(b'xn--mnchen-3ya\xc3\xa4'), or passing a bytes value read from a binary source without first decoding it. Any non-ASCII byte is impossible in a well-formed A-label.

Common situations: Reading A-labels from binary sources (files opened 'rb', sockets, DB BLOBs), mixing str/bytes across an API boundary, or framework code passing raw request bytes into a URL/IDNA decode path.

Related errors


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