Completed
Push — master ( 87b7dd...8c4489 )
by Ramon
12s
created

jsons.deserializers.default_union_deserializer()   A

Complexity

Conditions 4

Size

Total Lines 21
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 10
nop 3
dl 0
loc 21
rs 9.9
c 0
b 0
f 0
1
"""
2
This module contains default deserializers. You can override the
3
deserialization process of a particular type as follows:
4
5
``jsons.set_deserializer(custom_deserializer, SomeClass)``
6
"""
7
import inspect
8
import re
9
from datetime import datetime
10
from enum import EnumMeta
11
from typing import List, Callable, Union, Optional
12
from jsons import _main_impl
13
from jsons._common_impl import get_class_name
14
from jsons._datetime_impl import get_datetime_inst
15
from jsons._main_impl import (
16
    RFC3339_DATETIME_PATTERN,
17
    snakecase,
18
    camelcase,
19
    pascalcase,
20
    lispcase
21
)
22
from jsons.exceptions import (
23
    UnfulfilledArgumentError,
24
    SignatureMismatchError,
25
    JsonsError,
26
    DeserializationError
27
)
28
29
30
def default_datetime_deserializer(obj: str,
31
                                  cls: type = datetime,
32
                                  **kwargs) -> datetime:
33
    """
34
    Deserialize a string with an RFC3339 pattern to a datetime instance.
35
    :param obj: the string that is to be deserialized.
36
    :param cls: not used.
37
    :param kwargs: not used.
38
    :return: a ``datetime.datetime`` instance.
39
    """
40
    pattern = RFC3339_DATETIME_PATTERN
41
    if '.' in obj:
42
        pattern += '.%f'
43
        # strptime allows a fraction of length 6, so trip the rest (if exists).
44
        regex_pattern = re.compile(r'(\.[0-9]+)')
45
        frac = regex_pattern.search(obj).group()
46
        obj = obj.replace(frac, frac[0:7])
47
    return get_datetime_inst(obj, pattern)
48
49
50
def default_list_deserializer(obj: list, cls: type = None, **kwargs) -> list:
51
    """
52
    Deserialize a list by deserializing all items of that list.
53
    :param obj: the list that needs deserializing.
54
    :param cls: the type optionally with a generic (e.g. List[str]).
55
    :param kwargs: any keyword arguments.
56
    :return: a deserialized list instance.
57
    """
58
    cls_ = None
59
    if cls and hasattr(cls, '__args__'):
60
        cls_ = cls.__args__[0]
61
    return [_main_impl.load(x, cls_, **kwargs) for x in obj]
62
63
64
def default_tuple_deserializer(obj: tuple,
65
                               cls: type = None,
66
                               **kwargs) -> object:
67
    """
68
    Deserialize a (JSON) list into a tuple by deserializing all items of that
69
    list.
70
    :param obj: the tuple that needs deserializing.
71
    :param cls: the type optionally with a generic (e.g. Tuple[str, int]).
72
    :param kwargs: any keyword arguments.
73
    :return: a deserialized tuple instance.
74
    """
75
    tuple_types = getattr(cls, '__tuple_params__', cls.__args__)
76
    if len(tuple_types) > 1 and tuple_types[1] is ...:
77
        tuple_types = [tuple_types[0]] * len(obj)
78
    list_ = [_main_impl.load(value, tuple_types[i], **kwargs)
79
             for i, value in enumerate(obj)]
80
    return tuple(list_)
81
82
83
def default_union_deserializer(obj: object, cls: Union, **kwargs) -> object:
84
    """
85
    Deserialize an object to any matching type of the given union. The first
86
    successful deserialization is returned.
87
    :param obj: The object that needs deserializing.
88
    :param cls: The Union type with a generic (e.g. Union[str, int]).
89
    :param kwargs: Any keyword arguments that are passed through the
90
    deserialization process.
91
    :return: An object of the first type of the Union that could be
92
    deserialized successfully.
93
    """
94
    for sub_type in cls.__args__:
95
        try:
96
            return _main_impl.load(obj, sub_type, **kwargs)
97
        except JsonsError:
98
            pass  # Try the next one.
99
    else:
100
        args_msg = ', '.join([get_class_name(cls_) for cls_ in cls.__args__])
101
        err_msg = ('Could not match the object of type "{}" to any type of '
102
                   'the Union: {}'.format(str(cls), args_msg))
