Completed
Push — master ( a0c5cf...fb555e )
by Ramon
19s queued 11s
created

_get_class_name_and_strip_cls()   A

Complexity

Conditions 4

Size

Total Lines 7
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 7
dl 0
loc 7
rs 10
c 0
b 0
f 0
cc 4
nop 2
1
from datetime import datetime, timezone
2
from typing import Optional, Callable, Union, MutableSequence, Tuple
3
from jsons._common_impl import get_class_name, META_ATTR
4
from jsons._datetime_impl import to_str
5
from jsons.classes import JsonSerializable
6
from jsons.classes.verbosity import Verbosity
7
from jsons.serializers.default_dict import default_dict_serializer
8
9
10
def default_object_serializer(
11
        obj: object,
12
        cls: Optional[type] = None,
13
        *,
14
        key_transformer: Optional[Callable[[str], str]] = None,
15
        strip_nulls: bool = False,
16
        strip_privates: bool = False,
17
        strip_properties: bool = False,
18
        strip_class_variables: bool = False,
19
        strip_attr: Union[str, MutableSequence[str], Tuple[str]] = None,
20
        verbose: Union[Verbosity, bool] = False,
21
        **kwargs) -> dict:
22
    """
23
    Serialize the given ``obj`` to a dict. All values within ``obj`` are also
24
    serialized. If ``key_transformer`` is given, it will be used to transform
25
    the casing (e.g. snake_case) to a different format (e.g. camelCase).
26
    :param obj: the object that is to be serialized.
27
    :param cls: the type of the object that is to be dumped.
28
    :param key_transformer: a function that will be applied to all keys in the
29
    resulting dict.
30
    :param strip_nulls: if ``True`` the resulting dict will not contain null
31
    values.
32
    :param strip_privates: if ``True`` the resulting dict will not contain
33
    private attributes (i.e. attributes that start with an underscore).
34
    :param strip_properties: if ``True`` the resulting dict will not contain
35
    values from @properties.
36
    :param strip_class_variables: if ``True`` the resulting dict will not
37
    contain values from class variables.
38
    :param strip_attr: can be a name or a collection of names of attributes
39
    that are not to be included in the dump.
40
    dict will not contain attributes with
41
    :param verbose: if ``True`` the resulting dict will contain meta
42
    information (e.g. on how to deserialize).
43
    :param kwargs: any keyword arguments that are to be passed to the
44
    serializer functions.
45
    :return: a Python dict holding the values of ``obj``.
46
    """
47
    if obj is None:
48
        return obj
49
    strip_attr = strip_attr or []
50
    if (not isinstance(strip_attr, MutableSequence)
51
            and not isinstance(strip_attr, tuple)):
52
        strip_attr = (strip_attr,)
53
    cls = cls or obj.__class__
54
    obj_dict = _get_dict_from_obj(obj, cls, strip_privates, strip_properties,
55
                                  strip_class_variables, strip_attr, **kwargs)
56
    kwargs_ = {**kwargs, 'verbose': verbose}
57
    verbose = Verbosity.from_value(verbose)
58
    if Verbosity.WITH_CLASS_INFO in verbose:
59
        # Set a flag in kwargs to temporarily store -cls.
60
        kwargs_['_store_cls'] = True
61
    result = default_dict_serializer(
62
        obj_dict,
63
        cls,
64
        key_transformer=key_transformer,
65
        strip_nulls=strip_nulls,
66
        strip_privates=strip_privates,
67
        strip_properties=strip_properties,
68
        strip_class_variables=strip_class_variables,
69
        strip_attr=strip_attr,
70
        **kwargs_)
71
    cls_name = get_class_name(cls, fully_qualified=True,
72
                              fork_inst=kwargs['fork_inst'])
73
    if not kwargs.get('_store_cls'):
74
        result = _get_dict_with_meta(result, cls_name, verbose,
75
                                     kwargs['fork_inst'])
76
    return result
