Repository: joke2k/faker
Pull request #1637 (2022-04-21 04:14:48)

Add mexican CLABE bank code for `es_MX` locale
### What does this change

Adds a CLABE generator to the `bank` provider of `es_MX`.

### What was wrong

It is the code used for inter-bank payments, so it was a missing piece for the locale.

Source: https://en.wikipedia.org/wiki/CLABE

### How this fixes it

NA



Patch:
diff --git a/.bumpversion.cfg b/.bumpversion.cfg
index 010f9c82..cb6e9af7 100644
--- a/.bumpversion.cfg
+++ b/.bumpversion.cfg
@@ -1,5 +1,5 @@
 [bumpversion]
-current_version = 13.3.5
+current_version = 13.4.0
 files = VERSION faker/__init__.py docs/conf.py
 commit = True
 tag = True
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 46ba3657..217eb3f2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,9 @@
 ## Changelog
 
+### [v13.4.0 - 2022-04-22](https://github.com/joke2k/faker/compare/v13.3.5...v13.4.0)
+
+* Add `it_IT` automotive provider. Thanks @Giorgionocera.
+
 ### [v13.3.5 - 2022-04-20](https://github.com/joke2k/faker/compare/v13.3.4...v13.3.5)
 
 * Fix error in currency provider data. Thanks @zerogonal.
diff --git a/VERSION b/VERSION
index 51a2b1d4..74302c51 100644
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-13.3.5
+13.4.0
diff --git a/docs/conf.py b/docs/conf.py
index db5b5460..35e6d23d 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -51,9 +51,9 @@ copyright = "2014, Daniele Faraglia"
 # built documents.
 #
 # The short X.Y version.
-version = "13.3.5"
+version = "13.4.0"
 # The full version, including alpha/beta/rc tags.
-release = "13.3.5"
+release = "13.4.0"
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.
diff --git a/faker/__init__.py b/faker/__init__.py
index 88964e67..488386ef 100644
--- a/faker/__init__.py
+++ b/faker/__init__.py
@@ -2,6 +2,6 @@ from faker.factory import Factory
 from faker.generator import Generator
 from faker.proxy import Faker
 
-VERSION = "13.3.5"
+VERSION = "13.4.0"
 
 __all__ = ("Factory", "Generator", "Faker")
