turbo_broccoli.custom.collections
Python standard collections and container types (de)serialization
1"""Python standard collections and container types (de)serialization""" 2 3from collections import deque, namedtuple 4from typing import Any, Callable, Tuple 5 6from turbo_broccoli.context import Context 7from turbo_broccoli.exceptions import DeserializationError, TypeNotSupported 8 9 10def _deque_to_json(deq: deque, ctx: Context) -> dict: 11 return { 12 "__type__": "collections.deque", 13 "__version__": 2, 14 "data": list(deq), 15 "maxlen": deq.maxlen, 16 } 17 18 19def _json_to_deque(dct: dict, ctx: Context) -> deque | None: 20 decoders = { 21 2: _json_to_deque_v2, 22 } 23 return decoders[dct["__version__"]](dct, ctx) 24 25 26def _json_to_deque_v2(dct: dict, ctx: Context) -> Any: 27 return deque(dct["data"], dct["maxlen"]) 28 29 30def _json_to_namedtuple(dct: dict, ctx: Context) -> Any: 31 decoders = { 32 2: _json_to_namedtuple_v2, 33 } 34 return decoders[dct["__version__"]](dct, ctx) 35 36 37def _json_to_namedtuple_v2(dct: dict, ctx: Context) -> Any: 38 return namedtuple(dct["class"], dct["data"].keys())(**dct["data"]) 39 40 41def _json_to_set(dct: dict, ctx: Context) -> set: 42 decoders = { 43 2: _json_to_set_v2, 44 } 45 return decoders[dct["__version__"]](dct, ctx) 46 47 48def _json_to_set_v2(dct: dict, ctx: Context) -> Any: 49 return set(dct["data"]) 50 51 52def _json_to_tuple(dct: dict, ctx: Context) -> tuple: 53 decoders = { 54 1: _json_to_tuple_v1, 55 } 56 return decoders[dct["__version__"]](dct, ctx) 57 58 59def _json_to_tuple_v1(dct: dict, ctx: Context) -> Any: 60 return tuple(dct["data"]) 61 62 63def _set_to_json(obj: set, ctx: Context) -> dict: 64 return {"__type__": "collections.set", "__version__": 2, "data": list(obj)} 65 66 67def _tuple_to_json(obj: tuple, ctx: Context) -> dict: 68 """ 69 Converts a tuple or namedtuple into a JSON document. 70 71 A tuple is a namedtuple if it has the following attributes: `_asdict`, 72 `_field_defaults`, `_fields`, `_make`, `_replace`. See 73 https://docs.python.org/3/library/collections.html#collections.namedtuple . 74 """ 75 attributes = ["_asdict", "_field_defaults", "_fields", "_make", "_replace"] 76 if not all(map(lambda a: hasattr(obj, a), attributes)): 77 return { 78 "__type__": "collections.tuple", 79 "__version__": 1, 80 "data": list(obj), 81 } 82 return { 83 "__type__": "collections.namedtuple", 84 "__version__": 2, 85 "class": obj.__class__.__name__, 86 "data": obj._asdict(), # type: ignore 87 } 88 89 90# pylint: disable=missing-function-docstring 91def from_json(dct: dict, ctx: Context) -> Any: 92 decoders = { 93 "collections.deque": _json_to_deque, 94 "collections.namedtuple": _json_to_namedtuple, 95 "collections.set": _json_to_set, 96 "collections.tuple": _json_to_tuple, 97 } 98 try: 99 type_name = dct["__type__"] 100 return decoders[type_name](dct, ctx) 101 except KeyError as exc: 102 raise DeserializationError() from exc 103 104 105def to_json(obj: Any, ctx: Context) -> dict: 106 """ 107 Serializes a Python collection into JSON by cases. See the README for the 108 precise list of supported types. The return dict has the following 109 structure: 110 111 - `collections.deque`: 112 113 ```py 114 { 115 "__type__": "collections.deque", 116 "__version__": 2, 117 "data": [...], 118 "maxlen": <int or None>, 119 } 120 ``` 121 122 - `collections.namedtuple` 123 124 ```py 125 { 126 "__type__": "collections.namedtuple", 127 "__version__": 2, 128 "class": <str>, 129 "data": {...}, 130 } 131 ``` 132 133 - `set` 134 135 ```py 136 { 137 "__type__": "collections.set", 138 "__version__": 2, 139 "data": [...], 140 } 141 ``` 142 143 - `tuple` 144 145 ```py 146 { 147 "__type__": "collections.tuple", 148 "__version__": 1, 149 "data": [...], 150 } 151 ``` 152 153 """ 154 encoders: list[Tuple[type, Callable[[Any, Context], dict]]] = [ 155 (deque, _deque_to_json), 156 (tuple, _tuple_to_json), 157 (set, _set_to_json), 158 ] 159 for t, f in encoders: 160 if isinstance(obj, t): 161 return f(obj, ctx) 162 raise TypeNotSupported()
92def from_json(dct: dict, ctx: Context) -> Any: 93 decoders = { 94 "collections.deque": _json_to_deque, 95 "collections.namedtuple": _json_to_namedtuple, 96 "collections.set": _json_to_set, 97 "collections.tuple": _json_to_tuple, 98 } 99 try: 100 type_name = dct["__type__"] 101 return decoders[type_name](dct, ctx) 102 except KeyError as exc: 103 raise DeserializationError() from exc
106def to_json(obj: Any, ctx: Context) -> dict: 107 """ 108 Serializes a Python collection into JSON by cases. See the README for the 109 precise list of supported types. The return dict has the following 110 structure: 111 112 - `collections.deque`: 113 114 ```py 115 { 116 "__type__": "collections.deque", 117 "__version__": 2, 118 "data": [...], 119 "maxlen": <int or None>, 120 } 121 ``` 122 123 - `collections.namedtuple` 124 125 ```py 126 { 127 "__type__": "collections.namedtuple", 128 "__version__": 2, 129 "class": <str>, 130 "data": {...}, 131 } 132 ``` 133 134 - `set` 135 136 ```py 137 { 138 "__type__": "collections.set", 139 "__version__": 2, 140 "data": [...], 141 } 142 ``` 143 144 - `tuple` 145 146 ```py 147 { 148 "__type__": "collections.tuple", 149 "__version__": 1, 150 "data": [...], 151 } 152 ``` 153 154 """ 155 encoders: list[Tuple[type, Callable[[Any, Context], dict]]] = [ 156 (deque, _deque_to_json), 157 (tuple, _tuple_to_json), 158 (set, _set_to_json), 159 ] 160 for t, f in encoders: 161 if isinstance(obj, t): 162 return f(obj, ctx) 163 raise TypeNotSupported()
Serializes a Python collection into JSON by cases. See the README for the precise list of supported types. The return dict has the following structure:
collections.deque
:{ "__type__": "collections.deque", "__version__": 2, "data": [...], "maxlen": <int or None>, }
collections.namedtuple
{ "__type__": "collections.namedtuple", "__version__": 2, "class": <str>, "data": {...}, }
set
{ "__type__": "collections.set", "__version__": 2, "data": [...], }
tuple
{ "__type__": "collections.tuple", "__version__": 1, "data": [...], }