Repository: joke2k/faker
Pull request #1054 (2019-11-14 19:32:14)

company details to russian provider
For Russian provider added TIN, PPC, PSRN generation


Patch:
diff --git a/faker/build_docs.py b/faker/build_docs.py
index 17a96d3d..9cda6437 100644
--- a/faker/build_docs.py
+++ b/faker/build_docs.py
@@ -65,7 +65,7 @@ def write_docs(*args, **kwargs):
         write_base_provider(fh, doc, base_provider)
 
     # Write docs for default locale providers
-    base_provider_formatters = [f for f in dir(BaseProvider)]
+    base_provider_formatters = list(dir(BaseProvider))
     formatters = doc.get_formatters(with_args=True, with_defaults=True,
                                     excludes=base_provider_formatters)
     for provider, fakers in formatters:
diff --git a/faker/cli.py b/faker/cli.py
index d407f0b0..a219d00d 100644
--- a/faker/cli.py
+++ b/faker/cli.py
@@ -62,7 +62,7 @@ def print_doc(provider_or_field=None,
     fake.seed_instance(seed)
 
     from faker.providers import BaseProvider
-    base_provider_formatters = [f for f in dir(BaseProvider)]
+    base_provider_formatters = list(dir(BaseProvider))
 
     if provider_or_field:
         if '.' in provider_or_field:
diff --git a/faker/providers/color/__init__.py b/faker/providers/color/__init__.py
index 92bb669d..4d039dc6 100644
--- a/faker/providers/color/__init__.py
+++ b/faker/providers/color/__init__.py
@@ -2,6 +2,7 @@
 from __future__ import unicode_literals
 from collections import OrderedDict
 
+from .color import RandomColor
 from .. import BaseProvider
 
 localized = True
@@ -180,3 +181,18 @@ class Provider(BaseProvider):
     def rgb_css_color(self):
         return 'rgb(%s)' % ','.join(
             map(str, (self.random_int(0, 255) for _ in range(3))))
+
+    def color(self, hue=None, luminosity=None, color_format='hex'):
+        """
+        Creates a color in specified format
+
+        :param hue: monochrome, red, orange, yellow, green, blue, purple, pink, a number
+                    from 0 to 360, or a tuple/list of 2 numbers from 0 to 360
+        :param luminosity: bright, dark, light, or random
+        :param color_format: hsv, hsl, rgb, or hex with hex being default
+        :return: color in the specified format
+        """
+
+        return RandomColor(self.generator).generate(
+            hue=hue, luminosity=luminosity, color_format=color_format,
+        )
diff --git a/faker/providers/color/color.py b/faker/providers/color/color.py
new file mode 100644
index 00000000..c354de79
--- /dev/null
+++ b/faker/providers/color/color.py
@@ -0,0 +1,280 @@
+"""
+Code adapted from:
+- https://github.com/davidmerfield/randomColor  (CC0)
+- https://github.com/kevinwuhoo/randomcolor-py  (MIT License)
+
+Additional reference from:
+- https://en.wikipedia.org/wiki/HSL_and_HSV
+"""
+
+import colorsys
+import math
+import random
+import sys
+
+import six
+
+
+COLOR_MAP = {
+    'monochrome': {
+        'hue_range': [0, 0],
+        'lower_bounds': [
+            [0, 0], [100, 0],
+        ],
+    },
+    'red': {
+        'hue_range': [-26, 18],
+        'lower_bounds': [
+            [20, 100], [30, 92], [40, 89],
+            [50, 85], [60, 78], [70, 70],
+            [80, 60], [90, 55], [100, 50],
+        ],
+    },
+    'orange': {
+        'hue_range': [19, 46],
+        'lower_bounds': [
+            [20, 100], [30, 93], [40, 88], [50, 86],
+            [60, 85], [70, 70], [100, 70],
+        ],
+    },
+    'yellow': {
+        'hue_range': [47, 62],
+        'lower_bounds': [
+            [25, 100], [40, 94], [50, 89], [60, 86],
+            [70, 84], [80, 82], [90, 80], [100, 75],
+        ],
+    },
+    'green': {
+        'hue_range': [63, 178],
+        'lower_bounds': [
+            [30, 100], [40, 90], [50, 85], [60, 81],
+            [70, 74], [80, 64], [90, 50], [100, 40],
+        ],
+    },
+    'blue': {
+        'hue_range': [179, 257],
+        'lower_bounds': [
+            [20, 100], [30, 86], [40, 80],
+            [50, 74], [60, 60], [70, 52],
+            [80, 44], [90, 39], [100, 35],
+        ],
+    },
+    'purple': {
+        'hue_range': [258, 282],
+        'lower_bounds': [
+            [20, 100], [30, 87], [40, 79],
+            [50, 70], [60, 65], [70, 59],
+            [80, 52], [90, 45], [100, 42],
+        ],
+    },
+    'pink': {
+        'hue_range': [283, 334],
+        'lower_bounds': [
+            [20, 100], [30, 90], [40, 86], [60, 84],
+            [80, 80], [90, 75], [100, 73],
+        ],
+    },
+}
+
+
+class RandomColor(object):
+
+    def __init__(self, generator=None, seed=None):
+        self.colormap = COLOR_MAP
+
+        # Option to specify a seed was not removed so this class
+        # can still be tested independently w/o generators
+        if generator:
+            self.random = generator.random
+        else:
+            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 = color_attrs['lower_bounds']
+            s_min = lower_bounds[0][0]
+            s_max = lower_bounds[-1][0]
+
+            b_min = lower_bounds[-1][1]
+            b_max = lower_bounds[0][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=None, luminosity=None, color_format='hex'):
+        # 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 we return the HSB color in the desired format
+        return self.set_format([h, s, b], color_format)
+
+    def pick_hue(self, hue):
+        hue_range = self.get_hue_range(hue)
+        hue = self.random_within(hue_range)
+
+        # Instead of storing red as two separate ranges,
+        # we group them, using negative numbers
+        if hue < 0:
+            hue += 360
+
+        return hue
+
+    def pick_saturation(self, hue, hue_name, luminosity):
+        if luminosity == 'random':
+            return self.random_within([0, 100])
+
+        if hue_name == 'monochrome':
+            return 0
+
+        saturation_range = self.get_saturation_range(hue)
+
+        s_min = saturation_range[0]
+        s_max = saturation_range[1]
+
+        if luminosity == 'bright':
+            s_min = 55
+        elif luminosity == 'dark':
+            s_min = s_max - 10
+        elif luminosity == 'light':
+            s_max = 55
+
+        return self.random_within([s_min, s_max])
+
+    def pick_brightness(self, h, s, luminosity):
+        b_min = self.get_minimum_brightness(h, s)
+        b_max = 100
+
+        if luminosity == 'dark':
+            b_max = b_min + 20
+        elif luminosity == 'light':
+            b_min = (b_max + b_min) / 2
+        elif luminosity == 'random':
+            b_min = 0
+            b_max = 100
+
+        return self.random_within([b_min, b_max])
+
+    def set_format(self, hsv, color_format):
+        if color_format == 'hsv':
+            color = 'hsv({}, {}, {})'.format(*hsv)
+
+        elif color_format == 'hsl':
+            hsl = self.hsv_to_hsl(hsv)
+            color = 'hsl({}, {}, {})'.format(*hsl)
+
+        elif color_format == 'rgb':
+            rgb = self.hsv_to_rgb(hsv)
+            color = 'rgb({}, {}, {})'.format(*rgb)
+
+        else:
+            rgb = self.hsv_to_rgb(hsv)
+            color = '#{:02x}{:02x}{:02x}'.format(*rgb)
+
+        return color
+
+    def get_minimum_brightness(self, h, s):
+        lower_bounds = self.get_color_info(h)['lower_bounds']
+
+        for i in range(len(lower_bounds) - 1):
+            s1 = lower_bounds[i][0]
+            v1 = lower_bounds[i][1]
+
+            s2 = lower_bounds[i + 1][0]
+            v2 = lower_bounds[i + 1][1]
+
+            if s1 <= s <= s2:
+                m = (v2 - v1) / (s2 - s1)
+                b = v1 - m * s1
+
+                return m * s + b
+
+        return 0
+
+    def get_hue_range(self, color_input):
+        if isinstance(color_input, (int, float)) and 0 <= color_input <= 360:
+            color_input = int(color_input)
+            return [color_input, color_input]
+
+        elif isinstance(color_input, six.string_types) and color_input in self.colormap:
+            return self.colormap[color_input]['hue_range']
+
+        elif color_input is None:
+            return [0, 360]
+
+        try:
+            v1, v2 = color_input
+            v1 = int(v1)
+            v2 = int(v2)
+        except (ValueError, TypeError):
+            msg = 'Hue must be a valid string, numeric type, or a tuple/list of 2 numeric types.'
+            raise TypeError(msg)
+        else:
+            if v2 < v1:
+                v1, v2 = v2, v1
+            if v1 < 0:
+                v1 = 0
+            if v2 > 360:
+                v2 = 360
+            return [v1, v2]
+
+    def get_saturation_range(self, hue):
+        return self.get_color_info(hue)['saturation_range']
+
+    def get_color_info(self, hue):
+        # Maps red colors to make picking hue easier
+        if 334 <= hue <= 360:
+            hue -= 360
+
+        for color_name, color in self.colormap.items():
+            if color['hue_range'][0] <= hue <= color['hue_range'][1]:
+                return self.colormap[color_name]
+        else:
+            raise ValueError('Value of hue `%s` is invalid.' % hue)
+
+    def random_within(self, r):
+        return self.random.randint(int(r[0]), int(r[1]))
+
+    @classmethod
+    def hsv_to_rgb(cls, hsv):
+        """
+        Converts HSV to RGB
+
+        :param hsv: 3-tuple of h, s, and v values
+        :return: 3-tuple of r, g, and b values
+        """
+        h, s, v = hsv
+        h = 1 if h == 0 else h
+        h = 359 if h == 360 else h
+
+        h = float(h)/360
+        s = float(s)/100
+        v = float(v)/100
+
+        rgb = colorsys.hsv_to_rgb(h, s, v)
+        return (int(c * 255) for c in rgb)
+
+    @classmethod
+    def hsv_to_hsl(cls, hsv):
+        """
+        Converts HSV to HSL
+
+        :param hsv: 3-tuple of h, s, and v values
+        :return: 3-tuple of h, s, and l values
+        """
+        h, s, v = hsv
+
+        s = float(s)/100
+        v = float(v)/100
+        l = 0.5 * v * (2 - s)   # noqa: E741
+
+        if l in [0, 1]:
+            s = 0
+        else:
+            s = v * s / (1 - math.fabs(2 * l - 1))
+        return (int(c) for c in [h, s * 100, l * 100])
diff --git a/faker/providers/company/ru_RU/__init__.py b/faker/providers/company/ru_RU/__init__.py
index afd895a2..36bfc778 100644
--- a/faker/providers/company/ru_RU/__init__.py
+++ b/faker/providers/company/ru_RU/__init__.py
@@ -1,9 +1,20 @@
 # coding=utf-8
 
 from __future__ import unicode_literals
+
+from faker.utils.datetime_safe import datetime
 from .. import Provider as CompanyProvider
 
 
+def calculate_checksum(value):
+    factors = [3, 7, 2, 4, 10, 3, 5, 9, 4, 6, 8][-len(value):]
+    check_sum = 0
+    for number, factor in zip(value, factors):
+        check_sum += int(number) * factor
+
+    return str((check_sum % 11) % 10)
+
+
 class Provider(CompanyProvider):
     formats = (
         '{{company_prefix}} «{{last_name}}»',
@@ -19,3 +30,64 @@ class Provider(CompanyProvider):
 
     def company_prefix(self):
         return self.random_element(self.company_prefixes)
+
+    def businesses_inn(self):
+        """
+        Returns tax identification number for businesses (ru. идентификационный номер налогоплательщика, ИНН).
+        """
+        region = '%02d' % self.random_int(min=1, max=92)
+        inspection = '%02d' % self.random_int(min=1, max=99)
+        tail = '%05d' % self.random_int(min=1, max=99999)
+        result = region + inspection + tail
+
+        return result + calculate_checksum(result)
+
+    def individuals_inn(self):
+        """
+        Returns tax identification number for individuals (ru. идентификационный номер налогоплательщика, ИНН).
+        """
+        region = '%02d' % self.random_int(min=1, max=92)
+        inspection = '%02d' % self.random_int(min=1, max=99)
+        tail = '%06d' % self.random_int(min=1, max=999999)
+        result = region + inspection + tail
+        result += calculate_checksum(result)
+
+        return result + calculate_checksum(result)
+
+    def businesses_ogrn(self):
+        """
+        Returns primary state registration number for businesses
+        (ru. основной государственный регистрационный номер, ОГРН).
+        """
+        sign = self.random_element(('1', '5'))
+        year = '%02d' % self.random_int(min=1, max=datetime.now().year - 2000)
+        region = '%02d' % self.random_int(min=1, max=92)
+        tail = '%07d' % self.random_int(min=1, max=9999999)
+
+        result = sign + year + region + tail
+
+        return result + str((int(result) % 11) % 10)
+
+    def individuals_ogrn(self):
+        """
+        Returns primary state registration number for individuals
+        (ru. основной государственный регистрационный номер, ОГРН).
+        """
+        year = '%02d' % self.random_int(min=1, max=datetime.now().year - 2000)
+        region = '%02d' % self.random_int(min=1, max=92)
+        tail = '%09d' % self.random_int(min=1, max=999999999)
+
+        result = '3' + year + region + tail
+
+        return result + str((int(result) % 13) % 10)
+
+    def kpp(self):
+        """
+        Returns tax registration reason code (ru. код причины постановки на учет, КПП).
+        """
+        region = '%02d' % self.random_int(min=1, max=92)
+        inspection = '%02d' % self.random_int(min=1, max=99)
+        reason = self.random_element(('01', '43', '44', '45'))
+        tail = '%03d' % self.random_int(min=1, max=999)
+
+        return region + inspection + reason + tail
diff --git a/faker/providers/credit_card/__init__.py b/faker/providers/credit_card/__init__.py
index 47f09628..94595fe4 100644
--- a/faker/providers/credit_card/__init__.py
+++ b/faker/providers/credit_card/__init__.py
@@ -4,6 +4,8 @@ from collections import OrderedDict
 
 from .. import BaseProvider
 
+localized = True
+
 
 class CreditCard(object):
 
diff --git a/faker/providers/credit_card/fa_IR/__init__.py b/faker/providers/credit_card/fa_IR/__init__.py
new file mode 100644
index 00000000..85721a7c
--- /dev/null
+++ b/faker/providers/credit_card/fa_IR/__init__.py
@@ -0,0 +1,75 @@
+# coding=utf-8
+
+from .. import Provider as CreditCardProvider
+from .. import CreditCard
+from collections import OrderedDict
+
+
+class Provider(CreditCardProvider):
+
+    # https://way2pay.ir/21653
+    prefix_ansar = ["627381"]
+    prefix_iran_zamin = ["505785"]
+    prefix_hekmat = ["636949"]
+    prefix_keshavarzi = ["603770"]
+    prefix_shahr = ["502806"]
+    prefix_mehr_eghtesad = ["606373"]
+    prefix_sarmayeh = ["639607"]
+    prefix_post_bank = ["627760"]
+    prefix_tose = ["628157"]
+    prefix_eghtesad_novin = ["627412"]
+    prefix_meli = ["603799"]
+    prefix_pasargad = ["502229"]
+    prefix_tourism_bank = ["505416"]
+    prefix_ghavamin = ["639599"]
+    prefix_day = ["502938"]
+    prefix_mellat = ["610433"]
+    prefix_tejarat = ["585983"]
+    prefix_moasse_mellal = ["606256"]
+    prefix_saman_bank = ["621986"]
+    prefix_kosar = ["505801"]
+    prefix_refah = ["589463"]
+    prefix_saderat = ["603761"]
+    prefix_tat = ["621986"]
+    prefix_sina = ["639346"]
+    prefix_kar_afarin = ["627488"]
+    prefix_sepah = ["589210"]
+    prefix_maskan = ["628023"]
+    prefix_parsian = ["622106"]
+    prefix_bim = ["627961"]
+
+    credit_card_types = OrderedDict((
+        ('ansar', CreditCard(u'انصار', prefix_ansar, 16, security_code='CVV2')),
+        ('iran_zamin', CreditCard(u'ایران زمین', prefix_iran_zamin, 16, security_code='CVV2')),
+        ('hekmat', CreditCard(u'حکمت', prefix_hekmat, 16, security_code='CVV2')),
+        ('keshavarzi', CreditCard(u'کشاورزی', prefix_keshavarzi, 16, security_code='CVV2')),
+        ('shahr', CreditCard(u'شهر', prefix_shahr, 16, security_code='CVV2')),
+        ('mehre_ghtesad', CreditCard(u'مهراقتصاد', prefix_mehr_eghtesad, 16, security_code='CVV2')),
+        ('sarmayeh', CreditCard(u'سرمایه', prefix_sarmayeh, 16, security_code='CVV2')),
+        ('post_bank', CreditCard(u'پست بانک', prefix_post_bank, 16, security_code='CVV2')),
+        ('tose', CreditCard(u'توسعه', prefix_tose, 16, security_code='CVV2')),
+        ('eghtesad_novin', CreditCard(u'اقتصاد نوین', prefix_eghtesad_novin, 16, security_code='CVV2')),
+        ('meli', CreditCard(u'ملی', prefix_meli, 16, security_code='CVV2')),
+        ('pasargad', CreditCard(u'پاسارگاد', prefix_pasargad, 16, security_code='CVV2')),
+        ('tourism_bank', CreditCard(u'گردشگری', prefix_tourism_bank, 16, security_code='CVV2')),
+        ('ghavamin', CreditCard(u'قوامین', prefix_ghavamin, 16, security_code='CVV2')),
+        ('day', CreditCard(u'دی', prefix_day, 16, security_code='CVV2')),
+        ('mellat', CreditCard(u'ملت', prefix_mellat, 16, security_code='CVV2')),
+        ('tejarat', CreditCard(u'تجارت', prefix_tejarat, 16, security_code='CVV2')),
+        ('mellal', CreditCard(u'ملل', prefix_moasse_mellal, 16, security_code='CVV2')),
+        ('saman', CreditCard(u'سامان', prefix_saman_bank, 16, security_code='CVV2')),
+        ('kosar', CreditCard(u'کوثر', prefix_kosar, 16, security_code='CVV2')),
+        ('refah', CreditCard(u'رفاه', prefix_refah, 16, security_code='CVV2')),
+        ('saderat', CreditCard(u'صادرات', prefix_saderat, 16, security_code='CVV2')),
+        ('tat', CreditCard(u'تات', prefix_tat, 16, security_code='CVV2')),
+        ('sina', CreditCard(u'سینا', prefix_sina, 16, security_code='CVV2')),
+        ('kar_afarin', CreditCard(u'کار آفرین', prefix_kar_afarin, 16, security_code='CVV2')),
+        ('sepah', CreditCard(u'سپه', prefix_sepah, 16, security_code='CVV2')),
+        ('maskan', CreditCard(u'مسکن', prefix_maskan, 16, security_code='CVV2')),
+        ('parsian', CreditCard(u'پارسیان', prefix_parsian, 16, security_code='CVV2')),
+        ('bim', CreditCard(u'صنعت و معدن', prefix_bim, 16, security_code='CVV2')),
+    ))
+
+    def credit_card_expire(self, start='now', end='+3y', date_format='%y/%m'):
+        expire_date = self.generator.date_time_between(start, end)
+        return expire_date.strftime(date_format)


Test patch:
diff --git a/tests/providers/test_color.py b/tests/providers/test_color.py
index 24f00314..073a911d 100644
--- a/tests/providers/test_color.py
+++ b/tests/providers/test_color.py
@@ -1,8 +1,11 @@
 import unittest
+import re
 from re import search
 from faker import Faker
 
+from faker.providers.color import RandomColor
 from faker.providers.color.hy_AM import Provider as HyAmProvider
+import six
 from six import string_types
 
 
@@ -45,6 +48,206 @@ class TestColor(unittest.TestCase):
         assert maxval <= 255
         assert minval >= 0
 
+    def test_color(self):
+        baseline_random_color = RandomColor(seed=4761)
+        expected = [baseline_random_color.generate() for _ in range(10000)]
+
+        # The `color` provider method should behave like the `generate`
+        # method of a standalone RandomColor instance for a given seed
+        self.factory.seed(4761)
+        colors = [self.factory.color() for _ in range(10000)]
+        assert colors == expected
+
+
+class TestRandomColor(unittest.TestCase):
+
+    seed = 4761
+    hsv_color_pattern = re.compile(
+        r'^hsv\('
+        r'(?P<h>\d|[1-9]\d|[1-3]\d{2}), '
+        r'(?P
\d|[1-9]\d|100), '
+        r'(?P<v>\d|[1-9]\d|100)\)$',
+    )
+    hsl_color_pattern = re.compile(
+        r'^hsl\('
+        r'(?P<h>\d|[1-9]\d|[1-3]\d{2}), '
+        r'(?P
\d|[1-9]\d|[1-3]\d{2}), '
+        r'(?P<l>\d|[1-9]\d|[1-3]\d{2})\)$',
+    )
+    rgb_color_pattern = re.compile(
+        r'^rgb\('
+        r'(?P<r>\d|[1-9]\d|[1-3]\d{2}), '
+        r'(?P<g>\d|[1-9]\d|[1-3]\d{2}), '
+        r'(?P<b>\d|[1-9]\d|[1-3]\d{2})\)$',
+    )
+    hex_color_pattern = re.compile(r'^#[0-9a-f]{6}$')
+
+    def setUp(self):
+        self.random_color = RandomColor(seed=self.seed)
+
+    def test_color_format_hsv(self):
+        for _ in range(10000):
+            hsv_color = self.random_color.generate(color_format='hsv')
+            match = self.hsv_color_pattern.match(hsv_color)
+            assert match
+            groupdict = match.groupdict()
+            assert 0 <= int(groupdict['h']) <= 360
+            assert 0 <= int(groupdict['s']) <= 100
+            assert 0 <= int(groupdict['v']) <= 100
+
+    def test_color_format_hsl(self):
+        for _ in range(10000):
+            hsl_color = self.random_color.generate(color_format='hsl')
+            match = self.hsl_color_pattern.match(hsl_color)
+            assert match
+            groupdict = match.groupdict()
+            assert 0 <= int(groupdict['h']) <= 360
+            assert 0 <= int(groupdict['s']) <= 100
+            assert 0 <= int(groupdict['l']) <= 100
+
+    def test_color_format_rgb(self):
+        for _ in range(10000):
+            rgb_color = self.random_color.generate(color_format='rgb')
+            match = self.rgb_color_pattern.match(rgb_color)
+            assert match
+            groupdict = match.groupdict()
+            assert 0 <= int(groupdict['r']) <= 255
+            assert 0 <= int(groupdict['g']) <= 255
+            assert 0 <= int(groupdict['b']) <= 255
+
+    def test_color_format_hex(self):
+        for _ in range(10000):
+            hex_color = self.random_color.generate(color_format='hex')
+            assert self.hex_color_pattern.match(hex_color)
+
+    def test_color_format_unspecified(self):
+        for _ in range(10000):
+            color = self.random_color.generate()
+            assert self.hex_color_pattern.match(color)
+
+    def test_hue_word(self):
+        if six.PY2:
+            expected = ['#f2f2f2', '#6b6b6b', '#939393', '#5e5e5e', '#aaaaaa']
+        else:
+            expected = ['#cecece', '#ededed', '#efefef', '#bcbcbc', '#777777']
+        colors = [self.random_color.generate(hue='monochrome') for _ in range(5)]
+        assert colors == expected
+
+        if six.PY2:
+            expected = ['#c46542', '#d8495f', '#c42c09', '#dd4b68', '#c6135e']
+        else:
+            expected = ['#ef0b31', '#f2b7ab', '#f74c55', '#a53822', '#8e3712']
+        colors = [self.random_color.generate(hue='red') for _ in range(5)]
+        assert colors == expected
+
+        if six.PY2:
+            expected = ['#fcda9f', '#ffa566', '#b55609', '#c9761e', '#fcd9c7']
+        else:
+            expected = ['#f98313', '#ddb77e', '#f9c413', '#f4ce81', '#ddae71']
+        colors = [self.random_color.generate(hue='orange') for _ in range(5)]
+        assert colors == expected
+
+        if six.PY2:
+            expected = ['#f2f75b', '#d8cb38', '#efe3a5', '#dbc053', '#eae096']
+        else:
+            expected = ['#dbe04e', '#efc621', '#fff65b', '#ceaf27', '#fcf9ae']
+        colors = [self.random_color.generate(hue='yellow') for _ in range(5)]
+        assert colors == expected
+
+        if six.PY2:
+            expected = ['#54d387', '#b1d15c', '#c0f78a', '#27b278', '#1bc43a']
+        else:
+            expected = ['#05876f', '#57e095', '#50ceaa', '#e4f7a0', '#698909']
+        colors = [self.random_color.generate(hue='green') for _ in range(5)]
+        assert colors == expected
+
+        if six.PY2:
+            expected = ['#0b577c', '#7ad3d3', '#4884ce', '#bae8f2', '#79cafc']
+        else:
+            expected = ['#2b839b', '#a4d3e8', '#3d2caa', '#3859a0', '#52349e']
+        colors = [self.random_color.generate(hue='blue') for _ in range(5)]
+        assert colors == expected
+
+        if six.PY2:
+            expected = ['#d2a7e5', '#a519fc', '#8b0ece', '#b17fe2', '#a949dd']
+        else:
+            expected = ['#a074e8', '#6122bf', '#9f76cc', '#250570', '#3c1599']
+        colors = [self.random_color.generate(hue='purple') for _ in range(5)]
+        assert colors == expected
+
+        if six.PY2:
+            expected = ['#f453c4', '#db81ac', '#f99fc9', '#e23fff', '#bb68cc']
+        else:
+            expected = ['#c605c6', '#fcc4ec', '#d979f7', '#ce108c', '#d3289d']
+        colors = [self.random_color.generate(hue='pink') for _ in range(5)]
+        assert colors == expected
+
+    def test_hue_tuple_beyond_limits(self):
+        baseline_random_color = RandomColor(seed=self.seed)
+        expected = [baseline_random_color.generate(hue=[0, 360]) for _ in range(1000)]
+
+        # Using a tuple with values not between 0 and 360 should yield the same results
+        # as using a tuple with clamped values for a given seed
+        colors = [self.random_color.generate(hue=[-100, 4500]) for _ in range(1000)]
+        assert colors == expected
+
+    def test_hue_tuple_inverted_values(self):
+        baseline_random_color = RandomColor(seed=self.seed)
+        expected = [baseline_random_color.generate(hue=[45, 75]) for _ in range(1000)]
+
+        # Using a tuple with inverted values should yield the same results
+        # as using the correctly ordered tuple for a given seed
+        colors = [self.random_color.generate(hue=[75, 45]) for _ in range(1000)]
+        assert colors == expected
+
+    def test_hue_invalid(self):
+        invalid_values = [
+            'invalid value',    # Unsupported string
+            [1, 2, 3],          # List with incorrect number of elements of valid data types
+            ['ab', 1],          # List with correct number of elements with invalid data types
+            self,               # Any other garbage
+        ]
+
+        for invalid_value in invalid_values:
+            with self.assertRaises(TypeError):
+                self.random_color.generate(hue=invalid_value)
+
+    def test_luminosity_word(self):
+        if six.PY2:
+            expected = ['#7c000a', '#018748', '#dd8a0d', '#000068', '#7c9308']
+        else:
+            expected = ['#2b7700', '#073c8c', '#d813aa', '#01961a', '#ce840e']
+        colors = [self.random_color.generate(luminosity='dark') for _ in range(5)]
+        assert colors == expected
+
+        if six.PY2:
+            expected = ['#11f7c9', '#3eafc9', '#6ad4f2', '#125582', '#e55098']
+        else:
+            expected = ['#16b5ff', '#6266ef', '#fc4e3f', '#b2ff70', '#a30424']
+        colors = [self.random_color.generate(luminosity='bright') for _ in range(5)]
+        assert colors == expected
+
+        if six.PY2:
+            expected = ['#ffafb5', '#fcc1a1', '#b7fcab', '#6df280', '#f599f7']
+        else:
+            expected = ['#f276a1', '#fcec94', '#aaffe5', '#ffbd7f', '#98f9dc']
+        colors = [self.random_color.generate(luminosity='light') for _ in range(5)]
+        assert colors == expected
+
+        if six.PY2:
+            expected = ['#11140f', '#7f7674', '#262116', '#376d20', '#535e53']
+        else:
+            expected = ['#070603', '#99a2a3', '#10a85c', '#3f4f0c', '#004f1c']
+        colors = [self.random_color.generate(luminosity='random') for _ in range(5)]
+        assert colors == expected
+
+    def test_luminosity_invalid(self):
+        baseline_random_color = RandomColor(seed=self.seed)
+        expected = [baseline_random_color.generate() for _ in range(1000)]
+
+        colors = [self.random_color.generate(luminosity='invalid_value') for _ in range(1000)]
+        assert colors == expected
+
 
 class TestHyAM(unittest.TestCase):
     """ Tests colors in the hy_AM locale """
