From 395c69812a6f7ab54c203d50381d81dbfafc0951 Mon Sep 17 00:00:00 2001 From: radixun <278968099+radixun@users.noreply.github.com> Date: Sun, 1 Nov 2026 14:31:50 +0400 Subject: [PATCH] Check IBAN length for each registered country --- src/validators/iban.py | 98 ++++++++++++++++++++++++++++++++++++++++++ tests/test_iban.py | 28 +++++++++++- 2 files changed, 124 insertions(+), 2 deletions(-) diff --git a/src/validators/iban.py b/src/validators/iban.py index cc6224b7..9f746781 100644 --- a/src/validators/iban.py +++ b/src/validators/iban.py @@ -6,6 +6,100 @@ # local from .utils import validator +# Swift IBAN Registry, release 103 (September 2026). +# https://www.swift.com/resource/iban-registry-pdf +_COUNTRY_LENGTHS = { + "AD": 24, + "AE": 23, + "AL": 28, + "AT": 20, + "AZ": 28, + "BA": 20, + "BE": 16, + "BG": 22, + "BH": 22, + "BI": 27, + "BR": 29, + "BY": 28, + "CH": 21, + "CR": 22, + "CY": 28, + "CZ": 24, + "DE": 22, + "DJ": 27, + "DK": 18, + "DO": 28, + "EE": 20, + "EG": 29, + "ES": 24, + "FI": 18, + "FK": 18, + "FO": 18, + "FR": 27, + "GB": 22, + "GE": 22, + "GI": 23, + "GL": 18, + "GR": 27, + "GT": 28, + "HN": 28, + "HR": 21, + "HU": 28, + "IE": 22, + "IL": 23, + "IQ": 23, + "IS": 26, + "IT": 27, + "JO": 30, + "KW": 30, + "KZ": 20, + "LB": 28, + "LC": 32, + "LI": 21, + "LT": 20, + "LU": 20, + "LV": 21, + "LY": 25, + "MC": 27, + "MD": 24, + "ME": 22, + "MK": 19, + "MN": 20, + "MR": 27, + "MT": 31, + "MU": 30, + "NI": 28, + "NL": 18, + "NO": 15, + "OM": 23, + "PK": 24, + "PL": 28, + "PS": 29, + "PT": 25, + "QA": 29, + "RO": 24, + "RS": 22, + "RU": 33, + "SA": 24, + "SC": 31, + "SD": 18, + "SE": 24, + "SI": 19, + "SK": 24, + "SM": 27, + "SO": 23, + "ST": 25, + "SV": 28, + "TL": 23, + "TN": 24, + "TR": 26, + "UA": 29, + "VA": 22, + "VG": 24, + "XK": 20, + "YE": 30, +} + def _char_value(char: str): """A=10, B=11, ..., Z=35 (case-insensitive).""" @@ -23,6 +117,9 @@ def _mod_check(value: str): def iban(value: str, /): """Return whether or not given value is a valid IBAN code. + Check the registered country code, national length, and checksum. + This does not check all national account rules or whether the account exists. + Examples: >>> iban('DE29100500001061045672') True @@ -40,6 +137,7 @@ def iban(value: str, /): return ( ( re.match(r"^[a-z]{2}[0-9]{2}[a-z0-9]{11,30}$", value, re.IGNORECASE | re.ASCII) + and len(value) == _COUNTRY_LENGTHS.get(value[:2].upper()) and _mod_check(value) ) if value diff --git a/tests/test_iban.py b/tests/test_iban.py index 73f12d88..fc672b16 100644 --- a/tests/test_iban.py +++ b/tests/test_iban.py @@ -7,7 +7,16 @@ from validators import ValidationError, iban -@pytest.mark.parametrize("value", ["GB82WEST12345698765432", "NO9386011117947"]) +@pytest.mark.parametrize( + "value", + [ + "GB82WEST12345698765432", + "NO9386011117947", + "DE29100500001061045672", + "LC55HEMM000100010012001200023015", + "RU5600000000000000000000000000000", + ], +) def test_returns_true_on_valid_iban(value: str): """Test returns true on valid iban.""" assert iban(value) @@ -19,7 +28,22 @@ def test_returns_true_on_valid_lowercase_iban(value: str): assert iban(value) -@pytest.mark.parametrize("value", ["GB81WEST12345698765432", "NO9186011117947"]) +@pytest.mark.parametrize( + "value", + [ + "GB81WEST12345698765432", + "NO9186011117947", + # These values have correct checksums but incorrect country lengths. + "DE3110050000106104567", + "DE631005000010610456720", + "NO37860111179470", + "LC67000000000000000000000000000", + "RU56000000000000000000000000000000", + # These prefixes have no registered IBAN format. + "ZZ66000000000000000000", + "US35000000000000000000", + ], +) def test_returns_failed_validation_on_invalid_iban(value: str): """Test returns failed validation on invalid iban.""" assert isinstance(iban(value), ValidationError)