Skip to content

Commit

Permalink
Fix handling of Enums in Literal types
Browse files Browse the repository at this point in the history
  • Loading branch information
brakhane committed Feb 16, 2022
1 parent 3ee4b40 commit f955733
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 3 deletions.
20 changes: 18 additions & 2 deletions src/cattr/converters.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,11 @@ def is_optional(typ):
and len(typ.__args__) == 2
)

def is_literal_containing_enums(typ):
return (
is_literal(typ)
and any(isinstance(val, Enum) for val in typ.__args__)
)

class Converter(object):
"""Converts between structured and unstructured data."""
Expand Down Expand Up @@ -155,7 +160,8 @@ def __init__(
lambda v, _: v,
),
(is_generic_attrs, self._gen_structure_generic, True),
(is_literal, self._structure_literal),
(is_literal, self._structure_simple_literal),
(is_literal_containing_enums, self._structure_enum_literal),
(is_sequence, self._structure_list),
(is_mutable_set, self._structure_set),
(is_frozenset, self._structure_frozenset),
Expand Down Expand Up @@ -404,11 +410,21 @@ def _structure_call(obj, cl):
return cl(obj)

@staticmethod
def _structure_literal(val, type):
def _structure_simple_literal(val, type):
if val not in type.__args__:
raise Exception(f"{val} not in literal {type}")
return val

@staticmethod
def _structure_enum_literal(val, type):
vals = {
(x.value if isinstance(x, Enum) else x): x for x in type.__args__
}
try:
return vals[val]
except KeyError:
raise Exception(f"{val} not in literal {type}") from None

# Attrs classes.

def structure_attrs_fromtuple(
Expand Down
44 changes: 43 additions & 1 deletion tests/test_structure_attrs.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Loading of attrs classes."""
from enum import Enum
from ipaddress import IPv4Address, IPv6Address, ip_address
from typing import Union
from unittest.mock import Mock
Expand Down Expand Up @@ -164,6 +165,27 @@ class ClassWithLiteral:
) == ClassWithLiteral(4)


@pytest.mark.skipif(is_py37, reason="Not supported on 3.7")
@pytest.mark.parametrize("converter_cls", [Converter, GenConverter])
def test_structure_literal_enum(converter_cls):
"""Structuring a class with a literal field works."""
from typing import Literal

converter = converter_cls()

class Foo(Enum):
FOO = 1
BAR = 2

@define
class ClassWithLiteral:
literal_field: Literal[Foo.FOO] = Foo.FOO

assert converter.structure(
{"literal_field": 1}, ClassWithLiteral
) == ClassWithLiteral(Foo.FOO)


@pytest.mark.skipif(is_py37, reason="Not supported on 3.7")
@pytest.mark.parametrize("converter_cls", [Converter, GenConverter])
def test_structure_literal_multiple(converter_cls):
Expand All @@ -172,9 +194,17 @@ def test_structure_literal_multiple(converter_cls):

converter = converter_cls()

class Foo(Enum):
FOO = 7
FOOFOO = 77

class Bar(int, Enum):
BAR = 8
BARBAR = 88

@define
class ClassWithLiteral:
literal_field: Literal[4, 5] = 4
literal_field: Literal[4, 5, Foo.FOO, Bar.BARBAR] = 4

assert converter.structure(
{"literal_field": 4}, ClassWithLiteral
Expand All @@ -183,6 +213,18 @@ class ClassWithLiteral:
{"literal_field": 5}, ClassWithLiteral
) == ClassWithLiteral(5)

cwl = converter.structure(
{"literal_field": 7}, ClassWithLiteral
)
assert cwl ==ClassWithLiteral(Foo.FOO)
assert isinstance(cwl.literal_field, Foo)

cwl = converter.structure(
{"literal_field": 88}, ClassWithLiteral
)
assert cwl ==ClassWithLiteral(Bar.BARBAR)
assert isinstance(cwl.literal_field, Bar)


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

0 comments on commit f955733

Please sign in to comment.