1"""IBAN."""
2
3# standard
4import re
5
6# local
7from .utils import validator
8
9
10def _char_value(char: str):
11 """A=10, B=11, ..., Z=35."""
12 return char if char.isdigit() else str(10 + ord(char) - ord("A"))
13
14
15def _mod_check(value: str):
16 """Check if the value string passes the mod97-test."""
17 # move country code and check numbers to end
18 rearranged = value[4:] + value[:4]
19 return int("".join(_char_value(char) for char in rearranged)) % 97 == 1
20
21
22@validator
23def iban(value: str, /):
24 """Return whether or not given value is a valid IBAN code.
25
26 Examples:
27 >>> iban('DE29100500001061045672')
28 True
29 >>> iban('123456')
30 ValidationError(func=iban, args={'value': '123456'})
31
32 Args:
33 value:
34 IBAN string to validate.
35
36 Returns:
37 (Literal[True]): If `value` is a valid IBAN code.
38 (ValidationError): If `value` is an invalid IBAN code.
39 """
40 return (
41 (re.match(r"^[a-z]{2}[0-9]{2}[a-z0-9]{11,30}$", value, re.IGNORECASE) and _mod_check(value))
42 if value
43 else False
44 )