enumplus 1.0.0__tar.gz → 1.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {enumplus-1.0.0 → enumplus-1.1.0}/PKG-INFO +1 -1
- {enumplus-1.0.0 → enumplus-1.1.0}/enumplus/__init__.py +1 -1
- {enumplus-1.0.0 → enumplus-1.1.0}/enumplus/enum.py +95 -10
- {enumplus-1.0.0 → enumplus-1.1.0}/enumplus/pydantic.py +13 -1
- {enumplus-1.0.0 → enumplus-1.1.0}/enumplus/serialize.py +9 -1
- {enumplus-1.0.0 → enumplus-1.1.0}/pyproject.toml +1 -1
- enumplus-1.1.0/tests/test_new_features.py +304 -0
- {enumplus-1.0.0 → enumplus-1.1.0}/tests/test_public_api.py +1 -1
- enumplus-1.1.0/tests/test_pydantic.py +79 -0
- enumplus-1.0.0/tests/test_pydantic.py +0 -43
- {enumplus-1.0.0 → enumplus-1.1.0}/.github/FUNDING.yml +0 -0
- {enumplus-1.0.0 → enumplus-1.1.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {enumplus-1.0.0 → enumplus-1.1.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {enumplus-1.0.0 → enumplus-1.1.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {enumplus-1.0.0 → enumplus-1.1.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {enumplus-1.0.0 → enumplus-1.1.0}/.github/workflows/ci.yml +0 -0
- {enumplus-1.0.0 → enumplus-1.1.0}/.github/workflows/release.yml +0 -0
- {enumplus-1.0.0 → enumplus-1.1.0}/.gitignore +0 -0
- {enumplus-1.0.0 → enumplus-1.1.0}/CODE_OF_CONDUCT.md +0 -0
- {enumplus-1.0.0 → enumplus-1.1.0}/CONTRIBUTING.md +0 -0
- {enumplus-1.0.0 → enumplus-1.1.0}/LICENSE +0 -0
- {enumplus-1.0.0 → enumplus-1.1.0}/README.md +0 -0
- {enumplus-1.0.0 → enumplus-1.1.0}/SECURITY.md +0 -0
- {enumplus-1.0.0 → enumplus-1.1.0}/SUPPORT.md +0 -0
- {enumplus-1.0.0 → enumplus-1.1.0}/tests/__init__.py +0 -0
- {enumplus-1.0.0 → enumplus-1.1.0}/tests/test_compatibility.py +0 -0
- {enumplus-1.0.0 → enumplus-1.1.0}/tests/test_enum.py +0 -0
- {enumplus-1.0.0 → enumplus-1.1.0}/tests/test_metadata_unpacking.py +0 -0
- {enumplus-1.0.0 → enumplus-1.1.0}/tests/test_ordered.py +0 -0
- {enumplus-1.0.0 → enumplus-1.1.0}/tests/test_serialize.py +0 -0
- {enumplus-1.0.0 → enumplus-1.1.0}/tests/test_type_hints.py +0 -0
|
@@ -27,6 +27,21 @@ class EnumMeta(enum.EnumMeta):
|
|
|
27
27
|
member_names: list[str] = list(getattr(namespace, "_member_names", []))
|
|
28
28
|
last_values: list[Any] = getattr(namespace, "_last_values", [])
|
|
29
29
|
|
|
30
|
+
class_config: dict[str, Any] = {}
|
|
31
|
+
_config_keys = {"serialize_by_name"}
|
|
32
|
+
|
|
33
|
+
ns_member_names = getattr(namespace, "_member_names", None)
|
|
34
|
+
|
|
35
|
+
for key in _config_keys:
|
|
36
|
+
if key in namespace:
|
|
37
|
+
class_config[key] = namespace[key]
|
|
38
|
+
dict.__delitem__(namespace, key)
|
|
39
|
+
if ns_member_names is not None and key in ns_member_names:
|
|
40
|
+
if isinstance(ns_member_names, dict):
|
|
41
|
+
del ns_member_names[key]
|
|
42
|
+
else:
|
|
43
|
+
ns_member_names.remove(key)
|
|
44
|
+
|
|
30
45
|
for key, value in list(namespace.items()):
|
|
31
46
|
if isinstance(value, tuple) and len(value) == 2 and isinstance(value[1], dict):
|
|
32
47
|
actual_value, metadata = value
|
|
@@ -38,6 +53,12 @@ class EnumMeta(enum.EnumMeta):
|
|
|
38
53
|
|
|
39
54
|
new_cls = super().__new__(cls, name, bases, namespace, **kwargs)
|
|
40
55
|
|
|
56
|
+
for key, value in class_config.items():
|
|
57
|
+
setattr(new_cls, key, value)
|
|
58
|
+
|
|
59
|
+
if not hasattr(new_cls, "serialize_by_name"):
|
|
60
|
+
new_cls.serialize_by_name = False
|
|
61
|
+
|
|
41
62
|
members: list[Any] = list(new_cls)
|
|
42
63
|
for index, member in enumerate(members):
|
|
43
64
|
metadata = member_metadata.get(member.name, {})
|
|
@@ -53,6 +74,8 @@ class EnumMeta(enum.EnumMeta):
|
|
|
53
74
|
|
|
54
75
|
return new_cls
|
|
55
76
|
|
|
77
|
+
serialize_by_name: bool = False
|
|
78
|
+
|
|
56
79
|
def __contains__(cls, item: Any) -> bool:
|
|
57
80
|
if isinstance(item, cls):
|
|
58
81
|
return True
|
|
@@ -72,7 +95,10 @@ class Enum(enum.Enum, metaclass=EnumMeta):
|
|
|
72
95
|
|
|
73
96
|
@property
|
|
74
97
|
def label(self) -> str:
|
|
75
|
-
|
|
98
|
+
label = self._label_
|
|
99
|
+
if callable(label):
|
|
100
|
+
return str(label())
|
|
101
|
+
return str(label)
|
|
76
102
|
|
|
77
103
|
@property
|
|
78
104
|
def metadata(self) -> dict[str, Any]:
|
|
@@ -89,7 +115,7 @@ class Enum(enum.Enum, metaclass=EnumMeta):
|
|
|
89
115
|
) from None
|
|
90
116
|
|
|
91
117
|
def __str__(self) -> str:
|
|
92
|
-
return self.
|
|
118
|
+
return self.label
|
|
93
119
|
|
|
94
120
|
def __repr__(self) -> str:
|
|
95
121
|
return f"<{type(self).__name__}.{self.name}: {self.value!r}>"
|
|
@@ -107,20 +133,36 @@ class Enum(enum.Enum, metaclass=EnumMeta):
|
|
|
107
133
|
return [(member.value, member.label) for member in cls]
|
|
108
134
|
|
|
109
135
|
@classmethod
|
|
110
|
-
def from_value(
|
|
136
|
+
def from_value(
|
|
137
|
+
cls, value: Any, default: Any = _SENTINEL, *, case_insensitive: bool = False
|
|
138
|
+
) -> Enum:
|
|
111
139
|
member: Any
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
140
|
+
if case_insensitive and isinstance(value, str):
|
|
141
|
+
lowered = value.lower()
|
|
142
|
+
for member in cls:
|
|
143
|
+
if isinstance(member.value, str) and member.value.lower() == lowered:
|
|
144
|
+
return cast(Enum, member)
|
|
145
|
+
else:
|
|
146
|
+
for member in cls:
|
|
147
|
+
if member.value == value:
|
|
148
|
+
return cast(Enum, member)
|
|
115
149
|
if default is not _SENTINEL:
|
|
116
150
|
return cast(Enum, default)
|
|
117
151
|
raise ValueError(f"{value!r} is not a valid {cls.__name__} value")
|
|
118
152
|
|
|
119
153
|
@classmethod
|
|
120
|
-
def from_name(
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
154
|
+
def from_name(
|
|
155
|
+
cls, name: str, default: Any = _SENTINEL, *, case_insensitive: bool = False
|
|
156
|
+
) -> Enum:
|
|
157
|
+
if case_insensitive:
|
|
158
|
+
upper = name.upper()
|
|
159
|
+
for member in cls:
|
|
160
|
+
if member.name.upper() == upper:
|
|
161
|
+
return member
|
|
162
|
+
else:
|
|
163
|
+
found = cls.__members__.get(name)
|
|
164
|
+
if found is not None:
|
|
165
|
+
return found
|
|
124
166
|
if default is not _SENTINEL:
|
|
125
167
|
return cast(Enum, default)
|
|
126
168
|
raise KeyError(f"{name!r} is not a valid {cls.__name__} name")
|
|
@@ -149,6 +191,49 @@ class Enum(enum.Enum, metaclass=EnumMeta):
|
|
|
149
191
|
def labels(cls) -> list[str]:
|
|
150
192
|
return [member.label for member in cls]
|
|
151
193
|
|
|
194
|
+
@classmethod
|
|
195
|
+
def keys(cls) -> list[str]:
|
|
196
|
+
return [member.name for member in cls]
|
|
197
|
+
|
|
198
|
+
@classmethod
|
|
199
|
+
def get(cls, value: Any, default: Any = None) -> Enum | None:
|
|
200
|
+
return cls.from_value(value, default=default)
|
|
201
|
+
|
|
202
|
+
@classmethod
|
|
203
|
+
def get_initial(cls) -> Enum:
|
|
204
|
+
members = list(cls)
|
|
205
|
+
if not members:
|
|
206
|
+
raise ValueError(f"{cls.__name__} has no members")
|
|
207
|
+
return members[0]
|
|
208
|
+
|
|
209
|
+
@classmethod
|
|
210
|
+
def get_final(cls) -> Enum:
|
|
211
|
+
members = list(cls)
|
|
212
|
+
if not members:
|
|
213
|
+
raise ValueError(f"{cls.__name__} has no members")
|
|
214
|
+
return members[-1]
|
|
215
|
+
|
|
216
|
+
@classmethod
|
|
217
|
+
def map(cls, mapping: dict[Enum, Any]) -> dict[str, Any]:
|
|
218
|
+
result: dict[str, Any] = {}
|
|
219
|
+
for member in cls:
|
|
220
|
+
result[member.name] = mapping.get(member, None)
|
|
221
|
+
return result
|
|
222
|
+
|
|
223
|
+
@classmethod
|
|
224
|
+
def to_dict(cls) -> dict[str, dict[str, Any]]:
|
|
225
|
+
result: dict[str, dict[str, Any]] = {}
|
|
226
|
+
for member in cls:
|
|
227
|
+
result[member.name] = {
|
|
228
|
+
"value": member.value,
|
|
229
|
+
"label": member.label,
|
|
230
|
+
"metadata": {
|
|
231
|
+
k: (v() if callable(v) else v)
|
|
232
|
+
for k, v in member._metadata_.items()
|
|
233
|
+
},
|
|
234
|
+
}
|
|
235
|
+
return result
|
|
236
|
+
|
|
152
237
|
@classmethod
|
|
153
238
|
def filter(cls, **kwargs: Any) -> list[Enum]:
|
|
154
239
|
if not kwargs:
|
|
@@ -14,16 +14,28 @@ def get_pydantic_core_schema(
|
|
|
14
14
|
) -> CoreSchema:
|
|
15
15
|
from pydantic_core import core_schema
|
|
16
16
|
|
|
17
|
+
serialize_by_name = getattr(cls, "serialize_by_name", False)
|
|
18
|
+
|
|
17
19
|
def validate(value: Any) -> Any:
|
|
18
20
|
if isinstance(value, cls):
|
|
19
21
|
return value
|
|
22
|
+
if serialize_by_name and isinstance(value, str):
|
|
23
|
+
try:
|
|
24
|
+
return cls.from_name(value)
|
|
25
|
+
except KeyError:
|
|
26
|
+
raise ValueError(f"{value!r} is not a valid {cls.__name__} name") from None
|
|
20
27
|
return cls.from_value(value)
|
|
21
28
|
|
|
29
|
+
if serialize_by_name:
|
|
30
|
+
serializer = lambda v: v.name
|
|
31
|
+
else:
|
|
32
|
+
serializer = lambda v: v.value
|
|
33
|
+
|
|
22
34
|
return core_schema.no_info_after_validator_function(
|
|
23
35
|
validate,
|
|
24
36
|
core_schema.any_schema(),
|
|
25
37
|
serialization=core_schema.plain_serializer_function_ser_schema(
|
|
26
|
-
|
|
38
|
+
serializer,
|
|
27
39
|
return_schema=core_schema.any_schema(),
|
|
28
40
|
),
|
|
29
41
|
)
|
|
@@ -6,6 +6,12 @@ from typing import Any, cast
|
|
|
6
6
|
from enumplus.enum import Enum
|
|
7
7
|
|
|
8
8
|
|
|
9
|
+
def _serialize_value(value: Any) -> Any:
|
|
10
|
+
if callable(value):
|
|
11
|
+
return value()
|
|
12
|
+
return value
|
|
13
|
+
|
|
14
|
+
|
|
9
15
|
def to_json(cls: type[Enum]) -> str:
|
|
10
16
|
data: dict[str, Any] = {
|
|
11
17
|
"name": cls.__name__,
|
|
@@ -14,7 +20,9 @@ def to_json(cls: type[Enum]) -> str:
|
|
|
14
20
|
"name": member.name,
|
|
15
21
|
"value": member.value,
|
|
16
22
|
"label": member.label,
|
|
17
|
-
"metadata":
|
|
23
|
+
"metadata": {
|
|
24
|
+
k: _serialize_value(v) for k, v in member._metadata_.items()
|
|
25
|
+
},
|
|
18
26
|
}
|
|
19
27
|
for member in cls
|
|
20
28
|
],
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from enumplus import Enum
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TestCaseInsensitive:
|
|
7
|
+
def test_from_value_case_insensitive(self) -> None:
|
|
8
|
+
class Color(Enum):
|
|
9
|
+
RED = "red"
|
|
10
|
+
GREEN = "green"
|
|
11
|
+
|
|
12
|
+
assert Color.from_value("Red", case_insensitive=True) is Color.RED
|
|
13
|
+
assert Color.from_value("GREEN", case_insensitive=True) is Color.GREEN
|
|
14
|
+
assert Color.from_value("rEd", case_insensitive=True) is Color.RED
|
|
15
|
+
|
|
16
|
+
def test_from_value_case_insensitive_not_found(self) -> None:
|
|
17
|
+
class Color(Enum):
|
|
18
|
+
RED = "red"
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
Color.from_value("blue", case_insensitive=True)
|
|
22
|
+
raise AssertionError("Should have raised ValueError")
|
|
23
|
+
except ValueError:
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
def test_from_value_case_insensitive_with_default(self) -> None:
|
|
27
|
+
class Color(Enum):
|
|
28
|
+
RED = "red"
|
|
29
|
+
|
|
30
|
+
assert Color.from_value("Blue", case_insensitive=True, default=None) is None
|
|
31
|
+
|
|
32
|
+
def test_from_name_case_insensitive(self) -> None:
|
|
33
|
+
class Color(Enum):
|
|
34
|
+
RED = "red"
|
|
35
|
+
GREEN = "green"
|
|
36
|
+
|
|
37
|
+
assert Color.from_name("red", case_insensitive=True) is Color.RED
|
|
38
|
+
assert Color.from_name("green", case_insensitive=True) is Color.GREEN
|
|
39
|
+
assert Color.from_name("ReD", case_insensitive=True) is Color.RED
|
|
40
|
+
|
|
41
|
+
def test_from_name_case_insensitive_not_found(self) -> None:
|
|
42
|
+
class Color(Enum):
|
|
43
|
+
RED = "red"
|
|
44
|
+
|
|
45
|
+
try:
|
|
46
|
+
Color.from_name("blue", case_insensitive=True)
|
|
47
|
+
raise AssertionError("Should have raised KeyError")
|
|
48
|
+
except KeyError:
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
def test_from_value_case_insensitive_non_string(self) -> None:
|
|
52
|
+
class Number(Enum):
|
|
53
|
+
ONE = 1
|
|
54
|
+
TWO = 2
|
|
55
|
+
|
|
56
|
+
assert Number.from_value(1, case_insensitive=True) is Number.ONE
|
|
57
|
+
assert Number.from_value(2, case_insensitive=True) is Number.TWO
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class TestToDict:
|
|
61
|
+
def test_to_dict_basic(self) -> None:
|
|
62
|
+
class Color(Enum):
|
|
63
|
+
RED = "red"
|
|
64
|
+
GREEN = "green"
|
|
65
|
+
|
|
66
|
+
d = Color.to_dict()
|
|
67
|
+
assert "RED" in d
|
|
68
|
+
assert d["RED"]["value"] == "red"
|
|
69
|
+
assert d["RED"]["label"] == "Red"
|
|
70
|
+
assert d["RED"]["metadata"] == {}
|
|
71
|
+
assert d["GREEN"]["value"] == "green"
|
|
72
|
+
assert d["GREEN"]["label"] == "Green"
|
|
73
|
+
|
|
74
|
+
def test_to_dict_with_metadata(self) -> None:
|
|
75
|
+
class Color(Enum):
|
|
76
|
+
RED = ("red", {"hex": "#FF0000"})
|
|
77
|
+
GREEN = "green"
|
|
78
|
+
|
|
79
|
+
d = Color.to_dict()
|
|
80
|
+
assert d["RED"]["metadata"] == {"hex": "#FF0000"}
|
|
81
|
+
assert d["RED"]["label"] == "Red"
|
|
82
|
+
assert d["GREEN"]["metadata"] == {}
|
|
83
|
+
|
|
84
|
+
def test_to_dict_with_custom_label(self) -> None:
|
|
85
|
+
class Status(Enum):
|
|
86
|
+
IN_PROGRESS = ("in_progress", {"label": "In Progress"})
|
|
87
|
+
|
|
88
|
+
d = Status.to_dict()
|
|
89
|
+
assert d["IN_PROGRESS"]["label"] == "In Progress"
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class TestGet:
|
|
93
|
+
def test_get_returns_member(self) -> None:
|
|
94
|
+
class Color(Enum):
|
|
95
|
+
RED = "red"
|
|
96
|
+
|
|
97
|
+
assert Color.get("red") is Color.RED
|
|
98
|
+
|
|
99
|
+
def test_get_returns_default(self) -> None:
|
|
100
|
+
class Color(Enum):
|
|
101
|
+
RED = "red"
|
|
102
|
+
|
|
103
|
+
assert Color.get("blue", default=None) is None
|
|
104
|
+
assert Color.get("blue", default=Color.RED) is Color.RED
|
|
105
|
+
|
|
106
|
+
def test_get_default_is_none(self) -> None:
|
|
107
|
+
class Color(Enum):
|
|
108
|
+
RED = "red"
|
|
109
|
+
|
|
110
|
+
assert Color.get("blue") is None
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class TestGetInitial:
|
|
114
|
+
def test_get_initial(self) -> None:
|
|
115
|
+
class Color(Enum):
|
|
116
|
+
RED = "red"
|
|
117
|
+
GREEN = "green"
|
|
118
|
+
BLUE = "blue"
|
|
119
|
+
|
|
120
|
+
assert Color.get_initial() is Color.RED
|
|
121
|
+
|
|
122
|
+
def test_get_initial_single_member(self) -> None:
|
|
123
|
+
class Single(Enum):
|
|
124
|
+
ONLY = 1
|
|
125
|
+
|
|
126
|
+
assert Single.get_initial() is Single.ONLY
|
|
127
|
+
|
|
128
|
+
def test_get_initial_empty_raises(self) -> None:
|
|
129
|
+
class Empty(Enum):
|
|
130
|
+
pass
|
|
131
|
+
|
|
132
|
+
try:
|
|
133
|
+
Empty.get_initial()
|
|
134
|
+
raise AssertionError("Should have raised ValueError")
|
|
135
|
+
except ValueError:
|
|
136
|
+
pass
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class TestGetFinal:
|
|
140
|
+
def test_get_final(self) -> None:
|
|
141
|
+
class Color(Enum):
|
|
142
|
+
RED = "red"
|
|
143
|
+
GREEN = "green"
|
|
144
|
+
BLUE = "blue"
|
|
145
|
+
|
|
146
|
+
assert Color.get_final() is Color.BLUE
|
|
147
|
+
|
|
148
|
+
def test_get_final_single_member(self) -> None:
|
|
149
|
+
class Single(Enum):
|
|
150
|
+
ONLY = 1
|
|
151
|
+
|
|
152
|
+
assert Single.get_final() is Single.ONLY
|
|
153
|
+
|
|
154
|
+
def test_get_final_empty_raises(self) -> None:
|
|
155
|
+
class Empty(Enum):
|
|
156
|
+
pass
|
|
157
|
+
|
|
158
|
+
try:
|
|
159
|
+
Empty.get_final()
|
|
160
|
+
raise AssertionError("Should have raised ValueError")
|
|
161
|
+
except ValueError:
|
|
162
|
+
pass
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class TestKeys:
|
|
166
|
+
def test_keys_returns_names(self) -> None:
|
|
167
|
+
class Color(Enum):
|
|
168
|
+
RED = "red"
|
|
169
|
+
GREEN = "green"
|
|
170
|
+
|
|
171
|
+
assert Color.keys() == ["RED", "GREEN"]
|
|
172
|
+
|
|
173
|
+
def test_keys_empty(self) -> None:
|
|
174
|
+
class Empty(Enum):
|
|
175
|
+
pass
|
|
176
|
+
|
|
177
|
+
assert Empty.keys() == []
|
|
178
|
+
|
|
179
|
+
def test_keys_equals_names(self) -> None:
|
|
180
|
+
class Color(Enum):
|
|
181
|
+
RED = "red"
|
|
182
|
+
GREEN = "green"
|
|
183
|
+
|
|
184
|
+
assert Color.keys() == Color.names()
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
class TestMap:
|
|
188
|
+
def test_map_basic(self) -> None:
|
|
189
|
+
class Color(Enum):
|
|
190
|
+
RED = "red"
|
|
191
|
+
GREEN = "green"
|
|
192
|
+
|
|
193
|
+
result = Color.map({Color.RED: "#FF0000", Color.GREEN: "#00FF00"})
|
|
194
|
+
assert result == {"RED": "#FF0000", "GREEN": "#00FF00"}
|
|
195
|
+
|
|
196
|
+
def test_map_partial(self) -> None:
|
|
197
|
+
class Color(Enum):
|
|
198
|
+
RED = "red"
|
|
199
|
+
GREEN = "green"
|
|
200
|
+
|
|
201
|
+
result = Color.map({Color.RED: "#FF0000"})
|
|
202
|
+
assert result == {"RED": "#FF0000", "GREEN": None}
|
|
203
|
+
|
|
204
|
+
def test_map_empty(self) -> None:
|
|
205
|
+
class Color(Enum):
|
|
206
|
+
RED = "red"
|
|
207
|
+
|
|
208
|
+
result = Color.map({})
|
|
209
|
+
assert result == {"RED": None}
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
class TestI18nLabels:
|
|
213
|
+
def test_callable_label(self) -> None:
|
|
214
|
+
translations = {"RED": "Rojo", "GREEN": "Verde"}
|
|
215
|
+
|
|
216
|
+
def translate(key: str) -> str:
|
|
217
|
+
return translations.get(key, key)
|
|
218
|
+
|
|
219
|
+
class Color(Enum):
|
|
220
|
+
RED = ("red", {"label": lambda: translate("RED")})
|
|
221
|
+
GREEN = ("green", {"label": lambda: translate("GREEN")})
|
|
222
|
+
|
|
223
|
+
assert Color.RED.label == "Rojo"
|
|
224
|
+
assert Color.GREEN.label == "Verde"
|
|
225
|
+
|
|
226
|
+
def test_callable_label_changes_with_locale(self) -> None:
|
|
227
|
+
current_locale: dict[str, str] = {"lang": "en"}
|
|
228
|
+
en = {"RED": "Red", "GREEN": "Green"}
|
|
229
|
+
es = {"RED": "Rojo", "GREEN": "Verde"}
|
|
230
|
+
|
|
231
|
+
def translate(key: str) -> str:
|
|
232
|
+
lang = current_locale["lang"]
|
|
233
|
+
table = es if lang == "es" else en
|
|
234
|
+
return table.get(key, key)
|
|
235
|
+
|
|
236
|
+
class Color(Enum):
|
|
237
|
+
RED = ("red", {"label": lambda: translate("RED")})
|
|
238
|
+
GREEN = ("green", {"label": lambda: translate("GREEN")})
|
|
239
|
+
|
|
240
|
+
assert Color.RED.label == "Red"
|
|
241
|
+
current_locale["lang"] = "es"
|
|
242
|
+
assert Color.RED.label == "Rojo"
|
|
243
|
+
assert Color.GREEN.label == "Verde"
|
|
244
|
+
current_locale["lang"] = "en"
|
|
245
|
+
assert Color.RED.label == "Red"
|
|
246
|
+
|
|
247
|
+
def test_non_callable_label_unchanged(self) -> None:
|
|
248
|
+
class Color(Enum):
|
|
249
|
+
RED = ("red", {"label": "Red"})
|
|
250
|
+
|
|
251
|
+
assert Color.RED.label == "Red"
|
|
252
|
+
|
|
253
|
+
def test_callable_label_in_choices(self) -> None:
|
|
254
|
+
class Color(Enum):
|
|
255
|
+
RED = ("red", {"label": lambda: "Rojo"})
|
|
256
|
+
|
|
257
|
+
assert Color.choices() == [("red", "Rojo")]
|
|
258
|
+
|
|
259
|
+
def test_callable_label_in_to_dict(self) -> None:
|
|
260
|
+
class Color(Enum):
|
|
261
|
+
RED = ("red", {"label": lambda: "Rojo"})
|
|
262
|
+
|
|
263
|
+
d = Color.to_dict()
|
|
264
|
+
assert d["RED"]["label"] == "Rojo"
|
|
265
|
+
|
|
266
|
+
def test_callable_label_in_str(self) -> None:
|
|
267
|
+
class Color(Enum):
|
|
268
|
+
RED = ("red", {"label": lambda: "Rojo"})
|
|
269
|
+
|
|
270
|
+
assert str(Color.RED) == "Rojo"
|
|
271
|
+
|
|
272
|
+
def test_callable_label_in_labels(self) -> None:
|
|
273
|
+
class Color(Enum):
|
|
274
|
+
RED = ("red", {"label": lambda: "Rojo"})
|
|
275
|
+
GREEN = ("green", {"label": lambda: "Verde"})
|
|
276
|
+
|
|
277
|
+
assert Color.labels() == ["Rojo", "Verde"]
|
|
278
|
+
|
|
279
|
+
def test_callable_label_in_to_json(self) -> None:
|
|
280
|
+
class Color(Enum):
|
|
281
|
+
RED = ("red", {"label": lambda: "Rojo"})
|
|
282
|
+
|
|
283
|
+
import json
|
|
284
|
+
|
|
285
|
+
data = json.loads(Color.to_json())
|
|
286
|
+
assert data["members"][0]["label"] == "Rojo"
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
class TestSerializeByName:
|
|
290
|
+
def test_flag_default_false(self) -> None:
|
|
291
|
+
class Color(Enum):
|
|
292
|
+
RED = "red"
|
|
293
|
+
GREEN = "green"
|
|
294
|
+
|
|
295
|
+
assert Color.serialize_by_name is False
|
|
296
|
+
|
|
297
|
+
def test_flag_set_true(self) -> None:
|
|
298
|
+
class Color(Enum):
|
|
299
|
+
RED = "red"
|
|
300
|
+
GREEN = "green"
|
|
301
|
+
|
|
302
|
+
serialize_by_name = True
|
|
303
|
+
|
|
304
|
+
assert Color.serialize_by_name is True
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
pytest.importorskip("pydantic")
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, ValidationError
|
|
8
|
+
|
|
9
|
+
from enumplus import Enum
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Color(Enum):
|
|
13
|
+
RED = "red"
|
|
14
|
+
GREEN = "green"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class MyModel(BaseModel):
|
|
18
|
+
color: Color
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_pydantic_validate_string() -> None:
|
|
22
|
+
model = MyModel(color="red") # type: ignore[arg-type]
|
|
23
|
+
assert model.color is Color.RED
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_pydantic_validate_member() -> None:
|
|
27
|
+
model = MyModel(color=Color.RED)
|
|
28
|
+
assert model.color is Color.RED
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_pydantic_invalid() -> None:
|
|
32
|
+
with pytest.raises(ValidationError):
|
|
33
|
+
MyModel(color="blue") # type: ignore[arg-type]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_pydantic_dump() -> None:
|
|
37
|
+
model = MyModel(color=Color.RED)
|
|
38
|
+
assert model.model_dump() == {"color": "red"}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_pydantic_dump_json() -> None:
|
|
42
|
+
model = MyModel(color=Color.RED)
|
|
43
|
+
assert model.model_dump_json() == '{"color":"red"}'
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class ColorByName(Enum):
|
|
47
|
+
RED = "red"
|
|
48
|
+
GREEN = "green"
|
|
49
|
+
|
|
50
|
+
serialize_by_name = True
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class MyModelByName(BaseModel):
|
|
54
|
+
color: ColorByName
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_pydantic_serialize_by_name_validate() -> None:
|
|
58
|
+
model = MyModelByName(color="RED") # type: ignore[arg-type]
|
|
59
|
+
assert model.color is ColorByName.RED
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_pydantic_serialize_by_name_validate_member() -> None:
|
|
63
|
+
model = MyModelByName(color=ColorByName.RED)
|
|
64
|
+
assert model.color is ColorByName.RED
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def test_pydantic_serialize_by_name_invalid() -> None:
|
|
68
|
+
with pytest.raises(ValidationError):
|
|
69
|
+
MyModelByName(color="blue") # type: ignore[arg-type]
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def test_pydantic_serialize_by_name_dump() -> None:
|
|
73
|
+
model = MyModelByName(color=ColorByName.RED)
|
|
74
|
+
assert model.model_dump() == {"color": "RED"}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def test_pydantic_serialize_by_name_dump_json() -> None:
|
|
78
|
+
model = MyModelByName(color=ColorByName.RED)
|
|
79
|
+
assert model.model_dump_json() == '{"color":"RED"}'
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import pytest
|
|
4
|
-
|
|
5
|
-
pytest.importorskip("pydantic")
|
|
6
|
-
|
|
7
|
-
from pydantic import BaseModel, ValidationError
|
|
8
|
-
|
|
9
|
-
from enumplus import Enum
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class Color(Enum):
|
|
13
|
-
RED = "red"
|
|
14
|
-
GREEN = "green"
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class MyModel(BaseModel):
|
|
18
|
-
color: Color
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def test_pydantic_validate_string() -> None:
|
|
22
|
-
model = MyModel(color="red") # type: ignore[arg-type]
|
|
23
|
-
assert model.color is Color.RED
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
def test_pydantic_validate_member() -> None:
|
|
27
|
-
model = MyModel(color=Color.RED)
|
|
28
|
-
assert model.color is Color.RED
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def test_pydantic_invalid() -> None:
|
|
32
|
-
with pytest.raises(ValidationError):
|
|
33
|
-
MyModel(color="blue") # type: ignore[arg-type]
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def test_pydantic_dump() -> None:
|
|
37
|
-
model = MyModel(color=Color.RED)
|
|
38
|
-
assert model.model_dump() == {"color": "red"}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def test_pydantic_dump_json() -> None:
|
|
42
|
-
model = MyModel(color=Color.RED)
|
|
43
|
-
assert model.model_dump_json() == '{"color":"red"}'
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|