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 ..context import Context
  7from ..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
 90def from_json(dct: dict, ctx: Context) -> Any:
 91    decoders = {
 92        "collections.deque": _json_to_deque,
 93        "collections.namedtuple": _json_to_namedtuple,
 94        "collections.set": _json_to_set,
 95        "collections.tuple": _json_to_tuple,
 96    }
 97    try:
 98        type_name = dct["__type__"]
 99        return decoders[type_name](dct, ctx)
100    except KeyError as exc:
101        raise DeserializationError() from exc
102
103
104def to_json(obj: Any, ctx: Context) -> dict:
105    """
106    Serializes a Python collection into JSON by cases. See the README for the
107    precise list of supported types. The return dict has the following
108    structure:
109
110    - `collections.deque`:
111
112        ```py
113        {
114            "__type__": "collections.deque",
115            "__version__": 2,
116            "data": [...],
117            "maxlen": <int or None>,
118        }
119        ```
120
121    - `collections.namedtuple`
122
123        ```py
124        {
125            "__type__": "collections.namedtuple",
126            "__version__": 2,
127            "class": <str>,
128            "data": {...},
129        }
130        ```
131
132    - `set`
133
134        ```py
135        {
136            "__type__": "collections.set",
137            "__version__": 2,
138            "data": [...],
139        }
140        ```
141
142    - `tuple`
143
144        ```py
145        {
146            "__type__": "collections.tuple",
147            "__version__": 1,
148            "data": [...],
149        }
150        ```
151
152    """
153    encoders: list[Tuple[type, Callable[[Any, Context], dict]]] = [
154        (deque, _deque_to_json),
155        (tuple, _tuple_to_json),
156        (set, _set_to_json),
157    ]
158    for t, f in encoders:
159        if isinstance(obj, t):
160            return f(obj, ctx)
161    raise TypeNotSupported()
def from_json(dct: dict, ctx: turbo_broccoli.context.Context) -> Any:
 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
def to_json(obj: Any, ctx: turbo_broccoli.context.Context) -> dict:
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()

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": [...],
    }