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
- Decode the bytes as ASCII (A-labels are ASCII by definition): idna.decode(s.decode('ascii')).
- If the source is UTF-8 text, decode it to str first and validate it is ASCII-range before calling decode().
- 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
- Decode bytes to str at the boundary before passing to idna.decode().
- Validate bytes are pure-ASCII before treating them as an A-label.
- Type-annotate domain parameters as str and enforce with a runtime check.
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
- should pass a unicode string to the function rather than a b
- Codepoint {_unot(cp_value)} not allowed at position {pos + 1
- Codepoint {_unot(cp_value)} at position {pos + 1} of {label!
- Malformed A-label, no Punycode eligible content found
- A-label must not end with a hyphen
AI-assisted analysis of pypa/pip@d7d0d0a394 (2026-08-04).
Data as JSON: /data/errors/971113d3279ebe6f.json.
Report an issue: GitHub.