Skip to content

Commit 49dd756

Browse files
committed
feat!: rework how enumeration works, now you have to create a new class of type Enum
Signed-off-by: Adria Montoto <75563346+adriamontoto@users.noreply.github.com>
1 parent 7333621 commit 49dd756

File tree

2 files changed

+86
-49
lines changed

2 files changed

+86
-49
lines changed

object_mother_pattern/mothers/enumeration_mother.py

Lines changed: 42 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,26 @@
22
EnumerationMother module.
33
"""
44

5+
from abc import ABC
56
from datetime import date, datetime
67
from enum import Enum
8+
from inspect import isclass
79
from random import choice
8-
from typing import Any, Generic, Iterable, TypeVar
10+
from sys import version_info
11+
from typing import Any, Generic, Iterable, TypeVar, get_args, get_origin
912
from uuid import UUID, uuid4
1013

14+
if version_info >= (3, 12):
15+
from typing import override # pragma: no cover
16+
else:
17+
from typing_extensions import override # pragma: no cover
18+
1119
from faker import Faker
1220

1321
E = TypeVar('E', bound=Enum)
1422

1523

16-
class EnumerationMother(Generic[E]):
24+
class EnumerationMother(ABC, Generic[E]):
1725
"""
1826
EnumerationMother class is responsible for creating random enum values.
1927
@@ -33,7 +41,11 @@ class ColorEnumeration(Enum):
3341
BLUE = 3
3442
3543
36-
color_mother = EnumerationMother(enumeration=ColorEnumeration)
44+
class ColorMother(EnumerationMother[ColorEnumeration]):
45+
pass
46+
47+
48+
color_mother = ColorMother()
3749
3850
color = color_mother.create()
3951
print(color)
@@ -43,43 +55,34 @@ class ColorEnumeration(Enum):
4355

4456
_enumeration: type[E]
4557

46-
def __init__(self, *, enumeration: type[E]) -> None:
58+
@override
59+
def __init_subclass__(cls, **kwargs: Any) -> None:
4760
"""
48-
Initialize the EnumerationMother with the specified enumeration class `enumeration`.
61+
Initializes the class.
4962
5063
Args:
51-
enumeration (type[E]): The enumeration class to generate values from.
64+
**kwargs (Any): Keyword arguments.
5265
5366
Raises:
54-
TypeError: If the provided `enumeration` is not a subclass of Enum.
55-
56-
Example:
57-
```python
58-
from enum import Enum, unique
59-
60-
from object_mother_pattern.mothers import EnumerationMother
61-
62-
63-
@unique
64-
class ColorEnumeration(Enum):
65-
RED = 1
66-
GREEN = 2
67-
BLUE = 3
67+
TypeError: If the class parameter is not an Enum subclass.
68+
TypeError: If the class is not parameterized.
69+
"""
70+
super().__init_subclass__(**kwargs)
6871

72+
for base in getattr(cls, '__orig_bases__', ()):
73+
if get_origin(tp=base) is EnumerationMother:
74+
enumeration, *_ = get_args(tp=base)
6975

70-
color_mother = EnumerationMother(enumeration=ColorEnumeration)
76+
if not (isclass(object=enumeration) and issubclass(enumeration, Enum)):
77+
raise TypeError(f'EnumerationMother[...] <<<{enumeration}>>> must be an Enum subclass. Got <<<{type(enumeration).__name__}>>> type.') # noqa: E501 # fmt: skip
7178

72-
color = color_mother.create()
73-
print(color)
74-
# >>> Color.GREEN
75-
```
76-
"""
77-
if type(enumeration) is not type(Enum):
78-
raise TypeError('EnumerationMother enumeration must be a subclass of Enum.')
79+
cls._enumeration = enumeration
80+
return
7981

80-
self._enumeration = enumeration
82+
raise TypeError('EnumerationMother must be parameterized, e.g. "class ColorMother(EnumerationMother[ColorEnumeration])".') # noqa: E501 # fmt: skip
8183

82-
def create(self, *, value: E | None = None) -> E:
84+
@classmethod
85+
def create(cls, *, value: E | None = None) -> E:
8386
"""
8487
Create a random enumeration value from the enumeration class. If a specific
8588
value is provided, it is returned after ensuring it is a member of the enumeration.
@@ -107,20 +110,24 @@ class ColorEnumeration(Enum):
107110
BLUE = 3
108111
109112
110-
color_mother = EnumerationMother(enumeration=ColorEnumeration)
113+
class ColorMother(EnumerationMother[ColorEnumeration]):
114+
pass
115+
116+
117+
color_mother = ColorMother()
111118
112119
color = color_mother.create()
113120
print(color)
114121
# >>> Color.GREEN
115122
```
116123
"""
117124
if value is not None:
118-
if type(value) is not self._enumeration:
119-
raise TypeError(f'{self._enumeration.__name__}Mother value must be an instance of {self._enumeration.__name__}.') # noqa: E501 # fmt: skip
125+
if not isinstance(value, cls._enumeration):
126+
raise TypeError(f'{cls._enumeration.__name__}Mother value must be an instance of <<<{cls._enumeration.__name__}>>> type.') # noqa: E501 # fmt: skip
120127

121-
return value
128+
return value # type: ignore[no-any-return]
122129

123-
return choice(seq=tuple(self._enumeration)) # noqa: S311
130+
return choice(seq=tuple(cls._enumeration)) # type: ignore[arg-type] # noqa: S311
124131

125132
@staticmethod
126133
def invalid_type(remove_types: Iterable[type[Any]] | None = None) -> Any: # noqa: C901

tests/mothers/test_enumeration_mother.py

Lines changed: 44 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"""
44

