pypa/pip · error · IDNAError

should pass a unicode string to the function rather than a b

Error message

should pass a unicode string to the function rather than a byte string.

What it means

Raised by encode() (core.py:549) when the input is not a str and cannot be decoded as ASCII — i.e. the caller passed a bytes/bytearray containing non-ASCII bytes. idna.encode() expects a Unicode (str) domain; bytes input is only tolerated if it is pure ASCII, otherwise it raises IDNAError with this message. The original UnicodeDecodeError/TypeError is chained as the cause.

Source

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

    :param transitional: Forwarded to :func:`uts46_remap` when ``uts46``
        is ``True``. Deprecated: emits a :class:`DeprecationWarning` and
        will be removed in a future version.
    :returns: The encoded domain as ASCII :class:`bytes`.
    :raises IDNAError: If the domain is empty, contains an invalid label,
        or exceeds the maximum domain length.
    """
    if transitional:
        warnings.warn(
            "Transitional processing has been removed from UTS #46. "
            "The transitional argument will be removed in a future version.",
            DeprecationWarning,
            stacklevel=2,
        )
    if not isinstance(s, str):
        try:
            s = str(s, "ascii")
        except (UnicodeDecodeError, TypeError) as err:
            raise IDNAError("should pass a unicode string to the function rather than a byte string.") from err
    if len(s) > _max_input_length:
        raise IDNAError("Domain too long")
    if uts46:
        s = uts46_remap(s, std3_rules, transitional)

    # 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 labels[-1] == "":
        del labels[-1]
        trailing_dot = True

View on GitHub (pinned to d7d0d0a394)

Solutions

  1. Decode the bytes to str with the correct encoding (usually UTF-8) before calling encode: idna.encode(s.decode('utf-8')).
  2. Make the caller-side type explicit: accept only str at your function boundary and coerce/validate early.
  3. If the value really is an ASCII A-label in bytes, decode as ASCII first: s.decode('ascii').

Example fix

// before
idna.encode(b'münchen.com')  # bytes with non-ASCII -> IDNAError

// after
idna.encode('münchen.com')              # pass a str
# or, coming from bytes:
idna.encode(raw.decode('utf-8'))
Defensive patterns

Strategy: type-guard

Validate before calling

def to_unicode_domain(s):
    if isinstance(s, (bytes, bytearray)):
        return s.decode('utf-8')
    if not isinstance(s, str):
        raise TypeError(f'expected str or bytes, got {type(s).__name__}')
    return s

encoded = idna.encode(to_unicode_domain(raw))

Type guard

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

Try / catch

import idna

try:
    encoded = idna.encode(raw)
except idna.IDNAError as err:
    if 'unicode string' in str(err):
        # raw was bytes with non-ASCII; decode and retry
        encoded = idna.encode(raw.decode('utf-8'))
    else:
        raise

Prevention

When it happens

Trigger: Calling idna.encode() with bytes that contain non-ASCII octets, e.g. idna.encode(b'münchen.com'), idna.encode(some_bytes_variable), or passing a UTF-8-encoded byte string read from a file/socket without decoding first.

Common situations: Reading domains from binary sources (files opened 'rb', network sockets, database BLOB columns) and forgetting to .decode('utf-8'); mixing str/bytes across an API boundary; porting code that previously handled bytes; or framework code that passes request bodies as bytes into a URL/IDNA path.

Related errors


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