Repository: joke2k/faker
Pull request #1862 (2023-05-08 09:48:18)

Feature/xml generator
### What does this change
Add XML provider for generating fake xml strings.
Use the pydict provider and arguments and convert it onto xml string via xmltodict library 




Patch:
diff --git a/.bumpversion.cfg b/.bumpversion.cfg
index d865cb3b..99a0e208 100644
--- a/.bumpversion.cfg
+++ b/.bumpversion.cfg
@@ -1,5 +1,5 @@
 [bumpversion]
-current_version = 18.6.2
+current_version = 18.8.0
 files = VERSION faker/__init__.py docs/conf.py
 commit = True
 tag = True
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 95f88849..3611c05f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,13 @@
 ## Changelog
 
+### [v18.8.0 - 2023-05-16](https://github.com/joke2k/faker/compare/v18.7.0...v18.8.0)
+
+* Add `zh_CN` bank locale. Thanks @nehCG.
+
+### [v18.7.0 - 2023-05-08](https://github.com/joke2k/faker/compare/v18.6.2...v18.7.0)
+
+* Add `license_plate` for `zh_CN` and `zh_TW`. Thanks @cyanghsieh.
+
 ### [v18.6.2 - 2023-05-03](https://github.com/joke2k/faker/compare/v18.6.1...v18.6.2)
 
 *  Improve accuracy of departments in `fr_FR` provider `postcode`. Thanks @tonial.
diff --git a/VERSION b/VERSION
index fc558a42..3efdc42f 100644
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-18.6.2
+18.8.0
diff --git a/docs/conf.py b/docs/conf.py
index b0c2c3d6..96fcf7dc 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 = "18.6.2"
+version = "18.8.0"
 # The full version, including alpha/beta/rc tags.
-release = "18.6.2"
+release = "18.8.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 740d7bc3..7578eeaa 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 = "18.6.2"
+VERSION = "18.8.0"
 
 __all__ = ("Factory", "Generator", "Faker")
diff --git a/faker/providers/automotive/zh_CN/__init__.py b/faker/providers/automotive/zh_CN/__init__.py
new file mode 100644
index 00000000..1982959f
--- /dev/null
+++ b/faker/providers/automotive/zh_CN/__init__.py
@@ -0,0 +1,47 @@
+from .. import Provider as AutomotiveProvider
+
+
+class Provider(AutomotiveProvider):
+    """
+    Implement automotive provider for `zh_CN` locale.
+    electric vehicles or downtown-restricted plates are not included
+    """
+
+    province_code = (
+        "京",
+        "津",
+        "冀",
+        "晋",
+        "蒙",
+        "辽",
+        "吉",
+        "黑",
+        "沪",
+        "苏",
+        "浙",
+        "皖",
+        "闽",
+        "赣",
+        "鲁",
+        "豫",
+        "鄂",
+        "湘",
+        "粤",
+        "桂",
+        "琼",
+        "渝",
+        "川",
+        "贵",
+        "云",
+        "藏",
+        "陕",
+        "甘",
+        "青",
+        "宁",
+        "新",
+    )
+
+    def license_plate(self) -> str:
+        """Generate a license plate."""
+        pattern: str = str(self.random_element(self.province_code)) + self.random_uppercase_letter() + "-#####"
+        return self.numerify(self.generator.parse(pattern))
diff --git a/faker/providers/bank/zh_CN/__init__.py b/faker/providers/bank/zh_CN/__init__.py
new file mode 100644
index 00000000..83b34e51
--- /dev/null
+++ b/faker/providers/bank/zh_CN/__init__.py
@@ -0,0 +1,37 @@
+from .. import Provider as BankProvider
+
+
+class Provider(BankProvider):
+    """Implement bank provider for ``zh_CN`` locale.
+    Source: https://zh.wikipedia.org/wiki/中国大陆银行列表
+    """
+
+    banks = (
+        "中国人民银行",
+        "国家开发银行",
+        "中国进出口银行",
+        "中国农业发展银行",
+        "交通银行",
+        "中国银行",
+        "中国建设银行",
+        "中国农业银行",
+        "中国工商银行",
+        "中国邮政储蓄银行",
+        "中国光大银行",
+        "中国民生银行",
+        "招商银行",
+        "中信银行",
+        "华夏银行",
+        "上海浦东发展银行",
+        "平安银行",
+        "广发银行",
+        "兴业银行",
+        "浙商银行",
+        "渤海银行",
+        "恒丰银行",
+        "西安银行",
+    )
+
+    def bank(self) -> str:
+        """Generate a bank name."""
+        return self.random_element(self.banks)
diff --git a/faker/providers/misc/__init__.py b/faker/providers/misc/__init__.py
index 19b07820..240249cb 100644
--- a/faker/providers/misc/__init__.py
+++ b/faker/providers/misc/__init__.py
@@ -14,6 +14,7 @@ from typing import Any, Callable, Dict, List, Optional, Sequence, Set, Tuple, Ty
 from faker.exceptions import UnsupportedFeature
 
 from .. import BaseProvider
+from ..python import TypesSpec
 
 localized = True
 
