Passed
Pull Request — master (#23)
by Ramon
55s
created

default_object_deserializer()   A

Complexity

Conditions 1

Size

Total Lines 28
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 14
dl 0
loc 28
rs 9.7
c 0
b 0
f 0
cc 1
nop 5
1
import inspect
2
from functools import partial
3
from typing import Optional, Callable, Tuple
4
from jsons._common_impl import get_class_name, META_ATTR
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
    obj, kwargs = _check_and_transform_keys(obj, key_transformer, **kwargs)
30
    kwargs['strict'] = strict
31
    constructor_args = _get_constructor_args(obj, cls, **kwargs)
32
    remaining_attrs = _get_remaining_args(obj, cls, constructor_args,
33
                                          strict, kwargs['fork_inst'])
34
    instance = cls(**constructor_args)
35
    _set_remaining_attrs(instance, remaining_attrs, **kwargs)
36
    return instance
37
38
39
def _get_constructor_args(obj, cls, attr_getters=None, **kwargs) -> dict:
40
    # Loop through the signature of cls: the type we try to deserialize to. For
41
    # every required parameter, we try to get the corresponding value from
42
    # json_obj.
43
    signature_parameters = inspect.signature(cls.__init__).parameters
44
    attr_getters = dict(**(attr_getters or {}))
45
    value_for_attr_part = partial(_get_value_for_attr, obj=obj, cls=cls,
46
                                  attr_getters=attr_getters, **kwargs)
47
    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_key does not seem to be defined.
Loading history...
Comprehensibility Best Practice introduced by
The variable sig does not seem to be defined.
Loading history...
48
                in signature_parameters.items() if sig_key != 'self')
49
    constructor_args_in_obj = {key: value for key, value in args_gen if key}
50
    return constructor_args_in_obj
51
52
53
def _get_value_for_attr(obj, cls, sig_key, sig, attr_getters, **kwargs):
54
    result = None, None
55
    if obj and sig_key in obj:
56
        # This argument is in obj.
57
        arg_cls = None
58
        if sig.annotation != inspect.Parameter.empty:
59
            arg_cls = sig.annotation
60
        value = load(obj[sig_key], arg_cls, **kwargs)
61
        result = sig_key, value
62
    elif sig_key in attr_getters:
63
        # There exists an attr_getter for this argument.
64
        attr_getter = attr_getters.pop(sig_key)
65
        result = sig_key, attr_getter()
66
    elif sig.default != inspect.Parameter.empty:
67
        # There is a default value for this argument.
68
        result = sig_key, sig.default
69
    elif sig.kind not in (inspect.Parameter.VAR_POSITIONAL,
70
                          inspect.Parameter.VAR_KEYWORD):
71
        # This argument is no *args or **kwargs and has no value.
72
        raise UnfulfilledArgumentError(
73
            'No value found for "{}"'.format(sig_key), sig_key, obj, cls)
74
    return result
75
76
77
def _set_remaining_attrs(instance,
78
                         remaining_attrs,
79
                         attr_getters=None,
80
                         **kwargs):
81
    # Set any remaining attributes on the newly created instance.
82
    attr_getters = attr_getters or {}
83
    for attr_name in remaining_attrs:
84
        loaded_attr = load(remaining_attrs[attr_name],
85
                           type(remaining_attrs[attr_name]),
86
                           **kwargs)
87
        try:
88
            setattr(instance, attr_name, loaded_attr)
89
        except AttributeError:
90
            pass  # This is raised when a @property does not have a setter.
91
    for attr_name, getter in attr_getters.items():
92
        setattr(instance, attr_name, getter())
93
94
95
def _check_and_transform_keys(obj: dict,
96
                              key_transformer: Optional[Callable[[str], str]],
97
                              **kwargs) -> Tuple[dict, dict]:
98
    if key_transformer:
99
        obj = {key_transformer(key): obj[key] for key in obj}
100
        kwargs = {
101
            **kwargs,
102
            'key_transformer': key_transformer
103
        }
104
    return obj, kwargs
105
106
107
def _get_remaining_args(obj: dict,
108
                        cls: type,
109
                        constructor_args: dict,
110
                        strict: bool,
111
                        fork_inst: type) -> dict:
112
    # Get the remaining args or raise if strict and the signature is unmatched.
113
    remaining_attrs = {attr_name: obj[attr_name] for attr_name in obj
114
                       if attr_name not in constructor_args
115
                       and attr_name != META_ATTR}
116
    if strict and remaining_attrs:
117
        unexpected_arg = list(remaining_attrs.keys())[0]
118
        err_msg = ('Type "{}" does not expect "{}"'
119
                   .format(get_class_name(cls, fork_inst=fork_inst),
120
                           unexpected_arg))
121
        raise SignatureMismatchError(err_msg, unexpected_arg, obj, cls)
122
    return remaining_attrs
123