jsons._load_impl._check_for_none()   A
last analyzed

Complexity

Conditions 3

Size

Total Lines 8
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 7
dl 0
loc 8
rs 10
c 0
b 0
f 0
cc 3
nop 2
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, Any, Type
9
10
from jsons._cache import clear
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
    T,
19
    can_match_with_none
20
)
21
from jsons._lizers_impl import get_deserializer
22
from jsons._validation import validate
23
from jsons.exceptions import DeserializationError, JsonsError, DecodeError
24
25
26
def load(
27
        json_obj: object,
28
        cls: Optional[Type[T]] = None,
29
        *,
30
        strict: bool = False,
31
        fork_inst: Optional[type] = StateHolder,
32
        attr_getters: Optional[Dict[str, Callable[[], object]]] = None,
33
        **kwargs) -> T:
34
    """
35
    Deserialize the given ``json_obj`` to an object of type ``cls``. If the
36
    contents of ``json_obj`` do not match the interface of ``cls``, a
37
    DeserializationError is raised.
38
39
    If ``json_obj`` contains a value that belongs to a custom class, there must
40
    be a type hint present for that value in ``cls`` to let this function know
41
    what type it should deserialize that value to.
42
43
44
    **Example**:
45
46
    >>> from typing import List
47
    >>> import jsons
48
    >>> class Person:
49
    ...     # No type hint required for name
50
    ...     def __init__(self, name):
51
    ...         self.name = name
52
    >>> class Family:
53
    ...     # Person is a custom class, use a type hint
54
    ...         def __init__(self, persons: List[Person]):
55
    ...             self.persons = persons
56
    >>> loaded = jsons.load({'persons': [{'name': 'John'}]}, Family)
57
    >>> loaded.persons[0].name
58
    'John'
59
60
    If no ``cls`` is given, a dict is simply returned, but contained values
61
    (e.g. serialized ``datetime`` values) are still deserialized.
62
63
    If `strict` mode is off and the type of `json_obj` exactly matches `cls`
64
    then `json_obj` is simply returned.
65
66
    :param json_obj: the dict that is to be deserialized.
67
    :param cls: a matching class of which an instance should be returned.
68
    :param strict: a bool to determine if the deserializer should be strict
69
    (i.e. fail on a partially deserialized `json_obj` or on `None`).
70
    :param fork_inst: if given, it uses this fork of ``JsonSerializable``.
71
    :param attr_getters: a ``dict`` that may hold callables that return values
72
    for certain attributes.
73
    :param kwargs: the keyword args are passed on to the deserializer function.
74
    :return: an instance of ``cls`` if given, a dict otherwise.
75
    """
76
    _check_for_none(json_obj, cls)
77
    if _should_skip(json_obj, cls, strict):
78
        validate(json_obj, cls, fork_inst)
79
        return json_obj
80
    if isinstance(cls, str):
81
        cls = get_cls_from_str(cls, json_obj, fork_inst)
82
    original_cls = cls
83
    cls, meta_hints = _check_and_get_cls_and_meta_hints(
84
        json_obj, cls, fork_inst, kwargs.get('_inferred_cls', False))
85
86
    deserializer = get_deserializer(cls, fork_inst)
87
88
    # Is this the initial call or a nested?
89
    initial = kwargs.get('_initial', True)
90
91
    kwargs_ = {
92
        'meta_hints': meta_hints,  # Overridable by kwargs.
93
        **kwargs,
94
        'strict': strict,
95
        'fork_inst': fork_inst,
96
        'attr_getters': attr_getters,
97
        '_initial': False,
98
        '_inferred_cls': cls is not original_cls,
99
    }
100
101
    return _do_load(json_obj, deserializer, cls, initial, **kwargs_)
102
103
104
def _do_load(json_obj: object,
105
             deserializer: callable,
106
             cls: type,
107
             initial: bool,
108
             **kwargs):
109
    cls_name = get_class_name(cls, fully_qualified=True)
110
    if deserializer is None:
111
        raise DeserializationError('No deserializer for type "{}"'.format(cls_name), json_obj, cls)
112
    try:
113
        result = deserializer(json_obj, cls, **kwargs)
114
        validate(result, cls, kwargs['fork_inst'])
115
    except Exception as err:
116
        clear()
117
        if isinstance(err, JsonsError):
118
            raise
119
        message = 'Could not deserialize value "{}" into "{}". {}'.format(json_obj, cls_name, err)