diff --git a/faker/providers/automotive/it_IT/__init__.py b/faker/providers/automotive/it_IT/__init__.py
new file mode 100644
index 00000000..c1545345
--- /dev/null
+++ b/faker/providers/automotive/it_IT/__init__.py
@@ -0,0 +1,15 @@
+from .. import Provider as AutomotiveProvider
+
+
+class Provider(AutomotiveProvider):
+    """Implement automotive provider for ``it_IT`` locale.
+
+    Sources:
+
+    - https://en.wikipedia.org/wiki/Vehicle_registration_plates_of_Italy
+    """
+
+    license_formats = (
+        # 1994-present
+        "??###??",
+    )
diff --git a/faker/providers/bank/es_MX/__init__.py b/faker/providers/bank/es_MX/__init__.py
new file mode 100644
index 00000000..d9d5f578
--- /dev/null
+++ b/faker/providers/bank/es_MX/__init__.py
@@ -0,0 +1,283 @@
+from typing import List, Optional, Tuple
+
+from .. import Provider as BankProvider
+
+
+def get_clabe_control_digit(clabe: str) -> int:
+    """Generate the checksum digit for a CLABE.
+
+    :param clabe: CLABE.
+    :return: The CLABE checksum digit.
+    """
+    factors = [3, 7, 1]
+    products: List[int] = []
+
+    for i, digit in enumerate(clabe[:17]):
+        products.append((int(digit) * factors[i % 3]) % 10)
+
+    return (10 - sum(products)) % 10
+
+
+def is_valid_clabe(clabe: str) -> bool:
+    """Check if a CLABE is valid using the checksum.
+
+    :param clabe: CLABE.
+    :return: True if the CLABE is valid, False otherwise.
+    """
+    if len(clabe) != 18 or not clabe.isdigit():
+        return False
+
+    return get_clabe_control_digit(clabe) == int(clabe[-1])
+
+
+class Provider(BankProvider):
+    """Bank provider for ``es_MX`` locale."""
+
+    banks: Tuple[str, ...] = (
+        "ABC Capital, S.A. I.B.M.",
+        "Acciones y Valores Banamex, S.A. de C.V., Casa de Bolsa",
+        "Actinver Casa de Bolsa, S.A. de C.V.",
+        "Akala, S.A. de C.V., Sociedad Financiera Popular",
+        "American Express Bank (México), S.A.",
+        "AXA Seguros, S.A. De C.V.",
+        "B y B Casa de Cambio, S.A. de C.V.",
+        "Banca Afirme, S.A.",
+        "Banca Mifel, S.A.",
+        "Banco Actinver, S.A.",
+        "Banco Ahorro Famsa, S.A.",
+        "Banco Autofin México, S.A.",
+        "Banco Azteca, S.A.",
+        "Banco BASE, S.A. de I.B.M.",
+        "Banco Compartamos, S.A.",
+        "Banco Credit Suisse (México), S.A.",
+        "Banco del Ahorro Nacional y Servicios Financieros, S.N.C.",
+        "Banco del Bajío, S.A.",
+        "Banco Inbursa, S.A.",
+        "Banco Inmobiliario Mexicano, S.A., Institución de Banca Múltiple",
+        "Banco Interacciones, S.A.",
+        "Banco Invex, S.A.",
+        "Banco J.P. Morgan, S.A.",
+        "Banco Mercantil del Norte, S.A.",
+        "Banco Monex, S.A.",
+        "Banco Multiva, S.A.",
+        "Banco Nacional de Comercio Exterior",
+        "Banco Nacional de México, S.A.",
+        "Banco Nacional de Obras y Servicios Públicos",
+        "Banco Nacional del Ejército, Fuerza Aérea y Armada",
+        "Banco PagaTodo S.A., Institución de Banca Múltiple",
+        "Banco Regional de Monterrey, S.A.",
+        "Banco Sabadell, S.A. I.B.M.",
+        "Banco Santander, S.A.",
+        "Banco Ve por Mas, S.A.",
+        "Banco Wal Mart de México Adelante, S.A.",
+        "BanCoppel, S.A.",
+        "Bank of America México, S.A.",
+        "Bank of Tokyo-Mitsubishi UFJ (México), S.A.",
+        "Bankaool, S.A., Institución de Banca Múltiple",
+        "Bansi, S.A.",
+        "Barclays Bank México, S.A.",
+        "BBVA Bancomer, S.A.",
+        "Bulltick Casa de Bolsa, S.A. de C.V.",
+        "Caja Popular Mexicana, S.C. de A.P. de R.L. De C.V.",
+        "Casa de Bolsa Finamex, S.A. de C.V.",
+        "Casa de Cambio Tíber, S.A. de C.V.",
+        "CI Casa de Bolsa, S.A. de C.V.",
+        "CLS Bank International",
+        "Consubanco, S.A.",
+        "Consultoría Internacional Banco, S.A.",
+        "Consultoría Internacional Casa de Cambio, S.A. de C.V.",
+        "Deutsche Bank México, S.A.",
+        "Deutsche Securities, S.A. de C.V.",
+        "Estructuradores del Mercado de Valores Casa de Bolsa, S.A. de C.V.",
+        "Evercore Casa de Bolsa, S.A. de C.V.",
+        "Financiera Nacional De Desarrollo Agropecuario, Rural, F y P.",
+        "Fincomún, Servicios Financieros Comunitarios, S.A. de C.V.",
+        "GBM Grupo Bursátil Mexicano, S.A. de C.V.",
+        "GE Money Bank, S.A.",
+        "HDI Seguros, S.A. de C.V.",
+        "Hipotecaria su Casita, S.A. de C.V.",
+        "HSBC México, S.A.",
+        "Industrial and Commercial Bank of China, S.A., Institución de Banca Múltiple",
+        "ING Bank (México), S.A.",
+        "Inter Banco, S.A.",
+        "Intercam Casa de Bolsa, S.A. de C.V.",
+        "Intercam Casa de Cambio, S.A. de C.V.",
+        "Inversora Bursátil, S.A. de C.V.",
+        "IXE Banco, S.A.",
+        "J.P. Morgan Casa de Bolsa, S.A. de C.V.",
+        "J.P. SOFIEXPRESS, S.A. de C.V., S.F.P.",
+        "Kuspit Casa de Bolsa, S.A. de C.V.",
+        "Libertad Servicios Financieros, S.A. De C.V.",
+        "MAPFRE Tepeyac S.A.",
+        "Masari Casa de Bolsa, S.A.",
+        "Merrill Lynch México, S.A. de C.V., Casa de Bolsa",
+        "Monex Casa de Bolsa, S.A. de C.V.",
+        "Multivalores Casa de Bolsa, S.A. de C.V. Multiva Gpo. Fin.",
+        "Nacional Financiera, S.N.C.",
+        "Opciones Empresariales Del Noreste, S.A. DE C.V.",
+        "OPERADORA ACTINVER, S.A. DE C.V.",
+        "Operadora De Pagos Móviles De México, S.A. De C.V.",
+        "Operadora de Recursos Reforma, S.A. de C.V.",
+        "OrderExpress Casa de Cambio , S.A. de C.V. AAC",
+        "Profuturo G.N.P., S.A. de C.V.",
+        "Scotiabank Inverlat, S.A.",
+        "SD. INDEVAL, S.A. de C.V.",
+        "Seguros Monterrey New York Life, S.A de C.V.",
+        "Sistema de Transferencias y Pagos STP, S.A. de C.V., SOFOM E.N.R.",
+        "Skandia Operadora S.A. de C.V.",
+        "Skandia Vida S.A. de C.V.",
+        "Sociedad Hipotecaria Federal, S.N.C.",
+        "Solución Asea, S.A. de C.V., Sociedad Financiera Popular",
+        "Sterling Casa de Cambio, S.A. de C.V.",
+        "Telecomunicaciones de México",
+        "The Royal Bank of Scotland México, S.A.",
+        "UBS Banco, S.A.",
+        "UNAGRA, S.A. de C.V., S.F.P.",
+        "Única Casa de Cambio, S.A. de C.V.",
+        "Valores Mexicanos Casa de Bolsa, S.A. de C.V.",
+        "Valué, S.A. de C.V., Casa de Bolsa",
+        "Vector Casa de Bolsa, S.A. de C.V.",
+        "Volkswagen Bank S.A. Institución de Banca Múltiple",
+        "Zúrich Compañía de Seguros, S.A.",
+        "Zúrich Vida, Compañía de Seguros, S.A.",
+    )
+
+    bank_codes: Tuple[int, ...] = (
+        2,
+        6,
+        9,
+        12,
+        14,
+        19,
+        21,
+        22,
+        30,
+        32,
+        36,
+        37,
+        42,
+        44,
+        58,
+        59,
+        60,
+        62,
+        72,
+        102,
+        103,
+        106,
+        108,
+        110,
+        112,
+        113,
+        116,
+        124,
+        126,
+        127,
+        128,
+        129,
+        130,
+        131,
+        132,
+        133,
+        134,
+        135,
+        136,
+        137,
+        138,
+        139,
+        140,
+        141,
+        143,
+        145,
+        147,
+        148,
+        150,
+        155,
+        156,
+        166,
+        168,
+        600,
+        601,
+        602,
+        604,
+        605,
+        606,
+        607,
+        608,
+        610,
+        611,
+        613,
+        614,
+        615,
+        616,
+        617,
+        618,
+        619,
+        620,
+        621,
+        622,
+        623,
+        624,
+        626,
+        627,
+        628,
+        629,
+        630,
+        631,
+        632,
+        633,
+        634,
+        636,
+        637,
+        638,
+        640,
+        642,
+        646,
+        647,
+        648,
+        649,
+        651,
+        652,
+        653,
+        655,
+        656,
+        659,
+        670,
+        674,
+        677,
+        679,
+        684,
+        901,
+        902,
+    )
+
+    def bank(self) -> str:
+        """Generate a mexican bank name.
+
+        :return: A mexican bank name.
+
+        :sample:
+        """
+        return self.random_element(self.banks)
+
+    def clabe(self, bank_code: Optional[int] = None) -> str:
+        """Generate a mexican bank account CLABE.
+
+        Sources:
+
+        - https://en.wikipedia.org/wiki/CLABE
+
+        :return: A fake CLABE number.
+
+        :sample:
+        :sample: bank_code=2
+        """
+        bank = bank_code or self.random_element(self.bank_codes)
+        city = self.random_int(0, 999)
+        branch = self.random_int(0, 9999)
+        account = self.random_int(0, 9999999)
+
+        result = f"{bank:03d}{city:03d}{branch:04d}{account:07d}"
+        control_digit = get_clabe_control_digit(result)
+
+        return result + str(control_digit)


