Source code for bec_lib.signature_serializer
"""
This module contains functions to serialize and deserialize function signatures.
"""
from __future__ import annotations
import ast
import builtins
import inspect
import operator
import sys
from collections.abc import Callable
from typing import Any, Literal, Union
from bec_lib.device import DeviceBase
from bec_lib.scan_items import ScanItem
[docs]
def serialize_dtype(dtype: type) -> Any:
"""
Convert a dtype to a string.
Args:
dtype (type): Data type
Returns:
str: String representation of the data type
"""
if hasattr(dtype, "__name__"):
name = dtype.__name__
# changed in python 3.10. Refactor this when we upgrade
if name not in ["Literal", "Union"]:
return name
if hasattr(dtype, "__module__"):
if dtype.__module__ == "typing":
if dtype.__class__.__name__ == "_UnionGenericAlias":
return " | ".join([serialize_dtype(x) for x in dtype.__args__])
if dtype.__class__.__name__ == "_LiteralGenericAlias":
return {"Literal": dtype.__args__}
if isinstance(dtype, str):
if sys.version_info[:3] >= (3, 10):
return dtype
# remove this when we upgrade to python 3.10
####
if dtype.startswith("typing.Literal["):
return {"Literal": ast.literal_eval(dtype[15:-1])}
if dtype.startswith("Literal["):
return {"Literal": ast.literal_eval(dtype[8:-1])}
return dtype
####
raise ValueError(f"Unknown dtype {dtype}")
[docs]
def deserialize_dtype(dtype: Any) -> type:
"""
Convert a serialized dtype to a type.
Args:
dtype (str): String representation of the data type
Returns:
type: Data type
"""
if dtype == "_empty":
# pylint: disable=protected-access
return inspect._empty
if isinstance(dtype, dict):
if "Literal" in dtype:
# remove this when we upgrade to python 3.10
#### remove this section
literal = Literal[str(dtype)]
literal.__args__ = dtype["Literal"]
return literal
#### remove this section
#### add this section when we upgrade to python 3.10
# return Literal[*dtype["Literal"]]
#### add this section when we upgrade to python 3.10
raise ValueError(f"Unknown dtype {dtype}")
if isinstance(dtype, str) and "|" in dtype:
# remove this when we upgrade to python 3.10
if sys.version_info[:3] < (3, 10):
union = Union[int, str]
union.__args__ = [deserialize_dtype(x.strip()) for x in dtype.split("|")]
return union
return operator.or_(*[deserialize_dtype(x.strip()) for x in dtype.split("|")])
builtin_type = builtins.__dict__.get(dtype)
if builtin_type:
return builtin_type
if dtype == "DeviceBase":
return DeviceBase
if dtype == "ScanItem":
return ScanItem
[docs]
def signature_to_dict(func: Callable, include_class_obj=False) -> dict:
"""
Convert a function signature to a dictionary.
The dictionary can be used to reconstruct the signature using dict_to_signature.
Args:
func (Callable): Function to be converted
Returns:
dict: Dictionary representation of the function signature
"""
out = []
params = inspect.signature(func).parameters
for param_name, param in params.items():
if not include_class_obj and param_name == "self" or param_name == "cls":
continue
# pylint: disable=protected-access
out.append(
{
"name": param_name,
"kind": param.kind.name,
"default": param.default if param.default != inspect._empty else "_empty",
"annotation": serialize_dtype(param.annotation),
}
)
return out
[docs]
def dict_to_signature(params: list[dict]) -> inspect.Signature:
"""
Convert a dictionary representation of a function signature to a signature object.
Args:
params (list[dict]): List of dictionaries representing the function signature
Returns:
inspect.Signature: Signature object
"""
out = []
for param in params:
# pylint: disable=protected-access
out.append(
inspect.Parameter(
name=param["name"],
kind=getattr(inspect.Parameter, param["kind"]),
default=param["default"] if param["default"] != "_empty" else inspect._empty,
annotation=deserialize_dtype(param["annotation"]),
)
)
return inspect.Signature(out)