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

jsons._common_impl.can_match_with_none()   A

Complexity

Conditions 2

Size

Total Lines 7
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 6
dl 0
loc 7
rs 10
c 0
b 0
f 0
cc 2
nop 1
1
"""
2
PRIVATE MODULE: do not import (from) it directly.
3
4
This module contains implementations of common functionality that can be used
5
throughout `jsons`.
6
"""
7
import builtins
8
import warnings
9
from importlib import import_module
10
from typing import Callable, Optional, Tuple, TypeVar, Any
11
from jsons._compatibility_impl import get_union_params
12
from jsons.exceptions import UnknownClassError
13
14
15
NoneType = type(None)
16
VALID_TYPES = (str, int, float, bool, list, tuple, set, dict, NoneType)
17
META_ATTR = '-meta'  # The name of the attribute holding meta info.
18
T = TypeVar('T')
19
20
21
class StateHolder:
22
    """
23
    This class holds the registered serializers and deserializers.
24
    """
25
    _fork_counter = 0
26
    _classes_serializers = list()
27
    _classes_deserializers = list()
28
    _serializers = dict()
29
    _deserializers = dict()
30
    _validators = dict()
31
    _classes_validators = list()
32
    _announced_classes = dict()
33
    _suppress_warnings = False
34
35
    @classmethod
36
    def _warn(cls, msg, *args, **kwargs):
37
        if not cls._suppress_warnings:
38
            msg_ = ('{} You can suppress warnings like this using '
39
                    'jsons.suppress_warnings().'.format(msg))
40
            warnings.warn(msg_, *args, **kwargs)
41
42
43
def get_class_name(cls: type,
44
                   transformer: Optional[Callable[[str], str]] = None,
45
                   fully_qualified: bool = False,
46
                   fork_inst: Optional[type] = StateHolder) -> Optional[str]:
47
    """
48
    Return the name of a class.
49
    :param cls: the class of which the name if to be returned.
50
    :param transformer: any string transformer, e.g. ``str.lower``.
51
    :param fully_qualified: if ``True`` return the fully qualified name (i.e.
52
    complete with module name).
53
    :param fork_inst if given, it uses this fork of ``JsonSerializable`` for
54
    finding the class name.
55
    :return: the name of ``cls``, transformed if a transformer is given.
56
    """
57
    transformer = transformer or (lambda x: x)
58
    cls_name = _get_special_cases(cls)
59
    if cls_name:
60
        return transformer(cls_name)
61
    if cls in fork_inst._announced_classes:
62
        return transformer(fork_inst._announced_classes[cls])
63
    cls_name = _get_simple_name(cls)
64
    if fully_qualified:
65
        module = _get_module(cls)
66
        if module:
67
            cls_name = '{}.{}'.format(module, cls_name)
68
    cls_name = transformer(cls_name)
69
    return cls_name
70
71
72
def _get_special_cases(cls: type):
73
    if (hasattr(cls, '__qualname__')
74
            and cls.__qualname__ == 'NewType.<locals>.new_type'):
75
        return cls.__name__
76
77
78
def get_cls_from_str(cls_str: str, source: object, fork_inst) -> type:
79
    cls = getattr(builtins, cls_str, None)
80
    if cls:
81
        return cls
82
    try:
83
        splitted = cls_str.split('.')
84
        module_name = '.'.join(splitted[:-1])
85
        cls_name = splitted[-1]
86
        cls_module = import_module(module_name)
87
        cls = getattr(cls_module, cls_name)
88
    except (ImportError, AttributeError, ValueError):
89
        cls = _lookup_announced_class(cls_str, source, fork_inst)
90
    return cls
91
92
93
def determine_precedence(
94
        cls: type,
95
        cls_from_meta: type,
96
        cls_from_type: type,
97
        inferred_cls: bool):
98
    order = [cls, cls_from_meta, cls_from_type]
99
    if inferred_cls:
100
        # The type from a verbose dumped object takes precedence over an
101
        # inferred type (e.g. T in List[T]).
102
        order = [cls_from_meta, cls, cls_from_type]
103
    # Now to return the first element in the order that holds a value.
104
    for elem in order:
105
        if elem:
106
            return elem
107
108
109
def get_cls_and_meta(
110
        json_obj: object,
111
        fork_inst: type) -> Tuple[Optional[type], Optional[dict]]:
112
    if isinstance(json_obj, dict) and META_ATTR in json_obj:
113
        cls_str = json_obj[META_ATTR]['classes']['/']
114
        cls = get_cls_from_str(cls_str, json_obj, fork_inst)
115
        return cls, json_obj[META_ATTR]
116
    return None, None
117
118
119
def can_match_with_none(cls: type):
120
    # Return True if cls allows None; None is a valid value with the given cls.
121
    result = cls in (Any, object, None, NoneType)
122
    if not result:
123
        cls_name = get_class_name(cls).lower()
124
        result = cls_name == 'union' and NoneType in get_union_params(cls)
125
    return result
126
127
128
def _lookup_announced_class(
129
        cls_str: str,
130
        source: object,
131
        fork_inst: type) -> type:
132
    cls = fork_inst._announced_classes.get(cls_str)
133
    if not cls:
134
        msg = ('Could not find a suitable type for "{}". Make sure it can be '
135
               'imported or that is has been announced.'.format(cls_str))
136
        raise UnknownClassError(msg, source, cls_str)
137
    return cls
138
139
140
def _get_simple_name(cls: type) -> str:
141
    if cls is None:
142
        cls = type(cls)
143
    cls_name = getattr(cls, '__name__', None)
144
    if not cls_name:
145
        cls_name = getattr(cls, '_name', None)
146
    if not cls_name:
147
        cls_name = repr(cls)
148
        cls_name = cls_name.split('[')[0]  # Remove generic types.
149
        cls_name = cls_name.split('.')[-1]  # Remove any . caused by repr.
150
        cls_name = cls_name.split(r"'>")[0]  # Remove any '>.
151
    return cls_name
152
153
154
def _get_module(cls: type) -> Optional[str]:
155
    builtin_module = str.__class__.__module__
156
    module = getattr(cls, '__module__', None)
157
    if module and module != builtin_module:
158
        return module
159