77
78
79
def _get_dict_from_obj(
80
        obj,
81
        cls=None,
82
        strip_privates=False,
83
        strip_properties=False,
84
        strip_class_variables=False,
85
        strip_attr=False,
86
        *_,
87
        **__) -> dict:
88
    strip_attr = tuple(strip_attr) + _ABC_ATTRS
89
    excluded_elems = dir(JsonSerializable)
90
    props, other_cls_vars = _get_class_props(obj.__class__)
91
    # Make sure to get the slots of cls, not of any parent:
92
    slots = cls.__slots__ if '__slots__' in cls.__dict__ else {}
93
    return {attr: obj.__getattribute__(attr) for attr in dir(obj)
94
            if not attr.startswith('__')
95
            and not (strip_privates and attr.startswith('_'))
96
            and not (strip_properties and attr in props)
97
            and not (strip_class_variables and attr in other_cls_vars)
98
            and attr not in strip_attr
99
            and attr != 'json'
100
            and not isinstance(obj.__getattribute__(attr), Callable)
101
            and (not slots or attr in slots)
102
            and attr not in excluded_elems}
103
104
105
def _get_class_props(cls):
106
    props = []
107
    other_cls_vars = []
108
    for n, v in _get_complete_class_dict(cls).items():
109
        list_to_append = props if isinstance(v, property) else other_cls_vars
110
        list_to_append.append(n)
111
    return props, other_cls_vars
112
113
114
def _get_complete_class_dict(cls):
115
    cls_dict = {}
116
    # Loop reversed so values of sub-classes override those of super-classes.
117
    for cls_or_elder in reversed(cls.mro()):
118
        cls_dict.update(cls_or_elder.__dict__)
119
    return cls_dict
120
121
122
def _get_dict_with_meta(
123
        obj: dict,
124
        cls_name: str,
125
        verbose: Verbosity,
126
        fork_inst: type) -> dict:
127
    # This function will add a -meta section to the given obj (provided that
128
    # the given obj has -cls attributes for all children).
129
    if verbose is Verbosity.WITH_NOTHING:
130
        return obj
131
132
    obj[META_ATTR] = {}
133
    if Verbosity.WITH_CLASS_INFO in verbose:
134
        collection_of_types = {}
135
        _fill_collection_of_types(obj, cls_name, '/', collection_of_types)
136
        collection_of_types['/'] = cls_name
137
        obj[META_ATTR]['classes'] = collection_of_types
138
    if Verbosity.WITH_DUMP_TIME in verbose:
139
        dump_time = to_str(datetime.now(tz=timezone.utc), True, fork_inst)
140
        obj[META_ATTR]['dump_time'] = dump_time
141
    return obj
142
143
144
def _fill_collection_of_types(
145
        obj_: dict,
146
        cls_name_: Optional[str],
147
        prefix: str,
148
        collection_of_types_: dict) -> str:
149
    # This function loops through obj_ to fill collection_of_types_ with the
150
    # class names. All of the -cls attributes are removed in the process.
151
    cls_name_ = _get_class_name_and_strip_cls(cls_name_, obj_)
152
    for attr in obj_:
153
        if attr != META_ATTR and isinstance(obj_[attr], dict):
154
            attr_class = _fill_collection_of_types(obj_[attr],
155
                                                   None,
156
                                                   prefix + attr + '/',
157
                                                   collection_of_types_)
158
            collection_of_types_[prefix + attr] = attr_class
159
    return cls_name_
160
161
162
def _get_class_name_and_strip_cls(cls_name: Optional[str], obj: dict) -> str:
163
    result = cls_name
164
    if not cls_name and '-cls' in obj:
165
        result = obj['-cls']
166
    if '-cls' in obj:
167
        del obj['-cls']
168
    return result
169
170
171
_ABC_ATTRS = ('_abc_registry', '_abc_cache', '_abc_negative_cache',
172
              '_abc_negative_cache_version', '_abc_impl')
173