Repository: joke2k/faker
Pull request #2060 (2024-06-16 13:05:55)

Addition sk_SK Geo
Addition sk_SK Geo


Patch:
diff --git a/.bumpversion.cfg b/.bumpversion.cfg
index 3377e576..99e8e993 100644
--- a/.bumpversion.cfg
+++ b/.bumpversion.cfg
@@ -1,5 +1,5 @@
 [bumpversion]
-current_version = 25.8.0
+current_version = 26.0.0
 files = VERSION faker/__init__.py docs/conf.py
 commit = True
 tag = True
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cab81022..f8599f1e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,11 +1,26 @@
 ## Changelog
 
+### [v26.0.0 - 2024-06-26](https://github.com/joke2k/faker/compare/v25.9.2...v26.0.0)
 
-### [v25.8.0 - 2024-05-07](https://github.com/joke2k/faker/compare/v25.7.0..v25.8.0)
+* Fix upper limit of nb_elements. Thanks @mileswatsonbjss.
+
+### [v25.9.2 - 2024-06-25](https://github.com/joke2k/faker/compare/v25.9.1...v25.9.2)
+
+* Remove duplicate entry in currency provider.
+
+### [v25.9.1 - 2024-06-20](https://github.com/joke2k/faker/compare/v25.9.0...v25.9.1)
+
+* Change `pydecimal` type hint for `min_value`, `max_value` to allow `int`s. Thanks @parsariyahi.
+
+### [v25.9.0 - 2024-06-20](https://github.com/joke2k/faker/compare/v25.8.0...v25.9.0)
+
+* Add support for Nigerian Yoruba names and surnames (`yo_NG`). Thanks @5uru.
+
+### [v25.8.0 - 2024-05-07](https://github.com/joke2k/faker/compare/v25.7.0...v25.8.0)
 
 * Add handshake emoji with different color variations to emoji provider list. Thanks @tamkc.
 
-### [v25.7.0 - 2024-05-07](https://github.com/joke2k/faker/compare/v25.6.0..v25.7.0)
+### [v25.7.0 - 2024-05-07](https://github.com/joke2k/faker/compare/v25.6.0...v25.7.0)
 
 * Add missing translation for countries in `pt-BR`. Thanks @LeonardoFurtado.
 
diff --git a/VERSION b/VERSION
index 11d4ad6c..1e212a91 100644
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-25.8.0
+26.0.0
diff --git a/docs/conf.py b/docs/conf.py
index 8d5933df..9f267530 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -51,9 +51,9 @@ copyright = f"2014-{datetime.now().year}, Daniele Faraglia"
 # built documents.
 #
 # The short X.Y version.
-version = "25.8.0"
+version = "26.0.0"
 # The full version, including alpha/beta/rc tags.
-release = "25.8.0"
+release = "26.0.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 c379fd59..f238d099 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 = "25.8.0"
+VERSION = "26.0.0"
 
 __all__ = ("Factory", "Generator", "Faker")
diff --git a/faker/providers/currency/__init__.py b/faker/providers/currency/__init__.py
index 1492d2ed..215eadc8 100644
--- a/faker/providers/currency/__init__.py
+++ b/faker/providers/currency/__init__.py
@@ -369,7 +369,6 @@ class Provider(BaseProvider):
         "TTD": "\u0024",
         "TVD": "\u0024",
         "TWD": "\u0024",
-        "TWD": "\u0024",
         "TZS": "Tsh",
         "UAH": "\u20B4",
         "UGX": "USh",
diff --git a/faker/providers/geo/sk_SK/__init__.py b/faker/providers/geo/sk_SK/__init__.py
index 886f3046..38dc71cd 100644
--- a/faker/providers/geo/sk_SK/__init__.py
+++ b/faker/providers/geo/sk_SK/__init__.py
@@ -77,4 +77,28 @@ class Provider(GeoProvider):
         ("48.44858", "18.91003", "Banská Štiavnica", "SK", "Europe/Banská Štiavnica"),
         ("49.337", "19.556", "Tvrdošín", "SK", "Europe/Tvrdošín"),
         ("48.61428", "20.99957", "Moldava nad Bodvou", "SK", "Europe/Moldava nad Bodvou"),