diff --git a/tests/providers/test_company.py b/tests/providers/test_company.py
index 5e71f20d..2b16d603 100644
--- a/tests/providers/test_company.py
+++ b/tests/providers/test_company.py
@@ -2,19 +2,21 @@
 
 from __future__ import unicode_literals
 
-import unittest
 import re
+import unittest
 
 import six
 
 from faker import Faker
+from faker.providers.company import ru_RU as ru
 from faker.providers.company.hy_AM import Provider as HyAmProvider
 from faker.providers.company.ja_JP import Provider as JaProvider
-from faker.providers.company.pt_BR import company_id_checksum
+from faker.providers.company.nl_NL import Provider as NlProvider
 from faker.providers.company.pl_PL import (
     company_vat_checksum, regon_checksum, local_regon_checksum, Provider as PlProvider,
 )
-from faker.providers.company.nl_NL import Provider as NlProvider
+from faker.providers.company.pt_BR import company_id_checksum
+from faker.utils.datetime_safe import datetime
 
 
 class TestFiFI(unittest.TestCase):
@@ -249,3 +251,68 @@ class TestTlPh(TestFilPh):
 
     def setup_factory(self):
         self.factory = Faker('tl_PH')
+
+
+class TestRuRu(unittest.TestCase):
+    """ Tests company in the ru_RU locale """
+
+    num_sample_runs = 1000
+
+    def setUp(self):
+        self.factory = Faker('ru_RU')
+
+    def test_calculate_checksum_nine_digits(self):
+        assert ru.calculate_checksum('164027304') == '7'
+        assert ru.calculate_checksum('629082979') == '0'
+        assert ru.calculate_checksum('0203184580') == '5'
+        assert ru.calculate_checksum('1113145630') == '0'
+        assert ru.calculate_checksum('70517081385') == '1'
+        assert ru.calculate_checksum('60307390550') == '0'
+
+    def test_businesses_inn(self):
+        for i in range(self.num_sample_runs):
+            inn = self.factory.businesses_inn()
+
+            assert len(inn) == 10
+            assert ru.calculate_checksum(inn[:9]) == inn[9]
+
+    def test_individuals_inn(self):
+        for i in range(self.num_sample_runs):
+            inn = self.factory.individuals_inn()
+
+            assert len(inn) == 12
+            assert ru.calculate_checksum(inn[:10]) == inn[10]
+            assert ru.calculate_checksum(inn[:11]) == inn[11]
+
+    def test_businesses_ogrn(self):
+        max_year = datetime.now().year - 2000
+
+        for i in range(self.num_sample_runs):
+            ogrn = self.factory.businesses_ogrn()
+
+            assert len(ogrn) == 13
+            assert ogrn[0] in ('1', '5')
+            assert 1 <= int(ogrn[1:3]) <= max_year
+            assert 1 <= int(ogrn[3:5]) <= 92
+            assert int(ogrn[:-1]) % 11 % 10 == int(ogrn[-1])
+
+    def test_individuals_ogrn(self):
+        max_year = datetime.now().year - 2000
+
+        for i in range(self.num_sample_runs):
+            ogrn = self.factory.individuals_ogrn()
+
+            assert len(ogrn) == 15
+            assert ogrn[0] == '3'
+            assert 1 <= int(ogrn[1:3]) <= max_year
+            assert 1 <= int(ogrn[3:5]) <= 92
+            assert int(ogrn[:-1]) % 13 % 10 == int(ogrn[-1])
+
+    def test_kpp(self):
+        for i in range(self.num_sample_runs):
+            kpp = self.factory.kpp()
+
+            assert len(kpp) == 9
+            assert 1 <= int(kpp[0:2]) <= 92
+            assert int(kpp[2:4]) > 0
+            assert kpp[4:6] in ('01', '43', '44', '45')
diff --git a/tests/providers/test_date_time.py b/tests/providers/test_date_time.py
index b8a41b6c..8d523e53 100644
--- a/tests/providers/test_date_time.py
+++ b/tests/providers/test_date_time.py
@@ -413,29 +413,29 @@ class TestDateTime(unittest.TestCase):
             Provider._parse_timedelta('foobar')
 
     def test_time_series(self):
