mirror of
https://github.com/tiqi-group/pydase.git
synced 2025-04-21 16:50:02 +02:00
udpates Client and ProxyClassFactory
- Client: - inherits from DataService now - acts as an observer of the proxy class and sends updates to the sio server - ProxyClassFactory - ProxyConnection is now a DeviceConnection -> users will see if the client is connected
This commit is contained in:
parent
36a70badce
commit
d100bb5fea
@ -1,15 +1,14 @@
|
||||
import logging
|
||||
import time
|
||||
from typing import TYPE_CHECKING, TypedDict
|
||||
from typing import Any, TypedDict
|
||||
|
||||
import socketio # type: ignore
|
||||
|
||||
from pydase.client.proxy_class_factory import ProxyClassFactory
|
||||
import pydase
|
||||
from pydase.client.proxy_class_factory import ProxyClassFactory, ProxyConnection
|
||||
from pydase.utils.serialization.deserializer import loads
|
||||
from pydase.utils.serialization.serializer import SerializedObject
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pydase.client.proxy_class_factory import ProxyClass
|
||||
from pydase.utils.serialization.serializer import SerializedObject, dump
|
||||
from pydase.utils.serialization.types import SerializedDataService
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -23,31 +22,56 @@ class NotifyDict(TypedDict):
|
||||
data: NotifyDataDict
|
||||
|
||||
|
||||
class Client:
|
||||
class Client(pydase.DataService):
|
||||
def __init__(self, hostname: str, port: int):
|
||||
self.sio = socketio.Client()
|
||||
self.setup_events()
|
||||
self.proxy_class_factory = ProxyClassFactory(self.sio)
|
||||
self.proxy: ProxyClass | None = None
|
||||
self.sio.connect(
|
||||
super().__init__()
|
||||
self._sio = socketio.Client()
|
||||
self._setup_events()
|
||||
self._proxy_class_factory = ProxyClassFactory(self._sio)
|
||||
self.proxy = ProxyConnection()
|
||||
self._sio.connect(
|
||||
f"ws://{hostname}:{port}",
|
||||
socketio_path="/ws/socket.io",
|
||||
transports=["websocket"],
|
||||
)
|
||||
while self.proxy is None:
|
||||
while not self.proxy._initialised:
|
||||
time.sleep(0.01)
|
||||
|
||||
def setup_events(self) -> None:
|
||||
@self.sio.event
|
||||
def class_structure(data: SerializedObject) -> None:
|
||||
self.proxy = self.proxy_class_factory.create_proxy(data)
|
||||
def _setup_events(self) -> None:
|
||||
@self._sio.event
|
||||
def class_structure(data: SerializedDataService) -> None:
|
||||
if not self.proxy._initialised:
|
||||
self.proxy = self._proxy_class_factory.create_proxy(data)
|
||||
else:
|
||||
# need to change to avoid overwriting the proxy class
|
||||
data["type"] = "DeviceConnection"
|
||||
self.proxy._notify_changed("", loads(data))
|
||||
|
||||
@self.sio.event
|
||||
@self._sio.event
|
||||
def notify(data: NotifyDict) -> None:
|
||||
if self.proxy is not None:
|
||||
self.proxy._notify_changed(
|
||||
data["data"]["full_access_path"], loads(data["data"]["value"])
|
||||
)
|
||||
# Notify the DataServiceObserver directly, not going through
|
||||
# self._notify_changed as this would trigger the "update_value" event
|
||||
super(pydase.DataService, self)._notify_changed(
|
||||
f"proxy.{data['data']['full_access_path']}",
|
||||
loads(data["data"]["value"]),
|
||||
)
|
||||
|
||||
def disconnect(self) -> None:
|
||||
self.sio.disconnect()
|
||||
self._sio.disconnect()
|
||||
|
||||
def _notify_changed(self, changed_attribute: str, value: Any) -> None:
|
||||
if (
|
||||
changed_attribute.startswith("proxy.")
|
||||
and all(part[0] != "_" for part in changed_attribute.split("."))
|
||||
and changed_attribute != "proxy.connected"
|
||||
):
|
||||
logger.debug(f"{changed_attribute}: {value}")
|
||||
|
||||
self._sio.call(
|
||||
"update_value",
|
||||
{
|
||||
"access_path": changed_attribute[6:],
|
||||
"value": dump(value),
|
||||
},
|
||||
)
|
||||
return super()._notify_changed(changed_attribute, value)
|
||||
|
@ -1,10 +1,14 @@
|
||||
import logging
|
||||
from collections.abc import Callable
|
||||
from copy import copy
|
||||
from typing import TYPE_CHECKING, Any, cast
|
||||
from typing import Any, cast
|
||||
|
||||
import socketio # type: ignore
|
||||
|
||||
import pydase
|
||||
import pydase.components
|
||||
import pydase.observer_pattern.observer
|
||||
from pydase.utils.helpers import is_property_attribute
|
||||
from pydase.utils.serialization.deserializer import Deserializer, loads
|
||||
from pydase.utils.serialization.serializer import (
|
||||
SerializedMethod,
|
||||
@ -12,31 +16,48 @@ from pydase.utils.serialization.serializer import (
|
||||
dump,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ProxyClass(pydase.DataService):
|
||||
__sio: socketio.Client
|
||||
class ProxyClassMixin:
|
||||
_sio: socketio.Client
|
||||
|
||||
def __setattr__(self, key, value):
|
||||
def __setattr__(self, key: str, value: Any) -> None:
|
||||
# prevent overriding of proxy attributes
|
||||
if hasattr(self, key) and isinstance(getattr(self, key), ProxyClass):
|
||||
if (
|
||||
not is_property_attribute(self, key)
|
||||
and hasattr(self, key)
|
||||
and isinstance(getattr(self, key), ProxyBaseClass)
|
||||
):
|
||||
raise AttributeError(f"{key} is read-only and cannot be overridden.")
|
||||
|
||||
super().__setattr__(key, value)
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
class ProxyBaseClass(pydase.DataService, ProxyClassMixin):
|
||||
pass
|
||||
|
||||
|
||||
class ProxyConnection(pydase.components.DeviceConnection, ProxyClassMixin):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self._initialised = False
|
||||
self._reconnection_wait_time = 1
|
||||
|
||||
@property
|
||||
def connected(self) -> bool:
|
||||
return self._sio.connected
|
||||
|
||||
|
||||
class ProxyClassFactory:
|
||||
def __init__(self, sio_client: socketio.Client) -> None:
|
||||
self.sio_client = sio_client
|
||||
|
||||
def create_proxy(self, data: SerializedObject) -> ProxyClass:
|
||||
proxy: ProxyClass = self._deserialize(data)
|
||||
return proxy
|
||||
def create_proxy(self, data: SerializedObject) -> ProxyConnection:
|
||||
proxy_class = self._deserialize_component_type(data, ProxyConnection)
|
||||
proxy_class._sio = self.sio_client
|
||||
proxy_class._initialised = True
|
||||
return proxy_class # type: ignore
|
||||
|
||||
def _deserialize(self, serialized_object: SerializedObject) -> Any:
|
||||
type_handler: dict[str | None, None | Callable[..., Any]] = {
|
||||
@ -65,15 +86,18 @@ class ProxyClassFactory:
|
||||
proxy_class = self._deserialize_component_type(
|
||||
serialized_object, component_class
|
||||
)
|
||||
proxy_class.__sio = self.sio_client
|
||||
proxy_class._sio = self.sio_client
|
||||
proxy_class._initialised = True
|
||||
return proxy_class
|
||||
return None
|
||||
|
||||
def _deserialize_method(self, serialized_object: SerializedMethod) -> Any:
|
||||
def method_proxy(self: ProxyClass, *args: Any, **kwargs: Any) -> Any:
|
||||
def _deserialize_method(
|
||||
self, serialized_object: SerializedMethod
|
||||
) -> Callable[..., Any]:
|
||||
def method_proxy(self: ProxyBaseClass, *args: Any, **kwargs: Any) -> Any:
|
||||
serialized_response = cast(
|
||||
dict[str, Any],
|
||||
self.__sio.call(
|
||||
self._sio.call(
|
||||
"trigger_method",
|
||||
{
|
||||
"access_path": serialized_object["full_access_path"],
|
||||
@ -88,7 +112,7 @@ class ProxyClassFactory:
|
||||
|
||||
def _deserialize_component_type(
|
||||
self, serialized_object: SerializedObject, base_class: type
|
||||
) -> ProxyClass:
|
||||
) -> pydase.DataService:
|
||||
def add_prefix_to_last_path_element(s: str, prefix: str) -> str:
|
||||
parts = s.split(".")
|
||||
parts[-1] = f"{prefix}_{parts[-1]}"
|
||||
@ -96,7 +120,7 @@ class ProxyClassFactory:
|
||||
|
||||
def create_proxy_class(serialized_object: SerializedObject) -> type:
|
||||
class_bases = (
|
||||
ProxyClass,
|
||||
ProxyBaseClass,
|
||||
base_class,
|
||||
)
|
||||
class_attrs: dict[str, Any] = {}
|
||||
@ -136,20 +160,20 @@ class ProxyClassFactory:
|
||||
return create_proxy_class(serialized_object)()
|
||||
|
||||
def _create_attr_property(self, serialized_attr: SerializedObject) -> property:
|
||||
def get(self: ProxyClass) -> Any: # type: ignore
|
||||
def get(self: ProxyBaseClass) -> Any: # type: ignore
|
||||
return loads(
|
||||
cast(
|
||||
SerializedObject,
|
||||
self.__sio.call("get_value", serialized_attr["full_access_path"]),
|
||||
self._sio.call("get_value", serialized_attr["full_access_path"]),
|
||||
)
|
||||
)
|
||||
|
||||
get.__doc__ = serialized_attr["doc"]
|
||||
|
||||
def set(self: ProxyClass, value: Any) -> None: # type: ignore
|
||||
def set(self: ProxyBaseClass, value: Any) -> None: # type: ignore
|
||||
result = cast(
|
||||
SerializedObject | None,
|
||||
self.__sio.call(
|
||||
self._sio.call(
|
||||
"update_value",
|
||||
{
|
||||
"access_path": serialized_attr["full_access_path"],
|
||||
|
Loading…
x
Reference in New Issue
Block a user