pypa/pip · error · IDNABidiError

Unknown directionality in label {label!r} at position {idx}

Error message

Unknown directionality in label {label!r} at position {idx}

What it means

Raised inside check_bidi (idna IDNABidiError) when unicodedata.bidirectional(cp) returns an empty string for some codepoint in the label. An empty bidi category means the running Python's unicodedata module predates the Unicode version that defines that codepoint, so idna cannot classify its directionality.

Source

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

    bidirectional categories ``R``, ``AL``, or ``AN``); set ``check_ltr``
    to ``True`` to apply it to LTR-only labels as well.

    :param label: The label to validate, as a Unicode string.
    :param check_ltr: If ``True``, apply the rules even when the label
        contains no RTL characters.
    :returns: ``True`` if the label satisfies the Bidi Rule.
    :raises IDNABidiError: If any of Bidi Rule conditions 1-6 are violated,
        or if the directional category of a codepoint cannot be determined.
    """
    if len(label) > _max_input_length:
        raise IDNAError("Label too long")
    # Bidi rules should only be applied if string contains RTL characters
    bidi_label = False
    for idx, cp in enumerate(label, 1):
        direction = unicodedata.bidirectional(cp)
        if direction == "":
            # String likely comes from a newer version of Unicode
            raise IDNABidiError(f"Unknown directionality in label {label!r} at position {idx}")
        if direction in _bidi_rtl_categories:
            bidi_label = True
    if not bidi_label and not check_ltr:
        return True

    # Bidi rule 1
    direction = unicodedata.bidirectional(label[0])
    if direction in _bidi_rtl_first:
        rtl = True
    elif direction == "L":
        rtl = False
    else:
        raise IDNABidiError(f"First codepoint in label {label!r} must be directionality L, R or AL")

    valid_ending = False
    number_type: Optional[str] = None
    for idx, cp in enumerate(label, 1):
        direction = unicodedata.bidirectional(cp)

View on GitHub (pinned to d7d0d0a394)

Solutions

  1. Upgrade Python to a release built against a newer Unicode version (CPython embeds the Unicode database at build time).
  2. Filter or reject input containing codepoints the runtime cannot classify before passing to idna.
  3. If on a managed runtime, install one bundled with up-to-date Unicode tables (e.g. python3.11+).

Example fix

# before
idna.encode('\U0001FA00somearabic\u0627')  # Unknown directionality on old Python

# after
# upgrade runtime, or pre-filter:
if any(unicodedata.bidirectional(c) == '' for c in label):
    raise ValueError('label uses unsupported codepoints for this runtime')
idna.encode(label)
Defensive patterns

Strategy: validation

Validate before calling

import unicodedata
def runtime_supports_label(label: str) -> bool:
    return all(unicodedata.bidirectional(c) != '' for c in label)

Type guard

import unicodedata
def is_label_bidi_known(label: str) -> bool:
    return all(unicodedata.bidirectional(c) for c in label)

Try / catch

from idna import IDNABidiError, encode
try:
    encode(label)
except IDNABidiError as e:
    if 'Unknown directionality' in str(e):
        # runtime Unicode tables are too old for this codepoint
        raise ValueError('upgrade Python runtime for this label') from e
    raise

Prevention

When it happens

Trigger: Encoding a domain containing a codepoint introduced in a Unicode version newer than the one compiled into the running CPython (e.g. a new emoji or historic script on Python built against Unicode 12 when the char is from Unicode 14). The label also must contain an RTL character (or check_ltr=True) for check_bidi to be reached.

Common situations: Running an older Python (e.g. 3.6/3.7) against modern domain data; shipping an app with a frozen/old runtime that lacks recent Unicode tables; CI on an old distro Python processing user-provided internationalized domains.

Related errors


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