From 12c68de55129df666279125b6c58ed54d8c587d2 Mon Sep 17 00:00:00 2001 From: Johnny Nazario Date: Tue, 27 Sep 2022 18:18:56 -0700 Subject: [PATCH 1/3] Add enum provider --- faker/documentor.py | 15 +++++++-- faker/providers/enum/__init__.py | 43 ++++++++++++++++++++++++++ tests/providers/test_enum.py | 52 ++++++++++++++++++++++++++++++++ tests/utils/test_utils.py | 1 + 4 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 faker/providers/enum/__init__.py create mode 100644 tests/providers/test_enum.py diff --git a/faker/documentor.py b/faker/documentor.py index 5164bf7cf5..b1fbc07c88 100644 --- a/faker/documentor.py +++ b/faker/documentor.py @@ -1,13 +1,21 @@ import inspect import warnings +from enum import Enum, auto -from typing import Any, Dict, List, Optional, Tuple, Union +from typing import Any, Dict, List, Optional, Tuple, Union, Type from .generator import Generator from .providers import BaseProvider from .proxy import Faker +class FakerEnum(Enum): + """Required for faker.providers.enum""" + + A = auto + B = auto + + class Documentor: def __init__(self, generator: Union[Generator, Faker]) -> None: """ @@ -52,7 +60,7 @@ def get_provider_formatters( continue arguments = [] - faker_args: List[str] = [] + faker_args: List[Union[str, Type[Enum]]] = [] faker_kwargs = {} if name == "binary": @@ -65,6 +73,9 @@ def get_provider_formatters( } ) + if name == "enum": + faker_args = [FakerEnum] + if with_args: # retrieve all parameter argspec = inspect.getfullargspec(method) diff --git a/faker/providers/enum/__init__.py b/faker/providers/enum/__init__.py new file mode 100644 index 0000000000..0658c4790a --- /dev/null +++ b/faker/providers/enum/__init__.py @@ -0,0 +1,43 @@ +from enum import Enum +from typing import TypeVar, Type, List, Iterable, cast + + +from .. import BaseProvider +from ...exceptions import BaseFakerException + +localized = False + +TEnum = TypeVar("TEnum", bound=Enum) + + +class EmptyEnumException(BaseFakerException): + pass + + +class Provider(BaseProvider): + """ + Implements a Provider for Enum types. + """ + + def enum(self, enum_cls: Type[TEnum]) -> TEnum: + """ + Returns a random enum of the provided input `Enum` type. + + :param enum_cls: The `Enum` type to produce the value for. + :returns: A randomly selected enum value. + """ + + if enum_cls is None: + raise ValueError("'enum_cls' cannot be None") + + if not issubclass(enum_cls, Enum): + raise TypeError(f"'enum_cls' must be an Enum type") + + members: List[TEnum] = list(cast(Iterable[TEnum], enum_cls)) + + if len(members) < 1: + raise EmptyEnumException( + f"The provided Enum: '{enum_cls.__name__}' has no members." + ) + + return self.random_element(members) diff --git a/tests/providers/test_enum.py b/tests/providers/test_enum.py new file mode 100644 index 0000000000..e1b4f7180e --- /dev/null +++ b/tests/providers/test_enum.py @@ -0,0 +1,52 @@ +import pytest + +from enum import Enum, auto + +from faker.providers.enum import EmptyEnumException + + +class _TestEnumWithNoElements(Enum): + pass + + +class _TestEnumWithSingleElement(Enum): + Single = auto + + +class _TestEnum(Enum): + A = auto + B = auto + C = auto + + +class TestEnumProvider: + + num_samples = 100 + + def test_enum(self, faker, num_samples): + # (1/3) ** 100 ~ 1.94e-48 probability of this test failing because a specific + # value was not sampled + for _ in range(num_samples): + actual = faker.enum(_TestEnum) + assert actual in (_TestEnum.A, _TestEnum.B, _TestEnum.C) + + def test_enum_single(self, faker): + assert faker.enum(_TestEnumWithSingleElement) == _TestEnumWithSingleElement.Single + assert faker.enum(_TestEnumWithSingleElement) == _TestEnumWithSingleElement.Single + + def test_empty_enum_raises(self, faker): + with pytest.raises( + EmptyEnumException, + match="The provided Enum: '_TestEnumWithNoElements' has no members.", + ): + faker.enum(_TestEnumWithNoElements) + + def test_none_raises(self, faker): + with pytest.raises(ValueError): + faker.enum(None) + + def test_incorrect_type_raises(self, faker): + not_an_enum_type = type("NotAnEnumType") + with pytest.raises(TypeError): + faker.enum(not_an_enum_type) + diff --git a/tests/utils/test_utils.py b/tests/utils/test_utils.py index 617d76e4ac..d321ca2b3e 100644 --- a/tests/utils/test_utils.py +++ b/tests/utils/test_utils.py @@ -86,6 +86,7 @@ def test_find_available_providers(self): "faker.providers.credit_card", "faker.providers.currency", "faker.providers.date_time", + "faker.providers.enum", "faker.providers.file", "faker.providers.geo", "faker.providers.internet", From b33e3f64e98014f2cc7db7bb0c8ebec117734a20 Mon Sep 17 00:00:00 2001 From: Flavio Curella <89607+fcurella@users.noreply.github.com> Date: Tue, 11 Oct 2022 11:34:14 -0500 Subject: [PATCH 2/3] Move Enum provider into Python provider --- faker/documentor.py | 2 +- faker/providers/enum/__init__.py | 43 ------------------------------ faker/providers/python/__init__.py | 32 +++++++++++++++++++++- tests/providers/test_enum.py | 2 +- tests/utils/test_utils.py | 1 - 5 files changed, 33 insertions(+), 47 deletions(-) delete mode 100644 faker/providers/enum/__init__.py diff --git a/faker/documentor.py b/faker/documentor.py index b1fbc07c88..3bfa3df574 100644 --- a/faker/documentor.py +++ b/faker/documentor.py @@ -2,7 +2,7 @@ import warnings from enum import Enum, auto -from typing import Any, Dict, List, Optional, Tuple, Union, Type +from typing import Any, Dict, List, Optional, Tuple, Type, Union from .generator import Generator from .providers import BaseProvider diff --git a/faker/providers/enum/__init__.py b/faker/providers/enum/__init__.py deleted file mode 100644 index 0658c4790a..0000000000 --- a/faker/providers/enum/__init__.py +++ /dev/null @@ -1,43 +0,0 @@ -from enum import Enum -from typing import TypeVar, Type, List, Iterable, cast - - -from .. import BaseProvider -from ...exceptions import BaseFakerException - -localized = False - -TEnum = TypeVar("TEnum", bound=Enum) - - -class EmptyEnumException(BaseFakerException): - pass - - -class Provider(BaseProvider): - """ - Implements a Provider for Enum types. - """ - - def enum(self, enum_cls: Type[TEnum]) -> TEnum: - """ - Returns a random enum of the provided input `Enum` type. - - :param enum_cls: The `Enum` type to produce the value for. - :returns: A randomly selected enum value. - """ - - if enum_cls is None: - raise ValueError("'enum_cls' cannot be None") - - if not issubclass(enum_cls, Enum): - raise TypeError(f"'enum_cls' must be an Enum type") - - members: List[TEnum] = list(cast(Iterable[TEnum], enum_cls)) - - if len(members) < 1: - raise EmptyEnumException( - f"The provided Enum: '{enum_cls.__name__}' has no members." - ) - - return self.random_element(members) diff --git a/faker/providers/python/__init__.py b/faker/providers/python/__init__.py index ceaa024f64..b8e1620fe0 100644 --- a/faker/providers/python/__init__.py +++ b/faker/providers/python/__init__.py @@ -3,13 +3,20 @@ import sys import warnings +from enum import Enum from decimal import Decimal -from typing import Any, Dict, Iterable, Iterator, List, Optional, Set, Tuple, Type, Union, no_type_check +from typing import Any, Dict, Iterable, Iterator, List, Optional, Set, Tuple, Type, TypeVar, Union, cast, no_type_check from .. import BaseProvider, ElementsType +from ...exceptions import BaseFakerException TypesNames = List[str] TypesSpec = Union[List[Type], Tuple[Type, ...]] +TEnum = TypeVar("TEnum", bound=Enum) + + +class EmptyEnumException(BaseFakerException): + pass class Provider(BaseProvider): @@ -417,3 +424,26 @@ def pystruct( }, } return types, d, nd + + def enum(self, enum_cls: Type[TEnum]) -> TEnum: + """ + Returns a random enum of the provided input `Enum` type. + + :param enum_cls: The `Enum` type to produce the value for. + :returns: A randomly selected enum value. + """ + + if enum_cls is None: + raise ValueError("'enum_cls' cannot be None") + + if not issubclass(enum_cls, Enum): + raise TypeError(f"'enum_cls' must be an Enum type") + + members: List[TEnum] = list(cast(Iterable[TEnum], enum_cls)) + + if len(members) < 1: + raise EmptyEnumException( + f"The provided Enum: '{enum_cls.__name__}' has no members." + ) + + return self.random_element(members) diff --git a/tests/providers/test_enum.py b/tests/providers/test_enum.py index e1b4f7180e..4e7d63db44 100644 --- a/tests/providers/test_enum.py +++ b/tests/providers/test_enum.py @@ -2,7 +2,7 @@ from enum import Enum, auto -from faker.providers.enum import EmptyEnumException +from faker.providers.python import EmptyEnumException class _TestEnumWithNoElements(Enum): diff --git a/tests/utils/test_utils.py b/tests/utils/test_utils.py index d321ca2b3e..617d76e4ac 100644 --- a/tests/utils/test_utils.py +++ b/tests/utils/test_utils.py @@ -86,7 +86,6 @@ def test_find_available_providers(self): "faker.providers.credit_card", "faker.providers.currency", "faker.providers.date_time", - "faker.providers.enum", "faker.providers.file", "faker.providers.geo", "faker.providers.internet", From 31e9f4dcbc5f300537ea2384b731cee805e94791 Mon Sep 17 00:00:00 2001 From: Flavio Curella <89607+fcurella@users.noreply.github.com> Date: Tue, 11 Oct 2022 11:40:09 -0500 Subject: [PATCH 3/3] lint code --- faker/documentor.py | 2 +- faker/providers/python/__init__.py | 10 ++++------ tests/providers/test_enum.py | 5 ++--- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/faker/documentor.py b/faker/documentor.py index 3bfa3df574..78aff9898e 100644 --- a/faker/documentor.py +++ b/faker/documentor.py @@ -1,7 +1,7 @@ import inspect import warnings -from enum import Enum, auto +from enum import Enum, auto from typing import Any, Dict, List, Optional, Tuple, Type, Union from .generator import Generator diff --git a/faker/providers/python/__init__.py b/faker/providers/python/__init__.py index b8e1620fe0..28128635a1 100644 --- a/faker/providers/python/__init__.py +++ b/faker/providers/python/__init__.py @@ -3,12 +3,12 @@ import sys import warnings -from enum import Enum from decimal import Decimal +from enum import Enum from typing import Any, Dict, Iterable, Iterator, List, Optional, Set, Tuple, Type, TypeVar, Union, cast, no_type_check -from .. import BaseProvider, ElementsType from ...exceptions import BaseFakerException +from .. import BaseProvider, ElementsType TypesNames = List[str] TypesSpec = Union[List[Type], Tuple[Type, ...]] @@ -437,13 +437,11 @@ def enum(self, enum_cls: Type[TEnum]) -> TEnum: raise ValueError("'enum_cls' cannot be None") if not issubclass(enum_cls, Enum): - raise TypeError(f"'enum_cls' must be an Enum type") + raise TypeError("'enum_cls' must be an Enum type") members: List[TEnum] = list(cast(Iterable[TEnum], enum_cls)) if len(members) < 1: - raise EmptyEnumException( - f"The provided Enum: '{enum_cls.__name__}' has no members." - ) + raise EmptyEnumException(f"The provided Enum: '{enum_cls.__name__}' has no members.") return self.random_element(members) diff --git a/tests/providers/test_enum.py b/tests/providers/test_enum.py index 4e7d63db44..e5161a1854 100644 --- a/tests/providers/test_enum.py +++ b/tests/providers/test_enum.py @@ -1,7 +1,7 @@ -import pytest - from enum import Enum, auto +import pytest + from faker.providers.python import EmptyEnumException @@ -49,4 +49,3 @@ def test_incorrect_type_raises(self, faker): not_an_enum_type = type("NotAnEnumType") with pytest.raises(TypeError): faker.enum(not_an_enum_type) -