Merge pull request #250 from tiqi-group/feat/client_auto_update_proxy

feat: adds auto_update_proxy argument to pydase.Client
This commit is contained in:
Mose Müller
2025-06-27 08:44:35 +02:00
committed by GitHub
2 changed files with 34 additions and 14 deletions

View File

@@ -23,6 +23,19 @@ The proxy acts as a local representation of the remote service, enabling intuiti
The proxy class automatically synchronizes with the server's attributes and methods, keeping itself up-to-date with any changes. This dynamic synchronization essentially mirrors the server's API, making it feel like you're working with a local object. The proxy class automatically synchronizes with the server's attributes and methods, keeping itself up-to-date with any changes. This dynamic synchronization essentially mirrors the server's API, making it feel like you're working with a local object.
## Automatic Proxy Updates
By default, the client listens for attribute and structure changes from the server and dynamically updates its internal proxy representation. This ensures that value changes or newly added attributes on the server appear in the client proxy without requiring reconnection or manual refresh.
This is useful, for example, when [integrating the client into another service](#integrating-the-client-into-another-service). However, if you want to avoid this behavior (e.g., to reduce network traffic or avoid frequent re-syncing), you can disable it. When passing `auto_update_proxy=False` to the client, the proxy will not track changes after the initial connection:
```python
client = pydase.Client(
url="ws://localhost:8001",
auto_update_proxy=False
)
```
## Direct API Access ## Direct API Access
In addition to using the `proxy` object, users may access the server API directly via the following methods: In addition to using the `proxy` object, users may access the server API directly via the following methods:
@@ -94,6 +107,7 @@ if __name__ == "__main__":
``` ```
In this example: In this example:
- The `MyService` class has a `proxy` attribute that connects to a `pydase` service at `<ip_addr>:<service_port>`. - The `MyService` class has a `proxy` attribute that connects to a `pydase` service at `<ip_addr>:<service_port>`.
- By setting `block_until_connected=False`, the service can start without waiting for the connection to succeed. - By setting `block_until_connected=False`, the service can start without waiting for the connection to succeed.
- The `client_id` is optional. If not specified, it defaults to the system hostname, which will be sent in the `X-Client-Id` HTTP header for logging or authentication on the server side. - The `client_id` is optional. If not specified, it defaults to the system hostname, which will be sent in the `X-Client-Id` HTTP header for logging or authentication on the server side.

View File

@@ -70,6 +70,8 @@ class Client:
proxy_url: An optional proxy URL to route the connection through. This is useful proxy_url: An optional proxy URL to route the connection through. This is useful
if the service is only reachable via an SSH tunnel or behind a firewall if the service is only reachable via an SSH tunnel or behind a firewall
(e.g., `socks5://localhost:2222`). (e.g., `socks5://localhost:2222`).
auto_update_proxy: If False, disables automatic updates from the server. Useful
for request-only clients where real-time synchronization is not needed.
Example: Example:
Connect to a service directly: Connect to a service directly:
@@ -98,7 +100,7 @@ class Client:
``` ```
""" """
def __init__( def __init__( # noqa: PLR0913
self, self,
*, *,
url: str, url: str,
@@ -106,6 +108,7 @@ class Client:
sio_client_kwargs: dict[str, Any] = {}, sio_client_kwargs: dict[str, Any] = {},
client_id: str | None = None, client_id: str | None = None,
proxy_url: str | None = None, proxy_url: str | None = None,
auto_update_proxy: bool = True, # new argument
): ):
# Parse the URL to separate base URL and path prefix # Parse the URL to separate base URL and path prefix
parsed_url = urllib.parse.urlparse(url) parsed_url = urllib.parse.urlparse(url)
@@ -123,6 +126,7 @@ class Client:
self._sio_client_kwargs = sio_client_kwargs self._sio_client_kwargs = sio_client_kwargs
self._loop: asyncio.AbstractEventLoop | None = None self._loop: asyncio.AbstractEventLoop | None = None
self._thread: threading.Thread | None = None self._thread: threading.Thread | None = None
self._auto_update_proxy = auto_update_proxy
self.proxy: ProxyClass self.proxy: ProxyClass
"""A proxy object representing the remote service, facilitating interaction as """A proxy object representing the remote service, facilitating interaction as
if it were local.""" if it were local."""
@@ -229,10 +233,12 @@ class Client:
async def _setup_events(self) -> None: async def _setup_events(self) -> None:
self._sio.on("connect", self._handle_connect) self._sio.on("connect", self._handle_connect)
self._sio.on("disconnect", self._handle_disconnect) self._sio.on("disconnect", self._handle_disconnect)
if self._auto_update_proxy:
self._sio.on("notify", self._handle_update) self._sio.on("notify", self._handle_update)
async def _handle_connect(self) -> None: async def _handle_connect(self) -> None:
logger.debug("Connected to '%s' ...", self._url) logger.debug("Connected to '%s' ...", self._url)
if self._auto_update_proxy:
serialized_object = cast( serialized_object = cast(
"SerializedDataService", await self._sio.call("service_serialization") "SerializedDataService", await self._sio.call("service_serialization")
) )