IBAN Validator — Python source
Validate International Bank Account Numbers (IBAN) with the mod-97 checksum, verify the country-specific length, and format the result. 100% client-side, no network.
This is the Python implementation — the same logic the interactive tool runs, in a shareable, citable form.
"""Pure IBAN validation logic — ISO 13616 mod-97 checksum.
Language: Python
CosmoDev polyglot showcase port of the ``iban-validator`` tool.
Ported from src/lib/iban.ts — display source, part of CosmoDev's
polyglot tool pages.
Normalizes the input (uppercase, strip whitespace/dashes), checks the
structural regex (country code + 2 check digits + 1-30 BBAN chars),
verifies the per-country length, and runs the ISO 13616 mod-97 checksum:
move the first 4 chars to the end, map A=10..Z=35, and confirm the
resulting integer is congruent to 1 mod 97. Python ints are arbitrary
precision, but the remainder is folded one digit at a time (it stays
under 97) to mirror the reference algorithm exactly. validate_iban never
raises — it reports failures through the ``error`` field.
"""
from __future__ import annotations
import re
from dataclasses import dataclass
from typing import Optional
# Per-country IBAN lengths (ISO 13616) — a representative subset.
IBAN_LENGTHS: dict[str, int] = {
"AL": 28, "AD": 24, "AT": 20, "AZ": 28, "BH": 22, "BY": 28, "BE": 16,
"BA": 20, "BR": 29, "BG": 22, "CR": 22, "HR": 21, "CY": 28, "CZ": 24,
"DK": 18, "DO": 28, "EE": 20, "FO": 18, "FI": 18, "FR": 27, "GE": 22,
"DE": 22, "GI": 23, "GR": 27, "GL": 18, "GT": 28, "HU": 28, "IS": 26,
"IE": 22, "IL": 23, "IT": 27, "JO": 30, "KZ": 20, "XK": 20, "KW": 30,
"LV": 21, "LB": 28, "LI": 21, "LT": 20, "LU": 20, "MK": 19, "MT": 31,
"MR": 27, "MU": 30, "MC": 27, "MD": 24, "ME": 22, "NL": 18, "NO": 15,
"PK": 24, "PS": 29, "PL": 28, "PT": 25, "QA": 29, "RO": 24, "LC": 32,
"SM": 27, "ST": 25, "SA": 24, "RS": 22, "SC": 31, "SK": 24, "SI": 19,
"SG": 19, "ES": 24, "SE": 24, "CH": 21, "TL": 23, "TN": 24, "TR": 26,
"UA": 29, "AE": 23, "GB": 22, "VG": 24,
}
_STRUCTURE = re.compile(r"^[A-Z]{2}[0-9]{2}[A-Z0-9]{1,30}$")
_CC_HEAD = re.compile(r"^[A-Z]{2}")
@dataclass
class IbanInfo:
"""Mirrors the TypeScript lib's result shape."""
input: str
cleaned: str
country_code: Optional[str] = None
valid: bool = False
checksum_ok: bool = False
length_ok: bool = False
expected_length: Optional[int] = None
formatted: str = ""
error: Optional[str] = None
def mod97_check(cleaned: str) -> bool:
"""ISO 13616 mod-97 checksum over a CLEANED iban (uppercase, no spaces)."""
# Move the first 4 chars (country + check) to the end.
rearranged = cleaned[4:] + cleaned[:4]
numeric: list[str] = []
for ch in rearranged:
code = ord(ch)
if 48 <= code <= 57:
numeric.append(ch)
elif 65 <= code <= 90:
numeric.append(str(code - 55)) # A=10 .. Z=35
else:
return False # invalid character
rem = 0
for ch in "".join(numeric):
rem = (rem * 10 + (ord(ch) - 48)) % 97
return rem == 1
def validate_iban(input: str) -> IbanInfo:
"""Validate an IBAN. Always returns IbanInfo; never raises."""
cleaned = re.sub(r"[\s-]", "", (input or "").upper())
cc = cleaned[:2] if _CC_HEAD.match(cleaned) else None
expected = IBAN_LENGTHS.get(cc) if cc else None
info = IbanInfo(
input=input or "",
cleaned=cleaned,
country_code=cc,
expected_length=expected,
formatted=re.sub(r"(.{4})(?=.)", r"\1 ", cleaned).strip(),
)
if not _STRUCTURE.match(cleaned):
info.error = "Invalid IBAN format."
return info
length_ok = expected is None or len(cleaned) == expected
checksum_ok = mod97_check(cleaned)
info.length_ok = length_ok
info.checksum_ok = checksum_ok
if not length_ok:
info.error = f"Length should be {expected} for {cc}."
return info
if not checksum_ok:
info.error = "Checksum failed."
return info
info.valid = True
return info
if __name__ == "__main__":
# GB82 WEST 1234 5698 7654 32 — a known-good reference IBAN.
print(validate_iban("GB82WEST12345698765432"))
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →