pypa/pip · error · IDNABidiError

Can not mix numeral types in a right-to-left label

Error message

Can not mix numeral types in a right-to-left label

What it means

IDNABidiError from check_bidi Bidi Rule 4: a right-to-left label may contain European Number (EN) or Arabic-Number (AN) codepoints, but not both. Mixing the two numeral systems (e.g. Western digits 0-9 with Arabic-Indic digits) in one RTL label is forbidden to keep rendering unambiguous.

Source

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

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

        if rtl:
            # Bidi rule 2
            if direction not in _bidi_rtl_allowed:
                raise IDNABidiError(f"Invalid direction for codepoint at position {idx} in a right-to-left label")
            # Bidi rule 3
            if direction in _bidi_rtl_valid_ending:
                valid_ending = True
            elif direction != "NSM":
                valid_ending = False
            # Bidi rule 4
            if direction in _bidi_rtl_numeric:
                if not number_type:
                    number_type = direction
                elif number_type != direction:
                    raise IDNABidiError("Can not mix numeral types in a right-to-left label")
        else:
            # Bidi rule 5
            if direction not in _bidi_ltr_allowed:
                raise IDNABidiError(f"Invalid direction for codepoint at position {idx} in a left-to-right label")
            # Bidi rule 6
            if direction in _bidi_ltr_valid_ending:
                valid_ending = True
            elif direction != "NSM":
                valid_ending = False

    if not valid_ending:
        raise IDNABidiError("Label ends with illegal codepoint directionality")

    return True


def check_initial_combiner(label: str) -> bool:
    """Reject labels that begin with a combining mark.

View on GitHub (pinned to d7d0d0a394)

Solutions

  1. Normalize all digits in the label to a single numeral system (prefer ASCII EN, or fully Arabic-Indic AN) before encoding.
  2. Use str.translate with a digit-unification mapping table.
  3. Validate with a regex that the label contains only one digit class.

Example fix

# before
idna.encode('اا1٢٣')  # Can not mix numeral types

# after
arabic_indic = {ord(a): ord(b) for a, b in zip('٠١٢٣٤٥٦٧٨٩', '0123456789')}
idna.encode('اا1٢٣'.translate(arabic_indic))
Defensive patterns

Strategy: validation

Validate before calling

import unicodedata
def unify_digits(label: str) -> str:
    # collapse Arabic-Indic and Extended Arabic-Indic digits to ASCII
    m = {ord(a): ord(b) for a, b in zip('٠١٢٣٤٥٦٧٨٩۰۱۲۳۴۵۶۷۸۹', '0123456789'*2)}
    return label.translate(m)
def has_single_numeral_type(label: str) -> bool:
    cats = {unicodedata.bidirectional(c) for c in label}
    return not ({'EN','AN'} <= cats and len({'EN','AN'} & cats) > 1)

Type guard

import unicodedata
def uses_single_numeral(label: str) -> bool:
    found = {unicodedata.bidirectional(c) for c in label}
    return not ({'EN','AN'} <= cats := found)

Try / catch

from idna import IDNABidiError
try:
    idna.encode(label)
except IDNABidiError as e:
    if 'mix numeral' in str(e):
        label = unify_digits(label)  # retry with unified digits
    else:
        raise

Prevention

When it happens

Trigger: A label containing both ASCII digits (0-9, category EN) and Arabic-Indic digits (٠-٩, category AN), or both EN and Extended Arabic-Indic digits, within an RTL (R/AL) label. Example: 'اا1٢' mixes '1' (EN) and '٢' (AN).

Common situations: Phone-number or product-code subdomains that combine ASCII digits with Arabic-Indic digits; data feeds merging localized numeric fragments; OCR or transliteration pipelines that normalize some but not all digits.

Related errors


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