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
- Normalize all digits in the label to a single numeral system (prefer ASCII EN, or fully Arabic-Indic AN) before encoding.
- Use str.translate with a digit-unification mapping table.
- 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
- Normalize all digits in a label to one system (ASCII EN or fully AN) before encoding.
- For numeric subdomains, prefer ASCII digits.
- Validate with a regex that only one digit class appears.
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
- First codepoint in label {label!r} must be directionality L,
- Invalid direction for codepoint at position {idx} in a right
- Invalid direction for codepoint at position {idx} in a left-
- Label ends with illegal codepoint directionality
- Unknown directionality in label {label!r} at position {idx}
AI-assisted analysis of pypa/pip@d7d0d0a394 (2026-08-04).
Data as JSON: /data/errors/58a455dc7a44fcb9.json.
Report an issue: GitHub.