Skip to content

Commit acf5264

Browse files
authored
Merge pull request yukinarit#123 from ydylla/bugfixes
Some bugfixes
2 parents 23c319e + a88ea40 commit acf5264

File tree

6 files changed

+88
-8
lines changed

6 files changed

+88
-8
lines changed

serde/compat.py

+2
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,8 @@ def iter_unions(cls: Type) -> Iterator[Type]:
159159
"""
160160
if is_union(cls):
161161
yield cls
162+
for arg in type_args(cls):
163+
yield from iter_unions(arg)
162164
if is_dataclass(cls):
163165
for f in fields(cls):
164166
yield from iter_unions(f.type)

serde/de.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -503,11 +503,11 @@ def tuple(self, arg: DeField) -> str:
503503
... @dataclass
504504
... class Foo: pass
505505
>>> Renderer('foo').render(DeField(Tuple[str, int, List[int], Foo], 'd', datavar='data'))
506-
'(data["d"][0], data["d"][1], [v for v in data["d"][2]], Foo.__serde__.funcs[\\'foo\\'](data["d"][3], reuse_instances=reuse_instances))'
506+
'(data["d"][0], data["d"][1], [v for v in data["d"][2]], Foo.__serde__.funcs[\\'foo\\'](data["d"][3], reuse_instances=reuse_instances),)'
507507
508508
>>> field = DeField(Tuple[str, int, List[int], Foo], 'd', datavar='data', index=0, iterbased=True)
509509
>>> Renderer('foo').render(field)
510-
"(data[0][0], data[0][1], [v for v in data[0][2]], Foo.__serde__.funcs['foo'](data[0][3], reuse_instances=reuse_instances))"
510+
"(data[0][0], data[0][1], [v for v in data[0][2]], Foo.__serde__.funcs['foo'](data[0][3], reuse_instances=reuse_instances),)"
511511
"""
512512
if is_bare_tuple(arg.type):
513513
return f'tuple({arg.data})'
@@ -516,7 +516,7 @@ def tuple(self, arg: DeField) -> str:
516516
for i, typ in enumerate(type_args(arg.type)):
517517
inner = arg[i]
518518
values.append(self.render(inner))
519-
return f'({", ".join(values)})'
519+
return f'({", ".join(values)},)' # trailing , is required for single element tuples
520520

521521
def dict(self, arg: DeField) -> str:
522522
"""

serde/se.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,7 @@ def {{func}}(obj, reuse_instances = {{serde_scope.reuse_instances_default}}, con
370370

371371
def render_union_func(cls: Type, union_args: List[Type]) -> str:
372372
template = """
373-
def {{func}}(obj, reuse_instances):
373+
def {{func}}(obj, reuse_instances, convert_sets):
374374
{% for name in serde_scope.types.keys() %}
375375
{{name}} = serde_scope.types['{{name}}']
376376
{% endfor %}
@@ -432,7 +432,7 @@ def render(self, arg: SeField) -> str:
432432
"{k.__serde__.funcs['to_iter'](k, reuse_instances=reuse_instances, convert_sets=convert_sets): v.__serde__.funcs['to_iter'](v, reuse_instances=reuse_instances, convert_sets=convert_sets) for k, v in foo.items()}"
433433
434434
>>> Renderer(TO_ITER).render(SeField(Tuple[str, Foo, int], 'foo'))
435-
"(foo[0], foo[1].__serde__.funcs['to_iter'](foo[1], reuse_instances=reuse_instances, convert_sets=convert_sets), foo[2])"
435+
"(foo[0], foo[1].__serde__.funcs['to_iter'](foo[1], reuse_instances=reuse_instances, convert_sets=convert_sets), foo[2],)"
436436
"""
437437
if is_dataclass(arg.type):
438438
return self.dataclass(arg)
@@ -524,7 +524,7 @@ def tuple(self, arg: SeField) -> str:
524524
r = arg[i]
525525
r.name = f'{arg.varname}[{i}]'
526526
rvalues.append(self.render(r))
527-
return f"({', '.join(rvalues)})"
527+
return f"({', '.join(rvalues)},)" # trailing , is required for single element tuples
528528

529529
def dict(self, arg: SeField) -> str:
530530
"""
@@ -553,7 +553,7 @@ def str(self, arg: SeField) -> str:
553553

