Repository: joke2k/faker
Pull request #1978 (2024-01-19 10:42:28)

Area code, add 830000 and parameterized

### What does this change

In the `zh_CN` provider for SSN , the area code is enhanced by adding `830000` and making it parameterized.

### What was wrong

The area code `830000` has been officially released by www.gov.cn. During actual testing, it is necessary to generate SSN in the specified region.

### How this fixes it

Firstly, `830000` is added to the `area_codes` list.
Then, in the `ssn()` method, a new parameter `area_code` is added, with the default value being `None`. 
If the passed `area_code` value exists in the `area_codes` list, it will be used to generate an SSN that meets the requirements. Otherwise, one of the codes from `area_codes` will be randomly selected to generate the SSN.


Patch:
diff --git a/.bumpversion.cfg b/.bumpversion.cfg
index 543d7fe9..ea78a35f 100644
--- a/.bumpversion.cfg
+++ b/.bumpversion.cfg
@@ -1,5 +1,5 @@
 [bumpversion]
-current_version = 22.2.0
+current_version = 22.5.0
 files = VERSION faker/__init__.py docs/conf.py
 commit = True
 tag = True
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index d6c5e210..046cb1b3 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -17,7 +17,7 @@ jobs:
     steps:
     - uses: actions/checkout@v4
     - name: Setup pip cache
-      uses: actions/cache@v3
+      uses: actions/cache@v4
       id: pipcache
       with:
         path: ~/.cache/pip
@@ -42,7 +42,7 @@ jobs:
     steps:
     - uses: actions/checkout@v4
     - name: Setup pip cache
-      uses: actions/cache@v3
+      uses: actions/cache@v4
       id: pipcache
       with:
         path: ~/.cache/pip
@@ -66,7 +66,7 @@ jobs:
     steps:
     - uses: actions/checkout@v4
     - name: Setup pip cache
-      uses: actions/cache@v3
+      uses: actions/cache@v4
       id: pipcache
       with:
         path: ~/.cache/pip
@@ -91,7 +91,7 @@ jobs:
     steps:
     - uses: actions/checkout@v4
     - name: Setup pip cache
-      uses: actions/cache@v3
+      uses: actions/cache@v4
       id: pipcache
       with:
         path: ~/.cache/pip
@@ -115,7 +115,7 @@ jobs:
     steps:
     - uses: actions/checkout@v4
     - name: Setup pip cache
-      uses: actions/cache@v3
+      uses: actions/cache@v4
       id: pipcache
       with:
         path: ~/.cache/pip
@@ -143,7 +143,7 @@ jobs:
     steps:
     - uses: actions/checkout@v4
     - name: Setup pip cache
-      uses: actions/cache@v3
+      uses: actions/cache@v4
       id: pipcache
       with:
         path: ~/.cache/pip
@@ -173,7 +173,7 @@ jobs:
     steps:
       - uses: actions/checkout@v4
       - name: Setup pip cache
-        uses: actions/cache@v3
+        uses: actions/cache@v4
         id: pipcache
         with:
           path: ~/.cache/pip
@@ -215,7 +215,7 @@ jobs:
     steps:
       - uses: actions/checkout@v4
       - name: Setup pip cache
-        uses: actions/cache@v3
+        uses: actions/cache@v4
         id: pipcache
         with:
           path: ~/.cache/pip
@@ -246,7 +246,7 @@ jobs:
     steps:
     - uses: actions/checkout@v4
     - name: Setup pip cache
-      uses: actions/cache@v3
+      uses: actions/cache@v4
       id: pipcache
       with:
         path: ~/.cache/pip
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ca1c272c..dc232663 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,18 @@
 ## Changelog
 
