Completed
Push — master ( 5f33ea...c0ff9a )
by Ramon
01:11
created

jsons.JsonSerializable.with_dump()   A

Complexity

Conditions 1

Size

Total Lines 28
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 8
nop 2
dl 0
loc 28
rs 10
c 0
b 0
f 0
1
"""
2
Works with Python3.5+
3
4
JSON (de)serialization (jsons) from and to dicts and plain old Python objects.
5
6
Works with dataclasses (Python3.7+).
7
8
9
**Example:**
10
11
    >>> from dataclasses import dataclass
12
    >>> @dataclass
13
    ... class Car:
14
    ...     color: str
15
    >>> @dataclass
16
    ... class Person:
17
    ...     car: Car
18
    ...     name: str
19
    >>> c = Car('Red')
20
    >>> p = Person(c, 'John')
21
    >>> dumped = dump(p)
22
    >>> dumped['name']
23
    'John'
24
    >>> dumped['car']['color']
25
    'Red'
26
    >>> p_reloaded = load(dumped, Person)
27
    >>> p_reloaded.name
28
    'John'
29
    >>> p_reloaded.car.color
30
    'Red'
31
32
33
Deserialization will work with older Python classes (Python3.5+) given that
34
type hints are present for custom types (i.e. any type that is not set at
35
the bottom of this module). Serialization will work with no type hints at
36
all.
37
38
39
**Example**
40
41
    >>> class Car:
42
    ...     def __init__(self, color):
43
    ...         self.color = color
44
    >>> class Person:
45
    ...     def __init__(self, car: Car, name):
46
    ...         self.car = car
47
    ...         self.name = name
48
    >>> c = Car('Red')
49
    >>> p = Person(c, 'John')
50
    >>> dumped = dump(p)
51
    >>> dumped['name']
52
    'John'
53
    >>> dumped['car']['color']
54
    'Red'
55
    >>> p_reloaded = load(dumped, Person)
56
    >>> p_reloaded.name
57
    'John'
58
    >>> p_reloaded.car.color
59
    'Red'
60
61
62
Alternatively, you can make use of the `JsonSerializable` class.
63
64
65
**Example**
66
67
    >>> class Car(JsonSerializable):
68
    ...     def __init__(self, color):
69
    ...         self.color = color
70
    >>> class Person(JsonSerializable):
71
    ...     def __init__(self, car: Car, name):
72
    ...         self.car = car
73
    ...         self.name = name
74
    >>> c = Car('Red')
75
    >>> p = Person(c, 'John')
76
    >>> dumped = p.json
77
    >>> dumped['name']
78
    'John'
79
    >>> dumped['car']['color']
80
    'Red'
81
    >>> p_reloaded = Person.from_json(dumped)
82
    >>> p_reloaded.name
83
    'John'
84
    >>> p_reloaded.car.color
85
    'Red'
86
87
"""
88
import json
89
from datetime import datetime
90
from enum import Enum
91
from jsons._common_impl import dump_impl, load_impl, CLASSES_SERIALIZERS, \
92
    CLASSES_DESERIALIZERS, SERIALIZERS, DESERIALIZERS
93
from jsons.deserializers import default_list_deserializer, \
94
    default_enum_deserializer, default_datetime_deserializer, \
95
    default_string_deserializer, default_primitive_deserializer, \
96
    default_object_deserializer, default_dict_deserializer, \
97
    default_tuple_deserializer
98
from jsons.serializers import default_list_serializer, \
99
    default_enum_serializer, default_datetime_serializer, \
100
    default_primitive_serializer, default_object_serializer, \
101
    KEY_TRANSFORMER_SNAKECASE, KEY_TRANSFORMER_CAMELCASE, \
102
    KEY_TRANSFORMER_PASCALCASE, KEY_TRANSFORMER_LISPCASE, \
103
    default_dict_serializer, default_tuple_serializer
