Skip to content

Commit

Permalink
feat: Support variable length tuples
Browse files Browse the repository at this point in the history
  • Loading branch information
yukinarit committed Nov 18, 2022
1 parent 9dcb0af commit 74dd3d4
Show file tree
Hide file tree
Showing 8 changed files with 39 additions and 3 deletions.
23 changes: 23 additions & 0 deletions examples/ellipsis.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from dataclasses import dataclass
from typing import Tuple

from serde import serde
from serde.json import from_json, to_json


@serde
@dataclass
class Foo:
v: Tuple[int, ...]


def main():
f = Foo(v=(10, 'foo'))
print(f"Into Json: {to_json(f)}")

s = '{"v": [10, "foo"]}'
print(f"From Json: {from_json(Foo, s)}")


if __name__ == '__main__':
main()
2 changes: 2 additions & 0 deletions examples/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import custom_class_serializer
import custom_field_serializer
import default
import ellipsis
import env
import flatten
import forward_reference
Expand Down Expand Up @@ -61,6 +62,7 @@ def run_all():
run(type_check_coerce)
run(user_exception)
run(pep681)
run(ellipsis)
if PY310:
import union_operator

Expand Down
2 changes: 2 additions & 0 deletions serde/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,8 @@ def typename(typ: Type[Any], with_typing_module: bool = False) -> str:
return f'Literal[{", ".join(str(e) for e in args)}]'
elif typ is Any:
return f'{mod}Any'
elif typ is Ellipsis:
return '...'
else:
# Get super type for NewType
inner = getattr(typ, '__supertype__', None)
Expand Down
2 changes: 2 additions & 0 deletions serde/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,8 @@ def is_instance(obj: Any, typ: Type) -> bool:
elif is_new_type_primitive(typ):
inner = getattr(typ, '__supertype__')
return isinstance(obj, inner)
elif typ is Ellipsis:
return True
else:
return isinstance(obj, typ)

Expand Down
4 changes: 2 additions & 2 deletions serde/de.py
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,7 @@ def from_obj(c: Type, o: Any, named: bool, reuse_instances: bool):
return deserialize_numpy_array_direct(c, o)
elif is_datetime(c):
return c.fromisoformat(o)
elif is_any(c):
elif is_any(c) or c is Ellipsis:
return o

return c(o)
Expand Down Expand Up @@ -569,7 +569,7 @@ def render(self, arg: DeField) -> str:
if reuse_instances else {from_iso}"
elif is_none(arg.type):
res = "None"
elif arg.type is Any:
elif arg.type is Any or arg.type is Ellipsis:
res = arg.data
elif isinstance(arg.type, TypeVar):
index = find_generic_arg(self.cls, arg.type)
Expand Down
2 changes: 1 addition & 1 deletion serde/se.py
Original file line number Diff line number Diff line change
Expand Up @@ -652,7 +652,7 @@ def render(self, arg: SeField) -> str:
res = f"{arg.varname} if reuse_instances else {arg.varname}.isoformat()"
elif is_none(arg.type):
res = "None"
elif arg.type is Any or isinstance(arg.type, TypeVar):
elif arg.type is Any or arg.type is Ellipsis or isinstance(arg.type, TypeVar):
res = f"to_obj({arg.varname}, True, False, False)"
elif is_generic(arg.type):
arg.type = get_origin(arg.type)
Expand Down
1 change: 1 addition & 0 deletions tests/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ def toml_not_supported(se, de, opt) -> bool:
param(set(), Set[int], toml_not_supported),
param((1, 1), Tuple[int, int]),
param((1, 1), Tuple),
param((1, 1), Tuple[int, ...]),
param({'a': 1}, Dict[str, int]),
param({'a': 1}, Dict),
param({'a': 1}, dict),
Expand Down
6 changes: 6 additions & 0 deletions tests/test_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ def test_types():
assert is_set(Set)
assert is_tuple(Tuple[int, int, int])
assert is_tuple(Tuple)
assert is_tuple(Tuple[int, ...])
assert is_dict(Dict[str, int])
assert is_dict(Dict)
assert is_opt(Optional[int])
Expand Down Expand Up @@ -81,6 +82,7 @@ class Foo(Generic[T]):
assert typename(List[int]) == "List[int]"
assert typename(Tuple) == "Tuple"
assert typename(Tuple[int, str]) == "Tuple[int, str]"
assert typename(Tuple[int, ...]) == "Tuple[int, ...]"
assert typename(Dict) == "Dict"
assert typename(Dict[str, Foo]) == "Dict[str, Foo]"
assert typename(Set) == "Set"
Expand All @@ -93,6 +95,7 @@ def test_iter_types():
assert [Dict, str, Pri, int, str, float, bool] == list(iter_types(Dict[str, Pri]))
assert [List, str] == list(iter_types(List[str]))
assert [Tuple, int, str, bool, float] == list(iter_types(Tuple[int, str, bool, float]))
assert [Tuple, int, Ellipsis] == list(iter_types(Tuple[int, ...]))
assert [PriOpt, Optional, int, Optional, str, Optional, float, Optional, bool] == list(iter_types(PriOpt))

@serde.serde
Expand Down Expand Up @@ -132,11 +135,13 @@ def test_type_args():
assert (List[int], type(None)) == type_args(Optional[List[int]])
assert (List[int], Dict[str, int]) == type_args(Union[List[int], Dict[str, int]])
assert (int, type(None), str) == type_args(Union[Optional[int], str])
assert (int, Ellipsis) == type_args(Tuple[int, ...])

if sys.version_info[:3] >= (3, 9, 0):
assert (int,) == type_args(list[int])
assert (int, str) == type_args(dict[int, str])
assert (int, str) == type_args(tuple[int, str])
assert (int, Ellipsis) == type_args(tuple[int, ...])


def test_union_args():
Expand Down Expand Up @@ -199,6 +204,7 @@ class Foo:
assert is_instance((10, "a"), tuple)
assert is_instance((10, 'foo', 100.0, True), Tuple[int, str, float, bool])
assert not is_instance((10, 'foo', 100.0, "last-type-is-wrong"), Tuple[int, str, float, bool])
assert is_instance((10, "a"), Tuple[int, ...])

# Tuple of dataclasses
assert is_instance((Int(10), Str('foo'), Float(100.0), Bool(True)), Tuple[Int, Str, Float, Bool])
Expand Down

0 comments on commit 74dd3d4

Please sign in to comment.