554554
def union_func(self, arg: SeField) -> str:
555555
func_name = union_func_name(UNION_SE_PREFIX, type_args(arg.type))
556-
return f"serde_scope.funcs['{func_name}']({arg.varname}, reuse_instances)"
556+
return f"serde_scope.funcs['{func_name}']({arg.varname}, reuse_instances, convert_sets)"
557557

558558

559559
def enum_value(cls, e):

setup.py

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
'pytest-flake8',
3333
'mypy',
3434
'flake8',
35+
'pre-commit',
3536
]
3637

3738
setup(

tests/test_basics.py

+15
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,21 @@ class Nested:
376376
assert p == de(Nested, se(p))
377377

378378

379+
@pytest.mark.parametrize('se,de', all_formats)
380+
def test_single_element_tuples(se, de):
381+
@deserialize
382+
@serialize
383+
@dataclass
384+
class Foo:
385+
a: Tuple[int]
386+
b: Tuple[uuid.UUID]
387+
388+
foo = Foo(a=(1,), b=(uuid.UUID("855f07da-c3cd-46f2-9557-b8dbeb99ff42"),))
389+
assert to_dict(foo) == {"a": foo.a, "b": foo.b}
390+
391+
assert foo == de(Foo, se(foo))
392+
393+
379394
@pytest.mark.parametrize('se,de', all_formats)
380395
def test_dataclass_default_factory(se, de):
381396
@deserialize

tests/test_union.py

+63-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import logging
22
from dataclasses import dataclass
33
from ipaddress import IPv4Address
4-
from typing import Dict, List, Optional, Union
4+
from typing import Dict, List, Optional, Tuple, Union
55
from uuid import UUID
66

77
import pytest
@@ -290,3 +290,65 @@ class Foo:
290290

291291
assert to_dict(Foo(10)) == {'BarBaz': 10}
292292
assert from_dict(Foo, {'BarBaz': 'foo'}) == Foo('foo')
293+
294+
295+
def test_union_with_list_of_other_class():
296+
@deserialize
297+
@serialize
298+
@dataclass
299+
class A:
300+
a: int
301+
302+
@deserialize
303+
@serialize
304+
@dataclass
305+
class B:
306+
b: Union[List[A], str]
307+
308+
b = B([A(1)])
309+
b_dict = {"b": [{"a": 1}]}
310+
assert to_dict(b) == b_dict
311+
assert from_dict(B, b_dict) == b
312+
313+
314+
# relates to https://github.com/yukinarit/pyserde/issues/113
315+
def test_union_with_union_in_nested_types():
316+
@deserialize
317+
@serialize
318+
@dataclass
319+
class A:
320+
v: Union[UUID, List[Union[UUID, int]]]
321+
322+
a_uuid = A([UUID("00611ee9-7ca3-41d3-9607-ea7268e264ea")])
323+
assert to_dict(a_uuid, reuse_instances=False) == {"v": ["00611ee9-7ca3-41d3-9607-ea7268e264ea"]}
324+
assert a_uuid == from_dict(A, to_dict(a_uuid, reuse_instances=False), reuse_instances=False)
325+
assert a_uuid == from_dict(A, to_dict(a_uuid, reuse_instances=True), reuse_instances=True)
326+
327+
a_int = A([1])
328+
assert to_dict(a_int) == {"v": [1]}
329+
assert a_int == from_dict(A, to_dict(a_int, reuse_instances=False), reuse_instances=False)
330+
assert a_int == from_dict(A, to_dict(a_int, reuse_instances=True), reuse_instances=True)
331+
332+
333+
# relates to https://github.com/yukinarit/pyserde/issues/113
334+
def test_union_with_union_in_nested_tuple():
335+
@deserialize
336+
@serialize
337+
@dataclass
338+
class A:
339+
v: Union[bool, Tuple[Union[str, int]]]
340+
341+
a_bool = A(False)
342+
a_bool_dict = {"v": False}
343+
assert to_dict(a_bool) == a_bool_dict
344+
assert from_dict(A, a_bool_dict) == a_bool
345+
346+
a_str = A(("a",))
347+
a_str_dict = {"v": ("a",)}
348+
assert to_dict(a_str) == a_str_dict
349+
assert from_dict(A, a_str_dict) == a_str
350+
351+
a_int = A((1,))
352+
a_int_dict = {"v": (1,)}
353+
assert to_dict(a_int) == a_int_dict
354+
assert from_dict(A, a_int_dict) == a_int

0 commit comments

Comments
 (0)