+        ("48.24371", "18.30846", "Vráble", "SK", "Europe/Vráble"),
+        ("47.85798", "17.76884", "Nagymegyer", "SK", "Europe/Nagymegyer"),
+        ("48.91447", "20.87514", "Krompachy", "SK", "Europe/Krompachy"),
+        ("48.33397", "17.30711", "Modra", "SK", "Europe/Modra"),
+        ("48.57787", "19.52574", "Hriňová", "SK", "Europe/Hriňová"),
+        ("49.03962", "19.72335", "Liptovský Hrádok", "SK", "Europe/Liptovský Hrádok"),
+        ("47.86984", "18.19233", "Stará ďala", "SK", "Europe/Stará ďala"),
+        ("49.4079", "19.48032", "Námestovo", "SK", "Europe/Námestovo"),
+        ("48.27474", "17.03173", "Stampfen", "SK", "Europe/Stampfen"),
+        ("48.07408", "18.94946", "Šahy", "SK", "Europe/Šahy"),
+        ("48.88628", "21.9393", "Chlmec", "SK", "Europe/Chlmec"),
+        ("48.3554", "19.06474", "Krupina", "SK", "Europe/Krupina"),
+        ("49.40429", "18.62258", "Turzovka", "SK", "Europe/Turzovka"),
+        ("48.70074", "21.66104", "Sečovce", "SK", "Europe/Sečovce"),
+        ("48.05075", "18.65421", "Želiezovce", "SK", "Europe/Želiezovce"),
+        ("48.42305", "18.64037", "Nová Baňa", "SK", "Europe/Nová Baňa"),
+        ("49.36101", "19.61249", "Trstená", "SK", "Europe/Trstená"),
+        ("49.06014", "20.20695", "Svit", "SK", "Europe/Svit"),
+        ("49.11328", "18.91714", "Vrútky", "SK", "Europe/Vrútky"),
+        ("49.27195", "21.90073", "Medzilaborce", "SK", "Europe/Medzilaborce"),
+        ("48.48123", "18.71565", "Žarnovica", "SK", "Europe/Žarnovica"),
+        ("48.85584", "20.93713", "Gelnica", "SK", "Europe/Gelnica"),
+        ("49.08899", "18.64007", "Rajec", "SK", "Europe/Rajec"),
+        ("48.74455", "22.18136", "Sobrance", "SK", "Europe/Sobrance"),
     )
diff --git a/faker/providers/lorem/__init__.py b/faker/providers/lorem/__init__.py
index f7304157..0899f58d 100644
--- a/faker/providers/lorem/__init__.py
+++ b/faker/providers/lorem/__init__.py
@@ -23,23 +23,16 @@ class Provider(BaseProvider):
     word_connector = " "
     sentence_punctuation = "."
 
