Completed
Push — master ( 7be5b2...0ef053 )
by Ramon
24s queued 10s
created

jsons._common_impl.get_parents()   A

Complexity

Conditions 4

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 10
dl 0
loc 17
rs 9.9
c 0
b 0
f 0
cc 4
nop 2
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
VALID_TYPES = (str, int, float, bool, list, tuple, set, dict, type(None))
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 _lookup_announced_class(
114
        cls_str: str,
115
        source: object,
116
        fork_inst: type) -> type:
117
    cls = fork_inst._announced_classes.get(cls_str)
118
    if not cls:
119
        msg = ('Could not find a suitable type for "{}". Make sure it can be '
120
               'imported or that is has been announced.'.format(cls_str))
121
        raise UnknownClassError(msg, source, cls_str)
122
    return cls
123
124
125
def _get_simple_name(cls: type) -> str:
126
    if cls is None:
127
        cls = type(cls)
128
    cls_name = getattr(cls, '__name__', None)
129
    if not cls_name:
130
        cls_name = getattr(cls, '_name', None)
131
    if not cls_name:
132
        cls_name = repr(cls)
133
        cls_name = cls_name.split('[')[0]  # Remove generic types.
134
        cls_name = cls_name.split('.')[-1]  # Remove any . caused by repr.
135
        cls_name = cls_name.split(r"'>")[0]  # Remove any '>.
136
    return cls_name
137
138
139
def _get_module(cls: type) -> Optional[str]:
140
    builtin_module = str.__class__.__module__
141
    module = cls.__module__
142
    if module and module != builtin_module:
143
        return module
144