Repository: joke2k/faker
Pull request #1902 (2023-09-01 06:38:17)

feat(en_bd address): BD addresses added in English
### What does this change

🇧🇩 BD addresses added in English.

aamibhoot: @fcurella lemme know 🙋🏻‍♂️

Patch:
diff --git a/.bumpversion.cfg b/.bumpversion.cfg
index 74b62a6e..d6778af2 100644
--- a/.bumpversion.cfg
+++ b/.bumpversion.cfg
@@ -1,5 +1,5 @@
 [bumpversion]
-current_version = 19.4.0
+current_version = 19.5.0
 files = VERSION faker/__init__.py docs/conf.py
 commit = True
 tag = True
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fd3c3454..6c0cc427 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,13 @@
 ## Changelog
 
+### [v19.5.0 - 2023-09-08](https://github.com/joke2k/faker/compare/v19.4.0...v19.5.0)
+
+* Add `color_rgb`, `color_rgb_float`, `color_hsv` and `color_hsl`. Thanks @fdemmer.
+
+### [v19.4.0 - 2023-09-07](https://github.com/joke2k/faker/compare/v19.3.1...v19.4.0)
+
+* Add `schemes` and `deep` argument to `uri` method.
+
 ### [v19.3.1 - 2023-08-23](https://github.com/joke2k/faker/compare/v19.3.0...v19.3.1)
 
 * Remove spurious space in `uk_UA` `first_name`. Thanks @Romissevd.
@@ -290,10 +298,6 @@
   `currency`, `date_time`, `geo`, `internet`, `job`, `person`, `phone_number` and `ssn`.
   Thanks @saanpritom.
 
-### [v14.1.0 - 2022-09-07](https://github.com/joke2k/faker/compare/v14.0.0...v14.1.0)
-
-* Add `schemes` and `deep` argument to `uri` method.
-
 ### [v14.0.0 - 2022-08-12](https://github.com/joke2k/faker/compare/v13.16.0...v14.0.0)
 
 * Fix `Provider._parse_date_time` to support timestamps. Thanks @mochi-moshi. 
diff --git a/VERSION b/VERSION
index 05c8bd68..912dbfae 100644
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-19.4.0
+19.5.0
diff --git a/docs/conf.py b/docs/conf.py
index c7c8e176..75a865cc 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 = "19.4.0"
+version = "19.5.0"
 # The full version, including alpha/beta/rc tags.
-release = "19.4.0"
+release = "19.5.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 0ada54f1..2ee4cf4c 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 = "19.4.0"
+VERSION = "19.5.0"
 
 __all__ = ("Factory", "Generator", "Faker")