-    def words(
+    def get_words_list(
         self,
-        nb: int = 3,
         part_of_speech: Optional[str] = None,
         ext_word_list: Optional[Sequence[str]] = None,
-        unique: bool = False,
     ) -> List[str]:
-        """Generate a tuple of words.
-
-        The ``nb`` argument controls the number of words in the resulting list,
-        and if ``ext_word_list`` is provided, words from that list will be used
-        instead of those from the locale provider's built-in word list.
+        """Get list of words.
 
-        If ``unique`` is ``True``, this method will return a list containing
-        unique words. Under the hood, |random_sample| will be used for sampling
-        without replacement. If ``unique`` is ``False``, |random_choices| is
-        used instead, and the list returned may contain duplicates.
+        ``ext_word_list`` is a parameter that allows the user to provide a list
+        of words to be used instead of the built-in word list. If ``ext_word_list``
+        is provided, then the value of ``part_of_speech`` is ignored.
 
         ``part_of_speech`` is a parameter that defines to what part of speech
         the returned word belongs. If ``ext_word_list`` is not ``None``, then
@@ -47,16 +40,16 @@ class Provider(BaseProvider):
         not correspond to an existent part of speech according to the set locale,
         then an exception is raised.
 
-        .. warning::
-           Depending on the length of a locale provider's built-in word list or
-           on the length of ``ext_word_list`` if provided, a large ``nb`` can
-           exhaust said lists if ``unique`` is ``True``, raising an exception.
+        :sample: part_of_speech="abc", ext_word_list=['abc', 'def', 'ghi', 'jkl']
+        :sample: part_of_speech="abc"
+        :sample: ext_word_list=['abc', 'def', 'ghi', 'jkl']
 
-        :sample:
-        :sample: nb=5
-        :sample: nb=5, ext_word_list=['abc', 'def', 'ghi', 'jkl']
-        :sample: nb=4, ext_word_list=['abc', 'def', 'ghi', 'jkl'], unique=True
+        .. warning::
+            Depending on the length of a locale provider's built-in word list or
+            on the length of ``ext_word_list`` if provided, a large ``nb`` can
+            exhaust said lists if ``unique`` is ``True``, raising an exception.
         """
+
         if ext_word_list is not None:
             word_list = ext_word_list
         elif part_of_speech:
@@ -67,6 +60,40 @@ class Provider(BaseProvider):
         else:
             word_list = self.word_list  # type: ignore[attr-defined]
 
+        return list(word_list)
+
+    def words(
+        self,
+        nb: int = 3,
+        ext_word_list: Optional[List[str]] = None,
+        unique: bool = False,
+    ) -> List[str]:
+        """Generate a tuple of words.
+
+        The ``nb`` argument controls the number of words in the resulting list,
+        and if ``ext_word_list`` is provided, words from that list will be used
+        instead of those from the locale provider's built-in word list.
+
+        if ``word_list`` is not provided, the method will use a default value of None,
+        which will result in the method calling the ``get_words_list`` method to get the
+        word list. If ``word_list`` is provided, the method will use the provided list.
+
+        If ``unique`` is ``True``, this method will return a list containing
+        unique words. Under the hood, |random_sample| will be used for sampling
+        without replacement. If ``unique`` is ``False``, |random_choices| is
+        used instead, and the list returned may contain duplicates.
+
+        :sample:
+        :sample: nb=5
+        :sample: nb=5, ext_word_list=['abc', 'def', 'ghi', 'jkl']
+        :sample: nb=4, ext_word_list=['abc', 'def', 'ghi', 'jkl'], unique=True
+        """
+
+        if ext_word_list is None:
+            word_list = self.get_words_list()
+        else:
+            word_list = ext_word_list
+
         if unique:
             unique_samples = cast(List[str], self.random_sample(word_list, length=nb))
             return unique_samples
@@ -82,7 +109,9 @@ class Provider(BaseProvider):
         :sample:
         :sample: ext_word_list=['abc', 'def', 'ghi', 'jkl']
         """
-        return self.words(1, part_of_speech, ext_word_list)[0]
+        word_list = self.get_words_list(part_of_speech, ext_word_list)
+
+        return self.words(1, word_list)[0]
 
     def sentence(
         self, nb_words: int = 6, variable_nb_words: bool = True, ext_word_list: Optional[Sequence[str]] = None
@@ -109,7 +138,8 @@ class Provider(BaseProvider):
         if variable_nb_words:
             nb_words = self.randomize_nb_elements(nb_words, min=1)
 
-        words = list(self.words(nb=nb_words, ext_word_list=ext_word_list))
+        word_list = self.get_words_list(ext_word_list=ext_word_list)
+        words = list(self.words(nb=nb_words, ext_word_list=word_list))
         words[0] = words[0].title()
 
         return self.word_connector.join(words) + self.sentence_punctuation
diff --git a/faker/providers/lorem/en_PH/__init__.py b/faker/providers/lorem/en_PH/__init__.py
index ea64408e..fedaf8c4 100644
--- a/faker/providers/lorem/en_PH/__init__.py
+++ b/faker/providers/lorem/en_PH/__init__.py
@@ -32,7 +32,10 @@ class Provider(LoremProvider):
         :sample: nb=5
         :sample: nb=5, unique=True
         """
-        return self.words(nb=nb, ext_word_list=self.english_word_list, unique=unique)
+
+        word_list = self.generator.get_words_list(ext_word_list=self.english_word_list)
+
+        return self.words(nb=nb, ext_word_list=word_list, unique=unique)
 
     def english_sentence(self, nb_words: int = 6, variable_nb_words: bool = True) -> str:
         """Generate a sentence in English.
diff --git a/faker/providers/person/yo_NG/__init__.py b/faker/providers/person/yo_NG/__init__.py
new file mode 100644
index 00000000..db857302
--- /dev/null
+++ b/faker/providers/person/yo_NG/__init__.py
@@ -0,0 +1,340 @@
+from .. import Provider as PersonProvider
+
+
+class Provider(PersonProvider):
+    """
+    A Faker provider for generating fake Zulu names in South Africa.
+    """
+
+    formats = (
+        "{{first_name_male}} {{last_name_male}}",
+        "{{first_name_male}} {{last_name_male}}",
+        "{{first_name_male}} {{last_name_male}}",
+        "{{first_name_male}} {{last_name_male}}",
+        "{{first_name_male}} {{last_name_male}} {{last_name_male}}",
+        "{{first_name_female}} {{last_name_female}}",
+        "{{first_name_female}} {{last_name_female}}",
+        "{{first_name_female}} {{last_name_female}}",
+        "{{first_name_female}} {{last_name_female}}",
+        "{{first_name_female}} {{last_name_female}} {{last_name_female}}",
+        "{{prefix_male}} {{first_name_male}} {{last_name_male}}",
+        "{{prefix_female}} {{first_name_female}} {{last_name_female}}",
+        "{{prefix_male}} {{first_name_male}} {{last_name_male}}",
+        "{{prefix_female}} {{first_name_female}} {{last_name_female}}",
+    )
+
+    # first names sourced from:
+    # 1. https://github.com/faker-js/faker/blob/next/src/locales/yo_NG/person/last_name.ts
+    # 2. https://github.com/faker-js/faker/blob/next/src/locales/yo_NG/person/male_first_name.ts
+
+    first_names_male = (
+        "Abayomi",
+        "Abiodun",
+        "Abiona",
+        "Adebiyi",
+        "Adebowale",
+        "Adedayo",
+        "Adedeji",
+        "Adekitan",
+        "Adekola",
+        "Adekunle",
+        "Adeleke",
+        "Adeniyi",
+        "Adeolu",
+        "Adeoti",
+        "Aderopo",
+        "Adeshina",
+        "Adesoji",
+        "Adetayo",
+        "Adeyi",
+        "Adigun",
+        "Afolarin",
+        "Ajala",
+        "Ajani",
+        "Akanmu",
+        "Akinkunmi",
+        "Akinlabi",
+        "Akinwale",
+        "Alade",
+        "Alamu",
+        "Anjolaoluwa",
+        "Ayinde",
+        "Ayodeji",
+        "Ayodele",
+        "Babasola",
+        "Babatunji",
+        "Babawale",
+        "Damife",
+        "Demilade",
+        "Durodola",
+        "Ekundayo",
+        "Esupofo",
+        "Folu",
+        "Gbadebo",
+        "Gbolahan",
+        "Gbowoade",
+        "Ibidapo",
+        "Ige",
+        "Ikeoluwa",
+        "Inioluwa",
+        "Iseoluwa",
+        "Ishola",
+        "Juwon",
+        "Keji",
+        "Kolawole",
+        "Korede",
+        "Leke",
+        "Lere",
+        "Niyilolawa",
+        "Oba",
+        "ObaniJesu",
+        "Ogooluwa",
+        "Oke",
+        "Oladare",
+        "Oladimeji",
+        "Olakunle",
+        "Olanrewaju",
+        "Olansile",
+        "Olumorotimi",
+        "Oluwafemi",
+        "Oluwagbemiga",
+        "Oluwamumibori",
+        "Oluwamuyiwa",
+        "Oluwasanmi",
+        "Oluwasegun",
+        "Oluwole",
+        "Omobobola",
+        "Omotayo",
+        "Osunleke",
+        "Seye",
+        "Shekoni",
+        "Sijuade",
+        "Tade",
+        "Temidayo",
+        "Toki",
+        "Tokunbo",
+        "Tomori",
+    )
+    first_names_female = (
+        "Aanuoluwapo",
+        "Abebi",
+        "Abeni",
+        "Abosede",
+        "Adebukola",
+        "Adenike",
+        "Adepeju",
+        "Adesewa",
+        "Adesua",
+        "Adetoke",
+        "Adetoun",
+        "Adunni",
+        "Ajoke",
+        "Amoke",
+        "Amope",
+        "Arike",
+        "Arinola",
+        "Asake",
+        "Atinuke",
+        "Awero",
+        "Ayinke",
+        "Ayoka",
+        "Bolatito",
+        "Boluwatife",
+        "Bunmi",
+        "Doyinsola",
+        "Eniola",
+        "Ewatomi",
+        "Fadekemi",
+        "Faderera",
+        "Fehintola",
+        "Fibikemi",
+        "Fikayomi",
+        "Folashade",
+        "Ibironke",
+        "Iretioluwa",
+        "Iyabode",
+        "Iyadunni",
+        "Kikelomo",
+        "Modupe",
+        "Mofifoluwa",
+        "Mojisola",
+        "Mojisoluwa",
+        "Moradeke",
+        "Morayo",
+        "Morenike",
+        "Morolake",
+        "Mosinmileoluwa",
+        "Mosunmola",
+        "Motunrayo",
+        "Moyosore",
+        "Ninioluwa",
+        "Olajumoke",
+        "Olasunmbo",
+        "Ololade",
+        "Olufunke",
+        "Olufunmilayo",
+        "Oluwakemi",
+        "Omobolanle",
+        "Omodunni",
+        "Omolabake",
+        "Omolara",
+        "Omosalewa",
+        "Omotara",
+        "Omotola",
+        "Omotoun",
+        "Omowumi",
+        "Oreofe",
+        "Oyenike",
+        "Oyindasola",
+        "Radeke",
+        "Ronke",
+        "Segilola",
+        "Similoluwa",
+        "Simisola",
+        "Sowande",
+        "Subomi",
+        "Titilayo",
+        "Tolulope",
+        "Toluwanimi",
+        "Wuraola",
+        "Yejide",
+        "Yetunde",
+        "Yewande",
+    )
+
+    first_names = first_names_male + first_names_female
+
+    # last names sourced from :
+    # 1. https://github.com/faker-js/faker/blob/next/src/locales/yo_NG/person/last_name.ts
+    last_names_male = (
+        "Adebisi",
+        "Adegbite",
+        "Adegoke",
+        "Adekunle",
+        "Adelakun",
+        "Adeleke",
+        "Adelusi",
+        "Ademiluyi",
+        "Aderibigbe",
+        "Aderogba",
+        "Adesiyan",
+        "Adeyemo",
+        "Adisa",
+        "Afolabi",
+        "Afolayan",
+        "Afonja",
+        "Ajao",
+        "Ajayi",
+        "Ajewole",
+        "Akinrinola",
+        "Alabi",
+        "Aloba",
+        "Awodiran",
+        "Awolowo",
+        "Ayandokun",
+        "Ayoola",
+        "Babtunde",
+        "Bakare",
+        "Balogun",
+        "Bamidele",
+        "Bamiloye",
+        "Edun",
+        "Fadipe",
+        "Fagunwa",
+        "Fajimi",
+        "Falabi",
+        "Faleti",
+        "Faloye",
+        "Fasasi",
+        "Ibikunle",
+        "Ilori",
+        "Ilupeju",
+        "Iyanda",
+        "Jaiyeola",
+        "Kolade",
+        "Kosoko",
+        "Koya",
+        "Makinde",
+        "Makinwa",
+        "Morawo",
+        "Ninalowo",
+        "Odetola",
+        "Odunsi",
+        "Ogindan",
+        "Oginni",
+        "Ogulana",
+        "Ogunbamigbe",
+        "Ogunbiyi",
+        "Ogunbo",
+        "Ogunde",
+        "Ogunwobi",
+        "Ogunyeye",
+        "Ojo",
+        "Ojua",
+        "Olabode",
+        "Oladipupo",
+        "Olaiya",
+        "Olasupo",
+        "Olowokeere",
+        "Oloyede",
+        "Olubode",
+        "Olugbayila",
+        "Olujimi",
+        "Olukotun",
+        "Olukunga",
+        "Olusanya",
+        "Oluwagbemi",
+        "Omidina",
+        "Omojola",
+        "Omotoso",
+        "Oparinde",
+        "Oshin",
+        "Osuntokun",
+        "Owokoniran",
+        "Owolabi",
+        "Owoyemi",
+        "Oyadiran",
+        "Oyaifo",
+        "Oyeniyi",
+        "Oyetoro",
+        "Oyeyemi",
+        "Oyinlola",
+        "Paimo",
+        "Salako",
+        "Salami",
+        "Shekoni",
+        "Sobowale",
+        "Soyinka",
+    )
+
+    # last names are not sex dependant
+    last_names_female = last_names_male
+    last_names = last_names_male + last_names_female
+
+    prefixes_female = (
+        "Mrs.",
+        "Ms.",
+        "Dr.",
+        "Alhaja",
+        "Mama",
+        "Iya",
+        "Madam",
+        "Chief",
+        "Lady",
+        "Erelu",
+        "Olori",
+        "Princess",
+    )
+
+    prefixes_male = (
+        "Mr.",
+        "Dr.",
+        "Alhaji",
+        "Baba",
+        "Ogbeni",
+        "Oloye",
+        "Chief",
+        "Prince",
+        "Oba",
+        "Kabiyesi",
+    )
diff --git a/faker/providers/python/__init__.py b/faker/providers/python/__init__.py
index b4ebef28..669efdad 100644
--- a/faker/providers/python/__init__.py
+++ b/faker/providers/python/__init__.py
@@ -7,6 +7,8 @@ from decimal import Decimal
 from enum import Enum
 from typing import Any, Dict, Iterable, Iterator, List, Optional, Set, Tuple, Type, TypeVar, Union, cast, no_type_check
 
+from faker.typing import BasicNumber
+
 from ...exceptions import BaseFakerException
 from .. import BaseProvider, ElementsType
 
@@ -284,8 +286,8 @@ class Provider(BaseProvider):
         left_digits: Optional[int] = None,
         right_digits: Optional[int] = None,
         positive: bool = False,
-        min_value: Optional[float] = None,
-        max_value: Optional[float] = None,
+        min_value: Optional[BasicNumber] = None,
+        max_value: Optional[BasicNumber] = None,
     ) -> Decimal:
         if left_digits is not None and left_digits < 0:
             raise ValueError("A decimal number cannot have less than 0 digits in its " "integer part")
@@ -464,9 +466,20 @@ class Provider(BaseProvider):
         :variable_nb_elements: is use variable number of elements for dictionary
         :value_types: type of dictionary values
         """
+
+        words_list_count = len(self.generator.get_words_list())
+
         if variable_nb_elements:
             nb_elements = self.randomize_nb_elements(nb_elements, min=1)
 
+        if nb_elements > words_list_count:
+            warnings.warn(
+                f"Number of nb_elements is greater than the number of words in the list."
+                f" {words_list_count} words will be used.",
+                RuntimeWarning,
+            )
+            nb_elements = words_list_count
+
         return dict(
             zip(
                 self.generator.words(nb_elements, unique=True),
diff --git a/faker/proxy.pyi b/faker/proxy.pyi
index 679c9ca1..e8931ca9 100644
--- a/faker/proxy.pyi
+++ b/faker/proxy.pyi
@@ -1588,6 +1588,32 @@ class Faker:
     def isbn10(self, separator: str = ...) -> str: ...
     def isbn13(self, separator: str = ...) -> str: ...
     def job(self) -> str: ...
+    def get_words_list(
+        self, part_of_speech: Optional[str] = ..., ext_word_list: Optional[Sequence[str]] = ...
+    ) -> List[str]:
+        """
+        Get list of words.
+
+        ``ext_word_list`` is a parameter that allows the user to provide a list
+        of words to be used instead of the built-in word list. If ``ext_word_list``
+        is provided, then the value of ``part_of_speech`` is ignored.
+
+        ``part_of_speech`` is a parameter that defines to what part of speech
+        the returned word belongs. If ``ext_word_list`` is not ``None``, then
+        ``part_of_speech`` is ignored. If the value of ``part_of_speech`` does
+        not correspond to an existent part of speech according to the set locale,
+        then an exception is raised.
+
+        :sample: part_of_speech="abc", ext_word_list=['abc', 'def', 'ghi', 'jkl']
+        :sample: part_of_speech="abc"
+        :sample: ext_word_list=['abc', 'def', 'ghi', 'jkl']
+
+        .. warning::
+            Depending on the length of a locale provider's built-in word list or
+            on the length of ``ext_word_list`` if provided, a large ``nb`` can
+            exhaust said lists if ``unique`` is ``True``, raising an exception.
+        """
+        ...
     def paragraph(
         self, nb_sentences: int = ..., variable_nb_sentences: bool = ..., ext_word_list: Optional[Sequence[str]] = ...
     ) -> str:
@@ -1703,13 +1729,7 @@ class Faker:
         :sample: ext_word_list=['abc', 'def', 'ghi', 'jkl']
         """
         ...
-    def words(
-        self,
-        nb: int = ...,
-        part_of_speech: Optional[str] = ...,
-        ext_word_list: Optional[Sequence[str]] = ...,
-        unique: bool = ...,
-    ) -> List[str]:
+    def words(self, nb: int = ..., ext_word_list: Optional[List[str]] = ..., unique: bool = ...) -> List[str]:
         """
         Generate a tuple of words.
 
@@ -1717,22 +1737,15 @@ class Faker:
         and if ``ext_word_list`` is provided, words from that list will be used
         instead of those from the locale provider's built-in word list.
 
+        if ``word_list`` is not provided, the method will use a default value of None,
+        which will result in the method calling the ``get_words_list`` method to get the
+        word list. If ``word_list`` is provided, the method will use the provided list.
+
         If ``unique`` is ``True``, this method will return a list containing
         unique words. Under the hood, |random_sample| will be used for sampling
         without replacement. If ``unique`` is ``False``, |random_choices| is
         used instead, and the list returned may contain duplicates.
 
-        ``part_of_speech`` is a parameter that defines to what part of speech
-        the returned word belongs. If ``ext_word_list`` is not ``None``, then
-        ``part_of_speech`` is ignored. If the value of ``part_of_speech`` does
-        not correspond to an existent part of speech according to the set locale,
-        then an exception is raised.
-
-        .. warning::
-           Depending on the length of a locale provider's built-in word list or
-           on the length of ``ext_word_list`` if provided, a large ``nb`` can
-           exhaust said lists if ``unique`` is ``True``, raising an exception.
-
         :sample:
         :sample: nb=5
         :sample: nb=5, ext_word_list=['abc', 'def', 'ghi', 'jkl']
@@ -2247,8 +2260,8 @@ class Faker:
         left_digits: Optional[int] = ...,
         right_digits: Optional[int] = ...,
         positive: bool = ...,
-        min_value: Optional[float] = ...,
-        max_value: Optional[float] = ...,
+        min_value: Union[float, int, None] = ...,
+        max_value: Union[float, int, None] = ...,
     ) -> Decimal: ...
     def pydict(
         self,
diff --git a/faker/typing.py b/faker/typing.py
index 65c41982..7f5c2eeb 100644
--- a/faker/typing.py
+++ b/faker/typing.py
@@ -33,6 +33,7 @@ class CreditCard:
         self.security_code_length = security_code_length
 
 
+BasicNumber = Union[float, int]
 CardType = TypeVar("CardType", "CreditCard", str)
 DateParseType = Union[date, datetime, timedelta, str, int]
 HueType = Union[str, float, int, Sequence[int]]


Test patch:
diff --git a/tests/providers/test_lorem.py b/tests/providers/test_lorem.py
index cb77ad3f..e3e0e0ff 100644
--- a/tests/providers/test_lorem.py
+++ b/tests/providers/test_lorem.py
@@ -165,29 +165,26 @@ class TestLoremProvider:
                 words = re.sub(r"[.\n]+", " ", text.lower()).split()
                 assert all(word in self.custom_word_list for word in words)
 
-    @pytest.mark.parametrize(
-        "nb,part_of_speech", [(10, "verb"), (18, "adverb"), (11, "noun")], ids=["verb", "adverb", "noun"]
-    )
-    def test_words_part_of_speech(self, faker, nb, part_of_speech):
-        words = faker.words(nb=nb, part_of_speech=part_of_speech)
-        assert (word in EnUsLoremProvider.parts_of_speech[part_of_speech] for word in words)
+    def test_get_default_words_list(self, faker):
+        words_list = faker.get_words_list()
+        assert all(word in EnUsLoremProvider.word_list for word in words_list)
+
+    @pytest.mark.parametrize("part_of_speech", [("verb"), ("adverb"), ("noun")], ids=["verb", "adverb", "noun"])
+    def test_get_words_list_part_of_speech(self, faker, part_of_speech):
+        words_list = faker.get_words_list(part_of_speech=part_of_speech)
+        assert (word in EnUsLoremProvider.parts_of_speech[part_of_speech] for word in words_list)
+
+    def test_get_words_list_invalid_part_of_speech(self, faker):
+        part_of_speech = "invalid part of speech"
 
-    @pytest.mark.parametrize("nb,part_of_speech", [(5, "abcdefg")], ids=["invalid part of speech"])
-    def test_words_invalid_part_of_speech(self, faker, nb, part_of_speech):
         with pytest.raises(ValueError) as exc_info:
-            faker.words(nb=nb, part_of_speech=part_of_speech)
+            faker.get_words_list(part_of_speech=part_of_speech)
 
         assert exc_info.type is ValueError
         assert exc_info.value.args[0] == f"{part_of_speech} is not recognized as a part of speech."
 
-    @pytest.mark.parametrize(
-        "nb,part_of_speech",
-        [(3, "adverb"), (5, "verb"), (4, "abcdefgh")],
-        ids=["ignore adverb", "ignore verb", "ignore invalid part of speech"],
-    )
-    def test_words_part_of_speech_ignored(self, faker, nb, part_of_speech):
-        words = faker.words(nb=nb, part_of_speech=part_of_speech, ext_word_list=self.custom_word_list)
-        assert len(words) == nb
+    def test_get_words_list_part_of_speech_ignored(self, faker):
+        words = faker.get_words_list(part_of_speech="ignored part of speech", ext_word_list=self.custom_word_list)
         assert all(word in self.custom_word_list for word in words)
 
 
diff --git a/tests/providers/test_person.py b/tests/providers/test_person.py
index 19ca4a6f..ee5ac157 100644
--- a/tests/providers/test_person.py
+++ b/tests/providers/test_person.py
@@ -36,6 +36,7 @@ from faker.providers.person.ta_IN import Provider as TaINProvider
 from faker.providers.person.th_TH import Provider as ThThProvider
 from faker.providers.person.uk_UA import Provider as UkUAProvider
 from faker.providers.person.uk_UA import translit as UkUATranslit
+from faker.providers.person.yo_NG import Provider as YoNGProvider
 from faker.providers.person.zh_CN import Provider as ZhCNProvider
 from faker.providers.person.zh_TW import Provider as ZhTWProvider
 from faker.providers.person.zu_ZA import Provider as ZuZAProvider
@@ -1397,6 +1398,85 @@ class TestGaIE(TestEnIE):
         Faker.seed(0)
 
 
+class TestYoNG(unittest.TestCase):
+    def setUp(self):
+        self.fake = Faker("yo_NG")
+        Faker.seed(0)
+
+    def test_last_name(self):
+        """
+        Test the generation of Zulu last names.
+        """
+        # There's no gender-specific last name in Zulu.
+        self.assertTrue(hasattr(YoNGProvider, "last_names_male"))
+        self.assertTrue(hasattr(YoNGProvider, "last_names_female"))
+
+        # All last names apply to all genders.
+        self.assertTrue(hasattr(YoNGProvider, "last_names"))
+
+        # General last name.
+        name = self.fake.last_name()
+        self.assertIsInstance(name, str)
+        self.assertIn(name, YoNGProvider.last_names)
+
+        # Females last name.
+        name = self.fake.last_name_female()
+        self.assertIsInstance(name, str)
+        self.assertIn(name, YoNGProvider.last_names)
+
+        # Male last name.
+        name = self.fake.last_name_male()
+        self.assertIsInstance(name, str)
+        self.assertIn(name, YoNGProvider.last_names)
+
+    def test_first_name(self):
+        """
+        Test the generation of Zulu first names.
+        """
+        # General first name.
+        name = self.fake.first_name()
+        self.assertIsInstance(name, str)
+        self.assertIn(name, YoNGProvider.first_names)
+
+        # Female first name.
+        name = self.fake.first_name_female()
+        self.assertIsInstance(name, str)
+        self.assertIn(name, YoNGProvider.first_names)
+        self.assertIn(name, YoNGProvider.first_names_female)
+
+        # Male first name.
+        name = self.fake.first_name_male()
+        self.assertIsInstance(name, str)
+        self.assertIn(name, YoNGProvider.first_names)
+        self.assertIn(name, YoNGProvider.first_names_male)
+
+    def test_full_name(self):
+        """
+        Test the generation of full Zulu names.
+        """
+        # Full name.
+        name = self.fake.name()
+        self.assertIsInstance(name, str)
+
+        full_name_parts = name.split()
+
+        if len(full_name_parts) == 2:
+            first_name = full_name_parts[0]
+            last_name = full_name_parts[1]
+            self.assertIn(first_name, YoNGProvider.first_names)
+            self.assertIn(last_name, YoNGProvider.last_names)
+        elif len(full_name_parts) == 3:
+            prefix = full_name_parts[0]
+            first_name = full_name_parts[1]
+            last_name = full_name_parts[2]
+
+            self.assertIn(prefix, YoNGProvider.prefixes_female + YoNGProvider.prefixes_male)
+            self.assertIn(first_name, YoNGProvider.first_names)
+            self.assertIn(last_name, YoNGProvider.last_names)
+        else:
+            raise AssertionError("Invalid number of name parts. Expected 2 or 3.")
+
+
 class TestZuZa(unittest.TestCase):
     def setUp(self):
         self.fake = Faker("zu_ZA")
diff --git a/tests/providers/test_python.py b/tests/providers/test_python.py
index 4935528e..4b89ebc3 100644
--- a/tests/providers/test_python.py
+++ b/tests/providers/test_python.py
@@ -3,7 +3,7 @@ import sys
 import unittest
 import warnings
 
-from typing import Iterable, Optional, Union
+from typing import Iterable, Optional, Type, Union
 from unittest.mock import patch
 
 import pytest
@@ -13,8 +13,20 @@ from faker import Faker
 
 @pytest.mark.parametrize("object_type", (None, bool, str, float, int, tuple, set, list, Iterable, dict))
 def test_pyobject(
-    object_type: Optional[Union[bool, str, float, int, tuple, set, list, Iterable, dict]],
-):
+    object_type: Optional[
+        Union[
+            Type[bool],
+            Type[str],
+            Type[float],
+            Type[int],
+            Type[tuple],
+            Type[set],
+            Type[list],
+            Type[Iterable],
+            Type[dict],
+        ]
+    ],
+) -> None:
     random_object = Faker().pyobject(object_type=object_type)
     if object_type is None:
         assert random_object is None
@@ -299,6 +311,34 @@ class TestPyfloat(unittest.TestCase):
         self.fake.pyfloat(min_value=2.3, max_value=2.5)
 
 
+class TestPyDict(unittest.TestCase):
+    def setUp(self):
+        self.fake = Faker()
+        Faker.seed(0)
+
+    def test_pydict_with_default_nb_elements(self):
+        result = self.fake.pydict()
+
+        self.assertEqual(len(result), 10)
+
+    def test_pydict_with_valid_number_of_nb_elements(self):
+        result = self.fake.pydict(nb_elements=5)
+
+        self.assertEqual(len(result), 5)
+
+    def test_pydict_with_invalid_number_of_nb_elements(self):
+        nb_elements = 10000
+
+        words_list_count = len(self.fake.get_words_list())
+        warning_msg = (
+            f"Number of nb_elements is greater than the number of words in the list."
+            f" {words_list_count} words will be used."
+        )
+        with pytest.warns(RuntimeWarning, match=warning_msg):
+            result = self.fake.pydict(nb_elements=nb_elements)
+            self.assertEqual(len(result), words_list_count)
+
+
 class TestPydecimal(unittest.TestCase):
     def setUp(self):
         self.fake = Faker()
diff --git a/tests/pytest/session_overrides/session_locale/__init__.py b/tests/pytest/session_overrides/session_locale/__init__.py
index 8cf482cd..fe75ca01 100644
--- a/tests/pytest/session_overrides/session_locale/__init__.py
+++ b/tests/pytest/session_overrides/session_locale/__init__.py
@@ -1,1 +1,1 @@
-_MODULE_LOCALES = ["en_GB"]
+_MODULE_LOCALES = ["en_US"]