Repository: joke2k/faker (python)

Issue:
Add provider `gj-IN` for Gujarati language
* Faker version:  <= 29.0.0
* OS: all

There is no provider for Gujarati language in faker


### Expected behavior

There should be a new provider

### Actual behavior

No Provider for `gj-IN`


Pull request description:
Added person provider for gu_IN locale with tests
### What does this change

Added male and female names with prefixes for gu_IN
Added tests for person provider

### What was wrong

There was no person provider for gu_IN locale

Fixes #2106

### Checklist

- [x] I have read the documentation about [CONTRIBUTING](https://github.com/joke2k/faker/blob/master/CONTRIBUTING.rst)
- [x] I have run `make lint`


Patch:
diff --git a/.bumpversion.cfg b/.bumpversion.cfg
index 88bdfeab..f5b2aad9 100644
--- a/.bumpversion.cfg
+++ b/.bumpversion.cfg
@@ -1,5 +1,5 @@
 [bumpversion]
-current_version = 30.4.0
+current_version = 30.6.0
 files = VERSION faker/__init__.py docs/conf.py
 commit = True
 tag = True
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 967678dc..c8b39f65 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,13 @@
 ## Changelog
 
+### [v30.6.0 - 2024-10-16](https://github.com/joke2k/faker/compare/v30.5.0...v30.6.0)
+
+* Add passport provider for `ru_RU` locale. Thanks @Abdujabbar.
+
+### [v30.5.0 - 2024-10-16](https://github.com/joke2k/faker/compare/v30.4.0...v30.5.0)
+
+* Add Providers for `ka_GE` locale. Thanks @onesch.
+
 ### [v30.4.0 - 2024-10-15](https://github.com/joke2k/faker/compare/v30.3.0...v30.4.0)
 
 * Add separate male/female names along with prefix/suffix for `hi_IN` locale. Thanks @wh0th3h3llam1.
diff --git a/VERSION b/VERSION
index 3b5f04e5..29dac4b5 100644
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-30.4.0
+30.6.0
diff --git a/docs/conf.py b/docs/conf.py
index 3de6be37..f13b9f93 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 = "30.4.0"
+version = "30.6.0"
 # The full version, including alpha/beta/rc tags.
-release = "30.4.0"
+release = "30.6.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 024f39ea..703b6b44 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 = "30.4.0"
+VERSION = "30.6.0"
 
 __all__ = ("Factory", "Generator", "Faker")
diff --git a/faker/providers/passport/__init__.py b/faker/providers/passport/__init__.py
index 0641ed71..31f14ed3 100644
--- a/faker/providers/passport/__init__.py
+++ b/faker/providers/passport/__init__.py
@@ -4,6 +4,8 @@ import re
 from string import ascii_uppercase
 from typing import Tuple
 
+from faker.typing import SexLiteral
+
 from .. import BaseProvider, ElementsType
 
 localized = True
@@ -19,7 +21,7 @@ class Provider(BaseProvider):
         birthday = self.generator.date_of_birth()
         return birthday
 
-    def passport_owner(self, gender: str = "X") -> Tuple[str, str]:
+    def passport_owner(self, gender: SexLiteral = "X") -> Tuple[str, str]:
         """Generate a given_name and surname for a passport owner
         The ``gender`` argument is the gender marker of a passport owner, which is a one character string
         that is either male, female, or non-binary.
diff --git a/faker/providers/passport/en_US/__init__.py b/faker/providers/passport/en_US/__init__.py
index b0afb53c..419eac8a 100644
--- a/faker/providers/passport/en_US/__init__.py
+++ b/faker/providers/passport/en_US/__init__.py
@@ -3,6 +3,8 @@ import random
 from datetime import date, timedelta
 from typing import Tuple
 
+from faker.typing import SexLiteral
+
 from .. import Provider as PassportProvider
 
 
@@ -64,7 +66,7 @@ class Provider(PassportProvider):
         expiry_date_format = expiry_date.strftime("%d ") + expiry_date.strftime("%b ") + expiry_date.strftime("%Y")
         return birth_date, issue_date_format, expiry_date_format
 
-    def passport_gender(self, seed: int = 0) -> str:
+    def passport_gender(self, seed: int = 0) -> SexLiteral:
         """Generates a string representing the gender displayed on a passport
 
         Sources:
@@ -75,7 +77,7 @@ class Provider(PassportProvider):
             random.seed(seed)
 
         genders = ["M", "F", "X"]
-        gender = random.choices(genders, weights=[0.493, 0.493, 0.014], k=1)[0]
+        gender: SexLiteral = random.choices(genders, weights=[0.493, 0.493, 0.014], k=1)[0]  # type: ignore
         return gender
 
     def passport_full(self) -> str:
diff --git a/faker/providers/passport/ru_RU/__init__.py b/faker/providers/passport/ru_RU/__init__.py
new file mode 100644
index 00000000..e29ecad8
--- /dev/null
+++ b/faker/providers/passport/ru_RU/__init__.py
@@ -0,0 +1,11 @@
+from typing import Tuple
+
+from faker.typing import SexLiteral
+
+from .. import Provider as PassportProvider
+
+
+class Provider(PassportProvider):
+    def passport_owner(self, gender: SexLiteral = "M") -> Tuple[str, str]:
+        given_name, surname = super().passport_owner(gender)
+        return surname, given_name
diff --git a/faker/providers/person/gu_IN/__init__.py b/faker/providers/person/gu_IN/__init__.py
new file mode 100644
index 00000000..fffd4aa0
--- /dev/null
+++ b/faker/providers/person/gu_IN/__init__.py
@@ -0,0 +1,157 @@
+from .. import Provider as PersonProvider
+
+
+class Provider(PersonProvider):
+
+    formats_male = (
+        "{{first_name_male}} {{last_name}}",
+        "{{prefix_male}} {{first_name_male}} {{last_name}}",
+    )
+
+    formats_female = (
+        "{{first_name_female}} {{last_name}}",
+        "{{prefix_female}} {{first_name_female}} {{last_name}}",
+    )
+
+    formats = (
+        "{{first_name}} {{last_name}}",
+        "{{prefix}} {{first_name}} {{last_name}}",
+    )
+
+    # names taken from https://www.behindthename.com/names/gender/feminine/usage/gujarati
+    first_names_female = (
+        "અંકિતા",
+        "અવની",
+        "હીરા",
+        "કાજલ",
+        "કિરણ",
+        "નેહા",
+        "નિશા",
+        "પૂજા",
+        "પ્રાચી",
+        "પ્રીતિ",
+        "પ્રીતિ",
+        "પૂજા",
+        "રચના",
+        "રાધીકા",
+        "શ્રેયા",
+        "શ્વેતા",
+        "સોનલ",
+        "તન્વી",
+        "તેજલ",
+        "ઉર્વી",
+        "વર્ષા",
+    )
+
+    # names taken from https://www.behindthename.com/names/gender/masculine/usage/gujarati
+    first_names_male = (
+        "અભિષેક",
+        "અજય",
+        "અક્ષય",
+        "આનંદ",
+        "અનિલ",
+        "અંકિત",
+        "અર્જુન",
+        "અરુણ",
+        "આશિષ",
+        "અશોક",
+        "ભારત",
+        "બ્રિજેશ",
+        "ચેતન",
+        "ચિરાગ",
+        "દર્શન",
+        "દીપા",
+        "દીપક",
+        "ધવલ",
+        "દિલીપ",
+        "દિનેશ",
+        "દીપા",
+        "દીપક",
+        "હરીશ",
+        "હર્ષ",
+        "હર્ષલ",
+        "હીરા",
+        "જગદીશ",
+        "જય",
+        "જયેશ",
+        "જિતેન્દ્ર",
+        "કાજલ",
+        "કમલ",
+        "કરણ",
+        "કિરણ",
+        "કિશન",
+        "કૃષ્ણ",
+        "કુમાર",
+        "કુનાલ",
+        "મહેન્દ્ર",
+        "મહેશ",
+        "મનોજ",
+        "મયૂર",
+        "મિતુલ",
+        "મુકેશ",
+        "નરેન્દ્ર",
+        "નીરજ",
+        "નિખિલ",
+        "નીરજ",
+        "નીરવ",
+        "નિશાંત",
+        "નિતિન",
+        "પંકજ",
+        "પાર્થ",
+        "પ્રકાશ",
+        "પ્રણવ",
+        "પ્રતિક",
+        "પ્રતિક",
+        "પ્રવીણ",
+        "પ્રવીણ",
+        "રાહુલ",
+        "રાજ",
+        "રાજેન્દ્ર",
+        "રાજેશ",
+        "રાકેશ",
+        "રમેશ",
+        "રવિ",
+        "રોહિત",
+        "સચિન",
+        "સમીર",
+        "સમીર",
+        "સંદિપ",
+        "સંદિપ",
+        "સંજય",
+        "સંજીવ",
+        "સંજીવ",
+        "શેખર",
+        "સિદ્ધાર્થ",
+        "સુભાષ",
+        "સુનીલ",
+        "સૂરજ",
+        "તુષાર",
+        "વસંત",
+        "વિક્રમ",
+        "વિપુલ",
+        "વિરાજ",
+        "વિશાલ",
+        "વિવેક",
+        "યશ",
+    )
+
+    first_names = first_names_female + first_names_male
+
+    # last names taken from https://surnames.behindthename.com/names/usage/gujarati
+    last_names = (
+        "ચૌધરી",
+        "ચૌધરી",
+        "ગઢવી",
+        "ગુપ્તા",
+        "જૈન",
+        "જોષી",
+        "કુમાર",
+        "પટેલ",
+        "શર્મા",
+    )
+
+    prefixes_female = ("શ્રીમતી", "કુમારી")
+
+    prefixes_male = ("શ્રી", "શ્રીમન")
+
+    prefixes = prefixes_female + prefixes_male
diff --git a/faker/proxy.pyi b/faker/proxy.pyi
index 0e5190d4..76362d6c 100644
--- a/faker/proxy.pyi
+++ b/faker/proxy.pyi
@@ -2368,7 +2368,7 @@ class Faker:
         """
         ...
 
-    def passport_gender(self, seed: int = ...) -> str:
+    def passport_gender(self, seed: int = ...) -> Literal["M", "F", "X"]:
         """
         Generates a string representing the gender displayed on a passport
 
@@ -2384,7 +2384,7 @@ class Faker:
         """
         ...
 
-    def passport_owner(self, gender: str = ...) -> Tuple[str, str]:
+    def passport_owner(self, gender: Literal["M", "F", "X"] = ...) -> Tuple[str, str]:
         """
         Generate a given_name and surname for a passport owner
         The ``gender`` argument is the gender marker of a passport owner, which is a one character string
@@ -2433,7 +2433,7 @@ class Faker:
 
     def phone_number(self) -> str: ...
     def profile(
-        self, fields: Optional[List[str]] = ..., sex: Optional[Literal["M", "F"]] = ...
+        self, fields: Optional[List[str]] = ..., sex: Optional[Literal["M", "F", "X"]] = ...
     ) -> Dict[str, Union[str, Tuple[Decimal, Decimal], List[str], datetime.date]]:
         """
         Generates a complete profile.
@@ -2442,8 +2442,8 @@ class Faker:
         ...
 
     def simple_profile(
-        self, sex: Optional[Literal["M", "F"]] = ...
-    ) -> Dict[str, Union[str, datetime.date, Literal["M", "F"]]]:
+        self, sex: Optional[Literal["M", "F", "X"]] = ...
+    ) -> Dict[str, Union[str, datetime.date, Literal["M", "F", "X"]]]:
         """
         Generates a basic profile with personal informations
         """
@@ -4182,7 +4182,7 @@ class Faker:
         """
         ...
 
-    def full_name(self, gender: Optional[Literal["M", "F"]] = ..., short: Optional[bool] = ...) -> str:
+    def full_name(self, gender: Optional[Literal["M", "F", "X"]] = ..., short: Optional[bool] = ...) -> str:
         """
         Generate Full Name
             - gender = 'M' or 'F' optional params
diff --git a/faker/typing.py b/faker/typing.py
index 7f5c2eeb..7d020943 100644
--- a/faker/typing.py
+++ b/faker/typing.py
@@ -37,7 +37,7 @@ BasicNumber = Union[float, int]
 CardType = TypeVar("CardType", "CreditCard", str)
 DateParseType = Union[date, datetime, timedelta, str, int]
 HueType = Union[str, float, int, Sequence[int]]
-SexLiteral = Literal["M", "F"]
+SexLiteral = Literal["M", "F", "X"]
 SeedType = Union[int, float, str, bytes, bytearray, None]
 
 


Test patch:
diff --git a/tests/providers/test_person.py b/tests/providers/test_person.py
index a8b1b4d0..bd5afc3c 100644
--- a/tests/providers/test_person.py
+++ b/tests/providers/test_person.py
@@ -20,6 +20,7 @@ from faker.providers.person.et_EE import Provider as EtEEProvider
 from faker.providers.person.fi_FI import Provider as FiProvider
 from faker.providers.person.fr_BE import Provider as FrBEProvider
 from faker.providers.person.ga_IE import Provider as GaIEProvider
+from faker.providers.person.gu_IN import Provider as GuINProvider
 from faker.providers.person.he_IL import Provider as HeILProvider
 from faker.providers.person.hi_IN import Provider as HiINProvider
 from faker.providers.person.hy_AM import Provider as HyAmProvider
@@ -332,7 +333,6 @@ class TestEnIN(unittest.TestCase):
 
 
 class TestEnPk(unittest.TestCase):
-
     def setUp(self):
         """Set up the Faker instance with the Pakistani locale."""
         self.fake = Faker("en_PK")
@@ -674,6 +674,68 @@ class TestGaIE(TestEnIE):
         Faker.seed(0)
 
 
+class TestGuIN(unittest.TestCase):
+    """Tests person in the gu_IN locale"""
+
+    def setUp(self):
+        self.fake = Faker("gu_IN")
+        Faker.seed(0)
+
+    """Verify that gender specific names are set correctly"""
+
+    def test_first_name(self):
+        name = self.fake.first_name()
+        self.assertIsInstance(name, str)
+        assert name in GuINProvider.first_names
+
+    def test_first_name_male(self):
+        name = self.fake.first_name_male()
+        self.assertIsInstance(name, str)
+        assert name in GuINProvider.first_names_male
+
+    def test_first_name_female(self):
+        name = self.fake.first_name_female()
+        self.assertIsInstance(name, str)
+        assert name in GuINProvider.first_names_female
+
+    def test_last_name(self):
+        last_name = self.fake.last_name()
+        self.assertIsInstance(last_name, str)
+        assert last_name in GuINProvider.last_names
+
+    def test_name(self):
+        name = self.fake.name().split()
+        assert all(isinstance(n, str) for n in name)
+        if len(name) == 3:
+            assert all(
+                [
+                    name[0] in GuINProvider.prefixes,
+                    name[1] in GuINProvider.first_names,
+                    name[2] in GuINProvider.last_names,
+                ]
+            )
+        else:
+            assert name[0] in GuINProvider.first_names
+            assert name[-1] in GuINProvider.last_names
+
+    """Verify that gender specific prefixes are set correctly"""
+
+    def test_prefix(self):
+        prefix = self.fake.prefix()
+        self.assertIsInstance(prefix, str)
+        assert prefix in GuINProvider.prefixes
+
+    def test_prefix_female(self):
+        prefix = self.fake.prefix_female()
+        self.assertIsInstance(prefix, str)
+        assert prefix in GuINProvider.prefixes_female
+
+    def test_prefix_male(self):
+        prefix = self.fake.prefix_male()
+        self.assertIsInstance(prefix, str)
+        assert prefix in GuINProvider.prefixes_male
+
+
 class TestHeIL(unittest.TestCase):
     """Tests person in the he_IL locale."""
 
@@ -1215,6 +1277,11 @@ class TestRuRU(unittest.TestCase):
         language_name = self.fake.language_name()
         assert language_name in RuProvider.language_names
 
+    def test_passport_owner(self):
+        surname, given_name = self.fake.passport_owner()
+        assert surname in RuProvider.last_names
+        assert given_name in RuProvider.first_names
+
 
 class TestSvSE(unittest.TestCase):
     def setUp(self):