Repository: joke2k/faker
Pull request #1075 (2019-12-09 09:31:01)

Add provider methods for dsv files
### What does this change

Allows generation of delimiter-separated values with a mostly same API to `csv.writer`

Related to #603 


Patch:
diff --git a/faker/providers/address/en_CA/__init__.py b/faker/providers/address/en_CA/__init__.py
index 66c4b97c..f558103c 100644
--- a/faker/providers/address/en_CA/__init__.py
+++ b/faker/providers/address/en_CA/__init__.py
@@ -5,6 +5,11 @@ from ..en import Provider as AddressProvider
 
 class Provider(AddressProvider):
 
+    #  Source: https://www.canadapost.ca/tools/pg/manual/PGaddress-e.asp#1449294
+    #
+    #  'W' and 'Z' are valid in non-initial position (easily verified in the
+    #  wild), but online official documentation is hard to find, so just ignore
+    #  them for now.
     postal_code_letters = (
         'A', 'B', 'C', 'E', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'R', 'S',
         'T', 'V', 'X', 'Y',
@@ -267,13 +272,20 @@ class Provider(AddressProvider):
     provinces = (
         'Alberta', 'British Columbia', 'Manitoba', 'New Brunswick',
         'Newfoundland and Labrador', 'Northwest Territories',
-        'New Brunswick', 'Nova Scotia', 'Nunavut', 'Ontario',
+        'Nova Scotia', 'Nunavut', 'Ontario',
         'Prince Edward Island', 'Quebec', 'Saskatchewan', 'Yukon Territory')
 
     provinces_abbr = (
         'AB', 'BC', 'MB', 'NB', 'NL', 'NT', 'NS',
         'NU', 'ON', 'PE', 'QC', 'SK', 'YT')
 
+    provinces_postcode_prefixes = {
+        'NL': ['A'], 'NS': ['B'], 'PE': ['C'], 'NB': ['E'],
+        'QC': ['G', 'H', 'J'], 'ON': ['K', 'L', 'M', 'N', 'P'],
+        'MB': ['R'], 'SK': ['S'], 'AB': ['T'], 'BC': ['V'],
+        'NU': ['X'], 'NT': ['X'], 'YT': ['Y'],
+    }
+
     city_formats = (
         '{{city_prefix}} {{first_name}}{{city_suffix}}',
         '{{city_prefix}} {{first_name}}',
@@ -316,16 +328,44 @@ class Provider(AddressProvider):
         """
         return self.random_element(self.postal_code_letters)
 
-    def postcode(self):
+    def _postcode_replace(self, postal_code_format):
         """
         Replaces all question mark ('?') occurrences with a random letter
-        from postal_code_formats then passes result to
-        numerify to insert numbers
+        from given postal_code_format, then passes result to numerify to insert
+        numbers
         """
         temp = re.sub(r'\?',
                       lambda x: self.postal_code_letter(),
-                      self.random_element(self.postal_code_formats))
+                      postal_code_format)
         return self.numerify(temp)
 
+    def postcode(self):
+        """
+        Returns a random postcode
+        """
+        return self._postcode_replace(
+            self.random_element(self.postal_code_formats))
+
+    def postcode_in_province(self, province_abbr=None):
+        """
+        Returns a random postcode within the provided province abbreviation
+        """
+        if province_abbr is None:
+            province_abbr = self.random_element(self.provinces_abbr)
+
+        if province_abbr in self.provinces_abbr:
+            postal_code_format = self.random_element(self.postal_code_formats)
+            postal_code_format = postal_code_format.replace(
+                '?',
+                self.generator.random_element(
+                    self.provinces_postcode_prefixes[province_abbr]),
+                1)
+            return self._postcode_replace(postal_code_format)
+        else:
+            raise Exception('Province Abbreviation not found in list')
+
+    def postalcode_in_province(self, province_abbr=None):
+        return self.postcode_in_province(province_abbr)
+
     def postalcode(self):
         return self.postcode()
diff --git a/faker/providers/address/ru_RU/__init__.py b/faker/providers/address/ru_RU/__init__.py
index 0ebe1c43..5eb6463c 100644
--- a/faker/providers/address/ru_RU/__init__.py
+++ b/faker/providers/address/ru_RU/__init__.py
@@ -1,128 +1,152 @@
 # coding=utf-8
 from __future__ import unicode_literals
-
 from .. import Provider as AddressProvider
 
 
 class Provider(AddressProvider):
-    street_suffixes = ['ул.']
+    city_suffixes = ('ск', 'вль', 'град', 'поль', 'ин', 'ов', 'бург')
+    street_suffixes = ('ул.', 'алл.', 'наб.', 'пр.', 'пер.', 'бул.', 'ш.')
+    region_suffixes = ('респ.', 'обл.', 'край', 'АО')
     city_formats = ('{{city_prefix}} {{city_name}}', )
-    street_name_formats = ('{{street_suffix}} {{street_title}}', )
-    street_address_formats = ('{{street_name}}, д. {{building_number}}', )
+    street_address_formats = ('{{street_name}}, д. {{building_number}}',
+                              '{{street_name}}, д. {{building_number}} к. {{building_number}}',
+                              '{{street_name}}, д. {{building_number}} стр. {{building_number}}')
     address_formats = ('{{city}}, {{street_address}}, {{postcode}}', )
-    postcode_formats = ('######', )
+    postcode_formats = ('######',)
+    building_number_formats = ('###', '##', '#', '#/#')
 
-    city_prefixes = (
-        'г.', 'п.', 'к.', 'с.', 'д.', 'клх', 'ст.',
-    )
+    city_prefixes = ('г.', 'п.', 'к.', 'с.', 'д.', 'клх', 'ст.')
+
+    street_suffixes_masc = ('пр.', 'пер.', 'бул.')
+    street_suffixes_fem = ('ул.', 'алл.', 'наб.')
+    street_suffixes_neu = ('ш.', )
 
     street_titles = (
         'Советская', 'Молодежная', 'Центральная', 'Школьная', 'Новая',
-        'Садовая', 'Лесная', 'Набережная', 'Ленина', 'Мира', 'Октябрьская',
-        'Зеленая', 'Комсомольская', 'Заречная', 'Первомайская', 'Гагарина',
-        'Полевая', 'Луговая', 'Пионерская', 'Кирова', 'Юбилейная', 'Северная',
-        'Пролетарская', 'Степная', 'Пушкина', 'Калинина', 'Южная', 'Колхозная',
-        'Рабочая', 'Солнечная', 'Железнодорожная', 'Восточная', 'Заводская',
-        'Чапаева', 'Нагорная', 'Строителей', 'Береговая', 'Победы', 'Горького',
-        'Кооперативная', 'Красноармейская', 'Совхозная', 'Речная',
-        'Спортивная', 'Озерная', 'Строительная', 'Парковая', 'Чкалова',
-        'Мичурина', 'Подгорная', 'Дружбы', 'Почтовая', 'Партизанская',
-        'Вокзальная', 'Лермонтова', 'Свободы', 'Дорожная', 'Дачная',
-        'Маяковского', 'Западная', 'Фрунзе', 'Дзержинского', 'Московская',
-        'Свердлова', 'Некрасова', 'Гоголя', 'Красная', 'Трудовая', 'Шоссейная',
-        'Чехова', 'Коммунистическая', 'Труда', 'Комарова', 'Матросова',
-        'Островского', 'Сосновая', 'Клубная', 'Куйбышева', 'Крупской',
-        'Березовая', 'Карла Маркса', '8 Марта', 'Больничная',
-        'Интернациональная', 'Суворова', 'Цветочная', 'Трактовая',
-        'Ломоносова', 'Горная', 'Космонавтов', 'Энергетиков', 'Шевченко',
-        'Весенняя', 'Механизаторов', 'Коммунальная', '40 лет Победы',
-        'Майская', 'Энгельса', 'Привокзальная', 'Таежная', 'Транспортная',
-        'Овражная', 'Светлая', '1 Мая', 'Вишневая', 'Чернышевского',
-        'Урицкого', 'Ключевая', 'Ворошилова', 'Тургенева', 'Толстого',
-        'Песчаная', 'Ленинградская', 'Профсоюзная', 'Буденного',
-        'Орджоникидзе', 'Герцена', 'Верхняя', 'Ленинская', 'Кирпичная',
-        'Мостовая', 'Щорса', 'Станционная', 'Луначарского', 'Энтузиастов',
-        'Титова', 'Лазо', '50 лет Октября', 'Уральская', 'Линейная',
-        'Пугачева', 'Володарского', 'Кутузова', 'Фабричная', 'Чайковского',
-        'Магистральная', 'Сибирская', 'Крестьянская', 'Мелиораторов',
-        'Российская', 'Новоселов', 'Тихая', 'Широкая', 'Нижняя', 'Белинского',
-        'Тельмана', 'Тимирязева', 'Котовского', 'Народная', '60 лет Октября',
-        'Промышленная', 'Кольцевая', 'Есенина', 'К.Маркса', 'Дальняя',
-        '40 лет Октября', 'Крылова', 'Декабристов', 'Базарная', 'Целинная',
-        'Родниковая', '70 лет Октября', 'Фурманова', 'Революционная',
-        'Социалистическая', 'Звездная', 'Студенческая', 'Гайдара', 'Мирная',
-        'Терешковой', 'Кубанская', 'Гаражная', 'Фестивальная', 'Гражданская',
-        'Песочная', 'Сиреневая', 'Ватутина', 'Сельская', 'Кузнечная',
-        'Пушкинская', 'Крайняя', 'Коммунаров', 'Гвардейская', 'Веселая',
-        'Загородная', 'Олимпийская', 'Гастелло', 'Жданова', 'Радищева',
-        'Нефтяников', 'Осипенко', 'Приозерная', 'Рябиновая', 'Заозерная',
-        'Нахимова', 'Боровая', 'Жукова', 'Павлова', 'Степана Разина',
-        'Урожайная', 'Попова', 'Торговая', 'Жуковского', 'Королева', 'Донская',
-        'Пограничная', 'Огородная', 'Пригородная', 'Грибоедова', 'Стадионная',
-        'Менделеева', 'Достоевского', 'Репина', 'Виноградная', 'Циолковского',
-        'Воровского', 'Киевская', 'Максима Горького', 'Революции',
-        'Индустриальная', 'Красногвардейская', 'Волжская', 'Кошевого',
-        'Пархоменко', 'Свободная', 'Кедровая', 'Подлесная', 'Полярная',
-        'Раздольная', 'Карьерная', 'Мельничная', 'Серова', 'Украинская',
-        'Шахтерская', 'Запрудная', 'Элеваторная', 'Театральная', 'Добролюбова',
-        'Геологическая', '50 лет Победы', 'Болотная', 'Красина', 'Коминтерна',
-        'Придорожная', '30 лет Победы', 'Кленовая', 'Разина', 'Черняховского',
-        'Казачья', 'Ветеранов', 'Малая', 'Пирогова', 'Льва Толстого',
-        'Геологов', 'Тукая', 'Морская', 'Волгоградская', 'Димитрова',
-        'Средняя', 'М.Горького', 'Безымянная', 'Краснофлотская', 'Братская',
-        'Розы Люксембург', 'Тенистая', 'Учительская', 'Маркса', 'Кавказская',
-        'Ушакова', 'Короткая', 'Деповская', 'Амурская', 'Сенная', 'Поселковая',
-        '9 Мая', 'Юности', 'Короленко', 'Прудовая', 'Шолохова', 'Депутатская',
-        'Авиационная', '50 лет ВЛКСМ', 'Черемушки', 'Кольцова', 'Плеханова',
-        'Аэродромная', 'Большая', 'Приморская', 'Алтайская', 'Макаренко',
-        'Глинки', 'Специалистов', 'Халтурина', 'Морозова', 'Тополиная',
-        'Ростовская', 'Тракторная', 'Коммуны', 'Мелиоративная', 'Ольховая',
-        'Славянская', 'Красных Партизан', 'Зои Космодемьянской', 'Карбышева',
-        'Баумана', 'Радужная', 'Марта 8', 'Правды', 'Маркса Карла',
-        'Краснодарская', 'Фадеева', '60 лет СССР', 'Стахановская',
-        'Ярославская', 'Челюскинцев', 'Коллективная', 'Ангарская',
-        'Олега Кошевого', 'Новостройка', 'Шмидта', 'Кузнецова', 'Локомотивная',
-        'Войкова', 'Панфилова', 'Ягодная', 'Смоленская', 'Тепличная',
-        'Карла Либкнехта', 'Парижской Коммуны', 'Водопроводная',
-        'Республиканская', 'Автомобилистов', 'Космодемьянской', 'Осенняя',
-        'Байкальская', 'Седова', 'Саратовская', 'Казанская', 'Блюхера',
-        'Воронежская', 'Демьяна Бедного', 'Брянская', 'Производственная',
-        'Спартака', 'Техническая', 'Енисейская', 'Николаева', 'Бабушкина',
-        'Севастопольская', 'Окружная', 'Октября', 'Щетинкина', 'Орловская',
-        'Хуторская', 'Тупиковая', 'Каштановая', 'Омская', 'Гончарова',
-        'Привольная', 'Щербакова', 'Курортная', 'Ивановская', 'Выгонная',
-        'Крымская', 'Путейская', 'Азина', 'Проезжая', 'Сурикова',
-        'Краснознаменная', 'Одесская', '9 Января', 'Подстанция',
-        'Логовая', 'Высокая', 'Ясная', 'Портовая', 'Снежная', 'Санаторная',
-        'Союзная', 'Волкова', 'Ульяновская', 'Никитина', 'Сахалинская',
-        'Горняцкая', 'Рылеева', 'Прибрежная', 'Химиков', 'Рыбацкая',
-        'Полтавская', 'Тамбовская', 'Красноярская', 'Новосельская',
-        'Проточная', 'Черноморская', 'Курчатова', 'Минская', 'Микрорайон',
-        'Главная', 'Докучаева', 'Вольная', 'Просвещения', 'Смирнова',
-        'Хвойная', 'Космическая', 'Моховая', 'Макарова', 'Курская',
-        'Курганная', 'Угловая', 'Камская', 'Инженерная', 'Лесозаводская',
-        'Астраханская', 'Иванова', 'Л.Толстого', 'Гафури', 'Высоцкого',
-        'Белорусская', 'Бажова', 'Кочубея', 'Заовражная', 'Азовская',
-        'Ручейная', 'Леонова', 'Волочаевская', 'Надежды', 'Ставропольская',
-        'Металлистов', 'Вавилова', 'Слободская', 'Ульянова', 'Тульская',
-        'Харьковская', 'Петровская', 'Павлика Морозова', 'Владимирская',
-        'Семашко', 'Высоковольтная', 'Шаумяна', 'Чайкиной', 'Лазурная',
-        'Ермака', 'Дорожников', 'Покровская', 'Новгородская', 'Ленская',
-        'Советской Армии', 'Сплавная', 'Ударная', 'Калужская', 'Монтажников',
-        'Прудная', 'Шишкина', 'Краснопартизанская', 'Ореховая', 'Таманская',
-        'Иркутская', 'Металлургов', 'Отрадная', 'Беляева', 'Большевистская',
-        'Троицкая', 'Лесхозная', 'Дружба', 'Васильковая', 'Серафимовича',
-        'Ильича', 'Мусы Джалиля', 'Механическая', 'Невского', 'Путевая',
-        'Клары Цеткин', 'Кузнецкая', 'Физкультурная', 'Леваневского',
-        'Водников', 'Вахитова', 'Станиславского', 'Черемуховая', 'Советов',
-        'Флотская', 'Угольная', 'Восьмого Марта', 'Пожарского', 'Папанина',
-        'Просторная', 'Поперечная', 'Городская', 'Победа', '8-е Марта',
-        'Абрикосовая', 'Бульварная', 'Прохладная', 'Томская', 'Энергетическая',
-        'Литейная', 'Медицинская', 'Заливная', 'Журавлева', 'Бригадная',
-        'Культуры', 'Мая 1', 'Минина', 'Машиностроителей', 'Детская', 'ДОС',
-        'Тюленина', 'Запорожская', 'Дальневосточная', 'Громова', 'О.Кошевого',
-        'Балтийская', 'Р.Люксембург', 'Февральская', 'Толбухина', 'Лунная',
-        'Дарвина', 'З.Космодемьянской', 'Высотная', 'Рязанская', 'Малиновая',
+        'Садовая', 'Лесная', 'Набережная', 'Октябрьская', 'Зеленая',
+        'Комсомольская', 'Заречная', 'Первомайская', 'Полевая', 'Луговая',
+        'Пионерская', 'Юбилейная', 'Северная', 'Пролетарская', 'Степная',
+        'Южная', 'Колхозная', 'Рабочая', 'Солнечная', 'Железнодорожная',
+        'Восточная', 'Заводская', 'Нагорная', 'Береговая', 'Кооперативная',
+        'Красноармейская', 'Совхозная', 'Речная', 'Спортивная', 'Озерная',
+        'Строительная', 'Парковая', 'Подгорная', 'Почтовая', 'Партизанская',
+        'Вокзальная', 'Дорожная', 'Дачная', 'Западная', 'Московская',
+        'Красная', 'Трудовая', 'Шоссейная', 'Коммунистическая', 'Сосновая',
+        'Клубная', 'Березовая', 'Больничная', 'Интернациональная', 'Цветочная',
+        'Трактовая', 'Горная', 'Весенняя', 'Коммунальная', 'Майская',
+        'Привокзальная', 'Таежная', 'Транспортная', 'Овражная', 'Светлая',
+        'Вишневая', 'Ключевая', 'Песчаная', 'Ленинградская', 'Профсоюзная',
+        'Верхняя', 'Ленинская', 'Кирпичная', 'Мостовая', 'Станционная',
+        'Уральская', 'Линейная', 'Фабричная', 'Магистральная', 'Сибирская',
+        'Крестьянская', 'Российская', 'Тихая', 'Широкая', 'Нижняя',
+        'Народная', 'Промышленная', 'Кольцевая', 'Дальняя', 'Базарная',
+        'Целинная', 'Родниковая', 'Революционная', 'Социалистическая', 'Звездная',
+        'Студенческая', 'Мирная', 'Кубанская', 'Гаражная', 'Фестивальная',
+        'Гражданская', 'Песочная', 'Сиреневая', 'Сельская', 'Кузнечная',
+        'Пушкинская', 'Крайняя', 'Гвардейская', 'Веселая', 'Загородная',
+        'Олимпийская', 'Приозерная', 'Рябиновая', 'Заозерная', 'Боровая',
+        'Урожайная', 'Торговая', 'Донская', 'Пограничная', 'Огородная',
+        'Пригородная', 'Стадионная', 'Виноградная', 'Киевская', 'Индустриальная',
+        'Красногвардейская', 'Волжская', 'Свободная', 'Кедровая', 'Подлесная',
+        'Полярная', 'Раздольная', 'Карьерная', 'Мельничная', 'Украинская',
+        'Шахтерская', 'Запрудная', 'Элеваторная', 'Театральная', 'Геологическая',
+        'Болотная', 'Придорожная', 'Кленовая', 'Казачья', 'Малая',
+        'Морская', 'Волгоградская', 'Средняя', 'Безымянная', 'Краснофлотская',
+        'Братская', 'Тенистая', 'Учительская', 'Кавказская', 'Короткая',
+        'Деповская', 'Амурская', 'Сенная', 'Поселковая', 'Прудовая',
+        'Депутатская', 'Авиационная', 'Аэродромная', 'Большая', 'Приморская',
+        'Алтайская', 'Тополиная', 'Ростовская', 'Тракторная', 'Мелиоративная',
+        'Ольховая', 'Славянская', 'Радужная', 'Краснодарская', 'Стахановская',
+        'Ярославская', 'Коллективная', 'Ангарская', 'Локомотивная', 'Ягодная',
+        'Смоленская', 'Тепличная', 'Водопроводная', 'Республиканская', 'Осенняя',
+        'Байкальская', 'Саратовская', 'Казанская', 'Воронежская', 'Брянская',
+        'Производственная', 'Техническая', 'Енисейская', 'Севастопольская', 'Окружная',
+        'Орловская', 'Хуторская', 'Тупиковая', 'Каштановая', 'Омская',
+        'Привольная', 'Курортная', 'Ивановская', 'Выгонная', 'Крымская',
+        'Путейская', 'Проезжая', 'Краснознаменная', 'Одесская', 'Логовая',
+        'Высокая', 'Ясная', 'Портовая', 'Снежная', 'Санаторная',
+        'Союзная', 'Ульяновская', 'Сахалинская', 'Горняцкая', 'Прибрежная',
+        'Рыбацкая', 'Полтавская', 'Тамбовская', 'Красноярская', 'Новосельская',
+        'Проточная', 'Черноморская', 'Минская', 'Главная', 'Вольная',
+        'Хвойная', 'Космическая', 'Моховая', 'Курская', 'Курганная',
+        'Угловая', 'Камская', 'Инженерная', 'Лесозаводская', 'Астраханская',
+        'Белорусская', 'Заовражная', 'Азовская', 'Ручейная', 'Волочаевская',
+        'Ставропольская', 'Слободская', 'Тульская', 'Харьковская', 'Петровская',
+        'Владимирская', 'Высоковольтная', 'Лазурная', 'Покровская', 'Новгородская',
+        'Ленская', 'Сплавная', 'Ударная', 'Калужская', 'Прудная',
+        'Краснопартизанская', 'Ореховая', 'Таманская', 'Иркутская', 'Отрадная',
+        'Большевистская', 'Троицкая', 'Лесхозная', 'Васильковая', 'Механическая',
+        'Путевая', 'Кузнецкая', 'Физкультурная', 'Черемуховая', 'Флотская',
+        'Угольная', 'Просторная', 'Поперечная', 'Городская', 'Абрикосовая',
+        'Бульварная', 'Прохладная', 'Томская', 'Энергетическая', 'Литейная',
+        'Медицинская', 'Заливная', 'Бригадная', 'Детская', 'Запорожская',
+        'Дальневосточная', 'Балтийская', 'Февральская', 'Лунная', 'Высотная',
+        'Рязанская', 'Малиновая',
+
+    )
+
+    street_titles_noflex = (
+        'Ленина', 'Мира', 'Гагарина', 'Кирова', 'Пушкина', 'Калинина',
+        'Чапаева', 'Строителей', 'Победы', 'Горького', 'Чкалова',
+        'Мичурина', 'Дружбы', 'Лермонтова', 'Свободы', 'Маяковского',
+        'Фрунзе', 'Дзержинского', 'Свердлова', 'Некрасова', 'Гоголя',
+        'Чехова', 'Труда', 'Комарова', 'Матросова', 'Островского',
+        'Куйбышева', 'Крупской', 'Карла Маркса', '8 Марта', 'Суворова',
+        'Ломоносова', 'Космонавтов', 'Энергетиков', 'Шевченко', 'Механизаторов',
+        '40 лет Победы', 'Энгельса', 'Чернышевского', 'Урицкого', 'Ворошилова',
+        'Тургенева', 'Толстого', 'Буденного', 'Орджоникидзе', 'Герцена',
+        'Щорса', 'Луначарского', 'Энтузиастов', 'Титова', 'Лазо',
+        '50 лет Октября', 'Пугачева', 'Володарского', 'Кутузова', 'Чайковского',
+        'Мелиораторов', 'Новоселов', 'Белинского', 'Тельмана', 'Тимирязева',
+        'Котовского', '60 лет Октября', 'Есенина', 'К.Маркса', '40 лет Октября',
+        'Крылова', 'Декабристов', '70 лет Октября', 'Фурманова', 'Гайдара',
+        'Терешковой', 'Ватутина', 'Коммунаров', 'Гастелло', 'Жданова',
+        'Радищева', 'Нефтяников', 'Осипенко', 'Нахимова', 'Жукова',
+        'Павлова', 'Степана Разина', 'Попова', 'Жуковского', 'Королева',
+        'Грибоедова', 'Менделеева', 'Достоевского', 'Репина', 'Циолковского',
+        'Воровского', 'Максима Горького', 'Революции', 'Кошевого', 'Пархоменко',
+        'Серова', 'Добролюбова', '50 лет Победы', 'Красина', 'Коминтерна',
+        '30 лет Победы', 'Разина', 'Черняховского', 'Ветеранов', 'Пирогова',
+        'Льва Толстого', 'Геологов', 'Димитрова', 'М.Горького', 'Розы Люксембург',
+        'Маркса', 'Ушакова', 'Юности', 'Короленко', 'Шолохова',
+        '50 лет ВЛКСМ', 'Черемушки', 'Кольцова', 'Плеханова', 'Макаренко',
+        'Глинки', 'Специалистов', 'Халтурина', 'Морозова', 'Коммуны',
+        'Красных Партизан', 'Зои Космодемьянской', 'Карбышева', 'Баумана', 'Марта 8',
+        'Правды', 'Маркса Карла', 'Фадеева', '60 лет СССР', 'Челюскинцев',
+        'Олега Кошевого', 'Новостройка', 'Шмидта', 'Кузнецова', 'Войкова',
+        'Панфилова', 'Карла Либкнехта', 'Парижской Коммуны', 'Автомобилистов', 'Космодемьянской',
+        'Седова', 'Блюхера', 'Демьяна Бедного', 'Спартака', 'Николаева',
+        'Бабушкина', 'Октября', 'Щетинкина', 'Гончарова', 'Щербакова',
+        'Азина', 'Сурикова', '9 Января', 'Подстанция', 'Волкова',
+        'Никитина', 'Рылеева', 'Химиков', 'Курчатова', 'Микрорайон',
+        'Докучаева', 'Просвещения', 'Смирнова', 'Макарова', 'Иванова',
+        'Л.Толстого', 'Гафури', 'Высоцкого', 'Бажова', 'Кочубея',
+        'Леонова', 'Надежды', 'Металлистов', 'Вавилова', 'Ульянова',
+        'Павлика Морозова', 'Семашко', 'Шаумяна', 'Чайкиной', 'Ермака',
+        'Дорожников', 'Советской Армии', 'Монтажников', 'Шишкина', 'Металлургов',
+        'Беляева', 'Дружба', 'Серафимовича', 'Ильича', 'Мусы Джалиля',
+        'Невского', 'Клары Цеткин', 'Леваневского', 'Водников', 'Вахитова',
+        'Станиславского', 'Советов', 'Восьмого Марта', 'Пожарского', 'Папанина',
+        'Победа', '8-е Марта', 'Журавлева', 'Культуры', 'Мая 1',
+        'Минина', 'Машиностроителей', 'ДОС', 'Тюленина', 'Громова',
+        'О.Кошевого', 'Р.Люксембург', 'Толбухина', 'Дарвина', 'З.Космодемьянской',
+        '1 Мая', '9 мая', 'Тукая',
+    )
+
+    street_titles_irregular_masc = (
+        'Полевой', 'Луговой', 'Степной', 'Заводской', 'Береговой',
+        'Речной', 'Трудовой', 'Ключевой', 'Мостовой', 'Кольцевой',
+        'Боровой', 'Донской', 'Морской', 'Сенной', 'Прудовой',
+        'Большрй', 'Окружной', 'Хуторской', 'Логовой', 'Моховой',
+        'Угловой', 'Слободской', 'Путевой', 'Городской', 'Рабочий',
+        'Верхний', 'Тихий', 'Широкий', 'Нижний', 'Дальний',
+        'Крайний', 'Казачий', 'Весенний', 'Средний', 'Короткий',
+        'Осенний', 'Проезжий', 'Высокий',
+    )
+
+    street_titles_irregular_neu = (
+        'Весеннее', 'Верхнее', 'Нижнее', 'Среднее', 'Дальнее',
+        'Крайнее', 'Казачье', 'Рабочее', 'Осеннее', 'Проезжее',
     )
 
     city_names = (
@@ -253,11 +277,122 @@ class Provider(AddressProvider):
         'Ясный (Оренб.)', 'Яхрома', 'Яшалта', 'Яшкуль',
     )
 
+    # https://en.wikipedia.org/wiki/Federal_subjects_of_Russia
+    region_republics = (
+        'Адыгея', 'Алтай', 'Башкортостан', 'Бурятия', 'Дагестан',
+        'Ингушетия', 'Кабардино-Балкария', 'Калмыкия', 'Карачаево-Черкесия', 'Карелия',
+        'Коми', 'Крым', 'Марий-Эл', 'Мордовия', 'Саха (Якутия)',
+        'Северная Осетия - Алания', 'Татарстан', 'Тыва', 'Удмуртия', 'Хакасия',
+        'Чечня', 'Чувашия',
+    )
+
+    region_krai = (
+        'Алтайский', 'Забайкальский', 'Камчатский', 'Краснодарский', 'Красноярский',
+        'Пермский', 'Приморский', 'Ставропольский', 'Хабаровский',
+    )
+
+    region_oblast = (
+        'Амурская', 'Архангельская', 'Астраханская', 'Белгородская', 'Брянская',
+        'Владимирская', 'Волгоградская', 'Вологодская', 'Воронежская', 'Ивановская',
+        'Иркутская', 'Калининградская', 'Калужская', 'Кемеровская', 'Кировская',
+        'Костромская', 'Курганская', 'Курская', 'Ленинградская', 'Липецкая',
+        'Магаданская', 'Московская', 'Мурманская', 'Нижегородская', 'Новгородская',
+        'Новосибирская', 'Омская', 'Оренбургская', 'Орловская', 'Пензенская',
+        'Псковская', 'Ростовская', 'Рязанская', 'Самарская', 'Саратовская',
+        'Сахалинская', 'Свердловская', 'Смоленская', 'Тамбовская', 'Тверская',
+        'Томская', 'Тульская', 'Тюменская', 'Ульяновская', 'Челябинская',
+        'Ярославская',
+    )
+
+    region_ao = (
+        'Еврейская', 'Ханты-Мансийский', 'Чукотский', 'Ямало-Ненецкий', 'Ненецкий',
+    )
+
+    countries = (
+        'Австралия', 'Австрия', 'Азербайджан', 'Албания',
+        'Алжир', 'Ангола', 'Андорра', 'Антигуа и Барбуда',
+        'Аргентина', 'Армения', 'Афганистан', 'Багамские Острова',
+        'Бангладеш', 'Барбадос', 'Бахрейн', 'Белоруссия', 'Белиз',
+        'Бельгия', 'Бенин', 'Болгария', 'Боливия', 'Босния и Герцеговина',
+        'Ботсвана', 'Бразилия', 'Бруней', 'Буркина-Фасо', 'Бурунди',
+        'Бутан', 'Вануату', 'Великобритания', 'Венгрия', 'Венесуэла',
+        'Восточный Тимор', 'Вьетнам', 'Габон', 'Гаити', 'Гайана',
+        'Гамбия', 'Гана', 'Гватемала', 'Гвинея', 'Гвинея-Бисау', 'Германия',
+        'Гондурас', 'Гренада', 'Греция', 'Грузия', 'Дания', 'Джибути',
+        'Доминика', 'Доминиканская Республика', 'Египет', 'Замбия',
+        'Зимбабве', 'Израиль', 'Индия', 'Индонезия', 'Иордания',
+        'Ирак', 'Иран', 'Ирландия', 'Исландия', 'Испания', 'Италия',
+        'Йемен', 'Кабо-Верде', 'Казахстан', 'Камбоджа', 'Камерун',
+        'Канада', 'Катар', 'Кения', 'Кипр', 'Киргизия', 'Кирибати',
+        'Китай', 'Колумбия', 'Коморы', 'Республика Конго',
+        'Демократическая Республика Конго', 'КНДР', 'Республика Корея',
+        'Коста-Рика', 'Кот-д’Ивуар', 'Куба', 'Кувейт', 'Лаос', 'Латвия',
+        'Лесото', 'Либерия', 'Ливан', 'Ливия', 'Литва', 'Лихтенштейн',
+        'Люксембург', 'Маврикий', 'Мавритания', 'Мадагаскар', 'Малави',
+        'Малайзия', 'Мали', 'Мальдивы', 'Мальта', 'Марокко', 'Маршалловы Острова',
+        'Мексика', 'Мозамбик', 'Молдавия', 'Монако', 'Монголия', 'Мьянма',
+        'Намибия', 'Науру', 'Непал', 'Нигер', 'Нигерия', 'Нидерланды',
+        'Никарагуа', 'Новая Зеландия', 'Норвегия', 'ОАЭ', 'Оман', 'Пакистан',
+        'Палау', 'Панама', 'Папуа', 'Парагвай', 'Перу', 'Польша', 'Португалия',
+        'Россия', 'Руанда', 'Румыния', 'Сальвадор', 'Самоа', 'Сан-Марино',
+        'Сан-Томе и Принсипи', 'Саудовская Аравия', 'Северная Македония',
+        'Сейшельские Острова', 'Сенегал', 'Сент-Винсент и Гренадины',
+        'Сент-Китс и Невис', 'Сент-Люсия', 'Сербия', 'Сингапур', 'Сирия',
+        'Словакия', 'Словения', 'США', 'Соломоновы Острова', 'Сомали',
+        'Судан', 'Суринам', 'Сьерра-Леоне', 'Таджикистан', 'Таиланд',
+        'Танзания', 'Того', 'Тонга', 'Тринидад и Тобаго', 'Тувалу',
+        'Тунис', 'Туркмения', 'Турция', 'Уганда', 'Узбекистан', 'Украина',
+        'Уругвай', 'Федеративные Штаты Микронезии', 'Фиджи', 'Филиппины',
+        'Финляндия', 'Франция', 'Хорватия', 'Центральноафриканская Республика',
+        'Чад', 'Черногория', 'Чехия', 'Чили', 'Швейцария', 'Швеция',
+        'Шри-Ланка', 'Эквадор', 'Экваториальная Гвинея', 'Эритрея',
+        'Эсватини', 'Эстония', 'Эфиопия', 'ЮАР', 'Южный Судан', 'Ямайка', 'Япония',
+    )
+
     def city_prefix(self):
         return self.random_element(self.city_prefixes)
 
     def city_name(self):
         return self.random_element(self.city_names)
 
+    def country(self):
+        return self.random_element(self.countries)
+
+    def region(self):
+        regions_suffix = self.random_element(self.region_suffixes)
+        region_name = ''
+        result = region_name + ' ' + regions_suffix
+        if regions_suffix == 'респ.':
+            region_name = self.random_element(self.region_republics)
+            result = regions_suffix + ' ' + region_name
+        elif regions_suffix == 'край':
+            region_name = self.random_element(self.region_krai)
+        elif regions_suffix == 'обл.':
+            region_name = self.random_element(self.region_oblast)
+        elif regions_suffix == 'АО':
+            region_name = self.random_element(self.region_ao)
+        return result
+
+    def street_suffix(self):
+        return self.random_element(self.street_suffixes)
+
     def street_title(self):
-        return self.random_element(self.street_titles)
+        return self.random_element(self.street_titles + self.street_titles_noflex)
+
+    def street_name(self):
+        suffix = self.street_suffix()
+        street = self.street_title()
+        stem = street[:-2]
+        inflexion = ''
+        result = street
+        if street not in self.street_titles_noflex:
+            if not (suffix in self.street_suffixes_fem and (stem in self.street_titles_irregular_masc
+                                                            or stem in self.street_titles_irregular_neu)):
+                if suffix in self.street_suffixes_masc:
+                    result = stem + inflexion
+                    inflexion = 'ый'
+                    if stem.endswith('ск') or stem.endswith('цк'):
+                        inflexion = 'ий'
+                elif suffix in self.street_suffixes_neu:
+                    inflexion = 'ое'
+        return suffix + ' ' + result
diff --git a/faker/providers/automotive/ru_RU/__init__.py b/faker/providers/automotive/ru_RU/__init__.py
index f56f3107..e3b4e5cf 100644
--- a/faker/providers/automotive/ru_RU/__init__.py
+++ b/faker/providers/automotive/ru_RU/__init__.py
@@ -8,7 +8,9 @@ from .. import Provider as AutomotiveProvider
 class Provider(AutomotiveProvider):
 
     # https://en.wikipedia.org/wiki/Vehicle_registration_plates_of_Russia
-    license_plate_letters = ('A', 'B', 'E', 'M', 'Н', 'О', 'Р', 'С', 'Т', 'У', 'Х')
+    license_plate_letters = ('A', 'B', 'E', 'K', 'M', 'Н', 'О', 'Р', 'С', 'Т', 'У', 'Х')
+    # https://ru.wikipedia.org/wiki/Категории_транспортных_средств
+    vehicle_categories = ('M', 'A', 'A1', 'B', 'B1', 'BE', 'C', 'C1', 'C1E', 'CE', 'D', 'D1', 'DE', 'Tm', 'Tb')
 
     license_plate_suffix = (
         # Republic of Adygea
@@ -198,13 +200,30 @@ class Provider(AutomotiveProvider):
         '94',
     )
 
-    license_plate_number = (
-        '##%'
+    license_plate_formats = (
+        '{{plate_letter}}###{{plate_letter}}{{plate_letter}}{{plate_suffix}}',
+        '{{plate_letter}}{{plate_letter}}###{{plate_suffix}}',
+        '{{plate_letter}}{{plate_letter}}####{{plate_suffix}}',
+        '####{{plate_letter}}{{plate_letter}}{{plate_suffix}}',
+        '###CD#{{plate_suffix}}',
+        '###D###{{plate_suffix}}',
+        '###T###{{plate_suffix}}',
+        '{{plate_letter}}####{{plate_suffix}}',
+        '###{{plate_letter}}{{plate_suffix}}',
+        '####{{plate_letter}}{{plate_suffix}}',
+        'K{{plate_letter}}{{plate_letter}}###{{plate_suffix}}',
+        'C{{plate_letter}}{{plate_letter}}###{{plate_suffix}}',
     )
 
     def license_plate(self):
-        return self.random_element(self.license_plate_letters) + \
-               self.numerify(self.generator.parse(self.license_plate_number)) + \
-               self.random_element(self.license_plate_letters) + \
-               self.random_element(self.license_plate_letters) + \
-               self.random_element(self.license_plate_suffix)
+        pattern = self.random_element(self.license_plate_formats)
+        return self.generator.parse(pattern)
+
+    def plate_letter(self):
+        return self.random_element(self.license_plate_letters)
+
+    def plate_suffix(self):
+        return self.random_element(self.license_plate_letters)
+
+    def vehicle_category(self):
+        return self.random_element(self.vehicle_categories)
diff --git a/faker/providers/misc/__init__.py b/faker/providers/misc/__init__.py
index 0247ecdd..068977a1 100644
--- a/faker/providers/misc/__init__.py
+++ b/faker/providers/misc/__init__.py
@@ -1,6 +1,7 @@
 # coding=utf-8
 
 from __future__ import unicode_literals
+import csv
 import hashlib
 import io
 import string
@@ -16,6 +17,8 @@ from .. import BaseProvider
 
 localized = True
 
+csv.register_dialect('faker-csv', csv.excel, quoting=csv.QUOTE_ALL)
+
 
 class Provider(BaseProvider):
     def boolean(self, chance_of_getting_true=50):
@@ -243,3 +246,106 @@ class Provider(BaseProvider):
                 tar_handle.addfile(tarinfo, file_buffer)
                 file_buffer.close()
         return tar_buffer.getvalue()
+
+    def dsv(self, dialect='faker-csv', header=None,
+            data_columns=('{{name}}', '{{address}}'),
+            num_rows=10, include_row_ids=False, **fmtparams):
+        """
+        Generic method that returns delimiter-separated values
+
+        This method's signature is mostly the same as the signature of csv.writer with some
+        additional keyword arguments for controlling data generation. Dialects and formatting
+        parameters are passed to the csv.writer object during its instantiation.
+
+        :param dialect: Name of a registered csv.Dialect subclass, defaults to 'faker-csv'
+                        which is a subclass of csv.excel with full quoting enabled
+        :param header: List of strings that will serve as the header row if supplied
+        :param data_columns: List of string tokens that will be passed to the pystr_format
+                             provider method during data generation
+        :param num_rows: Number of rows of data to generate
+        :param include_row_ids: True to include a sequential row ID column
+        :param fmtparams: Formatting parameters expected by csv.writer
+        :return: Delimiter-separated values, csv by default
+        """
+
+        if not isinstance(num_rows, int) or num_rows <= 0:
+            raise ValueError('`num_rows` must be a positive integer')
+        if not isinstance(data_columns, (list, tuple)):
+            raise TypeError('`data_columns` must be a tuple or a list')
+        if header is not None:
+            if not isinstance(header, (list, tuple)):
+                raise TypeError('`header` must be a tuple or a list')
+            if len(header) != len(data_columns):
+                raise ValueError('`header` and `data_columns` must have matching lengths')
+
+        dsv_buffer = six.StringIO()
+        writer = csv.writer(dsv_buffer, dialect=dialect, **fmtparams)
+
+        if header:
+            if include_row_ids:
+                header = list(header)
+                header.insert(0, 'ID')
+            writer.writerow(header)
+
+        for row_num in range(1, num_rows + 1):
+            if six.PY2:
+                row = [self.generator.pystr_format(column).encode('utf-8') for column in data_columns]
+            else:
+                row = [self.generator.pystr_format(column) for column in data_columns]
+            if include_row_ids:
+                row.insert(0, str(row_num))
+
+            writer.writerow(row)
+
+        dsv = dsv_buffer.getvalue()
+        if six.PY2:
+            return dsv.decode('utf-8')
+        return dsv
+
+    def csv(self, header=None, data_columns=('{{name}}', '{{address}}'), num_rows=10, include_row_ids=False):
+        """
+        Helper method that returns comma-separated values using the faker-csv dialect
+
+        :param header: List of strings that will serve as the header row if supplied
+        :param data_columns: List of strings expected by the pystr_format provider method
+        :param num_rows: Number of rows of data to generate
+        :param include_row_ids: True to include a sequential row ID column
+        :return: Comma-separated values
+        """
+
+        return self.dsv(
+            header=header, data_columns=data_columns, num_rows=num_rows,
+            include_row_ids=include_row_ids, delimiter=str(','),
+        )
+
+    def tsv(self, header=None, data_columns=('{{name}}', '{{address}}'), num_rows=10, include_row_ids=False):
+        """
+        Helper method that returns tab-separated values using the faker-csv dialect
+
+        :param header: List of strings that will serve as the header row if supplied
+        :param data_columns: List of strings expected by the pystr_format provider method
+        :param num_rows: Number of rows of data to generate
+        :param include_row_ids: True to include a sequential row ID column
+        :return: Tab-separated values
+        """
+
+        return self.dsv(
+            header=header, data_columns=data_columns, num_rows=num_rows,
+            include_row_ids=include_row_ids, delimiter=str('\t'),
+        )
+
+    def psv(self, header=None, data_columns=('{{name}}', '{{address}}'), num_rows=10, include_row_ids=False):
+        """
+        Helper method that returns pipe-separated values using the faker-csv dialect
+
+        :param header: List of strings that will serve as the header row if supplied
+        :param data_columns: List of strings expected by the pystr_format provider method
+        :param num_rows: Number of rows of data to generate
+        :param include_row_ids: True to include a sequential row ID column
+        :return: Pipe-separated values
+        """
+
+        return self.dsv(
+            header=header, data_columns=data_columns, num_rows=num_rows,
+            include_row_ids=include_row_ids, delimiter=str('|'),
+        )


Test patch:
diff --git a/tests/providers/test_address.py b/tests/providers/test_address.py
index e1d6a009..18cb2638 100644
--- a/tests/providers/test_address.py
+++ b/tests/providers/test_address.py
@@ -275,22 +275,33 @@ class TestEnCA(unittest.TestCase):
 
     def setUp(self):
         self.factory = Faker('en_CA')
+        self.valid_postcode_letter_re = r'[{}]'.format(
+            ''.join(EnCaProvider.postal_code_letters))
+        self.valid_postcode_re = r"{0}[0-9]{0} ?[0-9]{0}[0-9]".format(
+            self.valid_postcode_letter_re)
 
     def test_postcode(self):
         for _ in range(100):
             postcode = self.factory.postcode()
-            assert re.match(r"[A-Z][0-9][A-Z] ?[0-9][A-Z][0-9]",
-                            postcode)
+            assert re.match(self.valid_postcode_re, postcode)
+
+    def test_postcode_in_province(self):
+        for province_abbr in EnCaProvider.provinces_abbr:
+            code = self.factory.postcode_in_province(province_abbr)
+            assert code[0] in EnCaProvider.provinces_postcode_prefixes[
+                province_abbr]
+
+        with self.assertRaises(Exception):
+            self.factory.postcode_in_province('XX')
 
     def test_postalcode(self):
         for _ in range(100):
             postalcode = self.factory.postalcode()
-            assert re.match(r"[A-Z][0-9][A-Z] ?[0-9][A-Z][0-9]",
-                            postalcode)
+            assert re.match(self.valid_postcode_letter_re, postalcode)
 
     def test_postal_code_letter(self):
         postal_code_letter = self.factory.postal_code_letter()
-        assert re.match(r"[A-Z]", postal_code_letter)
+        assert re.match(self.valid_postcode_letter_re, postal_code_letter)
 
     def test_province(self):
         province = self.factory.province()
@@ -1099,3 +1110,42 @@ class TestTlPh(TestEnPh):
 
     def setup_factory(self):
         self.factory = Faker('tl_PH')
+
+
+class TestRuRU(unittest.TestCase):
+    """ Tests addresses in the ru_RU locale """
+
+    def setUp(self):
+        self.factory = Faker('ru_RU')
+
+    def test_city_name(self):
+        city = self.factory.city_name()
+        assert isinstance(city, string_types)
+
+    def test_country(self):
+        country = self.factory.country()
+        assert isinstance(country, string_types)
+
+    def test_region(self):
+        region = self.factory.region()
+        assert isinstance(region, string_types)
+
+    def test_postcode(self):
+        postcode = self.factory.postcode()
+        assert isinstance(postcode, string_types)
+
+    def test_city_prefix(self):
+        city_prefix = self.factory.city_prefix()
+        assert isinstance(city_prefix, string_types)
+
+    def test_street_suffix(self):
+        street_suffix = self.factory.street_suffix()
+        assert isinstance(street_suffix, string_types)
+
+    def test_street_title(self):
+        street_title = self.factory.street_title()
+        assert isinstance(street_title, string_types)
+
+    def test_street_name(self):
+        street_name = self.factory.street_name()
+        assert isinstance(street_name, string_types)
diff --git a/tests/providers/test_automotive.py b/tests/providers/test_automotive.py
index c52bffcc..88d2ac98 100644
--- a/tests/providers/test_automotive.py
+++ b/tests/providers/test_automotive.py
@@ -113,3 +113,17 @@ class TestTlPh(TestEnPh):
 
     def setup_factory(self):
         self.factory = Faker('tl_PH')
+
+
+class TestRuRU(unittest.TestCase):
+
+    def setUp(self):
+        self.factory = Faker('ru_RU')
+
+    def test_ru_RU_plate_format(self):
+        plate = self.factory.license_plate()
+        assert isinstance(plate, string_types)
+
+    def test_vehicle_category(self):
+        category = self.factory.vehicle_category()
+        assert isinstance(category, string_types)
diff --git a/tests/providers/test_misc.py b/tests/providers/test_misc.py
index f4769010..ef41c690 100644
--- a/tests/providers/test_misc.py
+++ b/tests/providers/test_misc.py
@@ -1,13 +1,20 @@
 # coding=utf-8
 
 from __future__ import unicode_literals
+import csv
 import io
+import itertools
 import tarfile
 import unittest
 import uuid
 import zipfile
 import six
 
+try:
+    from unittest.mock import patch
+except ImportError:
+    from mock import patch
+
 from faker import Faker
 
 
@@ -354,3 +361,152 @@ class TestMisc(unittest.TestCase):
                     uncompressed_size=uncompressed_size, num_files=num_files,
                     min_file_size=min_file_size, compression=compression,
                 )
+
+    def test_dsv_with_invalid_values(self):
+        with self.assertRaises(ValueError):
+            self.factory.dsv(num_rows='1')
+
+        with self.assertRaises(ValueError):
+            self.factory.dsv(num_rows=0)
+
+        with self.assertRaises(TypeError):
+            self.factory.dsv(header=None, data_columns=1)
+
+        with self.assertRaises(TypeError):
+            self.factory.dsv(header=1, data_columns=['???'])
+
+        with self.assertRaises(ValueError):
+            self.factory.dsv(header=['Column 1', 'Column 2'], data_columns=['???'])
+
+    def test_dsv_no_header(self):
+        Faker.seed(0)
+        data_columns = ['????', '?????']
+        for _ in range(10):
+            num_rows = self.factory.random.randint(1, 1000)
+            dsv = self.factory.dsv(header=None, data_columns=data_columns, num_rows=num_rows)
+            reader = csv.reader(six.StringIO(dsv), dialect='faker-csv')
+
+            # Verify each row has correct number of columns
+            for row in reader:
+                assert len(row) == len(data_columns)
+
+            # Verify correct number of lines read
+            assert reader.line_num == num_rows
+
+    def test_dsv_with_valid_header(self):
+        Faker.seed(0)
+        header = ['Column 1', 'Column 2']
+        data_columns = ['????', '?????']
+        for _ in range(10):
+            num_rows = self.factory.random.randint(1, 1000)
+            dsv = self.factory.dsv(header=header, data_columns=data_columns, num_rows=num_rows)
+            reader = csv.reader(six.StringIO(dsv), dialect='faker-csv')
+
+            # Verify each row has correct number of columns
+            for row in reader:
+                assert len(row) == len(data_columns)
+
+            # Verify correct number of lines read (additional header row)
+            assert reader.line_num == num_rows + 1
+
+    def test_dsv_with_row_ids(self):
+        Faker.seed(0)
+        data_columns = ['????', '?????']
+        for _ in range(10):
+            counter = 0
+            num_rows = self.factory.random.randint(1, 1000)
+            dsv = self.factory.dsv(
+                header=None, data_columns=data_columns,
+                num_rows=num_rows, include_row_ids=True,
+            )
+            reader = csv.reader(six.StringIO(dsv), dialect='faker-csv')
+
+            # Verify each row has correct number of columns
+            # and row ids increment correctly
+            for row in reader:
+                assert len(row) == len(data_columns) + 1
+                counter += 1
+                assert row[0] == str(counter)
+
+            # Verify correct number of lines read
+            assert reader.line_num == num_rows
+
+    def test_dsv_data_columns(self):
+        num_rows = 10
+        data_columns = ['{{name}}', '#??-####', '{{address}}', '{{phone_number}}']
+        with patch.object(self.factory['en_US'], 'pystr_format') as mock_pystr_format:
+            mock_pystr_format.assert_not_called()
+            self.factory.dsv(data_columns=data_columns, num_rows=num_rows)
+
+            # pystr_format will be called for each data column and each row
+            calls = mock_pystr_format.call_args_list
+            assert len(calls) == num_rows * len(data_columns)
+
+            # Elements in data_columns will be used in sequence per row generation cycle
+            column_cycle = itertools.cycle(data_columns)
+            for args, kwargs in calls:
+                assert args[0] == next(column_cycle)
+                assert kwargs == {}
+
+    def test_dsv_csvwriter_kwargs(self):
+        data_keys = ['header', 'data_columns', 'num_rows', 'include_row_ids']
+        test_kwargs = {
+            'dialect': 'excel',
+            'header': ['Column 1', 'Column 2'],
+            'data_columns': ['????', '?????'],
+            'num_rows': 5,
+            'include_row_ids': True,
+            'delimiter': ';',
+            'invalid_kwarg': 'invalid_value',
+        }
+        with patch('faker.providers.misc.csv.writer') as mock_writer:
+            mock_writer.assert_not_called()
+            self.factory.dsv(**test_kwargs)
+            assert mock_writer.call_count == 1
+
+            # Remove all data generation kwargs
+            for key in data_keys:
+                del test_kwargs[key]
+
+            # Verify csv.writer was called with the remaining kwargs
+            for args, kwargs in mock_writer.call_args_list:
+                assert kwargs == test_kwargs
+
+    def test_csv_helper_method(self):
+        kwargs = {
+            'header': ['Column 1', 'Column 2'],
+            'data_columns': ['????', '?????'],
+            'num_rows': 5,
+            'include_row_ids': True,
+        }
+        with patch('faker.providers.misc.Provider.dsv') as mock_dsv:
+            mock_dsv.assert_not_called()
+            self.factory.csv(**kwargs)
+            kwargs['delimiter'] = ','
+            mock_dsv.assert_called_once_with(**kwargs)
+
+    def test_tsv_helper_method(self):
+        kwargs = {
+            'header': ['Column 1', 'Column 2'],
+            'data_columns': ['????', '?????'],
+            'num_rows': 5,
+            'include_row_ids': True,
+        }
+        with patch('faker.providers.misc.Provider.dsv') as mock_dsv:
+            mock_dsv.assert_not_called()
+            self.factory.tsv(**kwargs)
+            kwargs['delimiter'] = '\t'
+            mock_dsv.assert_called_once_with(**kwargs)
+
+    def test_psv_helper_method(self):
+        kwargs = {
+            'header': ['Column 1', 'Column 2'],
+            'data_columns': ['????', '?????'],
+            'num_rows': 5,
+            'include_row_ids': True,
+        }
+        with patch('faker.providers.misc.Provider.dsv') as mock_dsv:
+            mock_dsv.assert_not_called()
+            self.factory.psv(**kwargs)
+            kwargs['delimiter'] = '|'
+            mock_dsv.assert_called_once_with(**kwargs)