Verified Commit 00e99d9c authored by Rolf van Kleef's avatar Rolf van Kleef
Browse files

Fixed defaulting

parent 5d9c0d96
Loading
Loading
Loading
Loading
Loading
+28 −15
Original line number Diff line number Diff line
@@ -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)

        if hasattr(cls, '_abstract') and cls._abstract:
            raise TypeError('Cannot deserialize into {}: is abstract.'.format(cls.__name__))
        classes = get_deserialization_classes(rule.type, data, try_all)

        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=k)
                    v = deserialize(r, data[k] if k in data else r.default, try_all, key='{}.{}'.format(key, k))
                    setattr(instance, k, v)

                return instance
            except TypeError as e:
                if not try_all:
                    raise e

        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
+103 −0
Original line number Diff line number Diff line
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'
            })