Passed
Pull Request — master (#49)
by Ramon
01:20
created

_get_dict_with_meta()   A

Complexity

Conditions 4

Size

Total Lines 20
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

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