2023-10-09 10:46:51 +02:00

97 lines
3.8 KiB
Python

import inspect
from enum import Enum
from typing import Any, Optional
from pydase import DataService
from pydase.components import NumberSlider
from pydase.units import Quantity
from pydase.utils.helpers import get_object_attr_from_path
from pydase.version import __version__
class RPCInterface(object):
"""RPC interface to be passed to tiqi_rpc.Server to interface with Ionizer."""
def __init__(
self, service: DataService, info: dict[str, Any] = {}, *args: Any, **kwargs: Any
) -> None:
self._service = service
self._info = info
async def version(self) -> str:
return f"pydase v{__version__}"
async def name(self) -> str:
return self._service.__class__.__name__
async def info(self) -> dict:
return self._info
async def get_props(self, name: Optional[str] = None) -> dict[str, Any]:
if name is None:
return self._service.serialize()
return self._service.serialize()
async def get_param(self, full_access_path: str) -> Any:
"""Returns the value of the parameter given by the full_access_path.
This method is called when Ionizer initilizes the Plugin or refreshes. The
widgets need to store the full_access_path in their name attribute.
"""
param = get_object_attr_from_path(self._service, full_access_path.split("."))
if isinstance(param, NumberSlider):
return param.value
elif isinstance(param, DataService):
return param.serialize()
elif inspect.ismethod(param):
# explicitly serialize any methods that will be returned
full_access_path = param.__name__
args = inspect.signature(param).parameters
return f"{full_access_path}({', '.join(args)})"
elif isinstance(param, Enum):
return param.value
elif isinstance(param, Quantity):
return param.m
else:
return param
async def set_param(self, full_access_path: str, value: Any) -> None:
parent_path_list = full_access_path.split(".")[:-1]
parent_object = get_object_attr_from_path(self._service, parent_path_list)
attr_name = full_access_path.split(".")[-1]
# I don't want to trigger the execution of a property getter as this might take
# a while when connecting to remote devices
if not isinstance(
getattr(type(parent_object), attr_name, None),
property,
):
current_value = getattr(parent_object, attr_name, None)
if isinstance(current_value, Enum) and isinstance(value, int):
# Ionizer sets the enums using the position of the definition order
# This works as definition order is kept, see e.g.
# https://docs.python.org/3/library/enum.html#enum.EnumType.__iter__
# I need to use the name attribute as this is what
# DataService.__set_attribute_based_on_type expects
value = list(current_value.__class__)[value].name
elif isinstance(current_value, NumberSlider):
parent_path_list.append(attr_name)
attr_name = "value"
self._service.update_DataService_attribute(parent_path_list, attr_name, value)
async def remote_call(self, full_access_path: str, *args: Any) -> Any:
full_access_path_list = full_access_path.split(".")
method_object = get_object_attr_from_path(self._service, full_access_path_list)
return method_object(*args)
async def emit(self, message: str) -> None:
self.notify(message)
def notify(self, message: str) -> None:
"""
This method will be overwritten by the tiqi-rpc server.
Args:
message (str): Notification message.
"""
return