120
        raise DeserializationError(message, json_obj, cls) from err
121
    else:
122
        if initial:
123
            # Clear all lru caches right before returning the initial call.
124
            clear()
125
        return result
126
127
128
def loads(
129
        str_: str,
130
        cls: Optional[Type[T]] = None,
131
        jdkwargs: Optional[Dict[str, object]] = None,
132
        *args,
133
        **kwargs) -> T:
134
    """
135
    Extend ``json.loads``, allowing a string to be loaded into a dict or a
136
    Python instance of type ``cls``. Any extra (keyword) arguments are passed
137
    on to ``json.loads``.
138
139
    :param str_: the string that is to be loaded.
140
    :param cls: a matching class of which an instance should be returned.
141
    :param jdkwargs: extra keyword arguments for ``json.loads`` (not
142
    ``jsons.loads``!)
143
    :param args: extra arguments for ``jsons.loads``.
144
    :param kwargs: extra keyword arguments for ``jsons.loads``.
145
    :return: a JSON-type object (dict, str, list, etc.) or an instance of type
146
    ``cls`` if given.
147
    """
148
    jdkwargs = jdkwargs or {}
149
    try:
150
        obj = json.loads(str_, **jdkwargs)
151
    except JSONDecodeError as err:
152
        raise DecodeError('Could not load a dict; the given string is not '
153
                          'valid JSON.', str_, cls, err) from err
154
    else:
155
        return load(obj, cls, *args, **kwargs)
156
157
158
def loadb(
159
        bytes_: bytes,
160
        cls: Optional[Type[T]] = None,
161
        encoding: str = 'utf-8',
162
        jdkwargs: Optional[Dict[str, object]] = None,
163
        *args,
164
        **kwargs) -> T:
165
    """
166
    Extend ``json.loads``, allowing bytes to be loaded into a dict or a Python
167
    instance of type ``cls``. Any extra (keyword) arguments are passed on to
168
    ``json.loads``.
169
170
    :param bytes_: the bytes that are to be loaded.
171
    :param cls: a matching class of which an instance should be returned.
172
    :param encoding: the encoding that is used to transform from bytes.
173
    :param jdkwargs: extra keyword arguments for ``json.loads`` (not
174
    ``jsons.loads``!)
175
    :param args: extra arguments for ``jsons.loads``.
176
    :param kwargs: extra keyword arguments for ``jsons.loads``.
177
    :return: a JSON-type object (dict, str, list, etc.) or an instance of type
178
    ``cls`` if given.
179
    """
180
    if not isinstance(bytes_, bytes):
181
        raise DeserializationError('loadb accepts bytes only, "{}" was given'
182
                                   .format(type(bytes_)), bytes_, cls)
183
    jdkwargs = jdkwargs or {}
184
    str_ = bytes_.decode(encoding=encoding)
185
    return loads(str_, cls, jdkwargs=jdkwargs, *args, **kwargs)
186
187
188
def _check_and_get_cls_and_meta_hints(
189
        json_obj: object,
190
        cls: type,
191
        fork_inst: type,
192
        inferred_cls: bool) -> Tuple[type, Optional[dict]]:
193
    # Check if json_obj is of a valid type and return the cls.
194
    if type(json_obj) not in VALID_TYPES:
195
        invalid_type = get_class_name(type(json_obj), fully_qualified=True)
196
        valid_types = [get_class_name(typ, fully_qualified=True)
197
                       for typ in VALID_TYPES]
198
        msg = ('Invalid type: "{}", only arguments of the following types are '
199
               'allowed: {}'.format(invalid_type, ", ".join(valid_types)))
200
        raise DeserializationError(msg, json_obj, cls)
201
202
    cls_from_meta, meta = get_cls_and_meta(json_obj, fork_inst)
203
    meta_hints = meta.get('classes', {}) if meta else {}
204
    return determine_precedence(
205
        cls, cls_from_meta, type(json_obj), inferred_cls), meta_hints
206
207
208
def _should_skip(json_obj: object, cls: type, strict: bool):
209
    return (not strict and type(json_obj) == cls) or cls is Any
210
211
212
def _check_for_none(json_obj: object, cls: type):
213
    # Check if the json_obj is None and whether or not that is fine.
214
    if json_obj is None and not can_match_with_none(cls):
215
        cls_name = get_class_name(cls)
216
        raise DeserializationError(
217
            message='NoneType cannot be deserialized into {}'.format(cls_name),
218
            source=json_obj,
219
            target=cls)
220