From 009b1730a3c69e22ae7175a1110b3b716ad53e30 Mon Sep 17 00:00:00 2001 From: othmane099 Date: Sat, 28 Mar 2026 15:27:29 +0100 Subject: [PATCH 1/3] add ssn provider for ar_DZ locale --- faker/providers/ssn/ar_DZ/__init__.py | 43 +++++++++++++++++++++++++++ tests/providers/test_ssn.py | 25 ++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 faker/providers/ssn/ar_DZ/__init__.py diff --git a/faker/providers/ssn/ar_DZ/__init__.py b/faker/providers/ssn/ar_DZ/__init__.py new file mode 100644 index 0000000000..94b2c83ec5 --- /dev/null +++ b/faker/providers/ssn/ar_DZ/__init__.py @@ -0,0 +1,43 @@ +from .. import Provider as SsnProvider + + +class Provider(SsnProvider): + """ + Sources: + + - https://fr.wikipedia.org/wiki/Num%C3%A9ro_d%27identification_national_(Alg%C3%A9rie) + - https://github.com/itshakim213/dz-nin-checker + """ + + def _control_key(self, base: str) -> str: + """Compute the 2-digit control key via modified Luhn over 16 digits.""" + total, alternate = 0, False + for i in range(len(base) - 1, -1, -1): + d = int(base[i]) * (2 if alternate else 1) + total += d - 9 if d > 9 else d + alternate = not alternate + remainder = total % 10 + return f"{(0 if remainder == 0 else 10 - remainder):02d}" + + def ssn(self) -> str: + """Generate an Algerian National Identification Number (NIN). + + Structure (18 digits): + + - 1 digit – nationality: ``1`` = Algerian, ``2`` = dual nationality + - 1 digit – sex: ``0`` = male, ``1`` = female + - 3 digits – last three digits of the birth-registration year + - 4 digits – commune code (wilaya 01–58 + commune 01–20) + - 5 digits – birth-certificate act number + - 2 digits – annual register serial number + - 2 digits – control key (modified Luhn) + """ + nationality = self.random_element(("1", "2")) + sex = self.random_element(("0", "1")) + year_code = f"{self.random_int(min=1950, max=2006) % 1000:03d}" + wilaya = self.random_int(min=1, max=58) + commune_code = f"{wilaya:02d}{self.random_int(min=1, max=20):02d}" + act_code = f"{self.random_int(min=1, max=99999):05d}" + register_code = f"{self.random_int(min=1, max=99):02d}" + base = nationality + sex + year_code + commune_code + act_code + register_code + return base + self._control_key(base) diff --git a/tests/providers/test_ssn.py b/tests/providers/test_ssn.py index c9d4ad7544..76dd9b9a82 100644 --- a/tests/providers/test_ssn.py +++ b/tests/providers/test_ssn.py @@ -1357,6 +1357,31 @@ def test_vat_id(self): assert len(vat) >= 2 and len(vat) <= 10 +class TestArDz(unittest.TestCase): + def setUp(self): + self.fake = Faker("ar_DZ") + Faker.seed(0) + + def test_ssn(self): + pattern = re.compile(r"[12][01]\d{3}(0[1-9]|[1-4]\d|5[0-8])\d{2}\d{5}\d{2}\d{2}") + for _ in range(100): + ssn = self.fake.ssn() + assert len(ssn) == 18 + assert pattern.fullmatch(ssn) is not None + + def test_ssn_control_key(self): + for _ in range(100): + ssn = self.fake.ssn() + base = ssn[:16] + total, alternate = 0, False + for i in range(len(base) - 1, -1, -1): + d = int(base[i]) * (2 if alternate else 1) + total += d - 9 if d > 9 else d + alternate = not alternate + remainder = total % 10 + assert f"{(0 if remainder == 0 else 10 - remainder):02d}" == ssn[16:] + + class TestAzAz(unittest.TestCase): num_sample_runs = 10 From c933faa179e61489b51065c8ad907d7154fec680 Mon Sep 17 00:00:00 2001 From: othmane099 Date: Sat, 28 Mar 2026 15:31:39 +0100 Subject: [PATCH 2/3] add ssn provider for fr_DZ locale --- faker/providers/ssn/fr_DZ/__init__.py | 5 +++++ tests/providers/test_ssn.py | 6 ++++++ 2 files changed, 11 insertions(+) create mode 100644 faker/providers/ssn/fr_DZ/__init__.py diff --git a/faker/providers/ssn/fr_DZ/__init__.py b/faker/providers/ssn/fr_DZ/__init__.py new file mode 100644 index 0000000000..5cf2d2995a --- /dev/null +++ b/faker/providers/ssn/fr_DZ/__init__.py @@ -0,0 +1,5 @@ +from ..ar_DZ import Provider as ArDzSsnProvider + + +class Provider(ArDzSsnProvider): + pass diff --git a/tests/providers/test_ssn.py b/tests/providers/test_ssn.py index 76dd9b9a82..bc923af91c 100644 --- a/tests/providers/test_ssn.py +++ b/tests/providers/test_ssn.py @@ -1382,6 +1382,12 @@ def test_ssn_control_key(self): assert f"{(0 if remainder == 0 else 10 - remainder):02d}" == ssn[16:] +class TestFrDz(TestArDz): + def setUp(self): + self.fake = Faker("fr_DZ") + Faker.seed(0) + + class TestAzAz(unittest.TestCase): num_sample_runs = 10 From 59dd970212fee583419ac3667a892c5d0a25137d Mon Sep 17 00:00:00 2001 From: othmane099 Date: Mon, 30 Mar 2026 17:46:35 +0100 Subject: [PATCH 3/3] follow alphabetical order --- tests/providers/test_ssn.py | 62 ++++++++++++++++++------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/tests/providers/test_ssn.py b/tests/providers/test_ssn.py index bc923af91c..a65304c01d 100644 --- a/tests/providers/test_ssn.py +++ b/tests/providers/test_ssn.py @@ -39,6 +39,31 @@ from faker.utils.checksums import luhn_checksum +class TestArDz(unittest.TestCase): + def setUp(self): + self.fake = Faker("ar_DZ") + Faker.seed(0) + + def test_ssn(self): + pattern = re.compile(r"[12][01]\d{3}(0[1-9]|[1-4]\d|5[0-8])\d{2}\d{5}\d{2}\d{2}") + for _ in range(100): + ssn = self.fake.ssn() + assert len(ssn) == 18 + assert pattern.fullmatch(ssn) is not None + + def test_ssn_control_key(self): + for _ in range(100): + ssn = self.fake.ssn() + base = ssn[:16] + total, alternate = 0, False + for i in range(len(base) - 1, -1, -1): + d = int(base[i]) * (2 if alternate else 1) + total += d - 9 if d > 9 else d + alternate = not alternate + remainder = total % 10 + assert f"{(0 if remainder == 0 else 10 - remainder):02d}" == ssn[16:] + + class TestSvSE(unittest.TestCase): def setUp(self): self.fake = Faker("sv_SE") @@ -860,6 +885,12 @@ def test_ssn_without_age_range(self): assert "98+" in ssn +class TestFrDz(TestArDz): + def setUp(self): + self.fake = Faker("fr_DZ") + Faker.seed(0) + + class TestFrFR(unittest.TestCase): def setUp(self): self.fake = Faker("fr_FR") @@ -1357,37 +1388,6 @@ def test_vat_id(self): assert len(vat) >= 2 and len(vat) <= 10 -class TestArDz(unittest.TestCase): - def setUp(self): - self.fake = Faker("ar_DZ") - Faker.seed(0) - - def test_ssn(self): - pattern = re.compile(r"[12][01]\d{3}(0[1-9]|[1-4]\d|5[0-8])\d{2}\d{5}\d{2}\d{2}") - for _ in range(100): - ssn = self.fake.ssn() - assert len(ssn) == 18 - assert pattern.fullmatch(ssn) is not None - - def test_ssn_control_key(self): - for _ in range(100): - ssn = self.fake.ssn() - base = ssn[:16] - total, alternate = 0, False - for i in range(len(base) - 1, -1, -1): - d = int(base[i]) * (2 if alternate else 1) - total += d - 9 if d > 9 else d - alternate = not alternate - remainder = total % 10 - assert f"{(0 if remainder == 0 else 10 - remainder):02d}" == ssn[16:] - - -class TestFrDz(TestArDz): - def setUp(self): - self.fake = Faker("fr_DZ") - Faker.seed(0) - - class TestAzAz(unittest.TestCase): num_sample_runs = 10