Repository: joke2k/faker
Pull request #1968 (2023-12-28 18:51:45)

Add bank provider for cs_CZ
### What does this change

Add bank provider for cs_CZ



Patch:
diff --git a/.bumpversion.cfg b/.bumpversion.cfg
index ad6f0b9f..5e6f3d77 100644
--- a/.bumpversion.cfg
+++ b/.bumpversion.cfg
@@ -1,5 +1,5 @@
 [bumpversion]
-current_version = 21.0.0
+current_version = 22.1.0
 files = VERSION faker/__init__.py docs/conf.py
 commit = True
 tag = True
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ebc3be70..4c2cd244 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,18 @@
 ## Changelog
 
+### [v22.1.0 - 2024-01-08](https://github.com/joke2k/faker/compare/v22.0.0...v22.1.0)
+
+* Add support for multiple file systems path rules. Thanks @parsariyahi.
+
+### [v22.0.0 - 2023-12-29](https://github.com/joke2k/faker/compare/v21.0.1...v22.0.0)
+
+* Remove dot from `file_name` when `extention` is the empty string. Thanks @gotofritz
+* Allow extension in `file_path` to take a list of allowed extensions, or empty for "no extension". Thanks @gotofritz
+
+### [v21.0.1 - 2023-12-29](https://github.com/joke2k/faker/compare/v21.0.0...v21.0.1)
+
+* Fix typo in `ru_RU` company names. Thanks @scalar438.
+
 ### [v21.0.0 - 2023-12-13](https://github.com/joke2k/faker/compare/v20.1.0...v21.0.0)
 
 * Fix: random sign in `pyfloat` when `positive=False`. Thanks @viraj-s15.
diff --git a/VERSION b/VERSION
index fb5b5130..ee5c2446 100644
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-21.0.0
+22.1.0
diff --git a/docs/conf.py b/docs/conf.py
index 6154f53b..9529bdc0 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 = "21.0.0"
+version = "22.1.0"
 # The full version, including alpha/beta/rc tags.
-release = "21.0.0"
+release = "22.1.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 cf25ffbf..46d0f344 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 = "21.0.0"
+VERSION = "22.1.0"
 
 __all__ = ("Factory", "Generator", "Faker")
diff --git a/faker/providers/bank/cs_CZ/__init__.py b/faker/providers/bank/cs_CZ/__init__.py
new file mode 100644
index 00000000..7ea79ab2
--- /dev/null
+++ b/faker/providers/bank/cs_CZ/__init__.py
@@ -0,0 +1,11 @@
+from .. import Provider as BankProvider
+
+
+class Provider(BankProvider):
+    """Implement bank provider for ``cs_CZ`` locale.
+
+    https://www.mbank.cz/informace-k-produktum/info/ucty/cislo-uctu-iban.html
+    """
+
+    bban_format = "####################"
+    country_code = "CZ"
diff --git a/faker/providers/file/__init__.py b/faker/providers/file/__init__.py
index 44ac4acd..d5a76aac 100644
--- a/faker/providers/file/__init__.py
+++ b/faker/providers/file/__init__.py
@@ -1,7 +1,7 @@
 import string
 
 from collections import OrderedDict
-from typing import Dict, Optional
+from typing import Dict, Literal, Optional, Sequence, Union
 
 from .. import BaseProvider, ElementsType
 
@@ -204,6 +204,18 @@ class Provider(BaseProvider):
             ("video", video_file_extensions),
         )
     )
+
+    file_systems_path_rules: Dict[str, Dict] = {
+        "windows": {
+            "root": "C:\\",
+            "separator": "\\",
+        },
+        "linux": {
+            "root": "/",
+            "separator": "/",
+        },
+    }
+
     unix_device_prefixes: ElementsType[str] = ("sd", "vd", "xvd")
 
     def mime_type(self, category: Optional[str] = None) -> str:
