mirror of
https://github.com/tiqi-group/pydase.git
synced 2025-06-23 20:47:59 +02:00
Merge pull request #246 from tiqi-group/feat/direct-api-access-client
Feat: direct api access client
This commit is contained in:
@ -23,7 +23,26 @@ 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.
|
||||||
|
|
||||||
### Accessing Services Behind Firewalls or SSH Gateways
|
## Direct API Access
|
||||||
|
|
||||||
|
In addition to using the `proxy` object, users may access the server API directly via the following methods:
|
||||||
|
|
||||||
|
```python
|
||||||
|
client = pydase.Client(url="ws://localhost:8001")
|
||||||
|
|
||||||
|
# Get the current value of an attribute
|
||||||
|
value = client.get_value("device.voltage")
|
||||||
|
|
||||||
|
# Update an attribute
|
||||||
|
client.update_value("device.voltage", 5.0)
|
||||||
|
|
||||||
|
# Call a method on the remote service
|
||||||
|
result = client.trigger_method("device.reset")
|
||||||
|
```
|
||||||
|
|
||||||
|
This bypasses the proxy and is useful for lower-level access to individual service endpoints.
|
||||||
|
|
||||||
|
## Accessing Services Behind Firewalls or SSH Gateways
|
||||||
|
|
||||||
If your service is only reachable through a private network or SSH gateway, you can route your connection through a local SOCKS5 proxy using the `proxy_url` parameter.
|
If your service is only reachable through a private network or SSH gateway, you can route your connection through a local SOCKS5 proxy using the `proxy_url` parameter.
|
||||||
|
|
||||||
|
@ -12,7 +12,12 @@ import aiohttp
|
|||||||
import socketio # type: ignore
|
import socketio # type: ignore
|
||||||
|
|
||||||
from pydase.client.proxy_class import ProxyClass
|
from pydase.client.proxy_class import ProxyClass
|
||||||
from pydase.client.proxy_loader import ProxyLoader
|
from pydase.client.proxy_loader import (
|
||||||
|
ProxyLoader,
|
||||||
|
get_value,
|
||||||
|
trigger_method,
|
||||||
|
update_value,
|
||||||
|
)
|
||||||
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
|
||||||
|
|
||||||
@ -253,3 +258,77 @@ class Client:
|
|||||||
data["data"]["full_access_path"],
|
data["data"]["full_access_path"],
|
||||||
loads(data["data"]["value"]),
|
loads(data["data"]["value"]),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_value(self, access_path: str) -> Any:
|
||||||
|
"""Retrieve the current value of a remote attribute.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
access_path: The dot-separated path to the attribute in the remote service.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The deserialized value of the remote attribute, or None if the client is not
|
||||||
|
connected.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```python
|
||||||
|
value = client.get_value("my_device.temperature")
|
||||||
|
print(value)
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self._loop is not None:
|
||||||
|
return get_value(
|
||||||
|
sio_client=self._sio,
|
||||||
|
loop=self._loop,
|
||||||
|
access_path=access_path,
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def update_value(self, access_path: str, new_value: Any) -> Any:
|
||||||
|
"""Set a new value for a remote attribute.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
access_path: The dot-separated path to the attribute in the remote service.
|
||||||
|
new_value: The new value to assign to the attribute.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```python
|
||||||
|
client.update_value("my_device.power", True)
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self._loop is not None:
|
||||||
|
update_value(
|
||||||
|
sio_client=self._sio,
|
||||||
|
loop=self._loop,
|
||||||
|
access_path=access_path,
|
||||||
|
value=new_value,
|
||||||
|
)
|
||||||
|
|
||||||
|
def trigger_method(self, access_path: str, *args: Any, **kwargs: Any) -> Any:
|
||||||
|
"""Trigger a remote method with optional arguments.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
access_path: The dot-separated path to the method in the remote service.
|
||||||
|
*args: Positional arguments to pass to the method.
|
||||||
|
**kwargs: Keyword arguments to pass to the method.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The return value of the method call, if any.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```python
|
||||||
|
result = client.trigger_method("my_device.calibrate", timeout=5)
|
||||||
|
print(result)
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self._loop is not None:
|
||||||
|
return trigger_method(
|
||||||
|
sio_client=self._sio,
|
||||||
|
loop=self._loop,
|
||||||
|
access_path=access_path,
|
||||||
|
args=list(args),
|
||||||
|
kwargs=kwargs,
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
@ -74,6 +74,21 @@ def update_value(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_value(
|
||||||
|
sio_client: socketio.AsyncClient,
|
||||||
|
loop: asyncio.AbstractEventLoop,
|
||||||
|
access_path: str,
|
||||||
|
) -> Any:
|
||||||
|
async def get_result() -> Any:
|
||||||
|
return await sio_client.call("get_value", access_path)
|
||||||
|
|
||||||
|
result = asyncio.run_coroutine_threadsafe(
|
||||||
|
get_result(),
|
||||||
|
loop=loop,
|
||||||
|
).result()
|
||||||
|
return ProxyLoader.loads_proxy(result, sio_client, loop)
|
||||||
|
|
||||||
|
|
||||||
class ProxyDict(dict[str, Any]):
|
class ProxyDict(dict[str, Any]):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@ -242,16 +257,11 @@ class ProxyClassMixin:
|
|||||||
self, attr_name: str, serialized_object: SerializedObject
|
self, attr_name: str, serialized_object: SerializedObject
|
||||||
) -> None:
|
) -> None:
|
||||||
def getter_proxy() -> Any:
|
def getter_proxy() -> Any:
|
||||||
async def get_result() -> Any:
|
return get_value(
|
||||||
return await self._sio.call(
|
sio_client=self._sio,
|
||||||
"get_value", serialized_object["full_access_path"]
|
|
||||||
)
|
|
||||||
|
|
||||||
result = asyncio.run_coroutine_threadsafe(
|
|
||||||
get_result(),
|
|
||||||
loop=self._loop,
|
loop=self._loop,
|
||||||
).result()
|
access_path=serialized_object["full_access_path"],
|
||||||
return ProxyLoader.loads_proxy(result, self._sio, self._loop)
|
)
|
||||||
|
|
||||||
dict.__setitem__(self._proxy_getters, attr_name, getter_proxy) # type: ignore
|
dict.__setitem__(self._proxy_getters, attr_name, getter_proxy) # type: ignore
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user