Skip to content

Commit

Permalink
Add support for dicts target-function parameters. Version 0.6.0
Browse files Browse the repository at this point in the history
  • Loading branch information
Dobiasd committed Feb 12, 2019
1 parent 6f6a536 commit 84425b1
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 14 deletions.
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

setuptools.setup(
name="undictify",
version="0.5.1",
version="0.6.0",
author="Tobias Hermann",
author_email="[email protected]",
description="Type-checked function calls at runtime",
Expand Down
34 changes: 30 additions & 4 deletions undictify/_unpack.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,6 @@ def _unpack_dict(func: WrappedOrFunc[TypeT], # pylint: disable=too-many-argumen
raise TypeError('Union members in target function'
'other than Optional or just built-in types'
'are not supported.')
if _is_dict_type(param.annotation):
raise TypeError('Dict members in target function are not supported.')
if param.name not in data:
if _is_optional_type(param.annotation):
call_arguments[param.name] = None
Expand All @@ -193,7 +191,8 @@ def _get_value(func: WrappedOrFunc[TypeT], value: Any, log_name: str,
skip_superfluous, convert_types)

if _is_dict(value):
return _get_dict_value(func, value) # Use settings of inner value
# Use settings of inner value
return _get_dict_value(func, value, skip_superfluous, convert_types)

allowed_types = list(map(_unwrap_decorator_type, _get_union_types(func) \
if _is_optional_type(func) \
Expand Down Expand Up @@ -251,10 +250,21 @@ def _get_list_value(func: Callable[..., TypeT], value: Any,
return result


def _get_dict_value(func: Callable[..., TypeT], value: Any) -> Any:
def _get_dict_value(func: Callable[..., TypeT], value: Any,
skip_superfluous: bool, convert_types: bool) -> Any:
assert _is_dict(value)
if _is_optional_type(func):
return _get_optional_type(func)(**value) # type: ignore
if _is_dict_type(func):
key_type = _get_dict_key_type(func)
value_type = _get_dict_value_type(func)
typed_dict = {}
for dict_key, dict_value in value.items():
typed_dict[_get_value(key_type, dict_key, 'dict_key',
skip_superfluous, convert_types)] = \
_get_value(value_type, dict_value, 'dict_value',
skip_superfluous, convert_types)
return typed_dict
return func(**value)


Expand Down Expand Up @@ -317,6 +327,22 @@ def _get_optional_type(optional_type: Callable[..., TypeT]) -> Type[TypeT]:
return args[0] # type: ignore


def _get_dict_key_type(dict_type: Callable[..., TypeT]) -> Type[TypeT]:
"""Return the type the keys of a Dict should have."""
assert _is_dict_type(dict_type)
args = dict_type.__args__ # type: ignore
assert len(args) == 2
return args[0] # type: ignore


def _get_dict_value_type(dict_type: Callable[..., TypeT]) -> Type[TypeT]:
"""Return the type the values of a Dict should have."""
assert _is_dict_type(dict_type)
args = dict_type.__args__ # type: ignore
assert len(args) == 2
return args[1] # type: ignore


def _get_type_name(the_type: Callable[..., TypeT]) -> str:
"""Return a printable name of a type."""
if _is_optional_type(the_type):
Expand Down
52 changes: 43 additions & 9 deletions undictify/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -866,7 +866,7 @@ def test_dumps_and_reads(self) -> None:
self.assertEqual(1, foo_obj.val)


class WithUnionOfBuildIns: # pylint: disable=too-few-public-methods
class WithUnionOfBuiltIns: # pylint: disable=too-few-public-methods
"""Dummy class with a Union member."""

def __init__(self, val: Union[int, str]) -> None:
Expand All @@ -881,12 +881,12 @@ def __init__(self, val: Union[str, FooNamedTuple]) -> None:


class TestUnpackingWithUnion(unittest.TestCase):
"""Make sure such classes are rejected."""
"""Make sure such only unions made of builtins are accepted."""

def test_ok(self) -> None:
"""Valid JSON string."""
object_repr = '{"val": "hi"}'
with_union = type_checked_call()(WithUnionOfBuildIns)(
with_union = type_checked_call()(WithUnionOfBuiltIns)(
**json.loads(object_repr))
self.assertEqual('hi', with_union.val)

Expand Down Expand Up @@ -917,18 +917,52 @@ def test_str(self) -> None:
class WithDict: # pylint: disable=too-few-public-methods
"""Dummy class with a Dict member."""

def __init__(self, val: Dict[int, str]) -> None:
self.val: Dict[int, str] = val
def __init__(self, val: Dict[str, int]) -> None:
self.val: Dict[str, int] = val


@type_checked_constructor() # pylint: disable=too-few-public-methods
class DictVal(NamedTuple):
"""Some dummy class as a NamedTuple."""
val: int
msg: str


class WithDictOfClass: # pylint: disable=too-few-public-methods
"""Dummy class with a Dict member."""

def __init__(self, val: Dict[str, DictVal]) -> None:
self.val: Dict[str, DictVal] = val


class TestUnpackingWithDict(unittest.TestCase):
"""Make sure such classes are rejected."""
"""Make sure such dicts are supported."""

def test_str(self) -> None:
"""Valid JSON string, but invalid target class."""
def test_builtin_val(self) -> None:
"""Valid JSON string."""
object_repr = '{"val": {"key1": 1, "key2": 2}}'
with_dict = type_checked_call()(WithDict)(**json.loads(object_repr))
self.assertEqual(1, with_dict.val['key1'])
self.assertEqual(2, with_dict.val['key2'])

def test_class_val(self) -> None:
"""Valid JSON string."""
object_repr = '{"val": {"name": {"val": 1, "msg": "hi"}}}'
with_dict = type_checked_call()(WithDictOfClass)(**json.loads(object_repr))
self.assertEqual(1, with_dict.val['name'].val)
self.assertEqual('hi', with_dict.val['name'].msg)

def test_class_invalid_key_name(self) -> None:
"""Invalid dict-key type."""
object_repr = '{"val": {"name": {"ha": 1, "msg": "hi"}}}'
with self.assertRaises(TypeError):
type_checked_call()(WithDictOfClass)(**json.loads(object_repr))

def test_class_invalid_value_type(self) -> None:
"""Invalid dict-value type."""
object_repr = '{"val": {"name": {"val": "ha", "msg": "hi"}}}'
with self.assertRaises(TypeError):
type_checked_call()(WithDict)(**json.loads(object_repr))
type_checked_call()(WithDictOfClass)(**json.loads(object_repr))


class WithArgs: # pylint: disable=too-few-public-methods
Expand Down

0 comments on commit 84425b1

Please sign in to comment.