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