From 58f49e09bc4a7553117ea931130f6c78a2216c7c Mon Sep 17 00:00:00 2001 From: fritz stelluto Date: Fri, 29 Dec 2023 17:00:43 +0100 Subject: [PATCH] Allow extension in `file_path` to take a list of allowed extensions, or empty for "no extension". Remove dot from `file_name` when `extention` is the empty string (#1966) --- faker/providers/file/__init__.py | 45 +++++++++++++++++++++++--------- tests/providers/test_file.py | 27 +++++++++++++++++-- 2 files changed, 57 insertions(+), 15 deletions(-) diff --git a/faker/providers/file/__init__.py b/faker/providers/file/__init__.py index 44ac4acdd2..a5ff736246 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, Optional, Sequence, Union from .. import BaseProvider, ElementsType @@ -223,21 +223,24 @@ def mime_type(self, category: Optional[str] = None) -> str: 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,23 +260,39 @@ def file_path( self, depth: int = 1, category: Optional[str] = None, - extension: Optional[str] = None, + extension: Optional[Union[str, Sequence[str]]] = None, absolute: Optional[bool] = True, ) -> 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| :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"] """ + if extension is not None and not isinstance(extension, str): + if len(extension): + extension = self.random_element(extension) + else: + extension = "" + file: str = self.file_name(category, extension) path: str = f"/{file}" for _ in range(0, depth): diff --git a/tests/providers/test_file.py b/tests/providers/test_file.py index bc46350a9e..539deea2b6 100644 --- a/tests/providers/test_file.py +++ b/tests/providers/test_file.py @@ -11,6 +11,21 @@ def setUp(self): 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,17 @@ def test_file_path(self): 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) + 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]$")