Passed
Branch master (272386)
by Ramon
01:04
created

default_primitive_deserializer()   A

Complexity

Conditions 1

Size

Total Lines 10
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nop 3
dl 0
loc 10
rs 10
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, timezone, timedelta
10
from enum import EnumMeta
11
from typing import List, Callable
12
from jsons import _common_impl
13
from jsons._common_impl import RFC3339_DATETIME_PATTERN, snakecase, \
14
    camelcase, pascalcase, lispcase
15
16
17
def default_datetime_deserializer(obj: str, _: datetime, **__) -> datetime:
18
    """
19
    Deserialize a string with an RFC3339 pattern to a datetime instance.
20
    :param obj:
21
    :param _: not used.
22
    :param __: not used.
23
    :return: a ``datetime.datetime`` instance.
24
    """
25
    pattern = RFC3339_DATETIME_PATTERN
26
    if '.' in obj:
27
        pattern += '.%f'
28
        # strptime allows a fraction of length 6, so trip the rest (if exists).
29
        regex_pattern = re.compile(r'(\.[0-9]+)')
30
        frac = regex_pattern.search(obj).group()
31
        obj = obj.replace(frac, frac[0:7])
32
    if obj[-1] == 'Z':
33
        dattim_str = obj[0:-1]
34
        dattim_obj = datetime.strptime(dattim_str, pattern)
35
        dattim_obj = datetime(dattim_obj.year, dattim_obj.month,
36
                              dattim_obj.day, dattim_obj.hour,
37
                              dattim_obj.minute, dattim_obj.second,
38
                              dattim_obj.microsecond, timezone.utc)
39
    else:
40
        dattim_str, offset = obj.split('+')
41
        dattim_obj = datetime.strptime(dattim_str, pattern)
42
        hours, minutes = offset.split(':')
43
        tz = timezone(offset=timedelta(hours=int(hours), minutes=int(minutes)))
