Commit 00e99d9c authored by Rolf H. B. van Kleef's avatar Rolf H. B. van Kleef

Fixed defaulting

parent 5d9c0d96
Pipeline #1260 canceled with stages
......@@ -86,22 +86,28 @@ class Deserializable(metaclass=BaseMeta):
return fields
def get_deserialization_class(t, d, try_all=False):
def get_deserialization_classes(t, d, try_all=True) -> List[type]:
candidates = []
for sc in t.__subclasses__():
if hasattr(sc, '_discriminators'):
for discriminator in sc._discriminators:
if not discriminator.check(d):
# Invalid
break
else:
# All were valid
try:
return get_deserialization_class(sc, t, try_all)
candidates.extend(get_deserialization_classes(sc, t, try_all))
except TypeError as e:
if not try_all:
raise e
return t
if not getattr(t, '_abstract', True):
candidates.append(t)
return candidates
def deserialize(rule: Rule, data, try_all=False, key=None):
def deserialize(rule: Rule, data, try_all=True, key='$'):
# In case of primitive types, attempt to assign.
try:
return rule.validate(key, data)
......@@ -111,7 +117,10 @@ def deserialize(rule: Rule, data, try_all=False, key=None):
if type(rule.type) is type(Union):
for arg in rule.type.__args__:
try:
return deserialize(Rule(arg), data, try_all, key)
v = deserialize(Rule(arg), data, try_all, key)
if v is None:
v = rule.default
return v
except TypeError:
pass
raise TypeError('{} did not match any of {} for key {}.'.format(type(data), rule.type.__args__, key))
......@@ -126,23 +135,27 @@ def deserialize(rule: Rule, data, try_all=False, key=None):
t = rule.type.__args__[0]
result = []
for i, v in enumerate(data):
result.append(deserialize(Rule(t), v, try_all, i))
result.append(deserialize(Rule(t), v, try_all, '{}.{}'.format(key, i)))
return result
if issubclass(rule.type, Deserializable):
if not isinstance(data, dict):
raise TypeError('Cannot deserialize non-dict into class.')
cls = get_deserialization_class(rule.type, data, try_all)
classes = get_deserialization_classes(rule.type, data, try_all)
if hasattr(cls, '_abstract') and cls._abstract:
raise TypeError('Cannot deserialize into {}: is abstract.'.format(cls.__name__))
for cls in classes:
try:
instance = cls()
for k, r in cls.get_attrs().items():
v = deserialize(r, data[k] if k in data else r.default, try_all, key='{}.{}'.format(key, k))
setattr(instance, k, v)
instance = cls()
for k, r in cls.get_attrs().items():
v = deserialize(r, data[k] if k in data else r.default, try_all, key=k)
setattr(instance, k, v)
return instance
except TypeError as e:
if not try_all:
raise e
return instance
raise TypeError('Unable to find matching non-abstract (sub)type of {} with key {}.'.format(rule.type, key))
raise TypeError('Unable to find a deserialization candidate for {} in {}.'.format(data, rule))
\ No newline at end of file
raise TypeError('Unable to find a deserialization candidate for {} with key {}.'.format(rule, key))
\ No newline at end of file
import unittest
from typing import List, Optional
from dict_deserializer.annotations import abstract
from dict_deserializer.deserializer import Deserializable, deserialize, Rule
@abstract
class Object(Deserializable):
def __init__(self, name=None):
self.name = name
name: str
def __repr__(self):
return 'Object(name="{}")'.format(self.name)
def __eq__(self, other):
return isinstance(other, Object) and other.name == self.name
class User(Object):
def __init__(self, full_name=None, calling_name=None, *args, **kwargs):
super(User, self).__init__(*args, **kwargs)
self.full_name = full_name
self.calling_name = calling_name
full_name: str
calling_name: Optional[str] = 'Unknown'
def __repr__(self):
if self.calling_name is None:
return 'User(super={}, full_name="{}")'\
.format(super(User, self).__repr__(), self.full_name)
return 'User(super={}, full_name="{}", calling_name="{}")'\
.format(super(User, self).__repr__(),
self.full_name, self.calling_name)
def __eq__(self, other):
return isinstance(other, User) and super(User, self).__eq__(other) and\
other.full_name == self.full_name and\
other.calling_name == self.calling_name
class Group(Object):
def __init__(self, members=None, *args, **kwargs):
super(Group, self).__init__(*args, **kwargs)
self.members = members
members: List[Object]
def __repr__(self):
return 'Group(super={}, members=[{}])'\
.format(super(Group, self).__repr__(),
','.join([m.__repr__() for m in self.members]))
def __eq__(self, other):
return isinstance(other, Group) and super(Group, self).__eq__(other)\
and other.members == self.members
class TestLists(unittest.TestCase):
def test_CorrectDeserializationForNestedWithTypeUnionsAndLists(self):
self.assertEqual(
Group(
name='IAPC',
members=[
User(name='Rolf', full_name='Rolf van Kleef', calling_name='Unknown'),
Group(name='Syscom', members=[
User(name='Kevin', full_name='Kevin Alberts', calling_name='Kevin'),
]),
],
),
deserialize(Rule(Object), {
'name': 'IAPC',
'members': [
{'name': 'Rolf', 'full_name': 'Rolf van Kleef'},
{'name': 'Syscom', 'members': [
{'name': 'Kevin', 'full_name': 'Kevin Alberts', 'calling_name': 'Kevin'},
]},
],
})
)
def test_FailDeserializeWithInvalidTypes(self):
with self.assertRaises(TypeError):
deserialize(Rule(Object), {
'name': 'Karel',
'full_name': 0.0,
})
with self.assertRaises(TypeError):
deserialize(Rule(Object), {
'name': 'Rolf',
'full_name': 'Rolf van Kleef',
'calling_name': False,
})
def test_DeserializeIntoAbstract(self):
with self.assertRaises(TypeError) as ctx:
deserialize(Rule(Object), {
'name': 'Test'
})
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment