Repository: joke2k/faker
Pull request #1989 (2024-02-06 21:09:31)

feat(uk_UA-lorem) Add uk_UA lorem provider
### Add uk_UA lorem provider and test




Patch:
diff --git a/.bumpversion.cfg b/.bumpversion.cfg
index e54e81fc..af3168e1 100644
--- a/.bumpversion.cfg
+++ b/.bumpversion.cfg
@@ -1,5 +1,5 @@
 [bumpversion]
-current_version = 22.7.0
+current_version = 23.0.0
 files = VERSION faker/__init__.py docs/conf.py
 commit = True
 tag = True
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 773b85aa..9ea54b4e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,9 @@
 ## Changelog
 
+### [v23.0.0 - 2024-02-06](https://github.com/joke2k/faker/compare/v22.7.0...v23.0.0)
+
+* Consistently throw an error if the start of a daterange is after the end. Thanks @prescod.
+
 ### [v22.7.0 - 2024-02-05](https://github.com/joke2k/faker/compare/v22.6.0...v22.7.0)
 
 * Add `uk_UA` automotive provider. Thanks @lozik.
diff --git a/VERSION b/VERSION
index 2062ac7e..84b76f4d 100644
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-22.7.0
+23.0.0
diff --git a/docs/conf.py b/docs/conf.py
index 2f06358c..a9893d9b 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -51,9 +51,9 @@ copyright = "2014-{0}, Daniele Faraglia".format(datetime.now().year)
 # built documents.
 #
 # The short X.Y version.
-version = "22.7.0"
+version = "23.0.0"
 # The full version, including alpha/beta/rc tags.
-release = "22.7.0"
+release = "23.0.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 f7e5b23d..ed47af8d 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 = "22.7.0"
+VERSION = "23.0.0"
 
 __all__ = ("Factory", "Generator", "Faker")
diff --git a/faker/providers/date_time/__init__.py b/faker/providers/date_time/__init__.py
index 14997342..752d437d 100644
--- a/faker/providers/date_time/__init__.py
+++ b/faker/providers/date_time/__init__.py
@@ -79,15 +79,15 @@ class Provider(BaseProvider):
     #       appropriate to generate random seconds with.
     if platform.system() == "Windows":
 
-        @property
-        def _rand_seconds(self) -> Callable[[int, int], int]:
-            return self.generator.random.randint
+        def _rand_seconds(self, start_datetime: int, end_datetime: int) -> float:
+            return self.generator.random.randint(start_datetime, end_datetime)
 
     else:
 
