Passed
Push — master ( b4eeac...5f33ea )
by Ramon
01:39
created

jsons.deserializers.default_tuple_deserializer()   A

Complexity

Conditions 2

Size

Total Lines 13
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 5
nop 3
dl 0
loc 13
rs 10
c 0
b 0
f 0
1
"""
0 ignored issues
show
Bug introduced by
There seems to be a cyclic import (jsons -> jsons.serializers).

Cyclic imports may cause partly loaded modules to be returned. This might lead to unexpected runtime behavior which is hard to debug.

Loading history...
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._common_impl import RFC3339_DATETIME_PATTERN, load_impl, \
13
    snakecase, camelcase, pascalcase, lispcase
14
15
16
def default_datetime_deserializer(obj: str, _: datetime, **__) -> datetime:
17
    """
18
    Deserialize a string with an RFC3339 pattern to a datetime instance.
19
    :param obj:
20
    :param _: not used.
21
    :param __: not used.
22
    :return: a `datetime.datetime` instance.
23
    """
24
    pattern = RFC3339_DATETIME_PATTERN
25
    if '.' in obj:
26
        pattern += '.%f'
27
        # strptime allows a fraction of length 6, so trip the rest (if exists).
28
        regex_pattern = re.compile(r'(\.[0-9]+)')
29
        frac = regex_pattern.search(obj).group()
30
        obj = obj.replace(frac, frac[0:7])
31
    if obj[-1] == 'Z':
32
        dattim_str = obj[0:-1]
33
        dattim_obj = datetime.strptime(dattim_str, pattern)
34
    else:
35
        dattim_str, offset = obj.split('+')
36
        dattim_obj = datetime.strptime(dattim_str, pattern)
37
        hours, minutes = offset.split(':')
38
        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...
39
        datetime_list = [dattim_obj.year, dattim_obj.month, dattim_obj.day,
40
                         dattim_obj.hour, dattim_obj.minute, dattim_obj.second,
41
                         dattim_obj.microsecond, tz]
42
        dattim_obj = datetime(*datetime_list)
43
    return dattim_obj
44
45
46
def default_list_deserializer(obj: List, cls, **kwargs) -> object:
47
    """
48
    Deserialize a list by deserializing all items of that list.
49
    :param obj: the list that needs deserializing.
50
    :param cls: the type with a generic (e.g. List[str]).
51
    :param kwargs: any keyword arguments.
52
    :return: a deserialized list instance.
53
    """
54
    cls_ = None
55
    if cls and hasattr(cls, '__args__'):
56
        cls_ = cls.__args__[0]
57
    return [load_impl(x, cls_, **kwargs) for x in obj]
58
59
60
def default_tuple_deserializer(obj: List, cls, **kwargs) -> object:
61
    """
62
    Deserialize a (JSON) list into a tuple by deserializing all items of that
63
    list.
64
    :param obj: the list that needs deserializing.
65
    :param cls: the type with a generic (e.g. Tuple[str, int]).
66
    :param kwargs: any keyword arguments.
67
    :return: a deserialized tuple instance.
68
    """
69
    tuple_types = cls.__args__
70
    list_ = [load_impl(obj[i], tuple_types[i], **kwargs)
71
             for i in range(len(obj))]
72
    return tuple(list_)
73
74
75
def default_dict_deserializer(obj: dict, _: type, **kwargs) -> object:
76
    """
77
    Deserialize a dict by deserializing all instances of that dict.
78
    :param obj: the dict that needs deserializing.
79
    :param cls: not used.
80
    :param kwargs: any keyword arguments.
81
    :return: a deserialized dict instance.
82
    """
83
    return {key: load_impl(obj[key], **kwargs) for key in obj}
84
85
86
def default_enum_deserializer(obj: str, cls: EnumMeta,
87
                              use_enum_name: bool = True, **__) -> object:
88
    """
89
    Deserialize an enum value to an enum instance. The serialized value must
90
    can be the name of the enum element or the value; dependent on
91
    `use_enum_name`.
92
    :param obj: the serialized enum.
93
    :param cls: the enum class.
94
    :param use_enum_name: determines whether the name or the value of an enum
95
    element should be used.
96
    :param __: not used.
97
    :return: the corresponding enum element instance.
