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

_get_complete_class_dict()   A

Complexity

Conditions 2

Size

Total Lines 6
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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