Repository: joke2k/faker

Issue:
Feature request: file_path extension=False to generate a path without extension
```
faker.file_path(extension=None)
```
 will generate something like `/event/amount.js`

### Actual behavior

There is no way to generate a path without an extension. 

### Expected behavior

This is counter-intuitive - `exrtension? None` should mean that there is no extension. 
And it would be useful to have a way to generate a path without an extension, regardless of how it is done.


### Suggestion

In order not to break backward compatibility, can I suggest changing extension to be either None, bool or a string, with the following behaviour

* True -> same as None
* False ->  no extension
* None ->  as it is now 
* a string -> the string

Thanks


Discussion:

Patch:
diff --git a/.bumpversion.cfg b/.bumpversion.cfg
index ad6f0b9f..fac9a4a5 100644
--- a/.bumpversion.cfg
+++ b/.bumpversion.cfg
@@ -1,5 +1,5 @@
 [bumpversion]
-current_version = 21.0.0
+current_version = 21.0.1
 files = VERSION faker/__init__.py docs/conf.py
 commit = True
 tag = True
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ebc3be70..f435b1a1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,9 @@
 ## Changelog
 
+### [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..a8f5438c 100644
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-21.0.0
+21.0.1
diff --git a/docs/conf.py b/docs/conf.py
index 6154f53b..ba4071b7 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 = "21.0.1"
 # The full version, including alpha/beta/rc tags.
-release = "21.0.0"
+release = "21.0.1"
 
 # 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..1073d343 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 = "21.0.1"
 
 __all__ = ("Factory", "Generator", "Faker")
diff --git a/faker/providers/company/ru_RU/__init__.py b/faker/providers/company/ru_RU/__init__.py
index f305bedc..23cde258 100644
--- a/faker/providers/company/ru_RU/__init__.py
+++ b/faker/providers/company/ru_RU/__init__.py
@@ -1048,7 +1048,7 @@ class Provider(CompanyProvider):
             "инициатив",
             "интернет-компаний",
             "интернет-магазинов",
-            "интернет-продавцоы",
+            "интернет-продавцов",
             "интернет-услуг",
             "интерфейсов",
             "инфопосредников",
diff --git a/faker/providers/file/__init__.py b/faker/providers/file/__init__.py
index 44ac4acd..a5ff7362 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 @@ 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,23 +260,39 @@ 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,
     ) -> 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):


Test patch:
diff --git a/tests/providers/test_file.py b/tests/providers/test_file.py
index bc46350a..539deea2 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,17 @@ 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)
+            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]$")