+### [v22.5.0 - 2024-01-22](https://github.com/joke2k/faker/compare/v22.
+4.0...v22.5.0)
+
+* Add `http_status_code` to internet provider. Thanks @dancergraham.
+
+### [v22.4.0 - 2024-01-19](https://github.com/joke2k/faker/compare/v22.
+3.0...v22.4.0)
+
+* Add `fa_IR` localization for currency provider. Thanks @parsariyahi.
+
+### [v22.3.0 - 2024-01-19](https://github.com/joke2k/faker/compare/v22.2.0...v22.3.0)
+
+* Add bank provider for `uk_UA`. Thanks @SanderFtn.
 
 ### [v22.2.0 - 2024-01-10](https://github.com/joke2k/faker/compare/v22.1.0...v22.2.0)
 
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
index dd586cb1..6bb754c4 100644
--- a/CONTRIBUTING.rst
+++ b/CONTRIBUTING.rst
@@ -24,11 +24,16 @@ Making Changes
    the new branch with ``git checkout fix/master/my_contribution``.
    Please avoid working directly on the ``master`` branch.
 -  Make commits of logical units.
--  Follow our `coding style`_.
+-  Follow our `coding style`_. Your can run ``make lint`` to format your code.
 -  Check for unnecessary whitespace with ``git diff --check`` before
    committing.
 -  Make sure you have added the necessary tests for your changes.
--  Run *all* the tests to assure nothing else was accidentally broken.
+-  Run *all* the tests to assure nothing else was accidentally broken:
+
+   .. code:: bash
+
+       $ tox
+
 
 Submitting Changes
 ------------------
diff --git a/VERSION b/VERSION
index 44e031ba..d1c5363f 100644
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-22.2.0
+22.5.0
diff --git a/docs/conf.py b/docs/conf.py
index f4a9f589..b027beaa 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 = "22.2.0"
+version = "22.5.0"
 # The full version, including alpha/beta/rc tags.
-release = "22.2.0"
+release = "22.5.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 4895e3dd..3e823d8e 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 = "22.2.0"
+VERSION = "22.5.0"
 
 __all__ = ("Factory", "Generator", "Faker")
diff --git a/faker/providers/bank/uk_UA/__init__.py b/faker/providers/bank/uk_UA/__init__.py
new file mode 100644
index 00000000..3b53dd45
--- /dev/null
+++ b/faker/providers/bank/uk_UA/__init__.py
@@ -0,0 +1,11 @@
+from .. import Provider as BankProvider
+
+
+class Provider(BankProvider):
+    """Implement bank provider for ``uk_UA`` locale.
+    Source for rules for bban format:
+    https://bank.gov.ua/en/iban
+    """
+
+    bban_format = "#" * 27
+    country_code = "UA"
diff --git a/faker/providers/currency/fa_IR/__init__.py b/faker/providers/currency/fa_IR/__init__.py
new file mode 100644
index 00000000..a15d7a1d
--- /dev/null
+++ b/faker/providers/currency/fa_IR/__init__.py
@@ -0,0 +1,8 @@
+from .. import Provider as CurrencyProvider
+
+
+class Provider(CurrencyProvider):
+    price_formats = ["###,###,000", "#,###,000,000", "%,###,###,###,###", "%,###,###,###,000,000"]
+
+    def pricetag(self) -> str:
+        return self.numerify(self.random_element(self.price_formats)) + "\uFDFC"
diff --git a/faker/providers/internet/__init__.py b/faker/providers/internet/__init__.py
index 9898c96e..d8d96fe7 100644
--- a/faker/providers/internet/__init__.py
+++ b/faker/providers/internet/__init__.py
@@ -131,6 +131,71 @@ class Provider(BaseProvider):
         "TRACE",
         "PATCH",
     )
