Passed
Pull Request — master (#23)
by Ramon
55s
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
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 import default_datetime_serializer
8
from jsons.serializers.default_dict import default_dict_serializer
9
10
11
def default_object_serializer(
12
        obj: object,
13
        key_transformer: Optional[Callable[[str], str]] = None,
14
        strip_nulls: bool = False,
15
        strip_privates: bool = False,
16
        strip_properties: bool = False,
17
        strip_class_variables: bool = False,
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 verbose: if ``True`` the resulting dict will contain meta
36
    information (e.g. on how to deserialize).
37
    :param kwargs: any keyword arguments that are to be passed to the
38
    serializer functions.
39
    :return: a Python dict holding the values of ``obj``.
40
    """
41
    cls = kwargs['cls'] or obj.__class__
42
    obj_dict = _get_dict_from_obj(obj, strip_privates, strip_properties,
43
                                  strip_class_variables, **kwargs)
44
    kwargs_ = {**kwargs}
45
    verbose = Verbosity.from_value(verbose)
46
    if Verbosity.WITH_CLASS_INFO in verbose:
47
        kwargs_['store_cls'] = True
48
    result = default_dict_serializer(
49
        obj_dict,
50
        key_transformer=key_transformer,
51
        strip_nulls=strip_nulls,
52
        strip_privates=strip_privates,
53
        strip_properties=strip_properties,
54
        strip_class_variables=strip_class_variables,
55
        **kwargs_)
56
    cls_name = get_class_name(cls, fully_qualified=True,
57
                              fork_inst=kwargs['fork_inst'])
58
    if kwargs.get('store_cls'):
59
        result['-cls'] = cls_name
60
    else:
61
        result = _get_dict_with_meta(result, cls_name, verbose,
62
                                     kwargs['fork_inst'])
63
    return result
64
65
66
def _get_dict_from_obj(obj,
67
                       strip_privates,
68
                       strip_properties,
69
                       strip_class_variables,
70
                       cls=None, *_, **__) -> dict:
71
    excluded_elems = dir(JsonSerializable)
72
    props, other_cls_vars = _get_class_props(obj.__class__)
73
    return {attr: obj.__getattribute__(attr) for attr in dir(obj)
74
            if not attr.startswith('__')
75
            and not (strip_privates and attr.startswith('_'))
76
            and not (strip_properties and attr in props)
77
            and not (strip_class_variables and attr in other_cls_vars)
78
            and attr != 'json'
79
            and not isinstance(obj.__getattribute__(attr), Callable)
80
            and (not cls or attr in cls.__slots__)
81
            and attr not in excluded_elems}
82
83
84
def _get_class_props(cls):
85
    props = []
86
    other_cls_vars = []
87
    for n, v in _get_complete_class_dict(cls).items():
88
        props.append(n) if type(v) is property else other_cls_vars.append(n)
89
    return props, other_cls_vars
90
91
92
def _get_complete_class_dict(cls):
93
    cls_dict = {}
94
    # Loop reversed so values of sub-classes override those of super-classes.
95
    for cls_or_elder in reversed(cls.mro()):
96
        cls_dict.update(cls_or_elder.__dict__)
97
    return cls_dict
98
99
100
def _get_dict_with_meta(
101
        obj: dict,
102
        cls_name: str,
103
        verbose: Verbosity,
104
        fork_inst: type) -> dict:
105
    # This function will add a -meta section to the given obj (provided that
106
    # the given obj has -cls attributes for all children).
107
    if verbose is Verbosity.WITH_NOTHING:
108
        return obj
109
110
    obj[META_ATTR] = {}
111
    if Verbosity.WITH_CLASS_INFO in verbose:
112
        collection_of_types = {}
113
        _fill_collection_of_types(obj, cls_name, '/', collection_of_types)
114
        collection_of_types['/'] = cls_name
115
        obj[META_ATTR]['classes'] = collection_of_types
116
    if Verbosity.WITH_DUMP_TIME in verbose:
117
        dump_time = to_str(datetime.now(tz=timezone.utc), True, fork_inst)
118
        obj[META_ATTR]['dump_time'] = dump_time
119
    return obj
120
121
122
def _fill_collection_of_types(obj_: dict,
123
                              cls_name_: Optional[str],
124
                              prefix: str,
125
                              collection_of_types_: dict) -> str:
126
    # This function loops through obj to fill collection_of_types_ with the
127
    # class names.
128
    cls_name_ = cls_name_ or obj_.pop('-cls')
129
    for attr in obj_:
130
        if attr != META_ATTR and isinstance(obj_[attr], dict):
131
            attr_class = _fill_collection_of_types(obj_[attr],
132
                                                   None,
133
                                                   prefix + attr + '/',
134
                                                   collection_of_types_)
135
            collection_of_types_[prefix + attr] = attr_class
136
    return cls_name_
137