Merge pull request #152 from tiqi-group/feat/client_context_manager

feat: adds a context manager to the client, fixes running loop issue
This commit is contained in:
Mose Müller 2024-08-13 07:16:05 +02:00 committed by GitHub
commit 2ebdb77433
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 34 additions and 28 deletions

View File

@ -161,7 +161,7 @@ Once the server is running, you can access the web interface in a browser:
In this interface, you can interact with the properties of your `Device` service. In this interface, you can interact with the properties of your `Device` service.
### Connecting to the Service via Python Client ### Connecting to the Service via Python RPC Client
You can connect to the service using the `pydase.Client`. Below is an example of how to establish a connection to a service and interact with it: You can connect to the service using the `pydase.Client`. Below is an example of how to establish a connection to a service and interact with it:
@ -183,31 +183,7 @@ The proxy acts as a local representative of the remote service, enabling straigh
The proxy class dynamically synchronizes with the server's exposed attributes. This synchronization allows the proxy to be automatically updated with any attributes or methods that the server exposes, essentially mirroring the server's API. This dynamic updating enables users to interact with the remote service as if they were working with a local object. The proxy class dynamically synchronizes with the server's exposed attributes. This synchronization allows the proxy to be automatically updated with any attributes or methods that the server exposes, essentially mirroring the server's API. This dynamic updating enables users to interact with the remote service as if they were working with a local object.
#### Tab Completion Support The RPC client also supports tab completion support in the interpreter, can be used as a context manager and integrates very well with other pydase services. For more information, please refer to the [documentation](https://pydase.readthedocs.io/en/latest/user-guide/interaction/main/#python-client).
In interactive environments such as Python interpreters and Jupyter notebooks, the proxy class supports tab completion, which allows users to explore available methods and attributes.
#### Integration within Another Service
You can also integrate a client proxy within another service. Here's how you can set it up:
```python
import pydase
class MyService(pydase.DataService):
# Initialize the client without blocking the constructor
proxy = pydase.Client(url="ws://<ip_addr>:<service_port>", 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()
# Create a server that exposes this service; adjust the web_port as needed
server = pydase.Server(service, web_port=8002). run()
```
In this setup, the `MyService` class has a `proxy` attribute that connects to a `pydase` service located at `<ip_addr>:8001`.
The `block_until_connected=False` argument allows the service to start up even if the initial connection attempt fails.
This configuration is particularly useful in distributed systems where services may start in any order.
### RESTful API ### RESTful API
The `pydase` RESTful API allows for standard HTTP-based interactions and provides access to various functionalities through specific routes. The `pydase` RESTful API allows for standard HTTP-based interactions and provides access to various functionalities through specific routes.

View File

@ -1,4 +1,4 @@
# Python Client # Python RPC Client
You can connect to the service using the `pydase.Client`. Below is an example of how to establish a connection to a service and interact with it: You can connect to the service using the `pydase.Client`. Below is an example of how to establish a connection to a service and interact with it:
@ -20,6 +20,19 @@ The proxy acts as a local representative of the remote service, enabling straigh
The proxy class dynamically synchronizes with the server's exposed attributes. This synchronization allows the proxy to be automatically updated with any attributes or methods that the server exposes, essentially mirroring the server's API. This dynamic updating enables users to interact with the remote service as if they were working with a local object. The proxy class dynamically synchronizes with the server's exposed attributes. This synchronization allows the proxy to be automatically updated with any attributes or methods that the server exposes, essentially mirroring the server's API. This dynamic updating enables users to interact with the remote service as if they were working with a local object.
## Context Manager
You can also use the client as a context manager which automatically opens and closes the connection again:
```python
import pydase
with pydase.Client(url="ws://localhost:8001") as client:
client.proxy.<my_method>()
```
## Tab Completion Support ## Tab Completion Support
In interactive environments such as Python interpreters and Jupyter notebooks, the proxy class supports tab completion, which allows users to explore available methods and attributes. In interactive environments such as Python interpreters and Jupyter notebooks, the proxy class supports tab completion, which allows users to explore available methods and attributes.

View File

@ -1,5 +1,6 @@
import asyncio import asyncio
import logging import logging
import sys
import threading import threading
from typing import TypedDict, cast from typing import TypedDict, cast
@ -10,6 +11,12 @@ from pydase.client.proxy_loader import ProxyClassMixin, ProxyLoader
from pydase.utils.serialization.deserializer import loads from pydase.utils.serialization.deserializer import loads
from pydase.utils.serialization.types import SerializedDataService, SerializedObject from pydase.utils.serialization.types import SerializedDataService, SerializedObject
if sys.version_info < (3, 11):
from typing_extensions import Self
else:
from typing import Self
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -24,7 +31,10 @@ class NotifyDict(TypedDict):
def asyncio_loop_thread(loop: asyncio.AbstractEventLoop) -> None: def asyncio_loop_thread(loop: asyncio.AbstractEventLoop) -> None:
asyncio.set_event_loop(loop) asyncio.set_event_loop(loop)
loop.run_forever() try:
loop.run_forever()
except RuntimeError:
logger.debug("Tried starting even loop, but it is running already")
class ProxyClass(ProxyClassMixin, pydase.components.DeviceConnection): class ProxyClass(ProxyClassMixin, pydase.components.DeviceConnection):
@ -108,6 +118,13 @@ class Client:
self._thread.start() self._thread.start()
self.connect(block_until_connected=block_until_connected) self.connect(block_until_connected=block_until_connected)
def __enter__(self) -> Self:
self.connect(block_until_connected=True)
return self
def __del__(self) -> None:
self.disconnect()
def connect(self, block_until_connected: bool = True) -> None: def connect(self, block_until_connected: bool = True) -> None:
connection_future = asyncio.run_coroutine_threadsafe( connection_future = asyncio.run_coroutine_threadsafe(
self._connect(), self._loop self._connect(), self._loop