Skip to content

Commit 230c291

Browse files
committed
Add support for enums, bump version to 0.9.0
1 parent d3b0bae commit 230c291

File tree

3 files changed

+77
-5
lines changed

3 files changed

+77
-5
lines changed

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
setuptools.setup(
77
name="undictify",
8-
version="0.8.1",
8+
version="0.9.0",
99
author="Tobias Hermann",
1010
author_email="[email protected]",
1111
description="Type-checked function calls at runtime",

undictify/_unpack.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
"""
22
undictify - Type-checked function calls at runtime
33
"""
4+
import enum
45
import inspect
56
import sys
6-
from enum import Enum
77
from functools import wraps
88
from typing import Any, Callable, Dict, List, Optional, Type, TypeVar, Union, get_type_hints, Tuple
99

@@ -20,7 +20,7 @@
2020
TypeT = TypeVar('TypeT')
2121

2222

23-
class ConverterTag(Enum):
23+
class ConverterTag(enum.Enum):
2424
"""When to apply a converter"""
2525
OPTIONAL = 1
2626
MANDATORY = 2
@@ -278,6 +278,11 @@ def _get_value(func: WrappedOrFunc[TypeT],
278278
f'{_get_type_name(type(result))}')
279279
return result
280280
if Any not in allowed_types and not _isinstanceofone(value, allowed_types):
281+
if _is_enum_type(func):
282+
for entry in func: # type: ignore
283+
if value == entry.value:
284+
return entry
285+
raise TypeError(f'Unable to instantiate {func} from {value}.')
281286
if optional_converters and param_name in optional_converters:
282287
result = optional_converters[param_name](value)
283288
if not _isinstanceofone(result, allowed_types):
@@ -300,11 +305,11 @@ def _get_value(func: WrappedOrFunc[TypeT],
300305
if isinstance(value, str) and func is bool: # type: ignore
301306
return _string_to_bool(value)
302307
return func(value)
303-
except ValueError:
308+
except ValueError as ex:
304309
raise TypeError(f'Can not convert {value} '
305310
f'from type {_get_type_name(value_type)} '
306311
f'into type {_get_type_name(func)} '
307-
f'for key {param_name}.')
312+
f'for key {param_name}.') from ex
308313

309314
raise TypeError(f'Key {param_name} has incorrect type: '
310315
f'{_get_type_name(value_type)} instead of '
@@ -499,6 +504,14 @@ def _is_optional_type(the_type: Callable[..., TypeT]) -> bool:
499504
return any(_is_none_type(union_arg) for union_arg in union_args)
500505

501506

507+
def _is_enum_type(the_type: Callable[..., TypeT]) -> bool:
508+
"""Return True if the type is an Enum."""
509+
try:
510+
return issubclass(the_type, enum.Enum) # type: ignore
511+
except TypeError:
512+
return False
513+
514+
502515
def _is_union_of_builtins_type(the_type: Callable[..., TypeT]) -> bool:
503516
"""Return True if the type is an Union only made of
504517
None, str, int, float and bool."""

undictify/tests.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
undictify - tests
33
"""
44

5+
import enum
56
import json
67
import pickle
78
import sys
@@ -1395,3 +1396,61 @@ def hello(x: dataclasses.InitVar[Any], y: dataclasses.InitVar[Any], # pylint: d
13951396

13961397
with self.assertRaises(TypeError):
13971398
hello(**{'x': 'hello', 'y': 'world', 'z': 'twelve'})
1399+
1400+
1401+
class SomeIntEnum(enum.Enum):
1402+
"""An enum with int values"""
1403+
FOO = 1
1404+
BAR = 2
1405+
1406+
1407+
class SomeAutoEnum(enum.Enum):
1408+
"""An enum with auto values"""
1409+
FOO = enum.auto()
1410+
BAR = enum.auto()
1411+
1412+
1413+
class SomeStrEnum(enum.Enum):
1414+
"""An enum with str values"""
1415+
FOO = "FOO"
1416+
BAR = "NOTEXACTLYBAR"
1417+
1418+
1419+
@type_checked_constructor()
1420+
class WithIntEnum(NamedTuple):
1421+
"""Some dummy class with int enum."""
1422+
int_enum: SomeIntEnum
1423+
1424+
1425+
@type_checked_constructor()
1426+
class WithStrEnum(NamedTuple):
1427+
"""Some dummy class with str enum."""
1428+
str_enum: SomeStrEnum
1429+
1430+
1431+
@type_checked_constructor()
1432+
class WithAutoEnum(NamedTuple):
1433+
"""Some dummy class with auto enum."""
1434+
auto_enum: SomeAutoEnum
1435+
1436+
1437+
class TestWithEnums(unittest.TestCase):
1438+
"""Enums should work too"""
1439+
1440+
def test_int_enum(self) -> None:
1441+
"""Valid JSON string."""
1442+
object_repr = '''{"int_enum": 2}'''
1443+
obj = WithIntEnum(**json.loads(object_repr))
1444+
self.assertEqual(SomeIntEnum.BAR, obj.int_enum)
1445+
1446+
def test_str_enum(self) -> None:
1447+
"""Valid JSON string."""
1448+
object_repr = '''{"str_enum": "NOTEXACTLYBAR"}'''
1449+
obj = WithStrEnum(**json.loads(object_repr))
1450+
self.assertEqual(SomeStrEnum.BAR, obj.str_enum)
1451+
1452+
def test_auto_enum(self) -> None:
1453+
"""Valid JSON string."""
1454+
object_repr = '''{"auto_enum": ''' + str(SomeAutoEnum.FOO.value) + '''}'''
1455+
obj = WithAutoEnum(**json.loads(object_repr))
1456+
self.assertEqual(SomeAutoEnum.FOO, obj.auto_enum)

0 commit comments

Comments
 (0)