Module: record_response

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.

from __future__ import annotations
import dataclasses
from typing import Any
from typing import Dict
from typing import List
from pydantic import BaseModel
from pydantic import Field
from cl.runtime import Context
from cl.runtime.backend.core.ui_app_state import UiAppState
from cl.runtime.backend.core.ui_app_state_key import UiAppStateKey
from cl.runtime.backend.core.user_key import UserKey
from cl.runtime.primitive.case_util import CaseUtil
from cl.runtime.records.class_info import ClassInfo
from cl.runtime.routers.schema.type_request import TypeRequest
from cl.runtime.routers.schema.type_response_util import TypeResponseUtil
from cl.runtime.routers.storage.record_request import RecordRequest
from cl.runtime.schema.field_decl import primitive_types  # TODO: Move definition to a separate module
from cl.runtime.schema.schema import Schema
from cl.runtime.serialization.string_serializer import StringSerializer
from cl.runtime.serialization.ui_dict_serializer import UiDictSerializer

RecordResponseSchema = Dict[str, Any]
RecordResponseData = Dict[str, Any]


def to_record_dict(node):  # TODO: Apply type hints
    """Recursively apply record dictionary conventions to the argument dictionary."""

    node_type = type(node)
    if node_type in primitive_types:
        # Primitive type, serialize as string
        # TODO: Apply custom formatting
        result = str(node)
        return result
    elif node_type is list:
        # TODO: !!! Generally should not skip nodes that have the value of None
        return [to_record_dict(v) for v in node if v is not None]
    elif node_type is tuple:
        # TODO: Decision on short alias
        # Tuple key, remove Key suffix from key type to obtain table name
        table = node[0].__name__
        result = ";".join([table] + node[1:])
        return result
    elif node_type.__name__.endswith("Key"):
        # Key type, use semicolon-delimited serialization
        # TODO: Do not use a method from dataclasses
        node_dict = dataclasses.asdict(node)
        result = ";".join(node_dict.keys())
        return result
    elif hasattr(node, "get_key"):
        # Data or record
        # TODO: Do not use a method from dataclasses
        node_dict = dataclasses.asdict(node)
        node_dict = {k: getattr(node, k) for k in node_dict.keys()}
        result = {k: to_record_dict(v) for k, v in node_dict.items() if v is not None}
        return result
    else:
        return node


def to_legacy_dict(node: Dict[str, Any] | List[Dict[str, Any]] | str) -> Dict[str, Any] | List[Dict[str, Any]] | str:
    """Recursively apply record dictionary conventions to the argument dictionary."""

    if isinstance(node, dict):
        # Skip nodes that have the value of None
        # Remove suffix _ from field names if present
        result = {
            CaseUtil.snake_to_pascal_case(k.removesuffix("_")): to_legacy_dict(v)
            for k, v in node.items()
            if v is not None
        }
        return result
    elif isinstance(node, list):
        # Skip nodes that have the value of None
        return [to_legacy_dict(v) for v in node if v is not None]
    elif isinstance(node, tuple):
        # TODO: Decision on short alias
        # Generic key, remove Key suffix from key type to obtain table name
        table = node[0].__name__
        result = ";".join([table] + node[1:])
        return result
    else:
        return node