-        series = [i for i in self.factory.time_series()]
+        series = list(self.factory.time_series())
         assert len(series), 30
         assert series[1][0] - series[0][0], timedelta(days=1)
 
         uniform = lambda dt: random.uniform(0, 5)  # noqa
-        series = [i for i in self.factory.time_series('now', '+1w', '+1d', uniform)]
+        series = list(self.factory.time_series('now', '+1w', '+1d', uniform))
         assert len(series), 7
         assert series[1][0] - series[0][0], timedelta(days=1)
 
         end = datetime.now() + timedelta(days=7)
-        series = [i for i in self.factory.time_series('now', end, '+1d', uniform)]
+        series = list(self.factory.time_series('now', end, '+1d', uniform))
         assert len(series), 7
         assert series[1][0] - series[0][0], timedelta(days=1)
 
         assert series[-1][0] <= end
 
         with pytest.raises(ValueError):
-            [i for i in self.factory.time_series('+1w', 'now', '+1d', uniform)]
+            list(self.factory.time_series('+1w', 'now', '+1d', uniform))
 
         with pytest.raises(ValueError):
-            [i for i in self.factory.time_series('now', '+1w', '+1d', 'uniform')]
+            list(self.factory.time_series('now', '+1w', '+1d', 'uniform'))
 
-        series = [i for i in self.factory.time_series('now', end, '+1d', uniform, tzinfo=utc)]
+        series = list(self.factory.time_series('now', end, '+1d', uniform, tzinfo=utc))
         assert len(series), 7
         assert series[1][0] - series[0][0], timedelta(days=1)
 
@@ -443,7 +443,7 @@ class TestDateTime(unittest.TestCase):
         end = datetime.now(utc).replace(microsecond=0)
         start = end - timedelta(days=15)
 
-        series = [i for i in self.factory.time_series(start_date=start, end_date=end, tzinfo=start.tzinfo)]
+        series = list(self.factory.time_series(start_date=start, end_date=end, tzinfo=start.tzinfo))
         assert series[0][0] == start
 
     def test_unix_time(self):