Repository: joke2k/faker (language: python)

Issue:
Update address provider for `en_IN`
* Faker version: 26.0.0
* OS: all

The address provider only have basic info on states. PIN Codes and union territories aren't added in the provider



Discussion / hints:

Fix patch:
diff --git a/faker/providers/address/en_IN/__init__.py b/faker/providers/address/en_IN/__init__.py
index 75e6d2960c..4355239994 100644
--- a/faker/providers/address/en_IN/__init__.py
+++ b/faker/providers/address/en_IN/__init__.py
@@ -1,4 +1,6 @@
-from .. import Provider as AddressProvider
+import random
+from typing import Optional
+from faker.providers.address import Provider as AddressProvider
 
 
 class Provider(AddressProvider):
@@ -391,6 +393,112 @@ class Provider(AddressProvider):
         "West Bengal",
     )
 
+    states_abbr = (
+        "AP",
+        "AR",
+        "AS",
+        "BR",
+        "CG",
+        "GA",
+        "GJ",
+        "HR",
+        "HP",
+        "JH",
+        "KA",
+        "KL",
+        "MP",
+        "MH",
+        "MN",
+        "ML",
+        "MZ",
+        "NL",
+        "OD",
+        "PB",
+        "RJ",
+        "SK",
+        "TN",
+        "TG",
+        "TR",
+        "UK",
+        "UP",
+        "WB",
+    )
+
+    union_territories = (
+        ("Andaman and Nicobar Islands",),
+        ("Chandigarh",),
+        ("Dadra and Nagar Haveli, Dadra & Nagar Haveli",),
+        ("Daman and Diu",),
+        ("Delhi, National Capital Territory of Delhi",),
+        ("Jammu and Kashmir",),
+        ("Ladakh",),
+        ("Lakshadweep",),
+        ("Pondicherry",),
+        ("Puducherry",),
+    )
+
+    union_territories_abbr = (
+        "AN",
+        "CH",
+        "DN",
+        "DD",
+        "DL",
+        "JK",
+        "LA",
+        "LD",
+        "PY",
+    )
+
+    # https://en.wikipedia.org/wiki/Postal_Index_Number
+
+    # FIXME: Some states such as `BR/JH` / `UK/UP` have similar PIN code ranges
+    # FIXME: as mentioned in above link.
+
+    state_pincode = {
+        "AP": [(510_000, 539_999)],
+        "AR": [(790_000, 792_999)],
+        "AS": [(780_000, 789_999)],
+        "BR": [(800_000, 859_999)],
+        "CG": [(490_000, 499_999)],
+        "GA": [(403_000, 403_999)],
+        "GJ": [(360_000, 399_999)],
+        "HR": [(120_000, 139_999)],
+        "HP": [(170_000, 179_999)],
+        "JH": [(800_000, 859_999)],
+        "KA": [(560_000, 599_999)],
+        "KL": [(670_000, 681_999), (683_000, 699_999)],
+        "MP": [(450_000, 489_999)],
+        "MH": [(400_000, 402_999), (404_000, 449_999)],
+        "MN": [(795_000, 795_999)],
+        "ML": [(793_000, 794_999)],
+        "MZ": [(796_000, 796_999)],
+        "NL": [(797_000, 798_999)],
+        "OD": [(750_000, 779_999)],
+        "PB": [(140_000, 159_999)],
+        "RJ": [(300_000, 349_999)],
+        "SK": [(737_000, 737_999)],
+        "TN": [(600_000, 669_999)],
+        "TG": [(500_000, 509_999)],
+        "TR": [(799_000, 799_999)],
+        "UK": [(200_000, 289_999)],
+        "UP": [(200_000, 289_999)],
+        "WB": [(700_000, 736_999), (738_000, 743_999), (745_000, 749_999)],
+    }
+
+    union_territories_pincode = {
+        "AN": [(744_000, 744_999)],
+        "CH": [(160_000, 169_999)],
+        "DN": [(396_000, 396_999)],
+        "DD": [(396_000, 396_999)],
+        "DL": [(110_000, 119_999)],
+        "JK": [(180_000, 199_999)],
+        "LA": [(180_000, 199_999)],
+        "LD": [(682_000, 682_999)],
+        "PY": [(605_000, 605_999)],
+    }
+
+    army_pincode = {"APS": (900_000, 999_999)}
+
     def city_name(self) -> str:
         return self.random_element(self.cities)
 
@@ -398,3 +506,71 @@ def administrative_unit(self) -> str:
         return self.random_element(self.states)
 
     state = administrative_unit