+    http_assigned_codes: ElementsType[int] = (
+        100,
+        101,
+        100,
+        101,
+        102,
+        103,
+        200,
+        201,
+        202,
+        203,
+        204,
+        205,
+        206,
+        207,
+        208,
+        226,
+        300,
+        301,
+        302,
+        303,
+        304,
+        305,
+        307,
+        308,
+        400,
+        401,
+        402,
+        403,
+        404,
+        405,
+        406,
+        407,
+        408,
+        409,
+        410,
+        411,
+        412,
+        413,
+        414,
+        415,
+        416,
+        417,
+        421,
+        422,
+        423,
+        424,
+        425,
+        426,
+        428,
+        429,
+        431,
+        451,
+        500,
+        501,
+        502,
+        503,
+        504,
+        505,
+        506,
+        507,
+        508,
+        510,
+        511,
+    )
 
     user_name_formats: ElementsType[str] = (
         "{{last_name}}.{{first_name}}",
@@ -308,6 +373,23 @@ class Provider(BaseProvider):
 
         return self.random_element(self.http_methods)
 
+    def http_status_code(self, include_unassigned: bool = True) -> int:
+        """Returns random HTTP status code
+        https://www.rfc-editor.org/rfc/rfc9110#name-status-codes
+        :param include_unassigned: Whether to include status codes which have
+            not yet been assigned or are unused
+
+        :return: a random three digit status code
+        :rtype: int
+
+        :example: 404
+
+        """
+        if include_unassigned:
+            return self.random_int(min=100, max=599)
+        else:
+            return self.random_element(self.http_assigned_codes)
+
     def url(self, schemes: Optional[List[str]] = None) -> str:
         """
         :param schemes: a list of strings to use as schemes, one will chosen randomly.
diff --git a/faker/providers/ssn/zh_CN/__init__.py b/faker/providers/ssn/zh_CN/__init__.py
index 69d5c937..30bf868d 100644
--- a/faker/providers/ssn/zh_CN/__init__.py
+++ b/faker/providers/ssn/zh_CN/__init__.py
@@ -9,6 +9,7 @@ from .. import Provider as SsnProvider
 class Provider(SsnProvider):
     # Extracted from
     # http://www.stats.gov.cn/tjsj/tjbz/xzqhdm/201504/t20150415_712722.html
+    # 《港澳台居民居住证申领发放办法》https://www.gov.cn/zhengce/content/2018-08/19/content_5314865.htm
     area_codes: List[str] = [
         "110000",
         "110100",
@@ -3522,13 +3523,17 @@ class Provider(SsnProvider):
         "710000",
         "810000",
         "820000",
+        "830000",
     ]
 
-    def ssn(self, min_age: int = 18, max_age: int = 90, gender: Optional[SexLiteral] = None) -> str:
+    def ssn(
+        self, min_age: int = 18, max_age: int = 90, gender: Optional[SexLiteral] = None, area_code: str = ""
+    ) -> str:
         """
         Return 18 character chinese personal identity code
 
         :param gender: F for female  M for male  None for default
+        :param area_code: None for default
         """
 
         def checksum(s):
@@ -3538,7 +3543,9 @@ class Provider(SsnProvider):
         birthday = datetime.date.today() - age
         birthday_str = birthday.strftime("%Y%m%d")
 
-        area_code: str = self.random_element(self.area_codes)
+        if area_code not in self.area_codes:
+            area_code = self.random_element(self.area_codes)
+
         ssn_without_checksum = self.numerify(area_code + birthday_str + "##")
 
         _number = ("0", "1", "2", "3", "4", "5", "6", "7", "8", "9")


Test patch:
diff --git a/tests/providers/test_bank.py b/tests/providers/test_bank.py
index 3826b82e..fe37cb5a 100644
--- a/tests/providers/test_bank.py
+++ b/tests/providers/test_bank.py
@@ -22,6 +22,7 @@ from faker.providers.bank.pl_PL import Provider as PlPlBankProvider
 from faker.providers.bank.pt_PT import Provider as PtPtBankProvider
 from faker.providers.bank.th_TH import Provider as ThThBankProvider
 from faker.providers.bank.tr_TR import Provider as TrTrBankProvider
+from faker.providers.bank.uk_UA import Provider as UkUaBankProvider
 
 
 def is_valid_iban(iban):
@@ -136,6 +137,21 @@ class TestPlPl:
             assert re.fullmatch(r"\d{2}\d{24}", iban[2:])
 
 
+class TestUkUa:
+    """Test uk_UA bank provider"""
+
+    def test_bban(self, faker, num_samples):
+        for _ in range(num_samples):
+            assert re.fullmatch(r"\d{27}", faker.bban())
+
+    def test_iban(self, faker, num_samples):
+        for _ in range(num_samples):
+            iban = faker.iban()
+            assert is_valid_iban(iban)
+            assert iban[:2] == UkUaBankProvider.country_code
+            assert re.fullmatch(r"\d{2}\d{27}", iban[2:])
+
+
 class TestEnGb:
     """Test en_GB bank provider"""
 
diff --git a/tests/providers/test_currency.py b/tests/providers/test_currency.py
index 751117e7..862a5081 100644
--- a/tests/providers/test_currency.py
+++ b/tests/providers/test_currency.py
@@ -247,6 +247,23 @@ class TestEsEs:
             assert isinstance(pricetag, str)
 
 
+class TestFaIr:
+    """Test fa_IR currency provider"""
+
+    num_samples = 100
+
+    @classmethod
+    def setup_class(cls):
+        from faker.providers.currency.fa_IR import Provider as FaIrCurrencyProvider
+
+        cls.provider = FaIrCurrencyProvider
+
+    def test_pricetag(self, faker, num_samples):
+        for _ in range(num_samples):
+            pricetag = faker.pricetag()
+            assert isinstance(pricetag, str)
+
+
 class TestFrCa:
     """Test fr_CA currency provider"""
 
diff --git a/tests/providers/test_internet.py b/tests/providers/test_internet.py
index 7a877ce5..3bd431fe 100644
--- a/tests/providers/test_internet.py
+++ b/tests/providers/test_internet.py
@@ -339,6 +339,16 @@ class TestInternetProvider:
 
         assert expected_methods == sorted(got_methods)
 
+    def test_http_status_code(self, faker, num_samples):
+        provider = InternetProvider(faker)
+        status_code = provider.http_status_code()
+        assert isinstance(status_code, int)
+        assert 100 <= status_code <= 599
+        status_code = provider.http_status_code(include_unassigned=False)
+        assert isinstance(status_code, int)
+        assert 100 <= status_code <= 599
+        assert status_code in InternetProvider.http_assigned_codes
+
     def test_dga(self, faker):
         assert faker.dga() != faker.dga()
 
diff --git a/tests/providers/test_ssn.py b/tests/providers/test_ssn.py
index 83ec0595..b41f08d2 100644
--- a/tests/providers/test_ssn.py
+++ b/tests/providers/test_ssn.py
@@ -1229,6 +1229,33 @@ class TestZhCN(unittest.TestCase):
         ssn = self.fake.ssn(gender="M")
         assert int(ssn[16]) % 2 == 1
 
+    def test_zh_CN_ssn_invalid_area_code_passed(self):
+        ssn = self.fake.ssn(area_code=12)
+        assert int(ssn[0:6]) > 0
+
+        ssn = self.fake.ssn(area_code={})
+        assert int(ssn[0:6]) > 0
+
+        ssn = self.fake.ssn(area_code=[])
+        assert int(ssn[0:6]) > 0
+
+        ssn = self.fake.ssn(area_code=None)
+        assert int(ssn[0:6]) > 0
+
+        ssn = self.fake.ssn()
+        assert int(ssn[0:6]) > 0
+
+    def test_zh_CN_ssn_area_code_passed(self):
+        #
+        ssn = self.fake.ssn(area_code="654225")
+        assert int(ssn[0:6]) == 654225
+
+        ssn = self.fake.ssn(area_code="820000")
+        assert int(ssn[0:6]) == 820000
+
+        ssn = self.fake.ssn(area_code="830000")
+        assert int(ssn[0:6]) == 830000
+
 
 class TestRoRO(unittest.TestCase):
     """Tests SSN in the ro_RO locale"""