Test patch:
diff --git a/tests/providers/test_automotive.py b/tests/providers/test_automotive.py
index 1570c05f..ded4ebf7 100644
--- a/tests/providers/test_automotive.py
+++ b/tests/providers/test_automotive.py
@@ -154,6 +154,12 @@ class TestFrFr(_SimpleAutomotiveTestMixin):
     license_plate_pattern: Pattern = re.compile(r"\d{3}-[A-Z]{3}-\d{2}|[A-Z]{2}-\d{3}-[A-Z]{2}")
 
 
+class TestItIt(_SimpleAutomotiveTestMixin):
+    """Test it_IT automotive provider methods"""
+
+    license_plate_pattern: Pattern = re.compile(r"[A-Z]{2}\d{3}[A-Z]{2}")
+
+
 class TestNoNo(_SimpleAutomotiveTestMixin):
     """Test no_NO automotive provider methods"""
 
diff --git a/tests/providers/test_bank.py b/tests/providers/test_bank.py
index bfa3532f..c8094002 100644
--- a/tests/providers/test_bank.py
+++ b/tests/providers/test_bank.py
@@ -10,6 +10,8 @@ from faker.providers.bank.en_GB import Provider as EnGbBankProvider
 from faker.providers.bank.en_IE import Provider as EnIeBankProvider
 from faker.providers.bank.en_PH import Provider as EnPhBankProvider
 from faker.providers.bank.es_ES import Provider as EsEsBankProvider
