Completed
Push — master ( a8a578...6c485c )
by Ramon
19s queued 10s
created

jsons._load_impl._check_for_none()   A

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