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)
|