@@ -613,6 +614,35 @@ class Provider(BaseProvider):
         data = [create_json_structure(data_columns) for _ in range(num_rows)]
         return json.dumps(data, indent=indent, cls=cls)
 
+    def xml(
+        self,
+        nb_elements: int = 10,
+        variable_nb_elements: bool = True,
+        value_types: Optional[TypesSpec] = None,
+        allowed_types: Optional[TypesSpec] = None,
+    ) -> str:
+        """
+        Returns some XML.
+
+        :nb_elements: number of elements for dictionary
+        :variable_nb_elements: is use variable number of elements for dictionary
+        :value_types: type of dictionary values
+
+        Note: this provider required xmltodict library installed
+        """
+        try:
+            import xmltodict
+        except ImportError:
+            raise UnsupportedFeature("`xml` requires the `xmltodict` Python library.", "xml")
+        _dict = self.generator.pydict(
+            nb_elements=nb_elements,
+            variable_nb_elements=variable_nb_elements,
+            value_types=value_types,
+            allowed_types=allowed_types,
+        )
+        _dict = {self.generator.word(): _dict}
+        return xmltodict.unparse(_dict)
+
     def fixed_width(self, data_columns: Optional[list] = None, num_rows: int = 10, align: str = "left") -> str:
         """
         Generate random fixed width values.
diff --git a/tox.ini b/tox.ini
index a33a1095..14d5b744 100644
--- a/tox.ini
+++ b/tox.ini
@@ -11,6 +11,7 @@ deps =
     validators>=0.13.0
     sphinx>=2.4,<3.0
     Pillow
+    xmltodict
 commands =
     coverage run --source=faker -m pytest {posargs}
     coverage run --source=faker -a -m pytest --exclusive-faker-session tests/pytest/session_overrides {posargs}


Test patch:
diff --git a/tests/providers/test_automotive.py b/tests/providers/test_automotive.py
index 654d4388..62f725b0 100644
--- a/tests/providers/test_automotive.py
+++ b/tests/providers/test_automotive.py
@@ -318,6 +318,12 @@ class TestNlBe(_SimpleAutomotiveTestMixin):
     license_plate_pattern: Pattern = re.compile(r"(\d{3}-[A-Z]{3})|" r"([A-Z]{3}-\d{3})|" r"([1-2]-[A-Z]{3}-\d{3})")
 
 
+class TestZhCn(_SimpleAutomotiveTestMixin):
+    """Test zh_CN automotive provider methods"""
+
+    license_plate_pattern: Pattern = re.compile(r"^[京津冀晋蒙辽吉黑沪苏浙皖闽赣鲁豫鄂湘粤桂琼川贵云渝藏陕甘青宁新]{1}[A-Z]{1}-[A-Z0-9]{5}")
+
+
 class TestZhTw(_SimpleAutomotiveTestMixin):
     """Test zh_TW automotive provider methods"""
 
diff --git a/tests/providers/test_bank.py b/tests/providers/test_bank.py
index 076f6c9a..cf065af3 100644
--- a/tests/providers/test_bank.py
+++ b/tests/providers/test_bank.py
@@ -442,3 +442,11 @@ class TestNlBe:
             assert code[4:6] == NlBeBankProvider.country_code
             assert code[6:8] in NlBeBankProvider.swift_location_codes
             assert code[8:11] in NlBeBankProvider.swift_branch_codes
+
+
+class TestZhCn:
+    """Test zh_CN bank provider"""
+
+    def test_bank(self, faker, num_samples):
+        for _ in range(num_samples):
+            assert re.match(r"[\u4e00-\u9fa5]{2,20}", faker.bank())
diff --git a/tests/providers/test_misc.py b/tests/providers/test_misc.py
index f50822f3..ec9fe57a 100644
--- a/tests/providers/test_misc.py
+++ b/tests/providers/test_misc.py
@@ -7,6 +7,7 @@ import re
 import tarfile
 import unittest
 import uuid
+import xml
 import zipfile
 
 try:
@@ -14,6 +15,11 @@ try:
 except ImportError:
     PIL = None
 
+try:
+    import xmltodict
+except ImportError:
+    xmltodict = None
+
 from typing import Pattern
 from unittest.mock import patch
 
@@ -455,6 +461,20 @@ class TestMiscProvider:
             for args, kwargs in mock_writer.call_args_list:
                 assert kwargs == test_kwargs
 
+    @unittest.skipUnless(xmltodict, "requires the Python xmltodict Library")
+    def test_xml(self, faker):
+        try:
+            xml.etree.ElementTree.fromstring(faker.xml())
+        except xml.etree.ElementTree.ParseError:
+            raise AssertionError("The XML format is invalid.")
+
+    def test_xml_no_xmltodict(self, faker):
+        with patch.dict("sys.modules", {"xmltodict": None}):
+            with pytest.raises(exceptions.UnsupportedFeature) as excinfo:
+                faker.xml()
+
+            assert excinfo.value.name == "xml"
+
     def test_csv_helper_method(self, faker):
         kwargs = {
             "header": ["Column 1", "Column 2"],