diff --git a/faker/providers/__init__.py b/faker/providers/__init__.py
index ff1b0a1b..add840a7 100644
--- a/faker/providers/__init__.py
+++ b/faker/providers/__init__.py
@@ -97,6 +97,7 @@ class BaseProvider:
         "en": (
             "AG",
             "AU",
+            "BD",
             "BW",
             "CA",
             "DK",
diff --git a/faker/providers/address/en_BD/__init__.py b/faker/providers/address/en_BD/__init__.py
new file mode 100644
index 00000000..be5c1618
--- /dev/null
+++ b/faker/providers/address/en_BD/__init__.py
@@ -0,0 +1,547 @@
+'''
+    Contributed by: @aamibhoot 🇧🇩
+'''
+
+from .. import Provider as AddressProvider
+
+
+class Provider(AddressProvider):
+    area_names = (
+        "Ali",
+        "Alam",
+        "Abhay",
+        "Anwar",
+        "Brahmin",
+        "Botia",
+        "Baghar",
+        "Begum",
+        "Bijoy",
+        "Bandar",
+        "Balia",
+        "Bajit",
+        "Baker",
+        "Borhan",
+        "Bakhsh",
+        "Badr",
+        "Biram",
+        "Biswnath",
+        "Chouddah",
+        "Chital",
+        "Daud",
+        "Daulat",
+        "Dev",
+        "Devi",
+        "Islam",
+        "Ful",
+        "Fakir",
+        "Fatik",
+        "Gopal",
+        "Gour",
+        "Haji",
+        "Hariram",
+        "Hossain",
+        "Hakim",
+        "Jibon",
+        "Jagannath",
+        "Kumar",
+        "Kali",
+        "Keshav",
+        "Qutub",
+        "Kabi",
+        "Kalia",
+        "Karim",
+        "Kazi",
+        "Kamal",
+        "Lal",
+        "Murad",
+        "Manohar",
+        "Mir",
+        "Mahes",
+        "Moral",
+        "Molla",
+        "Mohammad",
+        "Maniram",
+        "Manik",
+        "Mirza",
+        "Mud",
+        "Mohan",
+        "Mahadev",
+        "Madhab",
+        "Nasir",
+        "Naria",
+        "Nazir",
+        "Nalitha",
+        "Nandi",
+        "Osmani",
+        "Pai",
+        "Palash",
+        "Parvati",
+        "Ram",
+        "Ray",
+        "Rani",
+        "Sona",
+        "Sharan",
+        "Shyam",
+        "Subarna",
+        "Siraj",
+        "Sakhi",
+        "Sadar",
+        "Sundar",
+        "Syed",
+        "Shahjahan",
+        "Shanti",
+        "Shib",
+        "Ter",
+        "Tara",
+        "Uzir",
+    )
+
+    building_names = (
+        "House No.",
+        "Building No.",
+        "House No.",
+        "Holding No.",
+    )
+
+    building_number_formats = ("%", "%#", "%##")
+
+    city_prefixes = ("North", "East", "West", "South", "Middle", "New", "Old")
+
+    city_suffixes = (
+        "Bazar",
+        "Bari",
+        "Char",
+        "Diya",
+        "Danga",
+        "Ganz",
+        "Gram",
+        "Gan",
+        "Gan",
+        "Garh",
+        "Hat",
+        "Har",
+        "Khali",
+        "Mati",
+        "Nagar",
+        "Pur",
+        "Tala",
+    )
+    
+    cities = (
+        "Barguna",
+        "Barisal",
+        "Bhola",
+        "Bandarban",
+        "Brahmanbaria",
+        "Bagherhat",
+        "Bogura",
+        "Chandpur",
+        "Chittagong",
+        "Cumilla",
+        "Cox's Bazar",
+        "Chuadanga",
+        "Dhaka",
+        "Dinajpur",
+        "Faripur",
+        "Feni",
+        "Gazipur",
+        "Gopalganj",
+        "Gaibandha",
+        "Habiganj",
+        "Jhalokati",
+        "Jessore",
+        "Jhenaidah",
+        "Jamalpur",
+        "Joypurhat",
+        "Khagrachhari",
+        "Kishoreganj",
+        "Khulna",
+        "Kushtia",
+        "Kurigram",
+        "Lakshmipur",
+        "Lalmonirhat",
+        "Madaripur",
+        "Manikganj",
+        "Munshiganj",
+        "Magura",
+        "Meherpur",
+        "Mymensingh",
+        "Maulvibazar",
+        "Noakhali",
+        "Narayanganj",
+        "Narsingdi",
+        "Narail",
+        "Netrokona",
+        "Naogaon",
+        "Naogaon",
+        "Chapainawabganj",
+        "Nilphamari",
+        "Patuakhali",
+        "Pirojpur",
+        "Pabna",
+        "Panchagarh",
+        "Rangpur",
+        "Shariatpur",
+        "Satkhira",
+        "Sherpur",
+        "Sirajganj",
+        "Sunamganj",
+        "Sylhet",
+        "Tangail",
+        "Thakurgaon",
+    )
+
+    countries = (
+        "Afghanistan",
+        "Albania",
+        "Algeria",
+        "American Samoa",
+        "Andorra",
+        "Angola",
+        "Anguilla",
+        "Antarctica (the territory South of 60 deg S)",
+        "Antigua and Barbuda",
+        "Argentina",
+        "Armenia",
+        "Aruba",
+        "Australia",
+        "Austria",
+        "Azerbaijan",
+        "Bahamas",
+        "Bahrain",
+        "Bangladesh",
+        "Barbados",
+        "Belarus",
+        "Belgium",
+        "Belize",
+        "Benin",
+        "Bermuda",
+        "Bhutan",
+        "Bolivia",
+        "Bosnia and Herzegovina",
+        "Botswana",
+        "Bouvet Island (Bouvetoya)",
+        "Brazil",
+        "British Indian Ocean Territory (Chagos Archipelago)",
+        "British Virgin Islands",
+        "Brunei Darussalam",
+        "Bulgaria",
+        "Burkina Faso",
+        "Burundi",
+        "Cambodia",
+        "Cameroon",
+        "Canada",
+        "Cape Verde",
+        "Cayman Islands",
+        "Central African Republic",
+        "Chad",
+        "Chile",
+        "China",
+        "Christmas Island",
+        "Cocos (Keeling) Islands",
+        "Colombia",
+        "Comoros",
+        "Congo",
+        "Congo",
+        "Cook Islands",
+        "Costa Rica",
+        "Cote d'Ivoire",
+        "Croatia",
+        "Cuba",
+        "Cyprus",
+        "Czech Republic",
+        "Denmark",
+        "Djibouti",
+        "Dominica",
+        "Dominican Republic",
+        "Ecuador",
+        "Egypt",
+        "El Salvador",
+        "Equatorial Guinea",
+        "Eritrea",
+        "Estonia",
+        "Ethiopia",
+        "Faroe Islands",
+        "Falkland Islands (Malvinas)",
+        "Fiji",
+        "Finland",
+        "France",
+        "French Guiana",
+        "French Polynesia",
+        "French Southern Territories",
+        "Gabon",
+        "Gambia",
+        "Georgia",
+        "Germany",
+        "Ghana",
+        "Gibraltar",
+        "Greece",
+        "Greenland",
+        "Grenada",
+        "Guadeloupe",
+        "Guam",
+        "Guatemala",
+        "Guernsey",
+        "Guinea",
+        "Guinea-Bissau",
+        "Guyana",
+        "Haiti",
+        "Heard Island and McDonald Islands",
+        "Holy See (Vatican City State)",
+        "Honduras",
+        "Hong Kong",
+        "Hungary",
+        "Iceland",
+        "India",
+        "Indonesia",
+        "Iran",
+        "Iraq",
+        "Ireland",
+        "Isle of Man",
+        "Israel",
+        "Italy",
+        "Jamaica",
+        "Japan",
+        "Jersey",
+        "Jordan",
+        "Kazakhstan",
+        "Kenya",
+        "Kiribati",
+        "Korea",
+        "Korea",
+        "Kuwait",
+        "Kyrgyz Republic",
+        "Lao People's Democratic Republic",
+        "Latvia",
+        "Lebanon",
+        "Lesotho",
+        "Liberia",
+        "Libyan Arab Jamahiriya",
+        "Liechtenstein",
+        "Lithuania",
+        "Luxembourg",
+        "Macao",
+        "Madagascar",
+        "Malawi",
+        "Malaysia",
+        "Maldives",
+        "Mali",
+        "Malta",
+        "Marshall Islands",
+        "Martinique",
+        "Mauritania",
+        "Mauritius",
+        "Mayotte",
+        "Mexico",
+        "Micronesia",
+        "Moldova",
+        "Monaco",
+        "Mongolia",
+        "Montenegro",
+        "Montserrat",
+        "Morocco",
+        "Mozambique",
+        "Myanmar",
+        "Namibia",
+        "Nauru",
+        "Nepal",
+        "Netherlands Antilles",
+        "Netherlands",
+        "New Caledonia",
+        "New Zealand",
+        "Nicaragua",
+        "Niger",
+        "Nigeria",
+        "Niue",
+        "Norfolk Island",
+        "North Macedonia",
+        "Northern Mariana Islands",
+        "Norway",
+        "Oman",
+        "Pakistan",
+        "Palau",
+        "Palestinian Territory",
+        "Panama",
+        "Papua New Guinea",
+        "Paraguay",
+        "Peru",
+        "Philippines",
+        "Pitcairn Islands",
+        "Poland",
+        "Portugal",
+        "Puerto Rico",
+        "Qatar",
+        "Reunion",
+        "Romania",
+        "Russian Federation",
+        "Rwanda",
+        "Saint Barthelemy",
+        "Saint Helena",
+        "Saint Kitts and Nevis",
+        "Saint Lucia",
+        "Saint Martin",
+        "Saint Pierre and Miquelon",
+        "Saint Vincent and the Grenadines",
+        "Samoa",
+        "San Marino",
+        "Sao Tome and Principe",
+        "Saudi Arabia",
+        "Senegal",
+        "Serbia",
+        "Seychelles",
+        "Sierra Leone",
+        "Singapore",
+        "Slovakia (Slovak Republic)",
+        "Slovenia",
+        "Solomon Islands",
+        "Somalia",
+        "South Africa",
+        "South Georgia and the South Sandwich Islands",
+        "Spain",
+        "Sri Lanka",
+        "Sudan",
+        "Suriname",
+        "Svalbard & Jan Mayen Islands",
+        "Swaziland",
+        "Sweden",
+        "Switzerland",
+        "Syrian Arab Republic",
+        "Taiwan",
+        "Tajikistan",
+        "Tanzania",
+        "Thailand",
+        "Timor-Leste",
+        "Togo",
+        "Tokelau",
+        "Tonga",
+        "Trinidad and Tobago",
+        "Tunisia",
+        "Turkey",
+        "Turkmenistan",
+        "Turks and Caicos Islands",
+        "Tuvalu",
+        "Uganda",
+        "Ukraine",
+        "United Arab Emirates",
+        "United Kingdom",
+        "United States of America",
+        "United States Minor Outlying Islands",
+        "United States Virgin Islands",
+        "Uruguay",
+        "Uzbekistan",
+        "Vanuatu",
+        "Venezuela",
+        "Vietnam",
+        "Wallis and Futuna",
+        "Western Sahara",
+        "Yemen",
+        "Zambia",
+        "Zimbabwe",
+    )
+
+    secondary_address_formats = (
+    "Flat %",
+    "Flat %#",
+    "Studio %",
+    "Studio %#",
+    "Apartment %",
+    "Apartment %#",
+    )
+
+    street_suffixes = (
+        "Avenue",
+        "Center",
+        "Square",
+        "Lane",
+        "Ghat",
+        "Corner",
+        "Lane",
+        "Highway",
+        "Mohalla",
+        "Moor",
+        "Para",
+        "Park",
+        "Plaza",
+        "Road",
+        "Road",
+        "Sorok",
+        "Station",
+        "Stand",
+    )
+
+    postcode_formats = ("%###",)
+    street_name_formats = (
+        "{{area_name}}{{street_suffix}}",
+        "{{city_prefix}} {{area_name}}{{street_suffix}}",
+        "{{city_prefix}} {{area_name}}{{city_suffix}}",
+        "{{area_name}}{{city_suffix}}",
+        "{{area_name}}{{city_suffix}} {{street_suffix}}",
+        "{{city_prefix}} {{area_name}}{{city_suffix}} {{street_suffix}}",
+    )
+    street_address_formats = (
+        "{{building_name}} {{building_number}}, {{street_name}}",
+        "{{secondary_address}}, {{building_name}} {{building_number}}, {{street_name}}",
+    )
+    town_formats = ("{{area_name}}{{city_suffix}}",)
+    address_formats = ("{{street_address}}, {{town}}, {{city}}, {{postcode}}",)
+
+    def administrative_unit(self) -> str:
+        """
+        :example: 'Dhaka'
+        """
+        return self.random_element(self.cities)
+
+    def area_name(self) -> str:
+        """
+        :example: 'Dhanmondi'
+        """
+        return self.random_element(self.area_names)
+
+    def building_name(self) -> str:
+        """
+        :example: 'House No.'
+        """
+        return self.random_element(self.building_names)
+
+    def building_number(self) -> str:
+        """
+        :example: '791'
+        """
+        return self.numerify(self.random_element(self.building_number_formats))
+
+    def city_prefix(self) -> str:
+        """
+        :example: 'North'
+        """
+        return self.random_element(self.city_prefixes)
+
+    def city(self) -> str:
+        """
+        :example: 'Dhaka'
+        """
+        return self.random_element(self.cities)
+
+    def postcode(self) -> str:
+        """
+        See
+        https://bdpost.portal.gov.bd/site/page/6aaeabe4-479b-4e5a-a671-e9e5b994bf9a
+        """
+        return self.numerify(self.random_element(self.postcode_formats))
+
+    def secondary_address(self) -> str:
+        """
+        As the generated string format is a Bengali word but English number so splitting the value by space
+        and then convert the English number to Bengali number and concat with generated Bengali word
+        and return
+        : example : 'Apartment 123'
+        """
+        value = self.bothify(self.random_element(self.secondary_address_formats))
+        word_list = value.split(" ")
+        return word_list[0] + " " + word_list[1]
+
+    def town(self) -> str:
+        """
+        :example: 'Dhanmondi'
+        """
+        pattern: str = self.random_element(self.town_formats)
+        return self.generator.parse(pattern)
\ No newline at end of file
diff --git a/faker/providers/color/__init__.py b/faker/providers/color/__init__.py
index e2aa0dec..358512b7 100644
--- a/faker/providers/color/__init__.py
+++ b/faker/providers/color/__init__.py
@@ -1,5 +1,6 @@
 from collections import OrderedDict
-from typing import Dict, Optional
+from functools import cached_property
+from typing import Dict, Optional, Tuple
 
 from ...typing import HueType
 from .. import BaseProvider, ElementsType
@@ -198,6 +199,10 @@ class Provider(BaseProvider):
         """Generate a color formatted as a CSS rgb() function."""
         return f"rgb({self.random_int(0, 255)},{self.random_int(0, 255)},{self.random_int(0, 255)})"
 
+    @cached_property
+    def _random_color(self):
+        return RandomColor(self.generator)
+
     def color(
         self,
         hue: Optional[HueType] = None,
@@ -238,8 +243,40 @@ class Provider(BaseProvider):
         :sample: hue=135, luminosity='dark', color_format='hsv'
         :sample: hue=(300, 20), luminosity='random', color_format='hsl'
         """
-        return RandomColor(self.generator).generate(
+        return self._random_color.generate(
             hue=hue,
             luminosity=luminosity,
             color_format=color_format,
         )
+
+    def color_rgb(
+        self,
+        hue: Optional[HueType] = None,
+        luminosity: Optional[str] = None,
+    ) -> Tuple[int, int, int]:
+        """Generate a RGB color tuple of integers in a human-friendly way."""
+        return self._random_color.generate_rgb(hue=hue, luminosity=luminosity)
+
+    def color_rgb_float(
+        self,
+        hue: Optional[HueType] = None,
+        luminosity: Optional[str] = None,
+    ) -> Tuple[float, float, float]:
+        """Generate a RGB color tuple of floats in a human-friendly way."""
+        return self._random_color.generate_rgb_float(hue=hue, luminosity=luminosity)
+
+    def color_hsl(
+        self,
+        hue: Optional[HueType] = None,
+        luminosity: Optional[str] = None,
+    ) -> Tuple[int, int, int]:
+        """Generate a HSL color tuple in a human-friendly way."""
+        return self._random_color.generate_hsl(hue=hue, luminosity=luminosity)
+
+    def color_hsv(
+        self,
+        hue: Optional[HueType] = None,
+        luminosity: Optional[str] = None,
+    ) -> Tuple[int, int, int]:
+        """Generate a HSV color tuple in a human-friendly way."""
+        return self._random_color.generate_hsv(hue=hue, luminosity=luminosity)
diff --git a/faker/providers/color/color.py b/faker/providers/color/color.py
index 91132f5c..379024f1 100644
--- a/faker/providers/color/color.py
+++ b/faker/providers/color/color.py
@@ -16,13 +16,16 @@ import math
 import random
 import sys
 
-from typing import TYPE_CHECKING, Dict, Hashable, Optional, Sequence, Tuple
+from typing import TYPE_CHECKING, Dict, Hashable, Literal, Optional, Sequence, Tuple
 
 if TYPE_CHECKING:
     from ...factory import Generator
 
 from ...typing import HueType
 
+ColorFormat = Literal["hex", "hsl", "hsv", "rgb"]
+
+
 COLOR_MAP: Dict[str, Dict[str, Sequence[Tuple[int, int]]]] = {
     "monochrome": {
         "hue_range": [(0, 0)],
@@ -144,37 +147,64 @@ class RandomColor:
             self.seed = seed if seed else random.randint(0, sys.maxsize)
             self.random = random.Random(self.seed)
 
-        for color_name, color_attrs in self.colormap.items():
-            lower_bounds: Sequence[Tuple[int, int]] = color_attrs["lower_bounds"]
-            s_min, b_max = lower_bounds[0]
-            s_max, b_min = lower_bounds[-1]
-
-            self.colormap[color_name]["saturation_range"] = [(s_min, s_max)]
-            self.colormap[color_name]["brightness_range"] = [(b_min, b_max)]
-
     def generate(
         self,
         hue: Optional[HueType] = None,
         luminosity: Optional[str] = None,
-        color_format: str = "hex",
+        color_format: ColorFormat = "hex",
     ) -> str:
-        """Generate a color.
+        """Generate and format a color.
 
         Whenever :meth:`color() <faker.providers.color.Provider.color>` is
         called, the arguments used are simply passed into this method, and this
         method handles the rest.
         """
+        # Generate HSV color tuple from picked hue and luminosity
+        hsv = self.generate_hsv(hue=hue, luminosity=luminosity)
+
+        # Return the HSB/V color in the desired string format
+        return self.set_format(hsv, color_format)
+
+    def generate_hsv(
+        self,
+        hue: Optional[HueType] = None,
+        luminosity: Optional[str] = None,
+    ) -> Tuple[int, int, int]:
+        """Generate a HSV color tuple."""
         # First we pick a hue (H)
         h = self.pick_hue(hue)
 
         # Then use H to determine saturation (S)
         s = self.pick_saturation(h, hue, luminosity)
 
-        # Then use S and H to determine brightness (B).
-        b = self.pick_brightness(h, s, luminosity)
+        # Then use S and H to determine brightness/value (B/V).
+        v = self.pick_brightness(h, s, luminosity)
 
-        # Then we return the HSB color in the desired format
-        return self.set_format((h, s, b), color_format)
+        return h, s, v
+
+    def generate_rgb(
+        self,
+        hue: Optional[HueType] = None,
+        luminosity: Optional[str] = None,
+    ) -> Tuple[int, int, int]:
+        """Generate a RGB color tuple of integers."""
+        return self.hsv_to_rgb(self.generate_hsv(hue=hue, luminosity=luminosity))
+
+    def generate_rgb_float(
+        self,
+        hue: Optional[HueType] = None,
+        luminosity: Optional[str] = None,
+    ) -> Tuple[float, float, float]:
+        """Generate a RGB color tuple of floats."""
+        return self.hsv_to_rgb_float(self.generate_hsv(hue=hue, luminosity=luminosity))
+
+    def generate_hsl(
+        self,
+        hue: Optional[HueType] = None,
+        luminosity: Optional[str] = None,
+    ) -> Tuple[int, int, int]:
+        """Generate a HSL color tuple."""
+        return self.hsv_to_hsl(self.generate_hsv(hue=hue, luminosity=luminosity))
 
     def pick_hue(self, hue: Optional[HueType]) -> int:
         """Return a numerical hue value."""
@@ -226,7 +256,7 @@ class RandomColor:
 
         return self.random_within((b_min, b_max))
 
-    def set_format(self, hsv: Tuple[int, int, int], color_format: str) -> str:
+    def set_format(self, hsv: Tuple[int, int, int], color_format: ColorFormat) -> str:
         """Handle conversion of HSV values into desired format."""
         if color_format == "hsv":
             color = f"hsv({hsv[0]}, {hsv[1]}, {hsv[2]})"
@@ -265,11 +295,11 @@ class RandomColor:
         """Return the hue range for a given ``color_input``."""
         if isinstance(color_input, (int, float)) and 0 <= color_input <= 360:
             color_input = int(color_input)
-            return (color_input, color_input)
+            return color_input, color_input
         elif isinstance(color_input, str) and color_input in self.colormap:
             return self.colormap[color_input]["hue_range"][0]
         elif color_input is None:
-            return (0, 360)
+            return 0, 360
 
         if isinstance(color_input, list):
             color_input = tuple(color_input)
@@ -285,12 +315,13 @@ class RandomColor:
                 v1, v2 = v2, v1
             v1 = max(v1, 0)
             v2 = min(v2, 360)
-            return (v1, v2)
+            return v1, v2
         raise TypeError("Hue must be a valid string, numeric type, or a tuple/list of 2 numeric types.")
 
     def get_saturation_range(self, hue: int) -> Tuple[int, int]:
         """Return the saturation range for a given numerical ``hue`` value."""
-        return self.get_color_info(hue)["saturation_range"][0]
+        saturation_bounds = [s for s, v in self.get_color_info(hue)["lower_bounds"]]
+        return min(saturation_bounds), max(saturation_bounds)
 
     def get_color_info(self, hue: int) -> Dict[str, Sequence[Tuple[int, int]]]:
         """Return the color info for a given numerical ``hue`` value."""
@@ -310,18 +341,27 @@ class RandomColor:
         return self.random.randint(int(r[0]), int(r[1]))
 
     @classmethod
-    def hsv_to_rgb(cls, hsv: Tuple[int, int, int]) -> Tuple[int, int, int]:
+    def hsv_to_rgb_float(cls, hsv: Tuple[int, int, int]) -> Tuple[float, float, float]:
         """Convert HSV to RGB.
 
         This method expects ``hsv`` to be a 3-tuple of H, S, and V values, and
-        it will return a 3-tuple of the equivalent R, G, and B values.
+        it will return a 3-tuple of the equivalent R, G, and B float values.
         """
         h, s, v = hsv
         h = max(h, 1)
         h = min(h, 359)
 
-        r, g, b = colorsys.hsv_to_rgb(h / 360, s / 100, v / 100)
-        return (int(r * 255), int(g * 255), int(b * 255))
+        return colorsys.hsv_to_rgb(h / 360, s / 100, v / 100)
+
+    @classmethod
+    def hsv_to_rgb(cls, hsv: Tuple[int, int, int]) -> Tuple[int, int, int]:
+        """Convert HSV to RGB.
+
+        This method expects ``hsv`` to be a 3-tuple of H, S, and V values, and
+        it will return a 3-tuple of the equivalent R, G, and B integer values.
+        """
+        r, g, b = cls.hsv_to_rgb_float(hsv)
+        return int(r * 255), int(g * 255), int(b * 255)
 
     @classmethod
     def hsv_to_hsl(cls, hsv: Tuple[int, int, int]) -> Tuple[int, int, int]:
@@ -334,7 +374,7 @@ class RandomColor:
 
         s_: float = s / 100.0
         v_: float = v / 100.0
-        l = 0.5 * (v_) * (2 - s_)  # noqa: E741
+        l = 0.5 * v_ * (2 - s_)  # noqa: E741
 
         s_ = 0.0 if l in [0, 1] else v_ * s_ / (1 - math.fabs(2 * l - 1))
-        return (int(h), int(s_ * 100), int(l * 100))
+        return int(h), int(s_ * 100), int(l * 100)


Test patch:
diff --git a/tests/providers/test_address.py b/tests/providers/test_address.py
index 35be9a81..4aef776b 100644
--- a/tests/providers/test_address.py
+++ b/tests/providers/test_address.py
@@ -14,6 +14,7 @@ from faker.providers.address.de_CH import Provider as DeChAddressProvider
 from faker.providers.address.de_DE import Provider as DeDeAddressProvider
 from faker.providers.address.el_GR import Provider as ElGrAddressProvider
 from faker.providers.address.en_AU import Provider as EnAuAddressProvider
+from faker.providers.address.en_BD import Provider as EnBdAddressProvider
 from faker.providers.address.en_CA import Provider as EnCaAddressProvider
 from faker.providers.address.en_GB import Provider as EnGbAddressProvider
 from faker.providers.address.en_IE import Provider as EnIeAddressProvider
@@ -410,6 +411,78 @@ class TestEnAu:
             assert state_abbr in EnAuAddressProvider.states_abbr
             assert state_abbr.isupper()
 
+class TestEnBd:
+    """ Test en_BD address provider methods """
+
+    def test_city_name(self, faker, num_samples):
+        for _ in range(num_samples):
+            city = faker.city_name()
+            assert isinstance(city, str)
+            assert city in EnBdAddressProvider.cities
+
+    def test_city_or_district_name(self, faker, num_samples):
+        for _ in range(num_samples):
+            city_or_district = faker.city_or_district_name()
+            assert isinstance(city_or_district, str)
+            assert city_or_district in EnBdAddressProvider.cities_or_districts
+
+    def test_district_name(self, faker, num_samples):
+        for _ in range(num_samples):
+            district = faker.district_name()
+            assert isinstance(district, str)
+            assert district in EnBdAddressProvider.districts
+
+    def test_division_name(self, faker, num_samples):
+        for _ in range(num_samples):
+            division = faker.division_name()
+            assert isinstance(division, str)
+            assert division in EnBdAddressProvider.divisions
+
+    def test_postcode(self, faker, num_samples):
+        for _ in range(num_samples):
+            postcode = faker.postcode()
+            assert isinstance(postcode, str)
+            assert re.fullmatch(r"\d{4}", postcode)
+
+    def test_postcode_in_city(self, faker, num_samples):
+        for _ in range(num_samples):
+            for city in EnBdAddressProvider.cities:
+                postcode = faker.postcode_in_city(city)
+                assert isinstance(postcode, str)
+                assert re.fullmatch(r"\d{4}", postcode)
+
+        with pytest.raises(Exception):
+            faker.postcode_in_city("XX")
+
+    def test_postcode_in_district(self, faker, num_samples):
+        for _ in range(num_samples):
+            for district in EnBdAddressProvider.districts:
+                postcode = faker.postcode_in_district(district)
+                assert isinstance(postcode, str)
+                assert re.fullmatch(r"\d{4}", postcode)
+
+        with pytest.raises(Exception):
+            faker.postcode_in_district("XX")
+
+    def test_postcode_in_division(self, faker, num_samples):
+        for _ in range(num_samples):
+            for division in EnBdAddressProvider.divisions:
+                postcode = faker.postcode_in_division(division)
+                assert isinstance(postcode, str)
+                assert re.fullmatch(r"\d{4}", postcode)
+
+        with pytest.raises(Exception):
+            faker.postcode_in_division("XX")
+
+    def test_postcode_in_region(self, faker, num_samples):
+        for _ in range(num_samples):
+            for region in EnBdAddressProvider.regions:
+                postcode = faker.postcode_in_region(region)
+                assert isinstance(postcode, str)
+                assert re.fullmatch(r"\d{4}", postcode)
+
+        with pytest.raises(Exception):
+            faker.postcode_in_region("XX")
 
 class TestEnCa:
     """Test en_CA address provider methods"""
diff --git a/tests/providers/test_color.py b/tests/providers/test_color.py
index 07706214..608277eb 100644
--- a/tests/providers/test_color.py
+++ b/tests/providers/test_color.py
@@ -47,15 +47,42 @@ class TestColorProvider:
             assert 0 <= b <= 255
 
     def test_color(self, faker, num_samples):
-        baseline_random_color = RandomColor(seed=4761)
-        expected = [baseline_random_color.generate() for _ in range(num_samples)]
+        random_color = self._seed_instances(faker, 4761)
+        expected = [random_color.generate() for _ in range(num_samples)]
 
         # The `color` provider method should behave like the `generate`
         # method of a standalone RandomColor instance for a given seed
-        faker.seed_instance(4761)
         colors = [faker.color() for _ in range(num_samples)]
         assert colors == expected
 
+    def _seed_instances(self, faker, seed):
+        faker.seed_instance(seed)
+        return RandomColor(seed=seed)
+
+    def test_color_rgb(self, faker, num_samples):
+        random_color = self._seed_instances(faker, 4761)
+        expected = [random_color.generate_rgb() for _ in range(num_samples)]
+        colors = [faker.color_rgb() for _ in range(num_samples)]
+        assert colors == expected
+
+    def test_color_rgb_float(self, faker, num_samples):
+        random_color = self._seed_instances(faker, 4761)
+        expected = [random_color.generate_rgb_float() for _ in range(num_samples)]
+        colors = [faker.color_rgb_float() for _ in range(num_samples)]
+        assert colors == expected
+
+    def test_color_hsl(self, faker, num_samples):
+        random_color = self._seed_instances(faker, 4761)
+        expected = [random_color.generate_hsl() for _ in range(num_samples)]
+        colors = [faker.color_hsl() for _ in range(num_samples)]
+        assert colors == expected
+
+    def test_color_hsv(self, faker, num_samples):
+        random_color = self._seed_instances(faker, 4761)
+        expected = [random_color.generate_hsv() for _ in range(num_samples)]
+        colors = [faker.color_hsv() for _ in range(num_samples)]
+        assert colors == expected
+
 
 class TestRandomColor:
     """Test RandomColor class"""
@@ -116,6 +143,42 @@ class TestRandomColor:
             color = self.random_color.generate()
             assert self.hex_color_pattern.fullmatch(color)
 
+    def test_rgb(self, num_samples):
+        for _ in range(num_samples):
+            value = self.random_color.generate_rgb()
+            assert len(value) == 3
+            for i in range(3):
+                assert isinstance(value[i], int)
+                assert 0 <= value[i] <= 255
+
+    def test_rgb_float(self, num_samples):
+        for _ in range(num_samples):
+            value = self.random_color.generate_rgb_float()
+            assert len(value) == 3
+            for i in range(3):
+                assert isinstance(value[i], float)
+                assert 0 <= value[i] <= 1
+
+    def test_hsl(self, num_samples):
+        for _ in range(num_samples):
+            value = self.random_color.generate_hsl()
+            assert len(value) == 3
+            for i in range(3):
+                assert isinstance(value[i], int)
+            assert 0 <= value[0] <= 360
+            assert 0 <= value[1] <= 100
+            assert 0 <= value[2] <= 100
+
+    def test_hsv(self, num_samples):
+        for _ in range(num_samples):
+            value = self.random_color.generate_hsl()
+            assert len(value) == 3
+            for i in range(3):
+                assert isinstance(value[i], int)
+            assert 0 <= value[0] <= 360
+            assert 0 <= value[1] <= 100
+            assert 0 <= value[2] <= 100
+
     def test_hue_integer(self):
         # HSV format is used, because whatever hue value supplied must be present in the output
         for hue in range(360):