55
from enum import Enum, auto, unique
6+
from re import escape
67

78
from pytest import mark, raises as assert_raises
89

@@ -20,37 +21,68 @@ class _TestEnum(Enum):
2021
VALUE_3 = auto()
2122

2223

24+
class TestEnumMother(EnumerationMother[_TestEnum]):
25+
"""
26+
TestEnumMother class.
27+
"""
28+
29+
2330
@mark.unit_testing
24-
def test_enumeration_mother_happy_path() -> None:
31+
def test_enumeration_mother_can_be_instantiated() -> None:
2532
"""
26-
Test EnumerationMother happy path.
33+
Test EnumerationMother can be instantiated.
2734
"""
28-
mother = EnumerationMother(enumeration=_TestEnum)
35+
EnumerationMother()
36+
37+
38+
@mark.unit_testing
39+
def test_enumeration_mother_init_with_invalid_type() -> None:
40+
"""
41+
Test EnumerationMother initialization with invalid type.
42+
"""
43+
value = EnumerationMother.invalid_type(remove_types=(list, set, tuple, dict))
44+
45+
with assert_raises(
46+
expected_exception=TypeError,
47+
match=r'EnumerationMother\[.*\] <<<.*>>> must be an Enum subclass. Got <<<.*>>> type.',
48+
):
2949

30-
assert mother.create() in list(_TestEnum)
50+
class BooleanMother(EnumerationMother[value]): # type: ignore[valid-type]
51+
pass
3152

3253

3354
@mark.unit_testing
34-
def test_enumeration_mother_init_invalid_enumeration_type() -> None:
55+
def test_enumeration_mother_init_without_parameterization() -> None:
3556
"""
36-
Test EnumerationMother initialization with invalid enumeration type.
57+
Test EnumerationMother initialization without parameterization.
3758
"""
3859
with assert_raises(
3960
expected_exception=TypeError,
40-
match='EnumerationMother enumeration must be a subclass of Enum.',
61+
match=escape(
62+
pattern='EnumerationMother must be parameterized, e.g. "class ColorMother(EnumerationMother[ColorEnumeration])".' # noqa: E501
63+
),
4164
):
42-
EnumerationMother(enumeration=EnumerationMother.invalid_type())
65+
66+
class ColorMother(EnumerationMother): # type: ignore[type-arg]
67+
pass
68+
69+
70+
@mark.unit_testing
71+
def test_enumeration_mother_happy_path() -> None:
72+
"""
73+
Test EnumerationMother happy path.
74+
"""
75+
assert TestEnumMother().create() in tuple(_TestEnum)
4376

4477

4578
@mark.unit_testing
4679
def test_enumeration_mother_create_with_value() -> None:
4780
"""
4881
Test EnumerationMother create method with specific value.
4982
"""
50-
mother = EnumerationMother(enumeration=_TestEnum)
5183
expected_value = _TestEnum.VALUE_1
5284

53-
result = mother.create(value=expected_value)
85+
result = TestEnumMother.create(value=expected_value)
5486

5587
assert result == expected_value
5688

@@ -60,13 +92,11 @@ def test_enumeration_mother_create_with_invalid_value_type() -> None:
6092
"""
6193
Test EnumerationMother create method with invalid value type.
6294
"""
63-
mother = EnumerationMother(enumeration=_TestEnum)
64-
6595
with assert_raises(
6696
expected_exception=TypeError,
67-
match='_TestEnumMother value must be an instance of _TestEnum.',
97+
match=r'_TestEnumMother value must be an instance of <<<.*>>> type.',
6898
):
69-
mother.create(value=EnumerationMother.invalid_type())
99+
TestEnumMother.create(value=EnumerationMother.invalid_type())
70100

71101

72102
@mark.unit_testing

0 commit comments

Comments
 (0)