@@ -223,21 +235,24 @@ class Provider(BaseProvider):
     def file_name(self, category: Optional[str] = None, extension: Optional[str] = None) -> str:
         """Generate a random file name with extension.
 
-        If ``extension`` is ``None``, a random extension will be created under
-        the hood using |file_extension| with the specified ``category``. If a
-        value for ``extension`` is provided, the value will be used instead,
-        and ``category`` will be ignored. The actual name part itself is
-        generated using |word|.
+        If ``extension`` is ``None``, a random extension will be created
+        under the hood using |file_extension| with the specified
+        ``category``. If a value for ``extension`` is provided, the
+        value will be used instead, and ``category`` will be ignored.
+        The actual name part itself is generated using |word|. If
+        extension is an empty string then no extension will be added,
+        and file_name will be the same as |word|.
 
         :sample: size=10
         :sample: category='audio'
         :sample: extension='abcdef'
         :sample: category='audio', extension='abcdef'
+        :sample: extension=''
         """
         if extension is None:
             extension = self.file_extension(category)
         filename: str = self.generator.word()
-        return f"{filename}.{extension}"
+        return f"{filename}.{extension}" if extension else filename
 
     def file_extension(self, category: Optional[str] = None) -> str:
         """Generate a file extension under the specified ``category``.
@@ -257,28 +272,58 @@ class Provider(BaseProvider):
         self,
         depth: int = 1,
         category: Optional[str] = None,
-        extension: Optional[str] = None,
+        extension: Optional[Union[str, Sequence[str]]] = None,
         absolute: Optional[bool] = True,
+        file_system_rule: Literal["linux", "windows"] = "linux",
     ) -> str:
         """Generate an pathname to a file.
 
-        This method uses |file_name| under the hood to generate the file name
-        itself, and ``depth`` controls the depth of the directory path, and
-        |word| is used under the hood to generate the different directory names.
+        This method uses |file_name| under the hood to generate the file
+        name itself, and ``depth`` controls the depth of the directory
+        path, and |word| is used under the hood to generate the
+        different directory names.
+
+        If ``absolute`` is ``True`` (default), the generated path starts
+        with ``/`` and is absolute. Otherwise, the generated path is
+        relative.
 
-        If ``absolute`` is ``True`` (default), the generated path starts with
-        ``/`` and is absolute. Otherwise, the generated path is relative.
+        If used, ``extension`` can be either a string, forcing that
+        extension, a sequence of strings (one will be picked at random),
+        or an empty sequence (the path will have no extension). Default
+        behaviour is the same as |file_name|
+
+        if ``file_system`` is set (default="linux"), the generated path uses
+        specified file system path standard, the list of valid file systems include:
+        ``'windows'``, ``'linux'``.
 
         :sample: size=10
         :sample: depth=3
         :sample: depth=5, category='video'
         :sample: depth=5, category='video', extension='abcdef'
+        :sample: extension=[]
+        :sample: extension=''
+        :sample: extension=["a", "bc", "def"]
+        :sample: depth=5, category='video', extension='abcdef', file_system='windows'
         """
-        file: str = self.file_name(category, extension)
-        path: str = f"/{file}"
+
+        if extension is not None and not isinstance(extension, str):
+            if len(extension):
+                extension = self.random_element(extension)
+            else:
+                extension = ""
+
+        fs_rule = self.file_systems_path_rules.get(file_system_rule, None)
+        if not fs_rule:
+            raise TypeError("Specified file system is invalid.")
+
+        root = fs_rule["root"]
+        seperator = fs_rule["separator"]
+
+        path: str = self.file_name(category, extension)
         for _ in range(0, depth):
-            path = f"/{self.generator.word()}{path}"
-        return path if absolute else path[1:]
+            path = f"{self.generator.word()}{seperator}{path}"
+
+        return root + path if absolute else path
 
     def unix_device(self, prefix: Optional[str] = None) -> str:
         """Generate a Unix device file name.


Test patch:
diff --git a/tests/providers/test_bank.py b/tests/providers/test_bank.py
index cf065af3..d373a4ce 100644
--- a/tests/providers/test_bank.py
+++ b/tests/providers/test_bank.py
@@ -4,6 +4,7 @@ import pytest
 
 from faker.providers.bank import Provider as BankProvider
 from faker.providers.bank.az_AZ import Provider as AzAzBankProvider
