Skip to content

Commit ae8330e

Browse files
committed
Fix handling of Enums in Literal types
1 parent 3ee4b40 commit ae8330e

File tree

3 files changed

+60
-4
lines changed

3 files changed

+60
-4
lines changed

HISTORY.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,11 @@ History
1111
(`#218 <https://github.com/python-attrs/cattrs/issues/218>`_)
1212
* Fix structuring bare ``typing.Tuple`` on Pythons lower than 3.9.
1313
(`#218 <https://github.com/python-attrs/cattrs/issues/218>`_)
14-
1514
* Fix a wrong ``AttributeError`` of an missing ``__parameters__`` attribute. This could happen
1615
when inheriting certain generic classes – for example ``typing.*`` classes are affected.
1716
(`#217 <https://github.com/python-attrs/cattrs/issues/217>`_)
17+
* Fix structuring of ``enum.Enum`` instances in ``typing.Literal`` types.
18+
(`#231 <https://github.com/python-attrs/cattrs/pull/231>`_)
1819

1920
1.10.0 (2022-01-04)
2021
-------------------

src/cattr/converters.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,12 @@ def is_optional(typ):
8686
)
8787

8888

89+
def is_literal_containing_enums(typ):
90+
return is_literal(typ) and any(
91+
isinstance(val, Enum) for val in typ.__args__
92+
)
93+
94+
8995
class Converter(object):
9096
"""Converts between structured and unstructured data."""
9197

@@ -155,7 +161,8 @@ def __init__(
155161
lambda v, _: v,
156162
),
157163
(is_generic_attrs, self._gen_structure_generic, True),
158-
(is_literal, self._structure_literal),
164+
(is_literal, self._structure_simple_literal),
165+
(is_literal_containing_enums, self._structure_enum_literal),
159166
(is_sequence, self._structure_list),
160167
(is_mutable_set, self._structure_set),
161168
(is_frozenset, self._structure_frozenset),
@@ -404,11 +411,21 @@ def _structure_call(obj, cl):
404411
return cl(obj)
405412

406413
@staticmethod
407-
def _structure_literal(val, type):
414+
def _structure_simple_literal(val, type):
408415
if val not in type.__args__:
409416
raise Exception(f"{val} not in literal {type}")
410417
return val
411418

419+
@staticmethod
420+
def _structure_enum_literal(val, type):
421+
vals = {
422+
(x.value if isinstance(x, Enum) else x): x for x in type.__args__
423+
}
424+
try:
425+
return vals[val]
426+
except KeyError:
427+
raise Exception(f"{val} not in literal {type}") from None
428+
412429
# Attrs classes.
413430

414431
def structure_attrs_fromtuple(

tests/test_structure_attrs.py

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""Loading of attrs classes."""
2+
from enum import Enum
23
from ipaddress import IPv4Address, IPv6Address, ip_address
34
from typing import Union
45
from unittest.mock import Mock
@@ -164,6 +165,27 @@ class ClassWithLiteral:
164165
) == ClassWithLiteral(4)
165166

166167

168+
@pytest.mark.skipif(is_py37, reason="Not supported on 3.7")
169+
@pytest.mark.parametrize("converter_cls", [Converter, GenConverter])
170+
def test_structure_literal_enum(converter_cls):
171+
"""Structuring a class with a literal field works."""
172+
from typing import Literal
173+
174+
converter = converter_cls()
175+
176+
class Foo(Enum):
177+
FOO = 1
178+
BAR = 2
179+
180+
@define
181+
class ClassWithLiteral:
182+
literal_field: Literal[Foo.FOO] = Foo.FOO
183+
184+
assert converter.structure(
185+
{"literal_field": 1}, ClassWithLiteral
186+
) == ClassWithLiteral(Foo.FOO)
187+
188+
167189
@pytest.mark.skipif(is_py37, reason="Not supported on 3.7")
168190
@pytest.mark.parametrize("converter_cls", [Converter, GenConverter])
169191
def test_structure_literal_multiple(converter_cls):
@@ -172,9 +194,17 @@ def test_structure_literal_multiple(converter_cls):
172194

173195
converter = converter_cls()
174196

197+
class Foo(Enum):
198+
FOO = 7
199+
FOOFOO = 77
200+
201+
class Bar(int, Enum):
202+
BAR = 8
203+
BARBAR = 88
204+
175205
@define
176206
class ClassWithLiteral:
177-
literal_field: Literal[4, 5] = 4
207+
literal_field: Literal[4, 5, Foo.FOO, Bar.BARBAR] = 4
178208

179209
assert converter.structure(
180210
{"literal_field": 4}, ClassWithLiteral
@@ -183,6 +213,14 @@ class ClassWithLiteral:
183213
{"literal_field": 5}, ClassWithLiteral
184214
) == ClassWithLiteral(5)
185215

216+
assert converter.structure(
217+
{"literal_field": 7}, ClassWithLiteral
218+
) == ClassWithLiteral(Foo.FOO)
219+
220+
cwl = converter.structure({"literal_field": 88}, ClassWithLiteral)
221+
assert cwl == ClassWithLiteral(Bar.BARBAR)
222+
assert isinstance(cwl.literal_field, Bar)
223+
186224

187225
@pytest.mark.skipif(is_py37, reason="Not supported on 3.7")
188226
@pytest.mark.parametrize("converter_cls", [Converter, GenConverter])

0 commit comments

Comments
 (0)