class RecordResponse(BaseModel):
    """Response data type for the /storage/record route."""

    schema_: RecordResponseSchema = Field(..., alias="schema")
    """Schema field of the response data type for the /storage/record route."""

    data: RecordResponseData | None
    """Data field of the response data type for the /storage/record route."""

    @classmethod
    def get_record(cls, request: RecordRequest) -> RecordResponse:
        """Implements /storage/record route."""

        if True:  # TODO: ";" not in request.key:
            # TODO: Use after module is specified
            record_type = Schema.get_type_by_short_name(request.type)
        else:
            key_tokens = request.key.split(";")
            key_module = CaseUtil.pascal_to_snake_case(key_tokens[0])
            key_class = key_tokens[1]

            # record_type = Schema.get_type_by_short_name(request.type)
            # TODO: Use after module is specified
            record_type = ClassInfo.get_class_type(f"{key_module}.{key_class}")

        # Get database from the current context
        db = Context.current().db

        # Load record from storage
        key_serializer = StringSerializer()

        # TODO (Roman): UiAppState record request from FE should have key in proper format where user is embedded key
        if record_type == UiAppState and "KEY" not in request.key:
            # TODO: Ensure user is specified in all deployment scenarios instead of using default value
            deserialized_key = UiAppStateKey(user=UserKey(username=request.key or "root"))
        else:
            deserialized_key = key_serializer.deserialize_key(request.key, record_type.get_key_type())

        # TODO: Review the use of is_record_optional flag here
        record = db.load_one(record_type, deserialized_key, is_record_optional=True)

        # Get type declarations based on the actual record type
        type_decl_dict = (
            TypeResponseUtil.get_type(
                TypeRequest(
                    name=type(record).__name__,
                    module=request.module,
                    user="root",
                ),
            )
            if record is not None
            else dict()
        )  # Handle not found record

        # TODO: Optimize speed using dacite or similar library

        ui_serializer = UiDictSerializer()
        # serialize record to ui format
        record_dict_in_legacy_format = ui_serializer.serialize_data(record)

        # TODO: Update to return record_dict after legacy dict format is removed
        return RecordResponse(schema=type_decl_dict, data=record_dict_in_legacy_format)

Functions

def to_legacy_dict(node: Dict[str, Any] | List[Dict[str, Any]] | str) -> Union[Dict[str, Any], List[Dict[str, Any]], str]

Recursively apply record dictionary conventions to the argument dictionary.

def to_record_dict(node)

Recursively apply record dictionary conventions to the argument dictionary.

Classes

class RecordResponse (**data: Any)

Response data type for the /storage/record route.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Expand source code
class RecordResponse(BaseModel):
    """Response data type for the /storage/record route."""

    schema_: RecordResponseSchema = Field(..., alias="schema")
    """Schema field of the response data type for the /storage/record route."""

    data: RecordResponseData | None
    """Data field of the response data type for the /storage/record route."""

    @classmethod
    def get_record(cls, request: RecordRequest) -> RecordResponse:
        """Implements /storage/record route."""

        if True:  # TODO: ";" not in request.key:
            # TODO: Use after module is specified
            record_type = Schema.get_type_by_short_name(request.type)
        else:
            key_tokens = request.key.split(";")
            key_module = CaseUtil.pascal_to_snake_case(key_tokens[0])
            key_class = key_tokens[1]

            # record_type = Schema.get_type_by_short_name(request.type)
            # TODO: Use after module is specified
            record_type = ClassInfo.get_class_type(f"{key_module}.{key_class}")

        # Get database from the current context
        db = Context.current().db

        # Load record from storage
        key_serializer = StringSerializer()

        # TODO (Roman): UiAppState record request from FE should have key in proper format where user is embedded key
        if record_type == UiAppState and "KEY" not in request.key:
            # TODO: Ensure user is specified in all deployment scenarios instead of using default value
            deserialized_key = UiAppStateKey(user=UserKey(username=request.key or "root"))
        else:
            deserialized_key = key_serializer.deserialize_key(request.key, record_type.get_key_type())

        # TODO: Review the use of is_record_optional flag here
        record = db.load_one(record_type, deserialized_key, is_record_optional=True)

        # Get type declarations based on the actual record type
        type_decl_dict = (
            TypeResponseUtil.get_type(
                TypeRequest(
                    name=type(record).__name__,
                    module=request.module,
                    user="root",
                ),
            )
            if record is not None
            else dict()
        )  # Handle not found record

        # TODO: Optimize speed using dacite or similar library

        ui_serializer = UiDictSerializer()
        # serialize record to ui format
        record_dict_in_legacy_format = ui_serializer.serialize_data(record)

        # TODO: Update to return record_dict after legacy dict format is removed
        return RecordResponse(schema=type_decl_dict, data=record_dict_in_legacy_format)

Ancestors

  • pydantic.main.BaseModel

Class variables

var data

Data field of the response data type for the /storage/record route.

var model_computed_fields
var model_config
var model_fields
var schema_

Schema field of the response data type for the /storage/record route.

Static methods

def get_record(request: RecordRequest) -> RecordResponse

Implements /storage/record route.