Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow extension in file_path to take a list of allowed extensions, or empty for "no extension" #1966

Merged
merged 4 commits into from
Dec 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 32 additions & 13 deletions faker/providers/file/__init__.py
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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``.
Expand All @@ -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):
Expand Down
27 changes: 25 additions & 2 deletions tests/providers/test_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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]$")
Expand Down