98
    """
99
    if use_enum_name:
100
        result = cls[obj]
101
    else:
102
        for elem in cls:
103
            if elem.value == obj:
104
                result = elem
105
                break
106
    return result
0 ignored issues
show
introduced by
The variable result does not seem to be defined for all execution paths.
Loading history...
107
108
109
def default_string_deserializer(obj: str, _: type = None, **kwargs) -> object:
110
    """
111
    Deserialize a string. If the given `obj` can be parsed to a date, a
112
    `datetime` instance is returned.
113
    :param obj: the string that is to be deserialized.
114
    :param _: not used.
115
    :param kwargs: any keyword arguments.
116
    :return: the deserialized obj.
117
    """
118
    try:
119
        return load_impl(obj, datetime, **kwargs)
120
    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...
121
        return obj
122
123
124
def default_primitive_deserializer(obj: object,
125
                                   _: type = None, **__) -> object:
126
    """
127
    Deserialize a primitive: it simply returns the given primitive.
128
    :param obj: the value that is to be deserialized.
129
    :param _: not used.
130
    :param __: not used.
131
    :return: `obj`.
132
    """
133
    return obj
134
135
136
def default_object_deserializer(obj: dict, cls: type,
137
                                key_transformer: Callable[[str], str] = None,
138
                                **kwargs) -> object:
139
    """
140
    Deserialize `obj` into an instance of type `cls`. If `obj` contains keys
141
    with a certain case style (e.g. camelCase) that do not match the style of
142
    `cls` (e.g. snake_case), a key_transformer should be used (e.g.
143
    KEY_TRANSFORMER_SNAKECASE).
144
    :param obj: a serialized instance of `cls`.
145
    :param cls: the type to which `obj` should be deserialized.
146
    :param key_transformer: a function that transforms the keys in order to
147
    match the attribute names of `cls`.
148
    :param kwargs: any keyword arguments that may be passed to the
149
    deserializers.
150
    :return: an instance of type `cls`.
151
    """
152
    if key_transformer:
153
        obj = {key_transformer(key): obj[key] for key in obj}
154
    signature_parameters = inspect.signature(cls.__init__).parameters
155
    # Loop through the signature of cls: the type we try to deserialize to. For
156
    # every required parameter, we try to get the corresponding value from
157
    # json_obj.
158
    constructor_args = dict()
159
    for signature_key, signature in signature_parameters.items():
160
        if obj and signature_key != 'self':
161
            if signature_key in obj:
162
                cls_ = None
163
                if signature.annotation != inspect._empty:
1 ignored issue
show
Coding Style Best Practice introduced by
It seems like _empty was declared protected and should not be accessed from this context.

Prefixing a member variable _ is usually regarded as the equivalent of declaring it with protected visibility that exists in other languages. Consequentially, such a member should only be accessed from the same class or a child class:

class MyParent:
    def __init__(self):
        self._x = 1;
        self.y = 2;

class MyChild(MyParent):
    def some_method(self):
        return self._x    # Ok, since accessed from a child class

class AnotherClass:
    def some_method(self, instance_of_my_child):
        return instance_of_my_child._x   # Would be flagged as AnotherClass is not
                                         # a child class of MyParent
Loading history...
164
                    cls_ = signature.annotation
165
                value = load_impl(obj[signature_key], cls_,
166
                                  key_transformer=key_transformer, **kwargs)
167
                constructor_args[signature_key] = value
168
169
    # The constructor arguments are gathered, create an instance.
170
    instance = cls(**constructor_args)
171
    # Set any remaining attributes on the newly created instance.
172
    remaining_attrs = {attr_name: obj[attr_name] for attr_name in obj
173
                       if attr_name not in constructor_args}
174
    for attr_name in remaining_attrs:
175
        loaded_attr = load_impl(remaining_attrs[attr_name],
176
                                type(remaining_attrs[attr_name]),
177
                                key_transformer=key_transformer, **kwargs)
178
        setattr(instance, attr_name, loaded_attr)
179
    return instance
180
181
182
# The following default key transformers can be used with the
183
# default_object_serializer.
184
KEY_TRANSFORMER_SNAKECASE = snakecase
185
KEY_TRANSFORMER_CAMELCASE = camelcase
186
KEY_TRANSFORMER_PASCALCASE = pascalcase
187
KEY_TRANSFORMER_LISPCASE = lispcase
188