def test_deserialization_with_yasoo_type_hints(self): @attrs(frozen=True) class Foo: a: int = attrib() l = [[Foo(i) for i in range(5)] for _ in range(5)] l2 = deserialize(serialize(l, type_key=None), obj_type=List_[List_[Foo]]) self.assertIsInstance(l2, list) self.assertIsInstance(l2[0], list) self.assertIsInstance(l2[0][0], Foo) self.assertEqual(l, l2) l = [set(i) for i in l] l2 = deserialize(serialize(l, type_key=None), obj_type=List_[Set_[Foo]]) self.assertIsInstance(l2, list) self.assertIsInstance(l2[0], list) self.assertIsInstance(l2[0][0], Foo) for i1, i2 in zip(l, l2): self.assertEqual(i1, set(i2)) d = dict(enumerate(l)) d2 = deserialize(serialize(d, type_key=None), obj_type=Dict_[int, Set_[Foo]]) self.assertIsInstance(d2, dict) self.assertEqual(d.keys(), d2.keys()) self.assertIsInstance(d2[0], list) self.assertIsInstance(d2[0][0], Foo) for k, v in d.items(): self.assertEqual(v, set(d2[k]))
def test_enum_deserialization_fallback_order(self): class Foo(Enum): A = 5 B = 'a' C = 'A' D = 'x' # Default - by name obj = deserialize({ENUM_VALUE_KEY: 'A'}, obj_type=Foo, globals=locals()) self.assertEqual(obj, Foo.A) # Fallback 1 - by case-insensitive name obj = deserialize({ENUM_VALUE_KEY: 'a'}, obj_type=Foo, globals=locals()) self.assertEqual(obj, Foo.A) # Fallback 2 - by value obj = deserialize({ENUM_VALUE_KEY: 'x'}, obj_type=Foo, globals=locals()) self.assertEqual(obj, Foo.D) # Failure with self.assertRaises(ValueError): deserialize({ENUM_VALUE_KEY: 'y'}, obj_type=Foo, globals=locals())
def test_deserialization_discovers_globals(self): data = {'child': {}} result = deserialize(dict(data), Child, globals=globals()) self.assertIsInstance(result, Child) self.assertEqual('Parent', type(result.child).__name__) with self.assertRaises(TypeError) as e: deserialize(dict(data), Child) self.assertIn('Found type annotation Parent', e.exception.args[0])
def test_attr_extraneous_fields(self): @attrs class Foo: pass try: deserialize({'a': 5}, Foo) self.fail('Deserialized with extraneous fields') except: pass
def test_enum(self): class MyEnum(Enum): FIRST = 1 Second = {'x': 1, 'y': 2} self.assertEqual( MyEnum.FIRST, deserialize(serialize(MyEnum.FIRST, type_key=None), MyEnum)) self.assertEqual( MyEnum.Second, deserialize(serialize(MyEnum.Second, type_key=None), MyEnum))
def test_remove_types(self): original = {'a': 1} type_key = '__type' data = serialize(original, type_key=type_key) restored = deserialize(data, obj_type=dict, type_key=None) self.assertIsInstance(restored, dict) self.assertNotEqual(original, restored) remove_type_data(data, type_key=type_key) restored = deserialize(data, obj_type=dict, type_key=None) self.assertIsInstance(restored, dict) self.assertEqual(original, restored)
def test_deserializer_registration_can_defer_dereference(self): class Foo: pass @staticmethod @deserializer def func(_) -> 'Foo': return Foo() with self.assertRaises(Exception): f = deserialize({}, Foo, globals=globals()) f = deserialize({}, Foo, globals=locals()) self.assertEqual(Foo, type(f))
def test_deserializer_temporary_unregister(self): class Foo: pass @deserializer_of(Foo) def func(_): return Foo() with self.assertRaises(TypeError) as e: with unregister_deserializers(Foo): deserialize({}, Foo, type_key=None) self.assertIn('attrs or dataclass classes', e.exception.args[0]) self.assertIsInstance(deserialize({}, Foo, type_key=None), Foo)
def test_deserialization_with_string_type_hint(self): class Foo: pass @deserializer_of(Foo) def deserialize_foo(_): return Foo() foo = deserialize({}, 'Foo', type_key=None, globals=locals()) self.assertIsInstance(foo, Foo) with self.assertRaises(TypeError) as e: deserialize({}, 'Bar', type_key=None, globals=locals()) self.assertIn('Bar', e.exception.args[0])
def test_deserializer_registration_including_descendants(self): class Foo: pass class Bar(Foo): pass @deserializer_of(Foo, include_descendants=True) def foo(_, obj_type=Foo) -> Foo: return obj_type() self.assertIsInstance(deserialize({}, Foo, type_key=None), Foo) self.assertNotIsInstance(deserialize({}, Foo, type_key=None), Bar) self.assertIsInstance(deserialize({}, Bar, type_key=None), Bar)
def test_attr_deserialization_with_type_hint_and_type_in_data(self): @attrs class Foo: a = attrib() @attrs class FakeFoo: a = attrib() @attrs class Bar: foo: FakeFoo = attrib() b = deserialize({ '__type': 'Bar', 'foo': { 'a': 5, '__type': 'Foo' } }, type_key='__type', globals=locals()) self.assertEqual(type(b), Bar) self.assertEqual(type(b.foo), Foo) self.assertEqual(b.foo.a, 5)
def _check_deserialization_of_inner_iterable_of_classes( self, iterable_type): class Foo: pass @deserializer def deserialize_foo(_) -> Foo: return Foo() it = [{_TYPE_KEY: 'Foo'} for _ in range(5)] deserialized = deserialize( { _TYPE_KEY: FooContainer.__name__, 'foo': { _TYPE_KEY: iterable_type.__name__, ITERABLE_VALUE_KEY: it } }, type_key=_TYPE_KEY, globals=dict(locals(), **globals())) self.assertIsInstance(deserialized, FooContainer) self.assertIsInstance(deserialized.foo, iterable_type) self.assertEqual(len(it), len(deserialized.foo)) for f in deserialized.foo: self.assertIsInstance(f, Foo)
def test_attr_missing_fields(self): @attrs class Foo: a = attrib() bar = attrib(default=None) try: deserialize({'a': 5}, Foo) except: self.fail('Failed to deserialize with non-mandatory field missing') try: deserialize({'bar': 5}, Foo) self.fail('Deserialized even though mandatory field is missing') except: pass
def test_rename_types(self): original = {'a': 1} type_key = '__type' class Foo: @staticmethod @serializer def serialize(_: 'Foo'): return dict(original) data = serialize(Foo(), type_key=type_key, fully_qualified_types=False, globals=locals()) with self.assertRaises(TypeError): deserialize(dict(data), globals=locals()) rename_types(data, type_key, {'Foo': 'builtins.dict'}) restored = deserialize(data, type_key=type_key) self.assertEqual(original, restored)
def test_enum_deserialization(self): class Foo(Enum): A = 5 B = 89 obj = deserialize({ENUM_VALUE_KEY: 5}, obj_type=Foo, globals=locals()) self.assertEqual(type(obj), Foo) self.assertEqual(obj, Foo.A)
def test_attr_deserialization_with_string_type_hint(self): @attrs class Foo: foo: Optional['Foo'] = attrib(default=None) foo = deserialize({'foo': {}}, Foo, type_key=None, globals=locals()) self.assertIsInstance(foo, Foo) self.assertIsInstance(foo.foo, Foo)
def test_attrs_with_only_primitives_no_type_hints(self): @attrs class Foo: i = attrib() f = attrib() s = attrib() b = attrib() n = attrib() f = Foo(i=1, f=.5, s='b', b=True, n=None) f2 = deserialize(serialize(f, fully_qualified_types=False), globals=locals()) self.assertIsInstance(f2, Foo) self.assertEqual(f2, f) f2 = deserialize(serialize(f, type_key=None), obj_type=Foo) self.assertIsInstance(f2, Foo) self.assertEqual(f2, f)
def test_attr_deserialization_with_allow_extra_fields(self): @attrs class Foo: pass foo = deserialize({'a': 5}, Foo, allow_extra_fields=True) self.assertIsInstance(foo, Foo) self.assertFalse(hasattr(foo, 'a'))
def test_attr_deserialization(self): @attrs class Foo: a = attrib() f = deserialize({'a': 5}, Foo) self.assertEqual(type(f), Foo) self.assertEqual(f.a, 5)
def test_dataclass_deserialization(self): @dataclass class Foo: a: Any f = deserialize({'a': 5}, Foo) self.assertEqual(type(f), Foo) self.assertEqual(f.a, 5)
def test_dataclass_missing_fields(self): @dataclass class Foo: a: Any bar: Any = field(default=None) try: deserialize({'a': 5}, Foo, None) except: self.fail( 'Failed to deserialize with non-mandatory field missing') try: deserialize({'bar': 5}, Foo, None) self.fail( 'Deserialized even though mandatory field is missing') except: pass
def test_deserializer_registration_datetime_overrides_default(self): _datetime = datetime.now() @deserializer_of(datetime) def foo(_) -> datetime: return _datetime self.assertEqual(_datetime, deserialize({'time': 0}, datetime, type_key=None))
def test_deserializer_registration(self): class Foo: pass @deserializer_of(Foo) def func(_): return Foo() f = deserialize({}, Foo) self.assertEqual(Foo, type(f))
def test_deserializer_registration_user_defined_generic(self): class Foo(Generic[T]): pass @deserializer def func(_) -> Foo: return Foo() f = deserialize({}, Foo[int]) self.assertIsInstance(f, Foo)
def test_stringified_dict_key_types(self): original = {'a': 1, 2: 'b', True: 3} serialized = serialize(original, stringify_dict_keys=True) self.assertIsInstance(serialized, dict) for k in serialized.keys(): self.assertIsInstance(k, str) restored = deserialize(serialized) self.assertEqual(original, restored)
def test_attrs_with_list_of_primitives_without_type_hint(self): @attrs class Foo: l = attrib() f = Foo([1, 'a', True, None]) f2 = deserialize(serialize(f, fully_qualified_types=False), globals=locals()) self.assertIsInstance(f2, Foo) self.assertIsInstance(f2.l, list) self.assertEqual(f.l, f2.l)
def test_enum_deserialization_case_insensitive(self): class Foo(Enum): AbC = 5 B = 89 obj = deserialize({ENUM_VALUE_KEY: Foo.AbC.name.lower()}, obj_type=Foo, globals=locals()) self.assertEqual(type(obj), Foo) self.assertEqual(obj, Foo.AbC)
def test_attr_deserialization_with_non_init_field(self): @attrs class Foo: a: int = attrib() b: str = attrib(init=False) f = deserialize({'a': 5, 'b': 'x'}, Foo, globals=locals()) self.assertIsInstance(f, Foo) self.assertEqual(5, f.a) self.assertEqual('x', f.b)
def test_attrs_with_mixed_tuple_of_primitives_and_type_hints(self): @attrs class Foo: a: Tuple[int, str] = attrib() f = Foo((8, 'dfkjh')) f2 = deserialize(serialize(f, type_key=None, fully_qualified_types=False), Foo, globals=locals()) self.assertIsInstance(f2, Foo) self.assertIsInstance(f2.a, Iterable) self.assertEqual(list(f.a), list(f2.a))
def test_deserializer_registration_type_hint(self): class Foo: pass @deserializer def func(_) -> Foo: return Foo() f = deserialize({}, Foo) self.assertEqual(Foo, type(f))