diff --git a/src/pyDataInterface/data_service/__init__.py b/src/pyDataInterface/data_service/__init__.py index 77faca0..1af532e 100644 --- a/src/pyDataInterface/data_service/__init__.py +++ b/src/pyDataInterface/data_service/__init__.py @@ -1,7 +1,9 @@ from .data_service import DataService from .data_service_list import DataServiceList +from .number_slider import NumberSlider __all__ = [ "DataService", "DataServiceList", + "NumberSlider", ] diff --git a/src/pyDataInterface/data_service/data_service.py b/src/pyDataInterface/data_service/data_service.py index 67a47d4..54e0036 100644 --- a/src/pyDataInterface/data_service/data_service.py +++ b/src/pyDataInterface/data_service/data_service.py @@ -472,7 +472,9 @@ class DataService(rpyc.Service): if isinstance(value, DataService): result[key] = { - "type": type(value).__name__, + "type": type(value).__name__ + if type(value).__name__ in ("NumberSlider") + else "DataService", "value": value.serialize(), "readonly": False, "doc": inspect.getdoc(value), @@ -542,3 +544,38 @@ class DataService(rpyc.Service): - value (Any): The value of the parameter. """ self._notification_callbacks.append(callback) + + def apply_updates(self, data: dict[str, Any]) -> None: + """ + Applies updates to the attributes of this DataService instance. + + For each key-value pair in the provided data dictionary, this function + checks if the attribute with the corresponding name exists in the instance, + and if the current value of this attribute is different from the new value. + If the attribute exists and the values are different, it updates the attribute + in the instance with the new value. + + Args: + data (dict): A dictionary containing the updates to be applied. The keys + should correspond to the names of attributes in the DataService instance + and the values should be the new values for these attributes. + + Note: + This function assumes that all values can be directly compared with + the != operator and assigned with the = operator. If some attributes need + more complex update logic, this function might not work correctly for them. + """ + + # TODO: check if attribute is DataService instance -> nested updates? + # Might not be necessary as each DataService instance change would trigger its + # own frontend_update notification. + + for key, new_value in data.items(): + if hasattr(self, key): + current_value = getattr(self, key) + if current_value != new_value: + setattr(self, key, new_value) + else: + logger.error( + f"Attribute {key} does not exist in the DataService instance." + ) diff --git a/src/pyDataInterface/data_service/number_slider.py b/src/pyDataInterface/data_service/number_slider.py new file mode 100644 index 0000000..966f1e4 --- /dev/null +++ b/src/pyDataInterface/data_service/number_slider.py @@ -0,0 +1,16 @@ +from .data_service import DataService + + +class NumberSlider(DataService): + def __init__( + self, + value: float | int = 0, + min: int = 0, + max: int = 100, + step_size: float = 1.0, + ) -> None: + self.min = min + self.max = max + self.value = value + self.step_size = step_size + super().__init__() diff --git a/src/pyDataInterface/server/web_server.py b/src/pyDataInterface/server/web_server.py index 93d10fe..725c861 100644 --- a/src/pyDataInterface/server/web_server.py +++ b/src/pyDataInterface/server/web_server.py @@ -9,6 +9,7 @@ from loguru import logger from pyDataInterface import DataService from pyDataInterface.config import OperationMode +from pyDataInterface.data_service import NumberSlider from pyDataInterface.version import __version__ @@ -52,7 +53,11 @@ class WebAPI: @sio.on("frontend_update") # type: ignore def handle_frontend_update(sid: str, data: FrontendUpdate) -> None: logger.debug(f"Received frontend update: {data}") - setattr(self.service, data["name"], data["value"]) + attr = getattr(self.service, data["name"]) + if isinstance(attr, DataService): + attr.apply_updates(data["value"]) + else: + setattr(self.service, data["name"], data["value"]) self.__sio = sio self.__sio_app = socketio.ASGIApp(self.__sio)