103
        raise DeserializationError(err_msg, obj, cls)
104
105
106
def default_set_deserializer(obj: list, cls: type, **kwargs) -> set:
107
    """
108
    Deserialize a (JSON) list into a set by deserializing all items of that
109
    list. If the list as a generic type (e.g. Set[datetime]) then it is
110
    assumed that all elements can be deserialized to that type.
111
    :param obj: the list that needs deserializing.
112
    :param cls: the type, optionally with a generic (e.g. Set[str]).
113
    :param kwargs: any keyword arguments.
114
    :return: a deserialized set instance.
115
    """
116
    cls_ = list
117
    if hasattr(cls, '__args__'):
118
        cls_ = List[cls.__args__[0]]
119
    list_ = default_list_deserializer(obj, cls_, **kwargs)
120
    return set(list_)
121
122
123
def default_dict_deserializer(
124
        obj: dict,
125
        cls: type,
126
        key_transformer: Optional[Callable[[str], str]] = None,
127
        **kwargs) -> dict:
128
    """
129
    Deserialize a dict by deserializing all instances of that dict.
130
    :param obj: the dict that needs deserializing.
131
    :param key_transformer: a function that transforms the keys to a different
132
    style (e.g. PascalCase).
133
    :param cls: not used.
134
    :param kwargs: any keyword arguments.
135
    :return: a deserialized dict instance.
136
    """
137
    key_transformer = key_transformer or (lambda key: key)
138
    kwargs_ = {**{'key_transformer': key_transformer}, **kwargs}
139
    if hasattr(cls, '__args__') and len(cls.__args__) > 1:
140
        sub_cls = cls.__args__[1]
141
        kwargs_['cls'] = sub_cls
142
    return {key_transformer(key): _main_impl.load(obj[key], **kwargs_)
143
            for key in obj}
144
145
146
def default_enum_deserializer(obj: str,
147
                              cls: EnumMeta,
148
                              use_enum_name: bool = True,
149
                              **kwargs) -> object:
150
    """
151
    Deserialize an enum value to an enum instance. The serialized value must
152
    can be the name of the enum element or the value; dependent on
153
    ``use_enum_name``.
154
    :param obj: the serialized enum.
155
    :param cls: the enum class.
156
    :param use_enum_name: determines whether the name or the value of an enum
157
    element should be used.
158
    :param kwargs: not used.
159
    :return: the corresponding enum element instance.
160
    """
161
    if use_enum_name:
162
        result = cls[obj]
163
    else:
164
        for elem in cls:
165
            if elem.value == obj:
166
                result = elem
167
                break
168
    return result
0 ignored issues
show
introduced by
The variable result does not seem to be defined for all execution paths.
Loading history...
169
170
171
def default_string_deserializer(obj: str,
172
                                cls: Optional[type] = None,
173
                                **kwargs) -> object:
174
    """
175
    Deserialize a string. If the given ``obj`` can be parsed to a date, a
176
    ``datetime`` instance is returned.
177
    :param obj: the string that is to be deserialized.
178
    :param cls: not used.
179
    :param kwargs: any keyword arguments.
180
    :return: the deserialized obj.
181
    """
182
    try:
183
        # Use load instead of default_datetime_deserializer to allow the
184
        # datetime deserializer to be overridden.
185
        return _main_impl.load(obj, datetime, **kwargs)
186
    except:
187
        return obj
188
189
190
def default_primitive_deserializer(obj: object,
191
                                   cls: Optional[type] = None,
192
                                   **kwargs) -> object:
193
    """
194
    Deserialize a primitive: it simply returns the given primitive.
195
    :param obj: the value that is to be deserialized.
196
    :param cls: not used.
197
    :param kwargs: not used.
198
    :return: ``obj``.
199
    """
200
    return obj
201
202
203
def default_object_deserializer(
204
        obj: dict,
205
        cls: type,
206
        key_transformer: Optional[Callable[[str], str]] = None,
207
        strict: bool = False,
208
        **kwargs) -> object:
