1
|
|
|
from collections.abc import Iterable |
2
|
|
|
from multiprocessing import Process |
3
|
|
|
from typing import Tuple, Optional |
4
|
|
|
|
5
|
|
|
from typish import get_args, get_type |
6
|
|
|
|
7
|
|
|
from jsons._dump_impl import dump |
8
|
|
|
from jsons._multitasking import multi_task |
9
|
|
|
from jsons.exceptions import SerializationError |
10
|
|
|
|
11
|
|
|
|
12
|
|
|
def default_iterable_serializer( |
13
|
|
|
obj: Iterable, |
14
|
|
|
cls: type = None, |
15
|
|
|
*, |
16
|
|
|
strict: bool = False, |
17
|
|
|
tasks: int = 1, |
18
|
|
|
task_type: type = Process, |
19
|
|
|
**kwargs) -> list: |
20
|
|
|
""" |
21
|
|
|
Serialize the given ``obj`` to a list of serialized objects. |
22
|
|
|
:param obj: the iterable that is to be serialized. |
23
|
|
|
:param cls: the (subscripted) type of the iterable. |
24
|
|
|
:param strict: a bool to determine if the serializer should be strict |
25
|
|
|
(i.e. only dumping stuff that is known to ``cls``). |
26
|
|
|
:param tasks: the allowed number of tasks (threads or processes). |
27
|
|
|
:param task_type: the type that is used for multitasking. |
28
|
|
|
:param kwargs: any keyword arguments that may be given to the serialization |
29
|
|
|
process. |
30
|
|
|
:return: a list of which all elements are serialized. |
31
|
|
|
""" |
32
|
|
|
# The meta kwarg store_cls is filtered out, because an iterable should have |
33
|
|
|
# its own -meta attribute. |
34
|
|
|
kwargs_ = {**kwargs, 'strict': strict} |
35
|
|
|
kwargs_.pop('_store_cls', None) |
36
|
|
|
if strict: |
37
|
|
|
cls_ = _determine_cls(obj, cls) |
38
|
|
|
subclasses = _get_subclasses(obj, cls_) |
39
|
|
|
else: |
40
|
|
|
subclasses = _get_subclasses(obj, None) |
41
|
|
|
|
42
|
|
|
if tasks < 2: |
43
|
|
|
result = [dump(elem, cls=subclasses[i], **kwargs_) |
44
|
|
|
for i, elem in enumerate(obj)] |
45
|
|
|
else: |
46
|
|
|
zipped_objs = list(zip(obj, subclasses)) |
47
|
|
|
result = multi_task(_do_dump, zipped_objs, tasks, task_type, **kwargs_) |
48
|
|
|
|
49
|
|
|
return result |
50
|
|
|
|
51
|
|
|
|
52
|
|
|
def _get_subclasses(obj: Iterable, cls: type = None) -> Tuple[type, ...]: |
53
|
|
|
subclasses = (None,) * len(obj) |
54
|
|
|
if cls: |
55
|
|
|
args = get_args(cls) |
56
|
|
|
if len(args) == 1: |
57
|
|
|
# E.g. List[int] |
58
|
|
|
subclasses = args * len(obj) |
59
|
|
|
elif len(args) > 1: |
60
|
|
|
# E.g. Tuple[int, str, str] |
61
|
|
|
subclasses = args |
62
|
|
|
if len(subclasses) != len(obj): |
63
|
|
|
msg = ('Not enough generic types ({}) in {}, expected {} to match ' |
64
|
|
|
'the iterable of length {}' |
65
|
|
|
.format(len(subclasses), cls, len(obj), len(obj))) |
66
|
|
|
raise SerializationError(msg) |
67
|
|
|
return subclasses |
68
|
|
|
|
69
|
|
|
|
70
|
|
|
def _do_dump(obj_cls_tuple: Tuple[object, type], *args, **kwargs): |
71
|
|
|
kwargs_ = {**kwargs} |
72
|
|
|
kwargs_['tasks'] = 1 |
73
|
|
|
return dump(*obj_cls_tuple, *args, **kwargs_) |
74
|
|
|
|
75
|
|
|
|
76
|
|
|
def _determine_cls(obj: Iterable, cls: Optional[type]) -> Optional[type]: |
77
|
|
|
cls_ = cls |
78
|
|
|
if not cls and hasattr(obj, '__getitem__') and len(obj) > 0: |
79
|
|
|
obj_with_only_one_elem = obj.__getitem__(slice(0, 1)) |
80
|
|
|
cls_ = get_type(obj_with_only_one_elem) |
81
|
|
|
return cls_ |
82
|
|
|
|