Passed
Push — master ( a44ae4...9adf86 )
by Ramon
01:25
created

jsons.deserializers.default_dict_deserializer()   A

Complexity

Conditions 2

Size

Total Lines 9
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 2
nop 3
dl 0
loc 9
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 Enum, EnumMeta
0 ignored issues
show
Unused Code introduced by
Unused Enum imported from enum
Loading history...
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('(\.[0-9]+)')
0 ignored issues
show
Bug introduced by
A suspicious escape sequence \. was found. Did you maybe forget to add an r prefix?

Escape sequences in Python are generally interpreted according to rules similar to standard C. Only if strings are prefixed with r or R are they interpreted as regular expressions.

The escape sequence that was used indicates that you might have intended to write a regular expression.

Learn more about the available escape sequences. in the Python documentation.

Loading history...
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 instances 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_dict_deserializer(obj: dict, _: type, **kwargs) -> object:
61
    """
62
    Deserialize a dict by deserializing all instances of that dict.
63
    :param obj: the dict that needs deserializing.
64
    :param cls: not used.
65
    :param kwargs: any keyword arguments.
66
    :return: a deserialized dict instance.
67
    """
68
    return {key: load_impl(obj[key], **kwargs) for key in obj}
69
70
71
def default_enum_deserializer(obj: str, cls: EnumMeta,
72
                              use_enum_name: bool = True, **__) -> object:
73
    """
74
    Deserialize an enum value to an enum instance. The serialized value must
75
    can be the name of the enum element or the value; dependent on
76
    `use_enum_name`.
77
    :param obj: the serialized enum.
78
    :param cls: the enum class.
79
    :param use_enum_name: determines whether the name or the value of an enum
80
    element should be used.
81
    :param __: not used.
82
    :return: the corresponding enum element instance.
83
    """
84
    if use_enum_name:
85
        result = cls[obj]
86
    else:
87
        for elem in cls:
88
            if elem.value == obj:
89
                result = elem
90
                break
91
    return result
0 ignored issues
show
introduced by
The variable result does not seem to be defined for all execution paths.
Loading history...
92
93
94
def default_string_deserializer(obj: str, _: type = None, **kwargs) -> object:
95
    """
96
    Deserialize a string. If the given `obj` can be parsed to a date, a
97
    `datetime` instance is returned.
98
    :param obj: the string that is to be deserialized.
99
    :param _: not used.
100
    :param kwargs: any keyword arguments.
101
    :return: the deserialized obj.
102
    """
103
    try:
104
        return load_impl(obj, datetime, **kwargs)
105
    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...
106
        return obj
107
108
109
def default_primitive_deserializer(obj: object,
110
                                   _: type = None, **__) -> object:
111
    """
112
    Deserialize a primitive: it simply returns the given primitive.
113
    :param obj: the value that is to be deserialized.
114
    :param _: not used.
115
    :param __: not used.
116
    :return: `obj`.
117
    """
118
    return obj
119
120
121
def default_object_deserializer(obj: dict, cls: type,
122
                                key_transformer: Callable[[str], str] = None,
123
                                **kwargs) -> object:
124
    """
125
    Deserialize `obj` into an instance of type `cls`. If `obj` contains keys
126
    with a certain case style (e.g. camelCase) that do not match the style of
127
    `cls` (e.g. snake_case), a key_transformer should be used (e.g.
128
    KEY_TRANSFORMER_SNAKECASE).
129
    :param obj: a serialized instance of `cls`.
130
    :param cls: the type to which `obj` should be deserialized.
131
    :param key_transformer: a function that transforms the keys in order to
132
    match the attribute names of `cls`.
133
    :param kwargs: any keyword arguments that may be passed to the
134
    deserializers.
135
    :return: an instance of type `cls`.
136
    """
137
    if key_transformer:
138
        obj = {key_transformer(key): obj[key] for key in obj}
139
    signature_parameters = inspect.signature(cls.__init__).parameters
140
    # Loop through the signature of cls: the type we try to deserialize to. For
141
    # every required parameter, we try to get the corresponding value from
142
    # json_obj.
143
    constructor_args = dict()
144
    for signature_key, signature in signature_parameters.items():
145
        if obj and signature_key is not 'self':
0 ignored issues
show
introduced by
Comparison to literal
Loading history...
146
            if signature_key in obj:
147
                cls_ = None
148
                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...
149
                    cls_ = signature.annotation
150
                value = load_impl(obj[signature_key], cls_,
151
                                  key_transformer=key_transformer, **kwargs)
152
                constructor_args[signature_key] = value
153
154
    # The constructor arguments are gathered, create an instance.
155
    instance = cls(**constructor_args)
156
    # Set any remaining attributes on the newly created instance.
157
    remaining_attrs = {attr_name: obj[attr_name] for attr_name in obj
158
                       if attr_name not in constructor_args}
159
    for attr_name in remaining_attrs:
160
        loaded_attr = load_impl(remaining_attrs[attr_name],
161
                                type(remaining_attrs[attr_name]),
162
                                key_transformer=key_transformer, **kwargs)
163
        setattr(instance, attr_name, loaded_attr)
164
    return instance
165
166
167
# The following default key transformers can be used with the
168
# default_object_serializer.
169
KEY_TRANSFORMER_SNAKECASE = snakecase
170
KEY_TRANSFORMER_CAMELCASE = camelcase
171
KEY_TRANSFORMER_PASCALCASE = pascalcase
172
KEY_TRANSFORMER_LISPCASE = lispcase
173