File: loader_selection.py

package info (click to toggle)
dataclass-wizard 0.37.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 2,924 kB
  • sloc: python: 17,189; makefile: 126; javascript: 23
file content (221 lines) | stat: -rw-r--r-- 7,263 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
from typing import Callable, Collection, Optional

from .class_helper import (get_meta, CLASS_TO_LOAD_FUNC,
                           CLASS_TO_LOADER, CLASS_TO_V1_LOADER,
                           set_class_loader, create_new_class, CLASS_TO_DUMP_FUNC, CLASS_TO_V1_DUMPER, set_class_dumper,
                           CLASS_TO_DUMPER)
from .constants import _LOAD_HOOKS, _DUMP_HOOKS
from .type_def import T, JSONObject


def asdict(o: T,
           *, cls=None,
           dict_factory=dict,
           exclude: 'Collection[str] | None' = None,
           **kwargs) -> JSONObject:
    # noinspection PyUnresolvedReferences
    """Return the fields of a dataclass instance as a new dictionary mapping
    field names to field values.

    Example usage:

      @dataclass
      class C:
          x: int
          y: int

      c = C(1, 2)
      assert asdict(c) == {'x': 1, 'y': 2}

    When directly invoking this function, an optional Meta configuration for
    the dataclass can be specified via ``DumpMeta``; by default, this will
    apply recursively to any nested dataclasses. Here's a sample usage of this
    below::

        >>> DumpMeta(key_transform='CAMEL').bind_to(MyClass)
        >>> asdict(MyClass(my_str="value"))

    If given, 'dict_factory' will be used instead of built-in dict.
    The function applies recursively to field values that are
    dataclass instances. This will also look into built-in containers:
    tuples, lists, and dicts.
    """
    # This likely won't be needed, as ``dataclasses.fields`` already has this
    # check.
    # if not _is_dataclass_instance(obj):
    #     raise TypeError("asdict() should be called on dataclass instances")

    cls = cls or type(o)

    try:
        dump = CLASS_TO_DUMP_FUNC[cls]
    except KeyError:
        dump = _get_dump_fn_for_dataclass(cls)

    return dump(o, dict_factory, exclude, **kwargs)


def fromdict(cls: type[T], d: JSONObject) -> T:
    """
    Converts a Python dictionary object to a dataclass instance.

    Iterates over each dataclass field recursively; lists, dicts, and nested
    dataclasses will likewise be initialized as expected.

    When directly invoking this function, an optional Meta configuration for
    the dataclass can be specified via ``LoadMeta``; by default, this will
    apply recursively to any nested dataclasses. Here's a sample usage of this
    below::

        >>> LoadMeta(key_transform='CAMEL').bind_to(MyClass)
        >>> fromdict(MyClass, {"myStr": "value"})

    """
    try:
        load = CLASS_TO_LOAD_FUNC[cls]
    except KeyError:
        load = _get_load_fn_for_dataclass(cls)

    return load(d)


def fromlist(cls: type[T], list_of_dict: list[JSONObject]) -> list[T]:
    """
    Converts a Python list object to a list of dataclass instances.

    Iterates over each dataclass field recursively; lists, dicts, and nested
    dataclasses will likewise be initialized as expected.

    """
    try:
        load = CLASS_TO_LOAD_FUNC[cls]
    except KeyError:
        load = _get_load_fn_for_dataclass(cls)

    return [load(d) for d in list_of_dict]


def _get_load_fn_for_dataclass(cls: type[T], v1=None) -> Callable[[JSONObject], T]:
    meta = get_meta(cls)
    if v1 is None:
        v1 = getattr(meta, 'v1', False)

    if v1:
        from .v1.loaders import load_func_for_dataclass as V1_load_func_for_dataclass
        # noinspection PyTypeChecker
        load = V1_load_func_for_dataclass(cls)
    else:
        from .loaders import load_func_for_dataclass
        load = load_func_for_dataclass(cls)

    # noinspection PyTypeChecker
    return load


def _get_dump_fn_for_dataclass(cls: type[T], v1=None) -> Callable[[JSONObject], T]:
    if v1 is None:
        v1 = getattr(get_meta(cls), 'v1', False)

    if v1:
        from .v1.dumpers import dump_func_for_dataclass as V1_dump_func_for_dataclass
        # noinspection PyTypeChecker
        dump = V1_dump_func_for_dataclass(cls)
    else:
        from .dumpers import dump_func_for_dataclass
        dump = dump_func_for_dataclass(cls)

    # noinspection PyTypeChecker
    return dump


def get_dumper(class_or_instance=None, create=True,
               base_cls: T = None,
               v1: Optional[bool] = None) -> type[T]:
    """
    Get the dumper for the class, using the following logic:

        * Return the class if it's already a sub-class of :class:`DumpMixin`
        * If `create` is enabled (which is the default), a new sub-class of
          :class:`DumpMixin` for the class will be generated and cached on the
          initial run.
        * Otherwise, we will return the base loader, :class:`DumpMixin`, which
          can potentially be shared by more than one dataclass.

    """
    if v1 is None:
        v1 = getattr(get_meta(class_or_instance), 'v1', False)

    if v1:
        cls_to_dumper = CLASS_TO_V1_DUMPER
        if base_cls is None:
            from .v1.dumpers import DumpMixin as V1_DumpMixin
            base_cls = V1_DumpMixin
    else:
        cls_to_dumper = CLASS_TO_DUMPER
        if base_cls is None:
            from .dumpers import DumpMixin
            base_cls = DumpMixin

    try:
        return cls_to_dumper[class_or_instance]

    except KeyError:
        # TODO figure out type errors

        if hasattr(class_or_instance, _DUMP_HOOKS):
            return set_class_dumper(
                cls_to_dumper, class_or_instance, class_or_instance)

        elif create:
            cls_loader = create_new_class(class_or_instance, (base_cls, ))
            return set_class_dumper(
                cls_to_dumper, class_or_instance, cls_loader)

        return set_class_dumper(
            cls_to_dumper, class_or_instance, base_cls)


def get_loader(class_or_instance=None, create=True,
               base_cls: T = None,
               v1: Optional[bool] = None) -> type[T]:
    """
    Get the loader for the class, using the following logic:

        * Return the class if it's already a sub-class of :class:`LoadMixin`
        * If `create` is enabled (which is the default), a new sub-class of
          :class:`LoadMixin` for the class will be generated and cached on the
          initial run.
        * Otherwise, we will return the base loader, :class:`LoadMixin`, which
          can potentially be shared by more than one dataclass.

    """
    if v1 is None:
        v1 = getattr(get_meta(class_or_instance), 'v1', False)

    if v1:
        cls_to_loader = CLASS_TO_V1_LOADER
        if base_cls is None:
            from .v1.loaders import LoadMixin as V1_LoadMixin
            base_cls = V1_LoadMixin
    else:
        cls_to_loader = CLASS_TO_LOADER
        if base_cls is None:
            from .loaders import LoadMixin
            base_cls = LoadMixin

    try:
        return cls_to_loader[class_or_instance]

    except KeyError:

        if hasattr(class_or_instance, _LOAD_HOOKS):
            return set_class_loader(
                cls_to_loader, class_or_instance, class_or_instance)

        elif create:
            cls_loader = create_new_class(class_or_instance, (base_cls, ))
            return set_class_loader(
                cls_to_loader, class_or_instance, cls_loader)

        return set_class_loader(
            cls_to_loader, class_or_instance, base_cls)