+from faker.providers.bank.cs_CZ import Provider as CsCZBankProvider
 from faker.providers.bank.de_CH import Provider as DeChBankProvider
 from faker.providers.bank.el_GR import Provider as ElGrBankProvider
 from faker.providers.bank.en_GB import Provider as EnGbBankProvider
@@ -56,6 +57,21 @@ class TestAzAz:
             bank = faker.bank()
             assert bank in AzAzBankProvider.banks
 
+class TestCsCz:
+    """Test cs_CZ bank provider"""
+
+    def test_bban(self, faker, num_samples):
+        for _ in range(num_samples):
+            assert re.fullmatch(r"\d{20}", 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] == CsCZBankProvider.country_code
+            assert re.fullmatch(r"\d{2}\d{20}", iban[2:])
+
+
 
 class TestNoNo:
     """Test no_NO bank provider"""
diff --git a/tests/providers/test_file.py b/tests/providers/test_file.py
index bc46350a..eb8935e5 100644
--- a/tests/providers/test_file.py
+++ b/tests/providers/test_file.py
@@ -11,6 +11,21 @@ class TestFile(unittest.TestCase):
         self.fake = Faker()
         Faker.seed(0)
 
+    def test_file_name(self):
+        for _ in range(100):
+            file_name = self.fake.file_name()
+            assert re.search(r"\w+\.\w+", file_name)
+            file_name = self.fake.file_name(extension=None)
+            assert re.search(r"\w+\.\w+", file_name)
+            file_name = self.fake.file_name(extension="pdf")
+            assert re.search(r"\w+\.pdf$", file_name)
+            file_name = self.fake.file_name(category="image")
+            assert re.search(r"\w+\.(bmp|gif|jpeg|jpg|png|tiff)$", file_name)
+            file_name = self.fake.file_name(category="image", extension="abcdef")
+            assert re.search(r"\w+\.abcdef$", file_name)
+            file_name = self.fake.file_name(extension="")
+            assert re.search(r"\w+$", file_name)
+
     def test_file_path(self):
         for _ in range(100):
             file_path = self.fake.file_path()
@@ -20,9 +35,22 @@ class TestFile(unittest.TestCase):
             file_path = self.fake.file_path(depth=3)
             assert re.search(r"\/\w+\/\w+\/\w+\.\w+", file_path)
             file_path = self.fake.file_path(extension="pdf")
-            assert re.search(r"\/\w+\/\w+\.pdf", file_path)
+            assert re.search(r"\/\w+\/\w+\.pdf$", file_path)
+            file_path = self.fake.file_path(extension=["a", "bc", "def", "ghij", "klmno"])
+            assert re.search(r"\/\w+\/\w+\.(a|bc|def|ghij|klmno)$", file_path)
+            file_path = self.fake.file_path(extension=None)
+            assert re.search(r"\/\w+\/\w+\.\w+", file_path)
+            file_path = self.fake.file_path(extension="")
+            assert re.search(r"\/\w+\/\w+$", file_path)
+            file_path = self.fake.file_path(extension=[])
+            assert re.search(r"\/\w+\/\w+$", file_path)
             file_path = self.fake.file_path(category="image")
             assert re.search(r"\/\w+\/\w+\.(bmp|gif|jpeg|jpg|png|tiff)", file_path)
+            file_path = self.fake.file_path(file_system_rule="windows")
+            assert re.search(r"\\\w+\\\w+\.\w+", file_path)
+            file_path = self.fake.file_path(file_system_rule="windows", category="image", absolute=True)
+            assert re.search(r"^[a-zA-Z]:\\\w+\\\w+\.\w+", file_path)
+            assert re.search(r"\\\w+\\\w+\.(bmp|gif|jpeg|jpg|png|tiff)$", file_path)
 
     def test_unix_device(self):
         reg_device = re.compile(r"^/dev/(vd|sd|xvd)[a-z]$")