-        @property
-        def _rand_seconds(self) -> Callable[[float, float], float]:
-            return self.generator.random.uniform
+        def _rand_seconds(self, start_datetime: int, end_datetime: int) -> float:
+            if start_datetime > end_datetime:
+                raise ValueError("empty range for _rand_seconds: start datetime must be before than end datetime")
+            return self.generator.random.uniform(start_datetime, end_datetime)
 
     centuries: ElementsType[str] = [
         "I",
diff --git a/faker/providers/lorem/uk_UA/__init__.py b/faker/providers/lorem/uk_UA/__init__.py
new file mode 100644
index 00000000..a9ad816d
--- /dev/null
+++ b/faker/providers/lorem/uk_UA/__init__.py
@@ -0,0 +1,505 @@
+from typing import Dict
+
+from .. import Provider as LoremProvider
+
+
+class Provider(LoremProvider):
+    """Implement lorem provider for ``uk_UA`` locale."""
+    word_list = (
+        "увійти",
+        "монета",
+        "підкинути",
+        "бажання",
+        "іспит",
+        "податковий",
+        "витягувати",
+        "приятель",
+        "здригатися",
+        "купа",
+        "порт",
+        "точно",
+        "заплакати",
+        "хата",
+        "правління",
+        "художній",
+        "болісно",
+        "зображати",
+        "ліхтарик",
+        "міф",
+        "сумний",
+        "небезпека",
+        "міра",
+        "пастух",
+        "факультет",
+        "мигнути",
+        "польовий",
+        "інший",
+        "виражений",
+        "забирати",
+        "рот",
+        "народ",
+        "відповідність",
+        "тута",
+        "комунізм",
+        "рішення",
+        "плід",
+        "співрозмовник",
+        "обуритися",
+        "гідність",
+        "господь",
+        "болото",
+        "інфекція",
+        "голубчик",
+        "синок",
+        "простір",
+        "прощення",
+        "раніше",
+        "хотіти",
+        "ленінград",
+        "даль",
+        "розвинений",
+        "близько",
+        "більше",
+        "спорт",
+        "епоха",
+        "відповісти",
+        "звільнити",
+        "порада",
+        "прохід",
+        "палець",
+        "вчора",
+        "пристойний",
+        "яскраво",
+        "білизна",
+        "коваль",
+        "несподівано",
+        "вперед",
+        "зате",
+        "кільце",
+        "перед",
+        "мить",
+        "плавно",
+        "тютюн",
+        "число",
+        "вивчити",
+        "важкий",
+        "міркування",
+        "салон",
+        "ідея",
+        "що",
+        "світило",
+        "порода",
+        "сумнівний",
+        "бок",
+        "очко",
+        "незручно",
+        "радити",
+        "відділ",
+        "помовчати",
+        "вітати",
+        "пробувати",
+        "дошлий",
+        "сміятися",
+        "наполегливо",
+        "здригнутися",
+        "затягнутися",
+        "танцювати",
+        "пісенька",
+        "вибирати",
+        "правильний",
+        "намір",
+        "здалеку",
+        "запустити",
+        "насолода",
+        "щур",
+        "летіти",
+        "космос",
+        "радість",
+        "поїзд",
+        "знаходити",
+        "гуляти",
+        "гіркий",
+        "бочок",
+        "ніч",
+        "щастя",
+        "знищення",
+        "диявол",
+        "коробка",
+        "спасти",
+        "шкіра",
+        "провінція",
+        "прелесть",
+        "в'язниця",
+        "вечір",
+        "низький",
+        "виблискувати",
+        "темніти",
+        "сонце",
+        "гараж",
+        "червʼяк",
+        "дружно",
+        "настати",
+        "блін",
+        "степ",
+        "самостійно",
+        "крутий",
+        "картинка",
+        "навіщо",
+        "робочий",
+        "незвичайний",
+        "армійський",
+        "труп",
+        "ягода",
+        "близько",
+        "монета",
+        "природний",
+        "юний",
+        "район",
+        "прихований",
+        "зловити",
+        "будівництво",
+        "палата",
+        "мить",
+        "триста",
+        "штаб",
+        "ламати",
+        "можливо",
+        "полюбити",
+        "чоловічок",
+        "легко",
+        "почуття",
+        "струмок",
+        "кишеня",
+        "гроші",
+        "неправда",
+        "порівняння",
+        "груди",
+        "від'їзд",
+        "виникнення",
+        "степ",
+        "збудження",
+        "діловий",
+        "отже",
+        "рідкий",
+        "синок",
+        "художній",
+        "покоління",
+        "розстебнути",
+        "їжа",
+        "вчений",
+        "секунда",
+        "заспокоїтися",
+        "навряд",
+        "аж",
+        "вскакивать",
+        "мимо",
+        "падати",
+        "потягнутися",
+        "загроза",
+        "розгубитися",
+        "бігати",
+        "склянка",
+        "о",
+        "кпсс",
+        "нині",
+        "підлога",
+        "реклама",
+        "при",
+        "шкільний",
+        "прем'єра",
+        "дальній",
+        "потрясти",
+        "звільнення",
+        "покидати",
+        "наступати",
+        "жити",
+        "який",
+        "образа",
+        "командування",
+        "дівка",
+        "висловлюватися",
+        "головний",
+        "другий",
+        "князь",
+        "соціалістичний",
+        "головка",
+        "залучати",
+        "через",
+        "господь",
+        "результат",
+        "відзначити",
+        "адже",
+        "падаль",
+        "покидати",
+        "художній",
+        "правий",
+        "висіти",
+        "лапа",
+        "каюта",
+        "занадто",
+        "нервово",
+        "серйозний",
+        "зима",
+        "заробити",
+        "ефект",
+        "прірва",
+        "плід",
+        "щось",
+        "що-небудь",
+        "казна-хто",
+        "висіти",
+        "холодно",
+        "єдиний",
+        "викинути",
+        "похмуро",
+        "вигнати",
+        "вмирати",
+        "інший",
+        "космос",
+        "природа",
+        "функція",
+        "поставити",
+        "оборот",
+        "услати",
+        "черговий",
+        "медицина",
+        "функція",
+        "зарплата",
+        "витримати",
+        "розлад",
+        "адвокат",
+        "затримати",
+        "поява",
+        "інвалід",
+        "інтелектуальний",
+        "досліджено",
+        "мати"
+        "ліворуч",
+        "хлопець",
+        "мільярд",
+        "гіркий",
+        "трубка",
+        "подробиця",
+        "паща",
+        "незвичний",
+        "угодний",
+        "засунути",
+        "мета",
+        "заборонити",
+        "дрімати",
+        "розуміти",
+        "приходити",
+        "нарада",
+        "постійний",
+        "аналіз",
+        "терапія",
+        "приятель",
+        "процес",
+        "академік",
+        "метал",
+        "розвернутися",
+        "жорстокий",
+        "інтернет",
+        "яблуко",
+        "банда",
+        "зміна",
+        "колектив",
+        "похорон",
+        "пристрій",
+        "квапливий",
+        "розводити",
+        "промовчати",
+        "підземний",
+        "полум'я",
+        "редактор",
+        "теорія",
+        "олівець",
+        "упор",
+        "означати",
+        "метелик",
+        "чотири",
+        "століття",
+        "різноманітний",
+        "вітрина",
+        "ніж",
+        "команда",
+        "шолом",
+        "недолік",
+        "протягувати",
+        "за",
+        "метал",
+        "домогтися",
+        "доба",
+        "чітко",
+        "надати",
+        "тисяча",
+        "заспівати",
+        "бригада",
+        "дрібниця",
+        "виражений",
+        "перетнути",
+        "сходити",
+        "взагалі",
+        "рис",
+        "банк",
+        "бак",
+        "передо",
+        "призначити",
+        "важливий",
+        "правління",
+        "палиця",
+        "трясти",
+        "упустити",
+        "вітрина",
+        "основа",
+        "так",
+        "мʼята",
+        "пірʼя",
+        "перебивати",
+        "дихання",
+        "застосовуватися",
+        "червень",
+        "бетонний",
+        "уникати",
+        "благати",
+        "м'який",
+        "заява",
+        "конференція",
+        "встати",
+        "свіжий",
+        "супроводжуватися",
+        "ланцюжок",
+        "вираз",
+        "кут",
+        "черевик",
+        "лягати",
+        "інструкція",
+        "присісти",
+        "решітка",
+        "єврейський",
+        "поріг",
+        "зелений",
+        "кордон",
+        "ставити",
+        "сміливий",
+        "суглоб",
+        "роса",
+        "демократія",
+        "вивести",
+        "конструкція",
+        "задерти",
+        "багряний",
+        "військовий",
+        "направо",
+        "житель",
+        "товар",
+        "солома",
+        "ґазда",
+        "ґаздиня",
+        "ґудзик",
+        "неправда",
+        "матерія",
+        "командувач",
+        "кидати",
+        "закласти",
+        "ліловий",
+        "слати",
+        "гіркий",
+        "простір",
+        "провал",
+        "сміття",
+        "наштовхнутися",
+        "торгівля",
+        "монета",
+        "місце",
+        "спалити",
+        "брову",
+        "лівий",
+        "хліб",
+        "коричневий",
+        "подвірʼя"
+        "потім",
+        "червонй",
+        "пристрасть",
+        "виднітися",
+        "розкішний",
+        "спосіб",
+        "багаття",
+        "заклад",
+        "пропадати",
+        "занадто",
+        "п'ятеро",
+        "програміст",
+        "кора",
+        "хлопчисько",
+        "тьмяний",
+        "несподіваний",
+        "танцювати",
+        "безглуздий",
+        "здригнутися",
+        "скинути",
+        "прошепотіти",
+        "безпорадний",
+        "рота",
+        "пісня",
+        "тривога",
+        "деякий",
+        "термін",
+        "пити",
+        "колишній",
+        "натиснути",
+        "видимо",
+        "валюта",
+        "набір",
+        "боєць",
+        "райком",
+        "новий",
+        "ковзати",
+        "керівник",
+        "вовк",
+        "зрідка",
+        "зрозумілий",
+        "пропаганда",
+        "зупинити",
+        "виконувати",
+        "хід",
+        "пані",
+        "друкувати",
+        "командир",
+        "знімати",
+        "страта",
+        "ручка",
+        "камінчик",
+        "нога",
+        "нестерпний",
+        "спорт",
+        "тривога",
+        "уточнити",
+        "актриса",
+        "повністю",
+        "покинути",
+        "блискучий",
+        "мотоцикл",
+        "дорогий",
+        "вказаний",
+        "ремінь",
+        "присвятити",
+        "один",
+        "а",
+        "їсти"
+        "діставати",
+        "господиня",
+        "шкарпетка",
+        "написати",
+        "єврейський",
+        "заклик",
+        "збільшуватися",
+        "байдужий",
+        "грати",
+        "співати",
+        "й",
+        "фахівець",
+        "купа-невеличка",
+    )
+
+    parts_of_speech: Dict[str, tuple] = {}


Test patch:
diff --git a/tests/providers/test_date_time.py b/tests/providers/test_date_time.py
index 797a3694..f6fba540 100644
--- a/tests/providers/test_date_time.py
+++ b/tests/providers/test_date_time.py
@@ -193,11 +193,17 @@ class TestDateTime(unittest.TestCase):
         assert self.fake.iso8601(tzinfo=utc, sep=" ")[10] == " "
         assert self.fake.iso8601(tzinfo=utc, sep="_")[10] == "_"
 
-    @pytest.mark.skipif(not sys.platform.startswith("win"), reason="windows does not support sub second precision")
+    @pytest.mark.skipif(
+        not sys.platform.startswith("win"),
+        reason="windows does not support sub second precision",
+    )
     def test_iso8601_fractional_seconds_win(self):
         assert len(self.fake.iso8601()) == 19
 
-    @pytest.mark.skipif(sys.platform.startswith("win"), reason="non windows does support sub second precision")
+    @pytest.mark.skipif(
+        sys.platform.startswith("win"),
+        reason="non windows does support sub second precision",
+    )
     def test_iso8601_fractional_seconds_non_win(self):
         assert len(self.fake.iso8601()) == 26
 
@@ -234,6 +240,10 @@ class TestDateTime(unittest.TestCase):
         assert datetime_start <= random_date
         assert datetime_end >= random_date
 
+    def test_date_time_between_dates_with_no_date_overlap(self):
+        with pytest.raises(ValueError):
+            self.fake.date_time_between_dates("-1y", "-2y")
+
     def test_date_time_between_dates_with_tzinfo(self):
         timestamp_start = random.randint(0, 2000000000)
         timestamp_end = timestamp_start + 1
@@ -545,13 +555,19 @@ class TestDateTime(unittest.TestCase):
             if platform.system() != "Windows":
                 del os.environ["TZ"]
 
-    @pytest.mark.skipif(not sys.platform.startswith("win"), reason="windows does not support sub second precision")
+    @pytest.mark.skipif(
+        not sys.platform.startswith("win"),
+        reason="windows does not support sub second precision",
+    )
     def test_unix_time_win(self):
         unix_time = self.fake.unix_time()
         assert isinstance(unix_time, float)
         assert unix_time % 1 == 0.0
 
-    @pytest.mark.skipif(sys.platform.startswith("win"), reason="non windows does support sub second precision")
+    @pytest.mark.skipif(
+        sys.platform.startswith("win"),
+        reason="non windows does support sub second precision",
+    )
     def test_unix_time_non_win(self):
         unix_time = self.fake.unix_time()
         assert isinstance(unix_time, float)
diff --git a/tests/providers/test_lorem.py b/tests/providers/test_lorem.py
index 40761a27..84bc53eb 100644
--- a/tests/providers/test_lorem.py
+++ b/tests/providers/test_lorem.py
@@ -10,6 +10,7 @@ from faker.providers.lorem.de_DE import Provider as DeDeLoremProvider
 from faker.providers.lorem.en_US import Provider as EnUsLoremProvider
 from faker.providers.lorem.fa_IR import Provider as FaIrLoremProvider
 from faker.providers.lorem.nl_BE import Provider as NlBeLoremProvider
+from faker.providers.lorem.uk_UA import Provider as UkUaLoremProvider
 
 
 class TestLoremProvider:
@@ -645,3 +646,71 @@ class TestNlBe:
         for _ in range(num_samples):
             words = faker.words(num_words)
             assert all(isinstance(word, str) and word in NlBeLoremProvider.word_list for word in words)
+
+
+class TestUkUa:
+    """Test uk_UA lorem provider"""
+    word_list = [word.lower() for word in UkUaLoremProvider.word_list]
+
+    def test_paragraph(self, faker, num_samples):
+        num_sentences = 10
+        for _ in range(num_samples):
+            paragraph = faker.paragraph(nb_sentences=num_sentences)
+            assert isinstance(paragraph, str)
+            words = paragraph.replace(".", "").split()
+            assert all(word.lower() in self.word_list for word in words)
+
+    def test_paragraphs(self, faker, num_samples):
+        num_paragraphs = 5
+        for _ in range(num_samples):
+            paragraphs = faker.paragraphs(nb=num_paragraphs)
+            for paragraph in paragraphs:
+                assert isinstance(paragraph, str)
+                words = paragraph.replace(".", "").split()
+                assert all(word.lower() in self.word_list for word in words)
+
+    def test_sentence(self, faker, num_samples):
+        num_words = 10
+        for _ in range(num_samples):
+            sentence = faker.sentence(nb_words=num_words)
+            assert isinstance(sentence, str)
+            words = sentence.replace(".", "").split()
+            assert all(word.lower() in self.word_list for word in words)
+
+    def test_sentences(self, faker, num_samples):
+        num_sentences = 5
+        for _ in range(num_samples):
+            sentences = faker.sentences(nb=num_sentences)
+            for sentence in sentences:
+                assert isinstance(sentence, str)
+                words = sentence.replace(".", "").split()
+                assert all(word.lower() in self.word_list for word in words)
+
+    def test_text(self, faker, num_samples):
+        num_chars = 25
+        for _ in range(num_samples):
+            text = faker.text(max_nb_chars=num_chars)
+            assert isinstance(text, str)
+            words = re.sub(r"[.\n]+", " ", text).split()
+            assert all(word.lower() in self.word_list for word in words)
+
+    def test_texts(self, faker, num_samples):
+        num_texts = 5
+        num_chars = 25
+        for _ in range(num_samples):
+            texts = faker.texts(max_nb_chars=num_chars, nb_texts=num_texts)
+            for text in texts:
+                assert isinstance(text, str)
+                words = re.sub(r"[.\n]+", " ", text).split()
+                assert all(word.lower() in self.word_list for word in words)
+
+    def test_word(self, faker, num_samples):
+        for _ in range(num_samples):
+            word = faker.word()
+            assert isinstance(word, str) and word in UkUaLoremProvider.word_list
+
+    def test_words(self, faker, num_samples):
+        num_words = 5
+        for _ in range(num_samples):
+            words = faker.words(num_words)
+            assert all(isinstance(word, str) and word in UkUaLoremProvider.word_list for word in words)