Source code for qcportal.serialization

import base64
import json
from typing import Any, Type, TypeVar

import msgpack
import numpy as np
import pydantic
import pydantic_core

_V = TypeVar("_V")


def _msgpack_encode(obj: Any) -> Any:
    # msgpack can encode bytes natively. So don't dump to json-compatible
    # types here. But if we do that, we still need to handle numpy arrays

    # Flatten numpy arrays
    # This is mostly for Molecule class
    # TODO - remove once all data in the database in converted. This is probably only called serverside (with dicts)
    if isinstance(obj, np.ndarray):
        if obj.shape:
            return obj.ravel().tolist()
        else:
            return obj.tolist()

    return pydantic_core.to_jsonable_python(obj)


class _JSONEncoder(json.JSONEncoder):
    def default(self, obj: Any) -> Any:
        # JSON does not handle byte arrays
        # So convert to base64
        if isinstance(obj, bytes):
            return {"_bytes_base64_": base64.b64encode(obj).decode("ascii")}

        # Flatten numpy arrays
        # This is mostly for Molecule class
        # TODO - remove once all data in the database in converted. This is probably only called serverside (with dicts)
        if isinstance(obj, np.ndarray):
            if obj.shape:
                return obj.ravel().tolist()
            else:
                return obj.tolist()

        return pydantic_core.to_jsonable_python(obj)


[docs] def deserialize(data: bytes | str, content_type: str, model: Type[_V]) -> _V: if content_type.startswith("application/"): content_type = content_type[12:] if content_type == "msgpack": d = msgpack.loads(data, raw=False, strict_map_key=False) return pydantic.TypeAdapter(model).validate_python(d) elif content_type == "json": return pydantic.TypeAdapter(model).validate_json(data) else: raise RuntimeError(f"Unknown content type for deserialization: {content_type}")
[docs] def serialize(data, content_type: str) -> bytes: if content_type.startswith("application/"): content_type = content_type[12:] if content_type == "msgpack": return msgpack.dumps(data, default=_msgpack_encode, use_bin_type=True) elif content_type == "json": return json.dumps(data, cls=_JSONEncoder).encode("utf-8") else: raise RuntimeError(f"Unknown content type for serialization: {content_type}")
[docs] def convert_numpy_recursive(obj, flatten=False): if isinstance(obj, dict): return {k: convert_numpy_recursive(v, flatten) for k, v in obj.items()} elif isinstance(obj, (list, tuple, set)): return [convert_numpy_recursive(v, flatten) for v in obj] elif isinstance(obj, np.ndarray): if obj.shape and flatten: return obj.ravel().tolist() else: return obj.tolist() else: return obj