blob: 937193800ec0fd35e8db48216bea46fed6bdb0c2 [file] [log] [blame]
#!/usr/bin/env fuchsia-vendored-python
# Copyright 2022 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from dataclasses import dataclass, field
import json
from typing import Dict, List, Optional, Set, Union
import unittest
from serialization import (
instance_to_dict,
instance_from_dict,
serialize_dict,
serialize_json,
serialize_fields_as,
)
class SerializeFieldsTest(unittest.TestCase):
"""Validate that fields of different kinds are properly serialized."""
def test_serialize_simple_class(self):
@dataclass
class SimpleClass:
int_field: int
str_field: str
instance = SimpleClass(42, "a string")
self.assertEqual(
instance_to_dict(instance),
{"int_field": 42, "str_field": "a string"},
)
def test_deserialize_simple_class(self):
@dataclass
class SimpleClass:
int_field: int
str_field: str
value = {"int_field": 42, "str_field": "a string"}
self.assertEqual(
instance_from_dict(SimpleClass, value), SimpleClass(42, "a string")
)
def test_deserialize_simple_class_with_default_value(self):
@dataclass
class SimpleClass:
int_field: int
str_field: str = "a default value"
value = {"int_field": 42}
self.assertEqual(
instance_from_dict(SimpleClass, value),
SimpleClass(42, "a default value"),
)
def test_deserialize_simple_class_with_default_factory(self):
@dataclass
class SimpleClass:
int_field: int
str_field: List[str] = field(default_factory=list)
value = {"int_field": 42}
self.assertEqual(
instance_from_dict(SimpleClass, value), SimpleClass(42, [])
)
def test_serialize_int_value_0(self):
@dataclass
class SimpleClass:
int_field: int
str_field: str
instance = SimpleClass(0, "a string")
self.assertEqual(
instance_to_dict(instance),
{"int_field": 0, "str_field": "a string"},
)
def test_serialize_optional_field_with_value(self):
@dataclass
class SimpleClassWithOptionalField:
int_field: Optional[int]
str_field: str
instance = SimpleClassWithOptionalField(21, "some value")
self.assertEqual(
instance_to_dict(instance),
{"int_field": 21, "str_field": "some value"},
)
def test_serialize_optional_field_without_value(self):
@dataclass
class SimpleClassWithOptionalField:
int_field: Optional[int]
str_field: str
instance = SimpleClassWithOptionalField(None, "some value")
self.assertEqual(
instance_to_dict(instance), {"str_field": "some value"}
)
def test_serialize_list_fields(self):
@dataclass
class SimpleClassWithList:
int_field: List[int] = field(default_factory=list)
str_field: str = "foo"
instance = SimpleClassWithList([1, 2, 3, 4, 5])
self.assertEqual(
instance_to_dict(instance),
{"int_field": [1, 2, 3, 4, 5], "str_field": "foo"},
)
def test_serialize_empty_list_fields(self):
@dataclass
class SimpleClassWithList:
int_field: List[int] = field(default_factory=list)
str_field: str = "foo"
instance = SimpleClassWithList()
self.assertEqual(
instance_to_dict(instance), {"int_field": [], "str_field": "foo"}
)
def test_deserialize_list_fields(self):
@dataclass
class SimpleClassWithList:
int_field: List[int] = field(default_factory=list)
str_field: str = "foo"
instance = SimpleClassWithList([1, 2, 3, 4, 5])
self.assertEqual(
instance_from_dict(
SimpleClassWithList,
{"int_field": [1, 2, 3, 4, 5], "str_field": "foo"},
),
instance,
)
def test_deserialize_empty_list_fields(self):
@dataclass
class SimpleClassWithList:
int_field: List[int] = field(default_factory=list)
str_field: str = "foo"
instance = SimpleClassWithList()
self.assertEqual(
instance_from_dict(
SimpleClassWithList, {"int_field": [], "str_field": "foo"}
),
instance,
)
def test_deserialize_missing_list_fields_empty(self):
@dataclass
class SimpleClassWithList:
int_field: List[int] = field(default_factory=list)
str_field: str = "foo"
instance = SimpleClassWithList()
self.assertEqual(
instance_from_dict(SimpleClassWithList, {"str_field": "foo"}),
instance,
)
def test_serialize_set_fields(self):
# Note that this also tests that sets are serialized into sorted-order
@dataclass
class SimpleClassWithSet:
int_field: Set[int] = field(default_factory=set)
str_field: str = "foo"
instance = SimpleClassWithSet(set([5, 4, 3, 2, 1]))
self.assertEqual(
instance_to_dict(instance),
{"int_field": [1, 2, 3, 4, 5], "str_field": "foo"},
)
def test_serialize_empty_set_fields(self):
@dataclass
class SimpleClassWithSet:
int_field: Set[int] = field(default_factory=set)
str_field: str = "foo"
instance = SimpleClassWithSet()
self.assertEqual(
instance_to_dict(instance), {"int_field": [], "str_field": "foo"}
)
def test_deserialize_set_fields(self):
@dataclass
class SimpleClassWithSet:
int_field: Set[int] = field(default_factory=set)
str_field: str = "foo"
instance = SimpleClassWithSet(set([5, 4, 3, 2, 1]))
self.assertEqual(
instance_from_dict(
SimpleClassWithSet,
{"int_field": [1, 2, 3, 4, 5], "str_field": "foo"},
),
instance,
)
def test_deserialize_empty_set_fields(self):
@dataclass
class SimpleClassWithSet:
int_field: Set[int] = field(default_factory=set)
str_field: str = "foo"
instance = SimpleClassWithSet()
self.assertEqual(
instance_from_dict(
SimpleClassWithSet, {"int_field": [], "str_field": "foo"}
),
instance,
)
def test_deserialize_missing_set_fields_empty(self):
@dataclass
class SimpleClassWithSet:
str_field: str = "foo"
int_field: Set[int] = field(default_factory=set)
instance = SimpleClassWithSet()
self.assertEqual(
instance_from_dict(SimpleClassWithSet, {"str_field": "foo"}),
instance,
)
def test_serialize_dict_fields(self):
@dataclass
class SimpleClassWithDict:
dict_field: Dict[str, int]
instance = SimpleClassWithDict({"one": 1, "two": 2, "three": 3})
self.assertEqual(
instance_to_dict(instance),
{"dict_field": {"one": 1, "two": 2, "three": 3}},
)
def test_serialize_fields_as(self):
@dataclass
@serialize_fields_as(int_field=str)
class SimpleClassWithMetdata:
int_field: int
str_field: str
instance = SimpleClassWithMetdata(7, "a string")
self.assertEqual(
instance_to_dict(instance),
{
"int_field": "7",
"str_field": "a string",
},
)
def test_serialize_fields_as_with_callable(self):
def my_serializer(value: int) -> str:
return f"The value is {value}."
@dataclass
@serialize_fields_as(int_field=my_serializer)
class SimpleClassWithMetdata:
int_field: int
str_field: str
instance = SimpleClassWithMetdata(7, "a string")
self.assertEqual(
instance_to_dict(instance),
{
"int_field": "The value is 7.",
"str_field": "a string",
},
)
def test_serialize_class_with_superclass(self):
@dataclass
class SimpleBaseClass:
int_field_base: int
str_field_base: str
@dataclass
class SimpleChildClass(SimpleBaseClass):
int_field_child: int
str_field_child: str
instance = SimpleChildClass(
int_field_base=42,
str_field_base="base",
int_field_child=84,
str_field_child="child",
)
self.assertEqual(
instance_to_dict(instance),
{
"int_field_base": 42,
"str_field_base": "base",
"int_field_child": 84,
"str_field_child": "child",
},
)
def test_deserialize_class_with_superclass(self):
@dataclass
class SimpleBaseClass:
int_field_base: int
str_field_base: str
@dataclass
class SimpleChildClass(SimpleBaseClass):
int_field_child: int
str_field_child: str
instance = SimpleChildClass(
int_field_base=42,
str_field_base="base",
int_field_child=84,
str_field_child="child",
)
self.assertEqual(
instance_from_dict(
SimpleChildClass,
{
"int_field_base": 42,
"str_field_base": "base",
"int_field_child": 84,
"str_field_child": "child",
},
),
instance,
)
def test_serialize_class_with_multiple_superclasses(self):
@dataclass
class RootClass:
int_field_root: int
@dataclass
class SimpleBaseClass(RootClass):
int_field_base: int
str_field_base: str
@dataclass
class AnotherBaseClass:
str_field_another: str
@dataclass
class SimpleChildClass(SimpleBaseClass, AnotherBaseClass):
int_field_child: int
str_field_child: str
instance = SimpleChildClass(
int_field_root=89,
int_field_base=42,
str_field_base="base",
str_field_another="another",
int_field_child=84,
str_field_child="child",
)
self.assertEqual(
instance_to_dict(instance),
{
"int_field_root": 89,
"int_field_base": 42,
"str_field_base": "base",
"str_field_another": "another",
"int_field_child": 84,
"str_field_child": "child",
},
)
def test_deserialize_class_with_multiple_superclasses(self):
@dataclass
class RootClass:
int_field_root: int
@dataclass
class SimpleBaseClass(RootClass):
int_field_base: int
str_field_base: str
@dataclass
class AnotherBaseClass:
str_field_another: str
@dataclass
class SimpleChildClass(SimpleBaseClass, AnotherBaseClass):
int_field_child: int
str_field_child: str
instance = SimpleChildClass(
int_field_root=89,
int_field_base=42,
str_field_base="base",
str_field_another="another",
int_field_child=84,
str_field_child="child",
)
self.assertEqual(
instance_from_dict(
SimpleChildClass,
{
"int_field_root": 89,
"int_field_base": 42,
"str_field_base": "base",
"str_field_another": "another",
"int_field_child": 84,
"str_field_child": "child",
},
),
instance,
)
def test_deserialize_class_with_union_fields_first_type(self):
@dataclass
class SimpleClass:
int_field: int
union_field: Union[int, List[str]]
self.assertEqual(
SimpleClass(42, 23),
instance_from_dict(
SimpleClass, {"int_field": 42, "union_field": 23}
),
)
def test_deserialize_class_with_union_fields_second_type(self):
@dataclass
class SimpleClass:
int_field: int
union_field: Union[int, List[str]]
self.assertEqual(
SimpleClass(42, ["23"]),
instance_from_dict(
SimpleClass, {"int_field": 42, "union_field": ["23"]}
),
)
def test_deserialize_class_with_optional_fields_new_syntax_with_value(self):
@dataclass
class SimpleClass:
int_field: int
str_field: str | None = None
self.assertEqual(
SimpleClass(42, "foo"),
instance_from_dict(
SimpleClass, {"int_field": 42, "str_field": "foo"}
),
)
def test_deserialize_class_with_optional_fields_new_syntax_without_value(
self,
):
@dataclass
class SimpleClass:
int_field: int
str_field: str | None = None
self.assertEqual(
SimpleClass(42),
instance_from_dict(SimpleClass, {"int_field": 42}),
)
def test_deserialize_class_with_optional_fields_new_syntax_with_explicit_none_value(
self,
):
@dataclass
class SimpleClass:
int_field: int
str_field: str | None = None
self.assertEqual(
SimpleClass(42),
instance_from_dict(
SimpleClass, {"int_field": 42, "str_field": None}
),
)
# Test when the field is not present
self.assertEqual(
SimpleClass(42),
instance_from_dict(SimpleClass, {"int_field": 42}),
)
def test_deserialize_class_with_union_fields_new_syntax_first_type(self):
@dataclass
class SimpleClass:
int_field: int
union_field: str | list[str]
self.assertEqual(
SimpleClass(42, "foo"),
instance_from_dict(
SimpleClass, {"int_field": 42, "union_field": "foo"}
),
)
def test_deserialize_class_with_union_fields_new_syntax_second_type(self):
@dataclass
class SimpleClass:
int_field: int
union_field: int | list[str]
self.assertEqual(
SimpleClass(42, ["foo"]),
instance_from_dict(
SimpleClass, {"int_field": 42, "union_field": ["foo"]}
),
)
def test_deserialize_class_with_union_fields_mixed_syntax_second_type(self):
@dataclass
class SimpleClass:
int_field: int
union_field: str | List[str]
self.assertEqual(
SimpleClass(42, ["foo"]),
instance_from_dict(
SimpleClass, {"int_field": 42, "union_field": ["foo"]}
),
)
def test_deserialize_class_with_optional_union_fields_new_syntax_first_type(
self,
):
@dataclass
class SimpleClass:
int_field: int
union_field: str | list[int] | None = None
self.assertEqual(
SimpleClass(42, "foo"),
instance_from_dict(
SimpleClass, {"int_field": 42, "union_field": "foo"}
),
)
def test_deserialize_class_with_optional_union_fields_new_syntax_second_type(
self,
):
@dataclass
class SimpleClass:
int_field: int
union_field: list[int] | str | None = None
self.assertEqual(
SimpleClass(42, [23]),
instance_from_dict(
SimpleClass, {"int_field": 42, "union_field": [23]}
),
)
def test_deserialize_class_with_optional_union_fields_new_syntax_with_no_value(
self,
):
@dataclass
class SimpleClass:
int_field: int
union_field: str | list[int] | None = None
self.assertEqual(
SimpleClass(42),
instance_from_dict(SimpleClass, {"int_field": 42}),
)
def test_deserialize_class_with_optional_union_fields_new_syntax_with_explicit_none_value(
self,
):
@dataclass
class SimpleClass:
int_field: int
union_field: str | list[int] | None = None
self.assertEqual(
SimpleClass(42),
instance_from_dict(
SimpleClass, {"int_field": 42, "union_field": None}
),
)
def test_serialize_nested_classes(self):
@dataclass
class Inner:
int_field: int
@dataclass
class Outer:
inner: Inner
self.assertEqual(
{"inner": {"int_field": 43}},
instance_to_dict(Outer(Inner(43))),
)
def test_deserialize_nested_classes(self):
@dataclass
class Inner:
int_field: int
@dataclass
class Outer:
inner: Inner
self.assertEqual(
Outer(Inner(43)),
instance_from_dict(Outer, {"inner": {"int_field": 43}}),
)
class SerializeToDictDecorator(unittest.TestCase):
"""Validate that the `@serialize_to_dict class decorator behaves correctly.
Note: These function correctly at runtime, but don't interact correctly with
PyRight, so they don't have proper static analysis and IDE type-checking.
"""
def test_to_dict_decorator(self):
@dataclass
@serialize_dict
class SimpleClass:
int_field: int
str_field: str
instance = SimpleClass(8, "some value")
self.assertEqual(
instance.to_dict(), {"int_field": 8, "str_field": "some value"}
)
def test_from_dict_decorator(self):
@dataclass
@serialize_dict
class SimpleClass:
int_field: int
str_field: str
raw = {"int_field": 8, "str_field": "some value"}
self.assertEqual(
SimpleClass.from_dict(raw), SimpleClass(8, "some value")
)
class SerializeToJsonDecorator(unittest.TestCase):
"""Validate that the `@serialize_to_json class decorator behaves correctly.
Note: These function correctly at runtime, but don't interact correctly with
PyRight, so they don't have proper static analysis and IDE type-checking.
"""
def test_to_json_decorator(self):
@dataclass
@serialize_json
class SimpleClass:
int_field: int
str_field: str
instance = SimpleClass(8, "some value")
result = instance.json_dumps(indent=6)
self.assertEqual(
result,
"""{
"int_field": 8,
"str_field": "some value"
}""",
)
def test_from_json_decorator(self):
@dataclass
@serialize_json
class SimpleClass:
int_field: int
str_field: str
raw = {"int_field": 8, "str_field": "some value"}
raw_json = json.dumps(raw)
self.assertEqual(
SimpleClass.json_loads(raw_json), SimpleClass(8, "some value")
)
def test_to_json_decorator_with_field_serializer(self):
def my_serializer(value: int) -> str:
return f"my value is {value}."
@dataclass
@serialize_fields_as(int_field=my_serializer)
@serialize_json
class SimpleClass:
int_field: int
str_field: str
instance = SimpleClass(8, "some value")
result = instance.json_dumps(indent=6)
self.assertEqual(
result,
"""{
"int_field": "my value is 8.",
"str_field": "some value"
}""",
)