209
    """
210
    Deserialize ``obj`` into an instance of type ``cls``. If ``obj`` contains
211
    keys with a certain case style (e.g. camelCase) that do not match the style
212
    of ``cls`` (e.g. snake_case), a key_transformer should be used (e.g.
213
    KEY_TRANSFORMER_SNAKECASE).
214
    :param obj: a serialized instance of ``cls``.
215
    :param cls: the type to which ``obj`` should be deserialized.
216
    :param key_transformer: a function that transforms the keys in order to
217
    match the attribute names of ``cls``.
218
    :param strict: deserialize in strict mode.
219
    :param kwargs: any keyword arguments that may be passed to the
220
    deserializers.
221
    :return: an instance of type ``cls``.
222
    """
223
    concat_kwargs = kwargs
224
    if key_transformer:
225
        obj = {key_transformer(key): obj[key] for key in obj}
226
        concat_kwargs = {
227
            **kwargs,
228
            'key_transformer': key_transformer
229
        }
230
    concat_kwargs['strict'] = strict
231
    signature_parameters = inspect.signature(cls.__init__).parameters
232
    constructor_args, getters = _get_constructor_args(obj,
233
                                                      cls,
234
                                                      signature_parameters,
235
                                                      **concat_kwargs)
236
    remaining_attrs = {attr_name: obj[attr_name] for attr_name in obj
237
                       if attr_name not in constructor_args}
238
    if strict and remaining_attrs:
239
        unexpected_arg = list(remaining_attrs.keys())[0]
240
        err_msg = 'Type "{}" does not expect "{}"'.format(get_class_name(cls),
241
                                                          unexpected_arg)
242
        raise SignatureMismatchError(err_msg, unexpected_arg, obj, cls)
243
    instance = cls(**constructor_args)
244
    _set_remaining_attrs(instance, remaining_attrs, **kwargs)
245
    return instance
246
247
248
def _get_constructor_args(obj,
249
                          cls,
250
                          signature_parameters,
251
                          attr_getters=None,
252
                          **kwargs):
253
    # Loop through the signature of cls: the type we try to deserialize to. For
254
    # every required parameter, we try to get the corresponding value from
255
    # json_obj.
256
    attr_getters = dict(**(attr_getters or {}))
257
    constructor_args_in_obj = dict()
258
    signatures = ((sig_key, sig) for sig_key, sig in
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...
259
                  signature_parameters.items() if sig_key != 'self')
260
    for sig_key, sig in signatures:
261
        if obj and sig_key in obj:
262
            # This argument is in obj.
263
            arg_cls = None
264
            if sig.annotation != inspect.Parameter.empty:
265
                arg_cls = sig.annotation
266
            value = _main_impl.load(obj[sig_key], arg_cls, **kwargs)
267
            constructor_args_in_obj[sig_key] = value
268
        elif sig_key in attr_getters:
269
            # There exists an attr_getter for this argument.
270
            attr_getter = attr_getters.pop(sig_key)
271
            constructor_args_in_obj[sig_key] = attr_getter()
272
        elif sig.default != inspect.Parameter.empty:
273
            # There is a default value for this argument.
274
            constructor_args_in_obj[sig_key] = sig.default
275
        elif sig.kind not in (inspect.Parameter.VAR_POSITIONAL,
276
                              inspect.Parameter.VAR_KEYWORD):
277
            # This argument is no *args or **kwargs and has no value.
278
            raise UnfulfilledArgumentError(
279
                'No value found for "{}"'.format(sig_key),
280
                sig_key, obj, cls)
281
282
    return constructor_args_in_obj, attr_getters
283
284
285
def _set_remaining_attrs(instance,
286
                         remaining_attrs,
287
                         attr_getters=None,
288
                         **kwargs):
289
    # Set any remaining attributes on the newly created instance.
290
    attr_getters = attr_getters or {}
291
    for attr_name in remaining_attrs:
292
        loaded_attr = _main_impl.load(remaining_attrs[attr_name],
293
                                      type(remaining_attrs[attr_name]),
294
                                      **kwargs)
295
        try:
296
            setattr(instance, attr_name, loaded_attr)
297
        except AttributeError:
298
            pass  # This is raised when a @property does not have a setter.
299
    for attr_name, getter in attr_getters.items():
300
        setattr(instance, attr_name, getter())
301
302
303
# The following default key transformers can be used with the
304
# default_object_serializer.
305
KEY_TRANSFORMER_SNAKECASE = snakecase
306
KEY_TRANSFORMER_CAMELCASE = camelcase
307
KEY_TRANSFORMER_PASCALCASE = pascalcase
308
KEY_TRANSFORMER_LISPCASE = lispcase
309