Passed
Pull Request — master (#17)
by Ramon
50s
created

default_object_deserializer()   A

Complexity

Conditions 4

Size

Total Lines 39
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 24
dl 0
loc 39
rs 9.304
c 0
b 0
f 0
cc 4
nop 5
1
import inspect
2
from functools import partial
3
from typing import Optional, Callable
4
from jsons._common_impl import get_class_name
5
from jsons._main_impl import load
6
from jsons.exceptions import SignatureMismatchError, UnfulfilledArgumentError
7
8
9
def default_object_deserializer(
10
        obj: dict,
11
        cls: type,
12
        key_transformer: Optional[Callable[[str], str]] = None,
13
        strict: bool = False,
14
        **kwargs) -> object:
15
    """
16
    Deserialize ``obj`` into an instance of type ``cls``. If ``obj`` contains
17
    keys with a certain case style (e.g. camelCase) that do not match the style
18
    of ``cls`` (e.g. snake_case), a key_transformer should be used (e.g.
19
    KEY_TRANSFORMER_SNAKECASE).
20
    :param obj: a serialized instance of ``cls``.
21
    :param cls: the type to which ``obj`` should be deserialized.
22
    :param key_transformer: a function that transforms the keys in order to
23
    match the attribute names of ``cls``.
24
    :param strict: deserialize in strict mode.
25
    :param kwargs: any keyword arguments that may be passed to the
26
    deserializers.
27
    :return: an instance of type ``cls``.
28
    """
29
    concat_kwargs = kwargs
30
    if key_transformer:
31
        obj = {key_transformer(key): obj[key] for key in obj}
32
        concat_kwargs = {
33
            **kwargs,
34
            'key_transformer': key_transformer
35
        }
36
    concat_kwargs['strict'] = strict
37
    constructor_args = _get_constructor_args(obj, cls, **concat_kwargs)
38
    remaining_attrs = {attr_name: obj[attr_name] for attr_name in obj
39
                       if attr_name not in constructor_args}
40
    if strict and remaining_attrs:
41
        unexpected_arg = list(remaining_attrs.keys())[0]
42
        err_msg = 'Type "{}" does not expect "{}"'.format(get_class_name(cls),
43
                                                          unexpected_arg)
44
        raise SignatureMismatchError(err_msg, unexpected_arg, obj, cls)
45
    instance = cls(**constructor_args)
46
    _set_remaining_attrs(instance, remaining_attrs, **kwargs)
47
    return instance
48
49
50
def _get_constructor_args(obj, cls, attr_getters=None, **kwargs):
51
    # Loop through the signature of cls: the type we try to deserialize to. For
52
    # every required parameter, we try to get the corresponding value from
53
    # json_obj.
54
    signature_parameters = inspect.signature(cls.__init__).parameters
55
    attr_getters = dict(**(attr_getters or {}))
56
    value_for_attr_part = partial(_get_value_for_attr, obj=obj, cls=cls,
57
                                  attr_getters=attr_getters, **kwargs)
58
    args_gen = (value_for_attr_part(sig_key=sig_key, sig=sig) for sig_key, sig
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable sig does not seem to be defined.
Loading history...
Comprehensibility Best Practice introduced by
The variable sig_key does not seem to be defined.
Loading history...
59
                in signature_parameters.items() if sig_key != 'self')
60
    constructor_args_in_obj = {key: value for key, value in args_gen if key}
61
    return constructor_args_in_obj
62
63
64
def _get_value_for_attr(obj, cls, sig_key, sig, attr_getters, **kwargs):
65
    result = None, None
66
    if obj and sig_key in obj:
67
        # This argument is in obj.
68
        arg_cls = None
69
        if sig.annotation != inspect.Parameter.empty:
70
            arg_cls = sig.annotation
71
        value = load(obj[sig_key], arg_cls, **kwargs)
72
        result = sig_key, value
73
    elif sig_key in attr_getters:
74
        # There exists an attr_getter for this argument.
75
        attr_getter = attr_getters.pop(sig_key)
76
        result = sig_key, attr_getter()
77
    elif sig.default != inspect.Parameter.empty:
78
        # There is a default value for this argument.
79
        result = sig_key, sig.default
80
    elif sig.kind not in (inspect.Parameter.VAR_POSITIONAL,
81
                          inspect.Parameter.VAR_KEYWORD):
82
        # This argument is no *args or **kwargs and has no value.
83
        raise UnfulfilledArgumentError(
84
            'No value found for "{}"'.format(sig_key), sig_key, obj, cls)
85
    return result
86
87
88
def _set_remaining_attrs(instance,
89
                         remaining_attrs,
90
                         attr_getters=None,
91
                         **kwargs):
92
    # Set any remaining attributes on the newly created instance.
93
    attr_getters = attr_getters or {}
94
    for attr_name in remaining_attrs:
95
        loaded_attr = load(remaining_attrs[attr_name],
96
                           type(remaining_attrs[attr_name]),
97
                           **kwargs)
98
        try:
99
            setattr(instance, attr_name, loaded_attr)
100
        except AttributeError:
101
            pass  # This is raised when a @property does not have a setter.
102
    for attr_name, getter in attr_getters.items():
103
        setattr(instance, attr_name, getter())
104