+from faker.providers.bank.es_MX import Provider as EsMxBankProvider
+from faker.providers.bank.es_MX import is_valid_clabe
 from faker.providers.bank.fi_FI import Provider as FiFiBankProvider
 from faker.providers.bank.fr_FR import Provider as FrFrBankProvider
 from faker.providers.bank.no_NO import Provider as NoNoBankProvider
@@ -191,6 +193,47 @@ class TestEsEs:
             assert re.fullmatch(r"\d{2}\d{20}", iban[2:])
 
 
+class TestEsMx:
+    """Test es_MX bank provider"""
+
+    def test_bank(self, faker, num_samples):
+        for _ in range(num_samples):
+            assert faker.bank() in EsMxBankProvider.banks
+
+    @pytest.mark.parametrize(
+        "clabe,validity",
+        [
+            ("002864631170560203", True),
+            ("002864631170560202", False),
+            ("00286463117056020", False),
+            ("0028646311705602030", False),
+            ("00286463117056020A", False),
+        ],
+        ids=[
+            "valid",
+            "bad_control_digit",
+            "too_short",
+            "too_long",
+            "non_numeric_characters",
+        ],
+    )
+    def test_clabe_validation(self, clabe, validity):
+        assert is_valid_clabe(clabe) is validity
+
+    def test_clabe(self, faker, num_samples):
+        for _ in range(num_samples):
+            clabe = faker.clabe()
+            assert is_valid_clabe(clabe)
+            assert int(clabe[:3].lstrip("0")) in EsMxBankProvider.bank_codes
+
+    def test_clabe_bank_code(self, faker, num_samples):
+        bank_code = 133
+        for _ in range(num_samples):
+            clabe = faker.clabe(bank_code=bank_code)
+            assert is_valid_clabe(clabe)
+            assert int(clabe[:3].lstrip("0")) == bank_code
+
+
 class TestFrFr:
     """Test fr_FR bank provider"""