Completed
Push — master ( 008c50...892606 )
by Ramon
11s
created

jsons._common_impl.get_class_name()   A

Complexity

Conditions 5

Size

Total Lines 24
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

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