0 ignored issues
show
Coding Style Naming introduced by
The name tz does not conform to the variable naming conventions ((([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
44
        datetime_list = [dattim_obj.year, dattim_obj.month, dattim_obj.day,
45
                         dattim_obj.hour, dattim_obj.minute, dattim_obj.second,
46
                         dattim_obj.microsecond, tz]
47
        dattim_obj = datetime(*datetime_list)
48
    return dattim_obj
49
50
51
def default_list_deserializer(obj: List, cls, **kwargs) -> object:
52
    """
53
    Deserialize a list by deserializing all items of that list.
54
    :param obj: the list that needs deserializing.
55
    :param cls: the type optionally with a generic (e.g. List[str]).
56
    :param kwargs: any keyword arguments.
57
    :return: a deserialized list instance.
58
    """
59
    cls_ = None
60
    if cls and hasattr(cls, '__args__'):
61
        cls_ = cls.__args__[0]
62
    return [_common_impl.load(x, cls_, **kwargs) for x in obj]
63
64
65
def default_tuple_deserializer(obj: List, cls, **kwargs) -> object:
66
    """
67
    Deserialize a (JSON) list into a tuple by deserializing all items of that
68
    list.
69
    :param obj: the list that needs deserializing.
70
    :param cls: the type optionally with a generic (e.g. Tuple[str, int]).
71
    :param kwargs: any keyword arguments.
72
    :return: a deserialized tuple instance.
73
    """
74
    if hasattr(cls, '__tuple_params__'):
75
        tuple_types = cls.__tuple_params__
76
    else:
77
        tuple_types = cls.__args__
78
    list_ = [_common_impl.load(obj[i], tuple_types[i], **kwargs)
79
             for i in range(len(obj))]
80
    return tuple(list_)
81
82
83
def default_set_deserializer(obj: List, cls, **kwargs) -> object:
84
    """
85
    Deserialize a (JSON) list into a set by deserializing all items of that
86
    list.
87
    :param obj: the list that needs deserializing.
88
    :param cls: the type optionally with a generic (e.g. Set[str]).
89
    :param kwargs: any keyword arguments.
90
    :return: a deserialized set instance.
91
    """
92
    cls_ = list
93
    if hasattr(cls, '__args__'):
94
        cls_ = List[cls.__args__[0]]
95
    list_ = default_list_deserializer(obj, cls_, **kwargs)
96
    return set(list_)
97
98
99
def default_dict_deserializer(obj: dict, _: type,
100
                              key_transformer: Callable[[str], str] = None,
101
                              **kwargs) -> object:
102
    """
103
    Deserialize a dict by deserializing all instances of that dict.
104
    :param obj: the dict that needs deserializing.
105
    :param key_transformer: a function that transforms the keys to a different
106
    style (e.g. PascalCase).
107
    :param cls: not used.
108
    :param kwargs: any keyword arguments.
109
    :return: a deserialized dict instance.
110
    """
111
    key_transformer = key_transformer or (lambda key: key)
112
    kwargs_ = {**{'key_transformer': key_transformer}, **kwargs}
113
    return {key_transformer(key): _common_impl.load(obj[key], **kwargs_)
114
            for key in obj}
115
116
117
def default_enum_deserializer(obj: str, cls: EnumMeta,
118
                              use_enum_name: bool = True, **__) -> object:
119
    """
120
    Deserialize an enum value to an enum instance. The serialized value must
121
    can be the name of the enum element or the value; dependent on
122
    ``use_enum_name``.
123
    :param obj: the serialized enum.
124
    :param cls: the enum class.
125
    :param use_enum_name: determines whether the name or the value of an enum
126
    element should be used.
127
    :param __: not used.
128
    :return: the corresponding enum element instance.
129
    """
130
    if use_enum_name:
131
        result = cls[obj]
132
    else:
133
        for elem in cls:
134
            if elem.value == obj:
135
                result = elem
136
                break
137
    return result
0 ignored issues
show
introduced by
The variable result does not seem to be defined for all execution paths.
Loading history...
138
139
140
def default_string_deserializer(obj: str, _: type = None, **kwargs) -> object:
141
    """
142
    Deserialize a string. If the given ``obj`` can be parsed to a date, a
143
    ``datetime`` instance is returned.
144
    :param obj: the string that is to be deserialized.
145
    :param _: not used.
146
    :param kwargs: any keyword arguments.
147
    :return: the deserialized obj.
148
    """
149
    try:
150
        # Use load instead of default_datetime_deserializer to allow the
151
        # datetime deserializer to be overridden.
152
        return _common_impl.load(obj, datetime, **kwargs)
153
    except:
0 ignored issues
show
Coding Style Best Practice introduced by
General except handlers without types should be used sparingly.

Typically, you would use general except handlers when you intend to specifically handle all types of errors, f.e. when logging. Otherwise, such general error handlers can mask errors in your application that you want to know of.

Loading history...
154
        return obj
155
156
157
def default_primitive_deserializer(obj: object,
158
                                   _: type = None, **__) -> object:
159
    """
160
    Deserialize a primitive: it simply returns the given primitive.
161
    :param obj: the value that is to be deserialized.
162
    :param _: not used.
163
    :param __: not used.
164
    :return: ``obj``.
165
    """
166
    return obj
167
168
169
def default_object_deserializer(obj: dict, cls: type,
170
                                key_transformer: Callable[[str], str] = None,
171
                                **kwargs) -> object:
172
    """
173
    Deserialize ``obj`` into an instance of type ``cls``. If ``obj`` contains
174
    keys with a certain case style (e.g. camelCase) that do not match the style
175
    of ``cls`` (e.g. snake_case), a key_transformer should be used (e.g.
176
    KEY_TRANSFORMER_SNAKECASE).
177
    :param obj: a serialized instance of ``cls``.
178
    :param cls: the type to which ``obj`` should be deserialized.
179
    :param key_transformer: a function that transforms the keys in order to
180
    match the attribute names of ``cls``.
181
    :param kwargs: any keyword arguments that may be passed to the
182
    deserializers.
183
    :return: an instance of type ``cls``.
184
    """
185
    concat_kwargs = kwargs
186
    if key_transformer:
187
        obj = {key_transformer(key): obj[key] for key in obj}
188
        concat_kwargs = {**kwargs, 'key_transformer': key_transformer}
189
    signature_parameters = inspect.signature(cls.__init__).parameters
190
    constructor_args = _get_constructor_args(obj, signature_parameters,
191
                                             **concat_kwargs)
192
    remaining_attrs = {attr_name: obj[attr_name] for attr_name in obj
193
                       if attr_name not in constructor_args}
194
    instance = cls(**constructor_args)
195
    _set_remaining_attrs(instance, remaining_attrs, **kwargs)
196
    return instance
197
198
199
def _get_constructor_args(obj, signature_parameters, **kwargs):
200
    # Loop through the signature of cls: the type we try to deserialize to. For
201
    # every required parameter, we try to get the corresponding value from
202
    # json_obj.
203
    constructor_args = dict()
204
    sigs = [(sig_key, sig) for sig_key, sig in signature_parameters.items()
205
            if obj and sig_key != 'self' and sig_key in obj]
206
    for sig_key, sig in sigs:
207
        cls = sig.annotation if sig.annotation != inspect._empty else None
208
        value = _common_impl.load(obj[sig_key], cls, **kwargs)
209
        constructor_args[sig_key] = value
210
    return constructor_args
211
212
213
def _set_remaining_attrs(instance, remaining_attrs, **kwargs):
214
    # Set any remaining attributes on the newly created instance.
215
    for attr_name in remaining_attrs:
216
        loaded_attr = _common_impl.load(remaining_attrs[attr_name],
217
                                             type(remaining_attrs[attr_name]),
0 ignored issues
show
Coding Style introduced by
Wrong continued indentation (remove 5 spaces).
Loading history...
218
                                             **kwargs)
0 ignored issues
show
Coding Style introduced by
Wrong continued indentation (remove 5 spaces).
Loading history...
219
        try:
220
            setattr(instance, attr_name, loaded_attr)
221
        except AttributeError:
222
            pass  # This is raised when a @property does not have a setter.
223
224
225
# The following default key transformers can be used with the
226
# default_object_serializer.
227
KEY_TRANSFORMER_SNAKECASE = snakecase
228
KEY_TRANSFORMER_CAMELCASE = camelcase
229
KEY_TRANSFORMER_PASCALCASE = pascalcase
230
KEY_TRANSFORMER_LISPCASE = lispcase
231