diff --git a/bec_widgets/cli/client_utils.py b/bec_widgets/cli/client_utils.py index 4694ebc4..01746a77 100644 --- a/bec_widgets/cli/client_utils.py +++ b/bec_widgets/cli/client_utils.py @@ -222,6 +222,7 @@ class BECGuiClient(RPCBase): self._ipython_registry: dict[str, RPCReference] = {} self.available_widgets = AvailableWidgetsNamespace() register_serializer_extension() + self._rpc_timeout = 5 #################### #### Client API #### @@ -232,6 +233,16 @@ class BECGuiClient(RPCBase): """The launcher object.""" return RPCBase(gui_id=f"{self._gui_id}:launcher", parent=self, object_name="launcher") + def set_rpc_timeout(self, timeout: float): + """Set the timeout for RPC calls to the GUI server. + + Args: + timeout(float): The timeout in seconds. + """ + if not isinstance(timeout, (int, float)) or timeout < 0: + raise ValueError("Timeout must be a non-negative number.") + self._rpc_timeout = timeout + def _safe_register_stream(self, endpoint: EndpointInfo, cb: Callable, **kwargs): """Check if already registered for registration in idempotent functions.""" if not self._client.connector.any_stream_is_registered(endpoint, cb=cb): diff --git a/bec_widgets/cli/rpc/rpc_base.py b/bec_widgets/cli/rpc/rpc_base.py index deceb383..a89bbdd8 100644 --- a/bec_widgets/cli/rpc/rpc_base.py +++ b/bec_widgets/cli/rpc/rpc_base.py @@ -24,6 +24,8 @@ else: # pylint: disable=protected-access +_DEFAULT_RPC_TIMEOUT = object() + def _name_arg(arg): if isinstance(arg, DeviceBaseWithConfig): @@ -154,6 +156,7 @@ class RPCReference: class RPCBase: + def __init__( self, gui_id: str | None = None, @@ -211,8 +214,8 @@ class RPCBase: self, method, *args, - wait_for_rpc_response=True, - timeout=5, + wait_for_rpc_response: bool = True, + timeout: float | None | object = _DEFAULT_RPC_TIMEOUT, gui_id: str | None = None, **kwargs, ) -> Any: @@ -223,13 +226,16 @@ class RPCBase: method: The method to call. args: The arguments to pass to the method. wait_for_rpc_response: Whether to wait for the RPC response. - timeout: The timeout for the RPC response. + timeout: The timeout for the RPC response. If omitted, the client's default RPC + timeout is used. If explicitly set to None, wait indefinitely. gui_id: The GUI ID to use for the RPC call. If None, the default GUI ID is used. kwargs: The keyword arguments to pass to the method. Returns: The result of the RPC call. """ + if timeout is _DEFAULT_RPC_TIMEOUT: + timeout = self._root._rpc_timeout if method in ["show", "hide", "raise"] and gui_id is None: obj = self._root._server_registry.get(self._gui_id) if obj is None: diff --git a/tests/unit_tests/test_client_utils.py b/tests/unit_tests/test_client_utils.py index 516bc34b..08795a7c 100644 --- a/tests/unit_tests/test_client_utils.py +++ b/tests/unit_tests/test_client_utils.py @@ -5,6 +5,7 @@ import pytest from bec_widgets.cli.client import BECDockArea from bec_widgets.cli.client_utils import BECGuiClient, _start_plot_process +from bec_widgets.cli.rpc.rpc_base import RPCBase, RPCResponseTimeoutError, rpc_timeout @pytest.fixture @@ -257,3 +258,11 @@ def test_client_utils_delete_falls_back_to_direct_close(): gui.delete("dock") widget._run_rpc.assert_called_once_with("close") + + +def test_client_utils_gui_client_set_rpc_timeout(): + gui = BECGuiClient() + assert gui._rpc_timeout == 5 + + gui.set_rpc_timeout(10) + assert gui._rpc_timeout == 10