Completed
Pull Request — master (#38)
by Ramon
01:21
created

jsons._common_impl.determine_precedence()   A

Complexity

Conditions 4

Size

Total Lines 14
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 11
dl 0
loc 14
rs 9.85
c 0
b 0
f 0
cc 4
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
    module = _get_module(cls)
54
    transformer = transformer or (lambda x: x)
55
    if not cls_name and hasattr(cls, '__origin__'):
56
        origin = cls.__origin__
57
        cls_name = get_class_name(origin, transformer,
58
                                  fully_qualified, fork_inst)
59
    if not cls_name:
60
        cls_name = str(cls)
61
    if fully_qualified and module:
62
        cls_name = '{}.{}'.format(module, cls_name)
63
    cls_name = transformer(cls_name)
64
    return cls_name
65
66
67
def get_cls_from_str(cls_str: str, source: object, fork_inst) -> type:
68
    cls = getattr(builtins, cls_str, None)
69
    if cls:
70
        return cls
71
    try:
72
        splitted = cls_str.split('.')
73
        module_name = '.'.join(splitted[:-1])
74
        cls_name = splitted[-1]
75
        cls_module = import_module(module_name)
76
        cls = getattr(cls_module, cls_name)
77
        if not cls or not isinstance(cls, type):
78
            cls = _lookup_announced_class(cls_str, source, fork_inst)
79
    except (ImportError, AttributeError, ValueError):
80
        cls = _lookup_announced_class(cls_str, source, fork_inst)
81
    return cls
82
83
84
def determine_precedence(
85
        cls: type,
86
        cls_from_meta: type,
87
        cls_from_type: type,
88
        inferred_cls: bool):
89
    order = [cls, cls_from_meta, cls_from_type]
90
    if inferred_cls:
91
        # The type from a verbose dumped object takes precedence over an
92
        # inferred type (e.g. T in List[T]).
93
        order = [cls_from_meta, cls, cls_from_type]
94
    # Now to return the first element in the order that holds a value.
95
    for elem in order:
96
        if elem:
97
            return elem
98
99
100
def get_cls_and_meta(
101
        json_obj: object,
102
        fork_inst: type) -> Tuple[Optional[type], Optional[dict]]:
103
    if isinstance(json_obj, dict) and META_ATTR in json_obj:
104
        cls_str = json_obj[META_ATTR]['classes']['/']
105
        cls = get_cls_from_str(cls_str, json_obj, fork_inst)
106
        return cls, json_obj[META_ATTR]
107
    return None, None
108
109
110
def get_parents(cls: type, lizers: list) -> list:
111
    """
112
    Return a list of serializers or deserializers that can handle a parent
113
    of ``cls``.
114
    :param cls: the type that
115
    :param lizers: a list of serializers or deserializers.
116
    :return: a list of serializers or deserializers.
117
    """
118
    parents = []
119
    for cls_ in lizers:
120
        try:
121
            if issubclass(cls, cls_):
122
                parents.append(cls_)
123
        except TypeError:
124
            pass  # Some types do not support `issubclass` (e.g. Union).
125
    return parents
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
    cls_name = getattr(cls, '__name__', None) #or _get_special_cases(cls)
142
    if not cls_name:
143
        cls_name = getattr(cls, '_name', None)
144
    if not cls_name:
145
        cls_name = repr(cls)
146
        cls_name = cls_name.split('[')[0]  # Remove generic types.
147
        cls_name = cls_name.split('.')[-1]  # Remove any . caused by repr.
148
    return cls_name
149
150
151
# def _get_special_cases(cls: type) -> str:
152
#     # This method contains all special cases that cannot (easily) be captured
153
#     # with a generic method.
154
#     if isinstance(cls, ForwardRef):
155
#         return 'ForwardRef'
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