From 42357d790199815783ae4dd83521908842d6a3a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mose=20M=C3=BCller?= Date: Mon, 12 Aug 2024 13:15:17 +0200 Subject: [PATCH 1/4] breaking: client takes url instead of hostname and port Connecting to secure services (with wss) was not possible. The user has to provide the whole URL now, which makes it much more flexible and less bug-prone. --- src/pydase/client/client.py | 39 ++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/src/pydase/client/client.py b/src/pydase/client/client.py index 537a628..17b1c6b 100644 --- a/src/pydase/client/client.py +++ b/src/pydase/client/client.py @@ -80,12 +80,12 @@ class Client: if it were local. Args: - hostname (str): - Hostname of the exposed service this client attempts to connect to. - Default is "localhost". - port (int): - Port of the exposed service this client attempts to connect on. - Default is 8001. + url (str): + The URL of the pydase Socket.IO server. This should always contain the + protocol and the hostname. + Examples: + - wss://my-service.example.com # for secure connections, use wss + - ws://localhost:8001 block_until_connected (bool): If set to True, the constructor will block until the connection to the service has been established. This is useful for ensuring the client is @@ -94,12 +94,11 @@ class Client: def __init__( self, - hostname: str, - port: int, + *, + url: str, block_until_connected: bool = True, ): - self._hostname = hostname - self._port = port + self._url = url self._sio = socketio.AsyncClient() self._loop = asyncio.new_event_loop() self.proxy = ProxyClass(sio_client=self._sio, loop=self._loop) @@ -107,29 +106,41 @@ class Client: target=asyncio_loop_thread, args=(self._loop,), daemon=True ) self._thread.start() + self.connect(block_until_connected=block_until_connected) + + def connect(self, block_until_connected: bool = True) -> None: connection_future = asyncio.run_coroutine_threadsafe( self._connect(), self._loop ) if block_until_connected: connection_future.result() + def disconnect(self) -> None: + connection_future = asyncio.run_coroutine_threadsafe( + self._disconnect(), self._loop + ) + connection_future.result() + async def _connect(self) -> None: - logger.debug("Connecting to server '%s:%s' ...", self._hostname, self._port) + logger.debug("Connecting to server '%s' ...", self._url) await self._setup_events() await self._sio.connect( - f"ws://{self._hostname}:{self._port}", + self._url, socketio_path="/ws/socket.io", transports=["websocket"], retry=True, ) + async def _disconnect(self) -> None: + await self._sio.disconnect() + async def _setup_events(self) -> None: self._sio.on("connect", self._handle_connect) self._sio.on("disconnect", self._handle_disconnect) self._sio.on("notify", self._handle_update) async def _handle_connect(self) -> None: - logger.debug("Connected to '%s:%s' ...", self._hostname, self._port) + logger.debug("Connected to '%s' ...", self._url) serialized_object = cast( SerializedDataService, await self._sio.call("service_serialization") ) @@ -141,7 +152,7 @@ class Client: self.proxy._connected = True async def _handle_disconnect(self) -> None: - logger.debug("Disconnected from '%s:%s' ...", self._hostname, self._port) + logger.debug("Disconnected from '%s' ...", self._url) self.proxy._connected = False async def _handle_update(self, data: NotifyDict) -> None: From 1ac08bf97d57cf36aca650b896ccb885339e9cdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mose=20M=C3=BCller?= Date: Mon, 12 Aug 2024 13:19:45 +0200 Subject: [PATCH 2/4] fixes client test --- tests/client/test_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/client/test_client.py b/tests/client/test_client.py index 7a66bb2..27c2cd0 100644 --- a/tests/client/test_client.py +++ b/tests/client/test_client.py @@ -45,7 +45,7 @@ def pydase_client() -> Generator[pydase.Client, None, Any]: thread = threading.Thread(target=server.run, daemon=True) thread.start() - client = pydase.Client(hostname="localhost", port=9999) + client = pydase.Client(url="ws://localhost:9999") yield client From 348ff092aaaaf47645f0949fdcb830143904f729 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mose=20M=C3=BCller?= Date: Mon, 12 Aug 2024 13:25:11 +0200 Subject: [PATCH 3/4] updates Readme with client instructions --- README.md | 6 ++++-- docs/user-guide/interaction/Python Client.md | 10 ++++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index a4ce2c4..7faaa9d 100644 --- a/README.md +++ b/README.md @@ -170,7 +170,8 @@ import pydase # Replace the hostname and port with the IP address and the port of the machine where # the service is running, respectively -client_proxy = pydase.Client(hostname="", port=8001).proxy +client_proxy = pydase.Client(url="ws://:").proxy +# client_proxy = pydase.Client(url="wss://your-domain.ch").proxy # if your service uses ssl-encryption # After the connection, interact with the service attributes as if they were local client_proxy.voltage = 5.0 @@ -195,7 +196,8 @@ import pydase class MyService(pydase.DataService): # Initialize the client without blocking the constructor - proxy = pydase.Client(hostname="", port=8001, block_until_connected=False).proxy + proxy = pydase.Client(url="ws://:", block_until_connected=False).proxy + # proxy = pydase.Client(url="wss://your-domain.ch", block_until_connected=False).proxy # communicating with ssl-encrypted service if __name__ == "__main__": service = MyService() diff --git a/docs/user-guide/interaction/Python Client.md b/docs/user-guide/interaction/Python Client.md index ea844f2..e41c43c 100644 --- a/docs/user-guide/interaction/Python Client.md +++ b/docs/user-guide/interaction/Python Client.md @@ -5,9 +5,10 @@ You can connect to the service using the `pydase.Client`. Below is an example of ```python import pydase -# Replace the hostname and port with the IP address and the port of the machine -# where the service is running, respectively -client_proxy = pydase.Client(hostname="", port=8001).proxy +# Replace the hostname and port with the IP address and the port of the machine where +# the service is running, respectively +client_proxy = pydase.Client(url="ws://:").proxy +# client_proxy = pydase.Client(url="wss://your-domain.ch").proxy # if your service uses ssl-encryption # Interact with the service attributes as if they were local client_proxy.voltage = 5.0 @@ -32,7 +33,8 @@ import pydase class MyService(pydase.DataService): # Initialize the client without blocking the constructor - proxy = pydase.Client(hostname="", port=8001, block_until_connected=False).proxy + proxy = pydase.Client(url="ws://:", block_until_connected=False).proxy + # proxy = pydase.Client(url="wss://your-domain.ch", block_until_connected=False).proxy # communicating with ssl-encrypted service if __name__ == "__main__": service = MyService() From 86bac8f9e5fb37c55bdde24ac0b6426dc1658da3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mose=20M=C3=BCller?= Date: Tue, 13 Aug 2024 07:03:49 +0200 Subject: [PATCH 4/4] updates version to v0.9.0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e621311..a881a33 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pydase" -version = "0.8.5" +version = "0.9.0" description = "A flexible and robust Python library for creating, managing, and interacting with data services, with built-in support for web and RPC servers, and customizable features for diverse use cases." authors = ["Mose Mueller "] readme = "README.md"