Completed
Push — master ( 7be5b2...0ef053 )
by Ramon
24s queued 10s
created

jsons._load_impl.load()   B

Complexity

Conditions 5

Size

Total Lines 70
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 26
dl 0
loc 70
rs 8.7893
c 0
b 0
f 0
cc 5
nop 6

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
"""
2
PRIVATE MODULE: do not import (from) it directly.
3
4
This module contains functionality for loading stuff from json.
5
"""
6
import json
7
from json import JSONDecodeError
8
from typing import Optional, Dict, Callable, Tuple
9
from jsons._lizers_impl import get_deserializer
10
from jsons.exceptions import DeserializationError, JsonsError, DecodeError
11
from jsons._common_impl import (
12
    StateHolder,
13
    get_cls_from_str,
14
    get_class_name,
15
    get_cls_and_meta,
16
    determine_precedence,
17
    VALID_TYPES)
18
19
20
def load(
21
        json_obj: object,
22
        cls: Optional[type] = None,
23
        strict: bool = False,
24
        fork_inst: Optional[type] = StateHolder,
25
        attr_getters: Optional[Dict[str, Callable[[], object]]] = None,
26
        **kwargs) -> object:
27
    """
28
    Deserialize the given ``json_obj`` to an object of type ``cls``. If the
29
    contents of ``json_obj`` do not match the interface of ``cls``, a
30
    DeserializationError is raised.
31
32
    If ``json_obj`` contains a value that belongs to a custom class, there must
33
    be a type hint present for that value in ``cls`` to let this function know
34
    what type it should deserialize that value to.
35
36
37
    **Example**:
38
39
    >>> from typing import List
40
    >>> import jsons
41
    >>> class Person:
42
    ...     # No type hint required for name
43
    ...     def __init__(self, name):
44
    ...         self.name = name
45
    >>> class Family:
46
    ...     # Person is a custom class, use a type hint
47
    ...         def __init__(self, persons: List[Person]):
48
    ...             self.persons = persons
49
    >>> loaded = jsons.load({'persons': [{'name': 'John'}]}, Family)
50
    >>> loaded.persons[0].name
51
    'John'
52
53
    If no ``cls`` is given, a dict is simply returned, but contained values
54
    (e.g. serialized ``datetime`` values) are still deserialized.
55
56
    If `strict` mode is off and the type of `json_obj` exactly matches `cls`
57
    then `json_obj` is simply returned.
58
59
    :param json_obj: the dict that is to be deserialized.
60
    :param cls: a matching class of which an instance should be returned.
61
    :param strict: a bool to determine if the deserializer should be strict
62
    (i.e. fail on a partially deserialized `json_obj` or on `None`).
63
    :param fork_inst: if given, it uses this fork of ``JsonSerializable``.
64
    :param attr_getters: a ``dict`` that may hold callables that return values
65
    for certain attributes.
66
    :param kwargs: the keyword args are passed on to the deserializer function.
67
    :return: an instance of ``cls`` if given, a dict otherwise.
68
    """
69
    if _should_skip(json_obj, cls, strict):
70
        return json_obj
71
    if isinstance(cls, str):
72
        cls = get_cls_from_str(cls, json_obj, fork_inst)
73
    cls, meta_hints = _check_and_get_cls_and_meta_hints(
74
        json_obj, cls, fork_inst, kwargs.get('_inferred_cls', False))
75
76
    deserializer = get_deserializer(cls, fork_inst)
77
    kwargs_ = {
78
        'strict': strict,
79
        'fork_inst': fork_inst,
80
        'attr_getters': attr_getters,
81
        'meta_hints': meta_hints,
82
        **kwargs
83
    }
84
    try:
85
        return deserializer(json_obj, cls, **kwargs_)
86
    except Exception as err:
87
        if isinstance(err, JsonsError):
88
            raise
89
        raise DeserializationError(str(err), json_obj, cls)
90
91
92
def loads(
93
        str_: str,
94
        cls: Optional[type] = None,
95
        jdkwargs: Optional[Dict[str, object]] = None,
96
        *args,
97
        **kwargs) -> object:
98
    """
99
    Extend ``json.loads``, allowing a string to be loaded into a dict or a
100
    Python instance of type ``cls``. Any extra (keyword) arguments are passed
101
    on to ``json.loads``.
102
103
    :param str_: the string that is to be loaded.
104
    :param cls: a matching class of which an instance should be returned.
105
    :param jdkwargs: extra keyword arguments for ``json.loads`` (not
106
    ``jsons.loads``!)
107
    :param args: extra arguments for ``jsons.loads``.
108
    :param kwargs: extra keyword arguments for ``jsons.loads``.
109
    :return: a JSON-type object (dict, str, list, etc.) or an instance of type
110
    ``cls`` if given.
111
    """
112
    jdkwargs = jdkwargs or {}
113
    try:
114
        obj = json.loads(str_, **jdkwargs)
115
    except JSONDecodeError as err:
116
        raise DecodeError('Could not load a dict; the given string is not '
117
                          'valid JSON.', str_, cls, err)
118
    else:
119
        return load(obj, cls, *args, **kwargs)
120
121
122
def loadb(
123
        bytes_: bytes,
124
        cls: Optional[type] = None,
125
        encoding: str = 'utf-8',
126
        jdkwargs: Optional[Dict[str, object]] = None,
127
        *args,
128
        **kwargs) -> object:
129
    """
130
    Extend ``json.loads``, allowing bytes to be loaded into a dict or a Python
131
    instance of type ``cls``. Any extra (keyword) arguments are passed on to
132
    ``json.loads``.
133
134
    :param bytes_: the bytes that are to be loaded.
135
    :param cls: a matching class of which an instance should be returned.
136
    :param encoding: the encoding that is used to transform from bytes.
137
    :param jdkwargs: extra keyword arguments for ``json.loads`` (not
138
    ``jsons.loads``!)
139
    :param args: extra arguments for ``jsons.loads``.
140
    :param kwargs: extra keyword arguments for ``jsons.loads``.
141
    :return: a JSON-type object (dict, str, list, etc.) or an instance of type
142
    ``cls`` if given.
143
    """
144
    if not isinstance(bytes_, bytes):
145
        raise DeserializationError('loadb accepts bytes only, "{}" was given'
146
                                   .format(type(bytes_)), bytes_, cls)
147
    jdkwargs = jdkwargs or {}
148
    str_ = bytes_.decode(encoding=encoding)
149
    return loads(str_, cls, jdkwargs=jdkwargs, *args, **kwargs)
150
151
152
def _check_and_get_cls_and_meta_hints(
153
        json_obj: object,
154
        cls: type,
155
        fork_inst: type,
156
        inferred_cls: bool) -> Tuple[type, Optional[dict]]:
157
    # Check if json_obj is of a valid type and return the cls.
158
    if type(json_obj) not in VALID_TYPES:
159
        invalid_type = get_class_name(type(json_obj), fork_inst=fork_inst,
160
                                      fully_qualified=True)
161
        valid_types = [get_class_name(typ, fork_inst=fork_inst,
162
                                      fully_qualified=True)
163
                       for typ in VALID_TYPES]
164
        msg = ('Invalid type: "{}", only arguments of the following types are '
165
               'allowed: {}'.format(invalid_type, ", ".join(valid_types)))
166
        raise DeserializationError(msg, json_obj, cls)
167
    if json_obj is None:
168
        raise DeserializationError('Cannot load None with strict=True',
169
                                   json_obj, cls)
170
171
    cls_from_meta, meta = get_cls_and_meta(json_obj, fork_inst)
172
    meta_hints = meta.get('classes', {}) if meta else {}
173
    return determine_precedence(
174
        cls, cls_from_meta, type(json_obj), inferred_cls), meta_hints
175
176
177
def _should_skip(json_obj: object, cls: type, strict: bool):
178
    if not strict and (json_obj is None or type(json_obj) == cls):
179
        return True
180