turbo_broccoli.custom.bytes

bytes (de)serialization utilities.

 1"""bytes (de)serialization utilities."""
 2
 3from base64 import b64decode, b64encode
 4from math import ceil
 5from typing import Any
 6
 7from turbo_broccoli.context import Context
 8from turbo_broccoli.exceptions import DeserializationError, TypeNotSupported
 9
10
11def _bytes_from_json_v3(dct: dict, ctx: Context) -> bytes:
12    if "data" in dct:
13        return b64decode(dct["data"])
14    path = ctx.id_to_artifact_path(dct["id"])
15    with path.open(mode="rb") as fp:
16        return fp.read()
17
18
19# pylint: disable=missing-function-docstring
20def from_json(dct: dict, ctx: Context) -> bytes | None:
21    decoders = {
22        3: _bytes_from_json_v3,
23    }
24    try:
25        return decoders[dct["__version__"]](dct, ctx)
26    except KeyError as exc:
27        raise DeserializationError() from exc
28
29
30def to_json(obj: Any, ctx: Context) -> dict:
31    """
32    Serializes a Python `bytes` object into JSON using a base64 + ASCII
33    scheme. The return dict has the following structure
34
35    ```py
36    {
37        "__type__": "bytes",
38        "__version__": 3,
39        "data": <ASCII str>,
40    }
41    ```
42
43    or
44
45    ```py
46    {
47        "__type__": "bytes",
48        "__version__": 3,
49        "id": <uuid4>,
50    }
51    ```
52
53    if the base64 encoding of the object is too large.
54
55    """
56    if not isinstance(obj, bytes):
57        raise TypeNotSupported()
58    # https://stackoverflow.com/a/32140193
59    b64_size = (ceil((len(obj) * 4) / 3) + 3) & ~3
60    if b64_size <= ctx.min_artifact_size:
61        return {
62            "__type__": "bytes",
63            "__version__": 3,
64            "data": b64encode(obj).decode("ascii"),
65        }
66    path, name = ctx.new_artifact_path()
67    with path.open(mode="wb") as fp:
68        fp.write(obj)
69    return {"__type__": "bytes", "__version__": 3, "id": name}
def from_json(dct: dict, ctx: turbo_broccoli.context.Context) -> bytes | None:
21def from_json(dct: dict, ctx: Context) -> bytes | None:
22    decoders = {
23        3: _bytes_from_json_v3,
24    }
25    try:
26        return decoders[dct["__version__"]](dct, ctx)
27    except KeyError as exc:
28        raise DeserializationError() from exc
def to_json(obj: Any, ctx: turbo_broccoli.context.Context) -> dict:
31def to_json(obj: Any, ctx: Context) -> dict:
32    """
33    Serializes a Python `bytes` object into JSON using a base64 + ASCII
34    scheme. The return dict has the following structure
35
36    ```py
37    {
38        "__type__": "bytes",
39        "__version__": 3,
40        "data": <ASCII str>,
41    }
42    ```
43
44    or
45
46    ```py
47    {
48        "__type__": "bytes",
49        "__version__": 3,
50        "id": <uuid4>,
51    }
52    ```
53
54    if the base64 encoding of the object is too large.
55
56    """
57    if not isinstance(obj, bytes):
58        raise TypeNotSupported()
59    # https://stackoverflow.com/a/32140193
60    b64_size = (ceil((len(obj) * 4) / 3) + 3) & ~3
61    if b64_size <= ctx.min_artifact_size:
62        return {
63            "__type__": "bytes",
64            "__version__": 3,
65            "data": b64encode(obj).decode("ascii"),
66        }
67    path, name = ctx.new_artifact_path()
68    with path.open(mode="wb") as fp:
69        fp.write(obj)
70    return {"__type__": "bytes", "__version__": 3, "id": name}

Serializes a Python bytes object into JSON using a base64 + ASCII scheme. The return dict has the following structure

{
    "__type__": "bytes",
    "__version__": 3,
    "data": <ASCII str>,
}

or

{
    "__type__": "bytes",
    "__version__": 3,
    "id": <uuid4>,
}

if the base64 encoding of the object is too large.