Passed
Pull Request — master (#42)
by Ramon
52s
created

jsons._common_impl._get_module()   A

Complexity

Conditions 3

Size

Total Lines 5
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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