104
105
dump = dump_impl
0 ignored issues
show
Coding Style Naming introduced by
The name dump does not conform to the constant naming conventions ((([A-Z_][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...
106
load = load_impl
0 ignored issues
show
Coding Style Naming introduced by
The name load does not conform to the constant naming conventions ((([A-Z_][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...
107
108
109
def dumps(obj: object, *args, **kwargs) -> str:
110
    """
111
    Extend ``json.dumps``, allowing any Python instance to be dumped to a
112
    string. Any extra (keyword) arguments are passed on to ``json.dumps``.
113
114
    :param obj: the object that is to be dumped to a string.
115
    :param args: extra arguments for ``json.dumps``.
116
    :param kwargs: extra keyword arguments for ``json.dumps``. They are also
117
    passed on to the serializer function.
118
    :return: ``obj`` as a ``str``.
119
    """
120
    return json.dumps(dump(obj, **kwargs), *args, **kwargs)
121
122
123
def loads(str_: str, cls: type = None, *args, **kwargs) -> object:
124
    """
125
    Extend ``json.loads``, allowing a string to be loaded into a dict or a
126
    Python instance of type ``cls``. Any extra (keyword) arguments are passed
127
    on to ``json.loads``.
128
129
    :param str_: the string that is to be loaded.
130
    :param cls: a matching class of which an instance should be returned.
131
    :param args: extra arguments for ``json.dumps``.
132
    :param kwargs: extra keyword arguments for ``json.dumps``. They are also
133
    passed on to the deserializer function.
134
    :return: an instance of type ``cls`` or a dict if no ``cls`` is given.
135
    """
136
    obj = json.loads(str_, *args, **kwargs)
137
    return load(obj, cls, **kwargs) if cls else obj
138
139
140
def set_serializer(func: callable, cls: type, high_prio: bool = True) -> None:
141
    """
142
    Set a serializer function for the given type. You may override the default
143
    behavior of ``jsons.load`` by setting a custom serializer.
144
145
    :param func: the serializer function.
146
    :param cls: the type this serializer can handle.
147
    :param high_prio: determines the order in which is looked for the callable.
148
    :return: None.
149
    """
150
    if cls:
151
        index = 0 if high_prio else len(CLASSES_SERIALIZERS)
152
        CLASSES_SERIALIZERS.insert(index, cls)
153
        SERIALIZERS[cls.__name__.lower()] = func
154
    else:
155
        SERIALIZERS['nonetype'] = func
156
157
158
def set_deserializer(func: callable, cls: type, high_prio: bool = True) -> None:
159
    """
160
    Set a deserializer function for the given type. You may override the
161
    default behavior of ``jsons.dump`` by setting a custom deserializer.
162
163
    :param func: the deserializer function.
164
    :param cls: the type this serializer can handle.
165
    :param high_prio: determines the order in which is looked for the callable.
166
    :return: None.
167
    """
168
    if cls:
169
        index = 0 if high_prio else len(CLASSES_DESERIALIZERS)
170
        CLASSES_DESERIALIZERS.insert(index, cls)
171
        DESERIALIZERS[cls.__name__.lower()] = func
172
    else:
173
        DESERIALIZERS['nonetype'] = func
174
175
176
class JsonSerializable:
0 ignored issues
show
Unused Code introduced by
The variable __class__ seems to be unused.
Loading history...
177
    """
178
    This class offers an alternative to using the `jsons.load` and `jsons.dump`
179
    methods. An instance of a class that inherits from JsonSerializable has the
180
    `json` property, which value is equivalent to calling `jsons.dump` on that
181
    instance. Furthermore, you can call `from_json` on that class, which is
182
    equivalent to calling `json.load` with that class as an argument.
183
    """
184
    @classmethod
185
    def with_dump(cls, **kwargs) -> type:
186
        """
187
        Return a class (`type`) that is based on JsonSerializable with the
188
        `dump` method being automatically provided the given `kwargs`.
189
190
        **Example:**
191
192
        >>> custom_serializable = JsonSerializable\
193
                .with_dump(key_transformer=KEY_TRANSFORMER_CAMELCASE)
194
        >>> class Person(custom_serializable):
195
        ...     def __init__(self, my_name):
196
        ...         self.my_name = my_name
197
        >>> p = Person('John')
198
        >>> p.json
199
        {'myName': 'John'}
200
201
        :param kwargs: the keyword args that are automatically provided to the
202
        `dump` method.
203
        :return: a class with customized behavior.
204
        """
205
        original_dump = cls.dump
206
207
        def _wrapper(inst, **kwargs_):
208
            return original_dump(inst, **{**kwargs_, **kwargs})
209
        t = type(JsonSerializable.__name__, (cls,), {})
0 ignored issues
show
Coding Style Naming introduced by
The name t 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...
210
        t.dump =_wrapper
0 ignored issues
show
Coding Style introduced by
Exactly one space required after assignment
Loading history...
211
        return t
212
213
    @classmethod
214
    def with_load(cls, **kwargs) -> type:
215
        """
216
        Return a class (`type`) that is based on JsonSerializable with the
217
        `load` method being automatically provided the given `kwargs`.
218
219
        **Example:**
220
221
        >>> custom_serializable = JsonSerializable\
222
                .with_load(key_transformer=KEY_TRANSFORMER_SNAKECASE)
223
        >>> class Person(custom_serializable):
224
        ...     def __init__(self, my_name):
225
        ...         self.my_name = my_name
226
        >>> p_json = {'myName': 'John'}
227
        >>> p = Person.from_json(p_json)
228
        >>> p.my_name
229
        'John'
230
231
        :param kwargs: the keyword args that are automatically provided to the
232
        `load` method.
233
        :return: a class with customized behavior.
234
        """
235
        original_load = cls.load
236
237
        def _wrapper(inst, **kwargs_):
238
            return original_load(inst, **{**kwargs_, **kwargs})
239
        t = type(JsonSerializable.__name__, (cls,), {})
0 ignored issues
show
Coding Style Naming introduced by
The name t 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...
240
        t.load =_wrapper
0 ignored issues
show
Coding Style introduced by
Exactly one space required after assignment
Loading history...
241
        return t
242
243
    @property
244
    def json(self) -> dict:
245
        """
246
        See `jsons.dump`.
247
        :return: this instance in a JSON representation (dict).
248
        """
249
        return self.dump()
250
251
    @classmethod
252
    def from_json(cls: type, json_obj: dict, **kwargs) -> object:
253
        """
254
        See `jsons.load`.
255
        :param json_obj: a JSON representation of an instance of the inheriting
256
        class
257
        :param kwargs: the keyword args are passed on to the deserializer
258
        function.
259
        :return: an instance of the inheriting class.
260
        """
261
        return cls.load(json_obj, **kwargs)
262
263
    def dump(self, **kwargs) -> dict:
264
        """
265
        See `jsons.dump`.
266
        :param kwargs: the keyword args are passed on to the serializer
267
        function.
268
        :return: this instance in a JSON representation (dict).
269
        """
270
        return dump(self, **kwargs)
271
272
    @classmethod
273
    def load(cls: type, json_obj: dict, **kwargs) -> object:
274
        """
275
        See `jsons.load`.
276
        :param kwargs: the keyword args are passed on to the serializer
277
        function.
278
        :return: this instance in a JSON representation (dict).
279
        """
280
        return load(json_obj, cls, **kwargs)
281
282
283
set_serializer(default_list_serializer, list)
284
set_serializer(default_tuple_serializer, tuple)
285
set_serializer(default_dict_serializer, dict)
286
set_serializer(default_enum_serializer, Enum)
287
set_serializer(default_datetime_serializer, datetime)
288
set_serializer(default_primitive_serializer, str)
289
set_serializer(default_primitive_serializer, int)
290
set_serializer(default_primitive_serializer, float)
291
set_serializer(default_primitive_serializer, bool)
292
set_serializer(default_primitive_serializer, None)
293
set_serializer(default_object_serializer, object, False)
294
set_deserializer(default_list_deserializer, list)
295
set_deserializer(default_tuple_deserializer, tuple)
296
set_deserializer(default_dict_deserializer, dict)
297
set_deserializer(default_enum_deserializer, Enum)
298
set_deserializer(default_datetime_deserializer, datetime)
299
set_deserializer(default_string_deserializer, str)
300
set_deserializer(default_primitive_deserializer, int)
301
set_deserializer(default_primitive_deserializer, float)
302
set_deserializer(default_primitive_deserializer, bool)
303
set_deserializer(default_primitive_deserializer, None)
304
set_deserializer(default_object_deserializer, object, False)
305