pypa/pip · error · IDNAError

Empty domain

Error message

Empty domain

What it means

Raised by idna.encode and idna.decode when the input domain string is effectively empty — after splitting on label separators, labels is empty or equals ['']. This catches inputs like '', or strings consisting only of separator characters.

Solutions

  1. Check for empty or whitespace-only input before calling encode/decode.
  2. Treat empty hostname as a special case (e.g. use 'localhost' or reject the form).
  3. Validate the URL contains a non-empty hostname before extracting it for IDNA.

Example fix

# before
host = parsed.hostname or ''
idna.encode(host)  # raises Empty domain when host is ''

# after
host = parsed.hostname
if not host:
    raise ValueError('Hostname is required')
idna.encode(host)
Defensive patterns

Strategy: validation

Validate before calling

def is_non_empty_domain(domain: str) -> bool:
    return bool(domain and domain.strip())

Type guard

def is_non_empty_domain(domain: str) -> bool:
    return isinstance(domain, str) and len(domain.strip()) > 0

Try / catch

from idna.core import IDNAError
try:
    result = idna.encode(domain)
except IDNAError:
    if not domain:
        raise ValueError('Domain must not be empty')
    raise

Prevention

When it happens

Trigger: Calling idna.encode('') or idna.decode(''), or passing a string of only dots such as '.' or '...' where all labels are stripped.

Common situations: Empty hostname field from a parsed URL; user form with blank domain; programmatic generation that produces empty strings; default/placeholder values passed through.

Related errors


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

Appendix: source

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

    """
    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:
            u = ulabel(label)
        except IDNAError:
            if display and label[:4].lower() == "xn--":
                u = label.lower()
            else:
                raise
        if u:
            result.append(u)
        else:
            raise IDNAError("Empty label")
    if trailing_dot:
        result.append("")
    return ".".join(result)

View on GitHub (pinned to f399c37189)