Repository: joke2k/faker

Issue:
pl_PL tasks - a lot of things to add
Hi,

I'm using faker quite often, but there is some missing functionalities for pl_PL, like e.g. PESEL generator (Polish id for every person), car plates or some more. 

Can I contribute on my own branch (e.g. pl-PL-features or something), to add missing functionalities?


Discussion:
Hi @adwojak ,

Thank for reaching out!

Feel free to fork the repo, create your branch and submit Pull Requests!
Thanks a lot!

Resolving patch:
diff --git a/faker/providers/person/pl_PL/__init__.py b/faker/providers/person/pl_PL/__init__.py
--- a/faker/providers/person/pl_PL/__init__.py
+++ b/faker/providers/person/pl_PL/__init__.py
@@ -21,28 +21,6 @@ def checksum_identity_card_number(characters):
     return check_digit
 
 
-def generate_pesel_checksum_value(pesel_digits):
-    """
-    Calculates and returns a control digit for given PESEL.
-    """
-    checksum_values = [9, 7, 3, 1, 9, 7, 3, 1, 9, 7]
-
-    checksum = sum((int(a) * b for a, b in zip(list(pesel_digits), checksum_values)))
-
-    return checksum % 10
-
-
-def checksum_pesel_number(pesel_digits):
-    """
-    Calculates and returns True if PESEL is valid.
-    """
-    checksum_values = [1, 3, 7, 9, 1, 3, 7, 9, 1, 3, 1]
-
-    checksum = sum((int(a) * b for a, b in zip(list(pesel_digits), checksum_values)))
-
-    return checksum % 10 == 0
-
-
 class Provider(PersonProvider):
     formats = (
         '{{first_name}} {{last_name}}',
@@ -725,30 +703,74 @@ def identity_card_number(self):
 
         return ''.join(str(character) for character in identity)
 
-    def pesel(self):
+    @staticmethod
+    def pesel_compute_check_digit(pesel):
+        checksum_values = [9, 7, 3, 1, 9, 7, 3, 1, 9, 7]
+        return sum(int(a) * b for a, b in zip(pesel, checksum_values)) % 10
+
+    def pesel(self, date_of_birth=None, sex=None):
         """
         Returns 11 characters of Universal Electronic System for Registration of the Population.
         Polish: Powszechny Elektroniczny System Ewidencji Ludności.
 
         PESEL has 11 digits which identifies just one person.
-        Month: if person was born in 1900-2000, december is 12. If person was born > 2000, we have to add 20 to month,
-        so december is 32.
-        Person id: last digit identifies person's sex. Even for females, odd for males.
+        pesel_date: if person was born in 1900-2000, december is 12. If person was born > 2000, we have to add 20 to
+        month, so december is 32.
+        pesel_sex: last digit identifies person's sex. Even for females, odd for males.
 
         https://en.wikipedia.org/wiki/PESEL
         """
+        if date_of_birth is None:
+            date_of_birth = self.generator.date_of_birth()
 
-        birth = self.generator.date_of_birth()
+        pesel_date = '{year}{month:02d}{day:02d}'.format(
+            year=date_of_birth.year, day=date_of_birth.day,
+            month=date_of_birth.month if date_of_birth.year < 2000 else date_of_birth.month + 20)
+        pesel_date = pesel_date[2:]
 
-        year_pesel = str(birth.year)[-2:]
-        month_pesel = birth.month if birth.year < 2000 else birth.month + 20
-        day_pesel = birth.day
-        person_id = self.random_int(1000, 9999)
+        pesel_core = ''.join(map(str, (self.random_digit() for _ in range(3))))
+        pesel_sex = self.random_digit()
 
-        current_pesel = '{year}{month:02d}{day:02d}{person_id:04d}'.format(year=year_pesel, month=month_pesel,
-                                                                           day=day_pesel,
-                                                                           person_id=person_id)
+        if (sex == 'M' and pesel_sex % 2 == 0) or (sex == 'F' and pesel_sex % 2 == 1):
+            pesel_sex = (pesel_sex + 1) % 10
+
+        pesel = '{date}{core}{sex}'.format(date=pesel_date, core=pesel_core, sex=pesel_sex)
+        pesel += str(self.pesel_compute_check_digit(pesel))
+
+        return pesel
+
+    @staticmethod
+    def pwz_doctor_compute_check_digit(x):
+        return sum((i+1)*d for i, d in enumerate(x)) % 11
+
+    def pwz_doctor(self):
+        """
+        Function generates an identification number for medical doctors
+        Polish: Prawo Wykonywania Zawodu (PWZ)
+
+        https://www.nil.org.pl/rejestry/centralny-rejestr-lekarzy/zasady-weryfikowania-nr-prawa-wykonywania-zawodu
+        """
+        core = [self.random_digit() for _ in range(6)]
+        check_digit = self.pwz_doctor_compute_check_digit(core)
+
+        if check_digit == 0:
+            core[-1] = (core[-1] + 1) % 10
+            check_digit = self.pwz_doctor_compute_check_digit(core)
+
+        return '{}{}'.format(check_digit, ''.join(map(str, core)))
+
+    def pwz_nurse(self, kind='nurse'):
+        """
+        Function generates an identification number for nurses and midwives
+        Polish: Prawo Wykonywania Zawodu (PWZ)
+
+        http://arch.nipip.pl/index.php/prawo/uchwaly/naczelnych-rad/w-roku-2015/posiedzenie-15-17-grudnia/3664-uchwala-
+        nr-381-vi-2015-w-sprawie-trybu-postepowania-dotyczacego-stwierdzania-i-przyznawania-prawa-wykonywania-zawodu-pi
+        elegniarki-i-zawodu-poloznej-oraz-sposobu-prowadzenia-rejestru-pielegniarek-i-rejestru-poloznych-przez-okregowe
+        -rady-pielegniarek-i-polo
+        """
+        region = self.random_int(1, 45)
+        core = [self.random_digit() for _ in range(5)]
+        kind_char = 'A' if kind == 'midwife' else 'P'
 
-        checksum_value = generate_pesel_checksum_value(current_pesel)
-        return '{pesel_without_checksum}{checksum_value}'.format(pesel_without_checksum=current_pesel,
-                                                                 checksum_value=checksum_value)
+        return '{:02d}{}{}'.format(region, ''.join(map(str, core)), kind_char)


Test patch:
diff --git a/tests/providers/test_person.py b/tests/providers/test_person.py
--- a/tests/providers/test_person.py
+++ b/tests/providers/test_person.py
@@ -4,9 +4,14 @@
 
 import re
 import unittest
-
+import datetime
 import six
 
+try:
+    from unittest import mock
+except ImportError:
+    import mock
+
 from faker import Faker
 from faker.providers.person.ar_AA import Provider as ArProvider
 from faker.providers.person.fi_FI import Provider as FiProvider
@@ -14,9 +19,9 @@
 from faker.providers.person.ne_NP import Provider as NeProvider
 from faker.providers.person.sv_SE import Provider as SvSEProvider
 from faker.providers.person.cs_CZ import Provider as CsCZProvider
+from faker.providers.person.pl_PL import Provider as PlPLProvider
 from faker.providers.person.pl_PL import (
     checksum_identity_card_number as pl_checksum_identity_card_number,
-    checksum_pesel_number as pl_checksum_pesel_number,
 )
 from faker.providers.person.zh_CN import Provider as ZhCNProvider
 from faker.providers.person.zh_TW import Provider as ZhTWProvider
@@ -207,13 +212,43 @@ def test_identity_card_number(self):
         for _ in range(100):
             assert re.search(r'^[A-Z]{3}\d{6}$', self.factory.identity_card_number())
 
-    def test_pesel_number_checksum(self):
-        assert pl_checksum_pesel_number('31090655159') is True
-        assert pl_checksum_pesel_number('95030853577') is True
-        assert pl_checksum_pesel_number('05260953442') is True
-        assert pl_checksum_pesel_number('31090655158') is False
-        assert pl_checksum_pesel_number('95030853576') is False
-        assert pl_checksum_pesel_number('05260953441') is False
+    @mock.patch.object(PlPLProvider, 'random_digit')
+    def test_pesel_birth_date(self, mock_random_digit):
+        mock_random_digit.side_effect = [3, 5, 8, 8, 7, 9, 9, 3]
+        assert self.factory.pesel(datetime.date(1999, 12, 31)) == '99123135885'
+        assert self.factory.pesel(datetime.date(2000,  1,  1)) == '00210179936'
+
+    @mock.patch.object(PlPLProvider, 'random_digit')
+    def test_pesel_sex_male(self, mock_random_digit):
+        mock_random_digit.side_effect = [1, 3, 4, 5, 6, 1, 7, 0]
+        assert self.factory.pesel(datetime.date(1909, 3, 3), 'M') == '09030313454'
+        assert self.factory.pesel(datetime.date(1913, 8, 16), 'M') == '13081661718'
+
+    @mock.patch.object(PlPLProvider, 'random_digit')
+    def test_pesel_sex_female(self, mock_random_digit):
+        mock_random_digit.side_effect = [4, 9, 1, 6, 6, 1, 7, 3]
+        assert self.factory.pesel(datetime.date(2007, 4, 13), 'F') == '07241349161'
+        assert self.factory.pesel(datetime.date(1933, 12, 16), 'F') == '33121661744'
+
+    @mock.patch.object(PlPLProvider, 'random_digit')
+    def test_pwz_doctor(self, mock_random_digit):
+        mock_random_digit.side_effect = [6, 9, 1, 9, 6, 5, 2, 7, 9, 9, 1, 5]
+        assert self.factory.pwz_doctor() == '2691965'
+        assert self.factory.pwz_doctor() == '4279915'
+
+    @mock.patch.object(PlPLProvider, 'random_digit')
+    def test_pwz_doctor_check_digit_zero(self, mock_random_digit):
+        mock_random_digit.side_effect = [0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 9, 9]
+        assert self.factory.pwz_doctor() == '6000012'
+        assert self.factory.pwz_doctor() == '1000090'
+
+    @mock.patch.object(PlPLProvider, 'random_int')
+    @mock.patch.object(PlPLProvider, 'random_digit')
+    def test_pwz_nurse(self, mock_random_digit, mock_random_int):
+        mock_random_digit.side_effect = [3, 4, 5, 6, 7, 1, 7, 5, 1, 2]
+        mock_random_int.side_effect = [45, 3]
+        assert self.factory.pwz_nurse(kind='nurse') == '4534567P'
+        assert self.factory.pwz_nurse(kind='midwife') == '0317512A'
 
 
 class TestCsCZ(unittest.TestCase):