Module: flat_dict_serializer

Expand source code
# Copyright (C) 2023-present The Project Contributors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import base64
import datetime as dt
import json
from typing import List
from uuid import UUID
from cl.runtime.records.protocols import TDataDict
from cl.runtime.serialization.dict_serializer import DictSerializer
from cl.runtime.serialization.string_value_parser_enum import StringValueCustomTypeEnum
from cl.runtime.serialization.string_value_parser_enum import StringValueParser


class FlatDictSerializer(DictSerializer):
    """
    Serialization for slot-based classes to flat dict (without nested fields).
    Complex types serialize as a json string.
    """

    primitive_type_names = ["NoneType", "float", "int"]

    def serialize_data(self, data, select_fields: List[str] | None = None, *, is_root: bool = False):
        if isinstance(data, str):
            return data

        if data.__class__.__name__ in super().primitive_type_names:
            serialized_data = data
        else:
            serialized_data = super().serialize_data(data, select_fields)

        value_custom_type = StringValueParser.get_custom_type(serialized_data)

        if not is_root and value_custom_type is not None:
            handled_serialized_value = None
            if serialized_data.__class__.__name__ in ("date", "datetime", "time"):
                handled_serialized_value = serialized_data.isoformat()
            elif serialized_data.__class__.__name__ == "bool":
                handled_serialized_value = self._deserialize_primitive(data, "bool")
            elif serialized_data.__class__.__name__ == "UUID":
                handled_serialized_value = str(data)
            elif serialized_data.__class__.__name__ == "bytes":
                handled_serialized_value = base64.b64encode(data).decode()
            elif isinstance(serialized_data, (dict, list)):
                # TODO (Roman): refactor to avoid nested data json dumps.
                #  It is enough to do single json dump for the entire object.
                handled_serialized_value = json.dumps(serialized_data)
            return (
                StringValueParser.add_type_prefix(handled_serialized_value, value_custom_type)
                if handled_serialized_value is not None
                else serialized_data
            )
        else:
            return serialized_data

    def deserialize_data(self, data: TDataDict):
        # check all str values if it is flattened from some type
        if isinstance(data, str):
            converted_data, custom_type = StringValueParser.parse(data)

            if custom_type is not None:
                if custom_type == StringValueCustomTypeEnum.DATE:
                    converted_data = dt.date.fromisoformat(converted_data)
                elif custom_type == StringValueCustomTypeEnum.DATETIME:
                    converted_data = dt.datetime.fromisoformat(converted_data)
                elif custom_type == StringValueCustomTypeEnum.TIME:
                    converted_data = dt.time.fromisoformat(converted_data)
                elif custom_type == StringValueCustomTypeEnum.BOOL:
                    converted_data = self._deserialize_primitive(converted_data, "bool")
                elif custom_type == StringValueCustomTypeEnum.UUID:
                    converted_data = UUID(converted_data)
                elif custom_type == StringValueCustomTypeEnum.BYTES:
                    converted_data = base64.b64decode(converted_data.encode())
                else:
                    converted_data = json.loads(converted_data)

            # TODO (Roman): consider to add serialize_primitive() method and override it
            # return deserialized primitives to avoid infinity recursion
            if converted_data.__class__.__name__ in super().primitive_type_names:
                return converted_data
        else:
            converted_data = data

        return super().deserialize_data(converted_data)

Classes

class FlatDictSerializer (*, pascalize_keys: bool = False)

Serialization for slot-based classes to flat dict (without nested fields). Complex types serialize as a json string.

Expand source code
class FlatDictSerializer(DictSerializer):
    """
    Serialization for slot-based classes to flat dict (without nested fields).
    Complex types serialize as a json string.
    """

    primitive_type_names = ["NoneType", "float", "int"]

    def serialize_data(self, data, select_fields: List[str] | None = None, *, is_root: bool = False):
        if isinstance(data, str):
            return data

        if data.__class__.__name__ in super().primitive_type_names:
            serialized_data = data
        else:
            serialized_data = super().serialize_data(data, select_fields)

        value_custom_type = StringValueParser.get_custom_type(serialized_data)

        if not is_root and value_custom_type is not None:
            handled_serialized_value = None
            if serialized_data.__class__.__name__ in ("date", "datetime", "time"):
                handled_serialized_value = serialized_data.isoformat()
            elif serialized_data.__class__.__name__ == "bool":
                handled_serialized_value = self._deserialize_primitive(data, "bool")
            elif serialized_data.__class__.__name__ == "UUID":
                handled_serialized_value = str(data)
            elif serialized_data.__class__.__name__ == "bytes":
                handled_serialized_value = base64.b64encode(data).decode()
            elif isinstance(serialized_data, (dict, list)):
                # TODO (Roman): refactor to avoid nested data json dumps.
                #  It is enough to do single json dump for the entire object.
                handled_serialized_value = json.dumps(serialized_data)
            return (
                StringValueParser.add_type_prefix(handled_serialized_value, value_custom_type)
                if handled_serialized_value is not None
                else serialized_data
            )
        else:
            return serialized_data

    def deserialize_data(self, data: TDataDict):
        # check all str values if it is flattened from some type
        if isinstance(data, str):
            converted_data, custom_type = StringValueParser.parse(data)

            if custom_type is not None:
                if custom_type == StringValueCustomTypeEnum.DATE:
                    converted_data = dt.date.fromisoformat(converted_data)
                elif custom_type == StringValueCustomTypeEnum.DATETIME:
                    converted_data = dt.datetime.fromisoformat(converted_data)
                elif custom_type == StringValueCustomTypeEnum.TIME:
                    converted_data = dt.time.fromisoformat(converted_data)
                elif custom_type == StringValueCustomTypeEnum.BOOL:
                    converted_data = self._deserialize_primitive(converted_data, "bool")
                elif custom_type == StringValueCustomTypeEnum.UUID:
                    converted_data = UUID(converted_data)
                elif custom_type == StringValueCustomTypeEnum.BYTES:
                    converted_data = base64.b64decode(converted_data.encode())
                else:
                    converted_data = json.loads(converted_data)

            # TODO (Roman): consider to add serialize_primitive() method and override it
            # return deserialized primitives to avoid infinity recursion
            if converted_data.__class__.__name__ in super().primitive_type_names:
                return converted_data
        else:
            converted_data = data

        return super().deserialize_data(converted_data)

Ancestors

Class variables

var primitive_type_names

Inherited from: DictSerializer.primitive_type_names

Detect primitive type by checking if class name is in this list.

Fields

var pascalize_keys -> bool

Inherited from: DictSerializer.pascalize_keys

If true, pascalize keys during serialization.

Methods

def deserialize_data(self, data: Dict[str, Union[Dict[str, ForwardRef('TDataField')], List[ForwardRef('TDataField')], str, float, bool, int, datetime.date, datetime.time, datetime.datetime, uuid.UUID, bytes, ForwardRef(None), enum.Enum]])

Inherited from: DictSerializer.deserialize_data

Deserialize object from data, invoke init_all after deserialization.

def serialize_data(self, data, select_fields: Optional[List[str]] = None, *, is_root: bool = False)

Inherited from: DictSerializer.serialize_data

Serialize to dictionary containing primitive types, dictionaries, or iterables …