+
+    def union_territory(self) -> str:
+        """Returns random union territory name"""
+
+        return self.random_element(self.union_territories)[0]
+
+    def pincode_in_state(
+        self, state_abbr: Optional[str] = None, include_union_territories=False
+    ) -> int:
+        """Random PIN Code within provided state abbreviation
+
+        :param state_abbr: State Abbr, defaults to None
+        :param include_union_territories: Include Union Territories ?, defaults to False
+        :raises ValueError: If incorrect state abbr
+        :return: PIN Code
+        """
+
+        known_abbrs = self.states_abbr
+        if include_union_territories:
+            known_abbrs += self.union_territories_abbr
+
+        if state_abbr is None:
+            state_abbr = self.random_element(known_abbrs)
+
+        if state_abbr in known_abbrs:
+            codes = self.state_pincode
+            if include_union_territories:
+                codes = codes | self.union_territories_pincode
+
+            pincode_range = self.random_element(codes[state_abbr])
+
+            return self.generator.random.randint(*pincode_range)
+
+        raise ValueError("State Abbreviation not found in list")
+
+    def pincode_in_military(self) -> int:
+        """Random PIN Code within Army Postal Service range"""
+
+        key = self.random_element(self.army_pincode.keys())
+
+        return self.generator.random.randint(*self.army_pincode[key])
+
+    # Aliases
+
+    def zipcode_in_state(
+        self, state_abbr: Optional[str] = None, include_union_territories=False
+    ) -> int:
+        return self.pincode_in_state(state_abbr, include_union_territories)
+
+    def postcode_in_state(
+        self, state_abbr: Optional[str] = None, include_union_territories=False
+    ) -> int:
+        return self.pincode_in_state(state_abbr, include_union_territories)
+
+    def pincode_in_army(self) -> int:
+        return self.pincode_in_military()
+
+    def zipcode_in_military(self) -> int:
+        return self.pincode_in_military()
+
+    def zipcode_in_army(self) -> int:
+        return self.pincode_in_military()
+
+    def postcode_in_military(self) -> int:
+        return self.pincode_in_military()
+
+    def postcode_in_army(self) -> int:
+        return self.pincode_in_military()


Test patch:
diff --git a/tests/providers/test_address.py b/tests/providers/test_address.py
index 6f5b3e410b..1c229cdc26 100644
--- a/tests/providers/test_address.py
+++ b/tests/providers/test_address.py
@@ -1865,17 +1865,58 @@ class TestEnIn:
     """Test en_IN address provider methods"""
 
     def test_city_name(self, faker, num_samples):
+        """Tests `city names` are fetched correctly"""
+
         for _ in range(num_samples):
             city_name = faker.city_name()
             assert isinstance(city_name, str)
             assert city_name in EnInAddressProvider.cities
 
     def test_state(self, faker, num_samples):
+        """Tests `states` are fetched correctly"""
+
         for _ in range(num_samples):
             state = faker.state()
             assert isinstance(state, str)
             assert state in EnInAddressProvider.states
 
+    def test_union_territories(self, faker, num_samples):
+        """Tests `union_territories` are fetched correctly"""
+
+        for _ in range(num_samples):
+            union_territory = faker.union_territory()
+            assert isinstance(union_territory, str)
+            assert (union_territory,) in EnInAddressProvider.union_territories
+
+    @pytest.mark.parametrize("pincodes", ["pincode_in_state", "zipcode_in_state", "postcode_in_state"])
+    def test_pincodes_in_state(self, faker, num_samples, pincodes):
+        """Test `pincodes` for state and union territories"""
+
+        for _ in range(num_samples):
+            include_ut = faker.random_element([True, False])
+            pincode = getattr(faker, pincodes)(include_union_territories=include_ut)
+            assert isinstance(pincode, int)
+            assert len(str(pincode)) == 6
+
+    @pytest.mark.parametrize(
+        "pincodes",
+        [
+            ("pincode_in_army"),
+            ("zipcode_in_army"),
+            ("postcode_in_army"),
+            ("postcode_in_military"),
+            ("zipcode_in_military"),
+            ("pincode_in_military"),
+        ],
+    )
+    def test_pincodes_in_military(self, faker, num_samples, pincodes):
+        """Test `pincodes` for Army"""
+
+        for _ in range(num_samples):
+            pincode = getattr(faker, pincodes)()
+            assert isinstance(pincode, int)
+            assert len(str(pincode)) == 6
+
 
 class TestSkSk:
     """Test sk_SK address provider methods"""