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 logging
|
||||||
import time
|
import time
|
||||||
from typing import TYPE_CHECKING, TypedDict
|
from typing import Any, TypedDict
|
||||||
|
|
||||||
import socketio # type: ignore
|
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.deserializer import loads
|
||||||
from pydase.utils.serialization.serializer import SerializedObject
|
from pydase.utils.serialization.serializer import SerializedObject, dump
|
||||||
|
from pydase.utils.serialization.types import SerializedDataService
|
||||||
if TYPE_CHECKING:
|
|
||||||
from pydase.client.proxy_class_factory import ProxyClass
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -23,31 +22,56 @@ class NotifyDict(TypedDict):
|
|||||||
data: NotifyDataDict
|
data: NotifyDataDict
|
||||||
|
|
||||||
|
|
||||||
class Client:
|
class Client(pydase.DataService):
|
||||||
def __init__(self, hostname: str, port: int):
|
def __init__(self, hostname: str, port: int):
|
||||||
self.sio = socketio.Client()
|
super().__init__()
|
||||||
self.setup_events()
|
self._sio = socketio.Client()
|
||||||
self.proxy_class_factory = ProxyClassFactory(self.sio)
|
self._setup_events()
|
||||||
self.proxy: ProxyClass | None = None
|
self._proxy_class_factory = ProxyClassFactory(self._sio)
|
||||||
self.sio.connect(
|
self.proxy = ProxyConnection()
|
||||||
|
self._sio.connect(
|
||||||
f"ws://{hostname}:{port}",
|
f"ws://{hostname}:{port}",
|
||||||
socketio_path="/ws/socket.io",
|
socketio_path="/ws/socket.io",
|
||||||
transports=["websocket"],
|
transports=["websocket"],
|
||||||
)
|
)
|
||||||
while self.proxy is None:
|
while not self.proxy._initialised:
|
||||||
time.sleep(0.01)
|
time.sleep(0.01)
|
||||||
|
|
||||||
def setup_events(self) -> None:
|
def _setup_events(self) -> None:
|
||||||
@self.sio.event
|
@self._sio.event
|
||||||
def class_structure(data: SerializedObject) -> None:
|
def class_structure(data: SerializedDataService) -> None:
|
||||||
self.proxy = self.proxy_class_factory.create_proxy(data)
|
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:
|
def notify(data: NotifyDict) -> None:
|
||||||
if self.proxy is not None:
|
# Notify the DataServiceObserver directly, not going through
|
||||||
self.proxy._notify_changed(
|
# self._notify_changed as this would trigger the "update_value" event
|
||||||
data["data"]["full_access_path"], loads(data["data"]["value"])
|
super(pydase.DataService, self)._notify_changed(
|
||||||
)
|
f"proxy.{data['data']['full_access_path']}",
|
||||||
|
loads(data["data"]["value"]),
|
||||||
|
)
|
||||||
|
|
||||||
def disconnect(self) -> None:
|
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
|
import logging
|
||||||
|
from collections.abc import Callable
|
||||||
from copy import copy
|
from copy import copy
|
||||||
from typing import TYPE_CHECKING, Any, cast
|
from typing import Any, cast
|
||||||
|
|
||||||
import socketio # type: ignore
|
import socketio # type: ignore
|
||||||
|
|
||||||
import pydase
|
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.deserializer import Deserializer, loads
|
||||||
from pydase.utils.serialization.serializer import (
|
from pydase.utils.serialization.serializer import (
|
||||||
SerializedMethod,
|
SerializedMethod,
|
||||||
@ -12,31 +16,48 @@ from pydase.utils.serialization.serializer import (
|
|||||||
dump,
|
dump,
|
||||||
)
|
)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
logger = logging.getLogger(__name__)
|
||||||
from collections.abc import Callable
|
|
||||||
|
|
||||||
|
|
||||||
class ProxyClass(pydase.DataService):
|
class ProxyClassMixin:
|
||||||
__sio: socketio.Client
|
_sio: socketio.Client
|
||||||
|
|
||||||
def __setattr__(self, key, value):
|
def __setattr__(self, key: str, value: Any) -> None:
|
||||||
# prevent overriding of proxy attributes
|
# 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.")
|
raise AttributeError(f"{key} is read-only and cannot be overridden.")
|
||||||
|
|
||||||
super().__setattr__(key, value)
|
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:
|
class ProxyClassFactory:
|
||||||
def __init__(self, sio_client: socketio.Client) -> None:
|
def __init__(self, sio_client: socketio.Client) -> None:
|
||||||
self.sio_client = sio_client
|
self.sio_client = sio_client
|
||||||
|
|
||||||
def create_proxy(self, data: SerializedObject) -> ProxyClass:
|
def create_proxy(self, data: SerializedObject) -> ProxyConnection:
|
||||||
proxy: ProxyClass = self._deserialize(data)
|
proxy_class = self._deserialize_component_type(data, ProxyConnection)
|
||||||
return proxy
|
proxy_class._sio = self.sio_client
|
||||||
|
proxy_class._initialised = True
|
||||||
|
return proxy_class # type: ignore
|
||||||
|
|
||||||
def _deserialize(self, serialized_object: SerializedObject) -> Any:
|
def _deserialize(self, serialized_object: SerializedObject) -> Any:
|
||||||
type_handler: dict[str | None, None | Callable[..., Any]] = {
|
type_handler: dict[str | None, None | Callable[..., Any]] = {
|
||||||
@ -65,15 +86,18 @@ class ProxyClassFactory:
|
|||||||
proxy_class = self._deserialize_component_type(
|
proxy_class = self._deserialize_component_type(
|
||||||
serialized_object, component_class
|
serialized_object, component_class
|
||||||
)
|
)
|
||||||
proxy_class.__sio = self.sio_client
|
proxy_class._sio = self.sio_client
|
||||||
|
proxy_class._initialised = True
|
||||||
return proxy_class
|
return proxy_class
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _deserialize_method(self, serialized_object: SerializedMethod) -> Any:
|
def _deserialize_method(
|
||||||
def method_proxy(self: ProxyClass, *args: Any, **kwargs: Any) -> Any:
|
self, serialized_object: SerializedMethod
|
||||||
|
) -> Callable[..., Any]:
|
||||||
|
def method_proxy(self: ProxyBaseClass, *args: Any, **kwargs: Any) -> Any:
|
||||||
serialized_response = cast(
|
serialized_response = cast(
|
||||||
dict[str, Any],
|
dict[str, Any],
|
||||||
self.__sio.call(
|
self._sio.call(
|
||||||
"trigger_method",
|
"trigger_method",
|
||||||
{
|
{
|
||||||
"access_path": serialized_object["full_access_path"],
|
"access_path": serialized_object["full_access_path"],
|
||||||
@ -88,7 +112,7 @@ class ProxyClassFactory:
|
|||||||
|
|
||||||
def _deserialize_component_type(
|
def _deserialize_component_type(
|
||||||
self, serialized_object: SerializedObject, base_class: type
|
self, serialized_object: SerializedObject, base_class: type
|
||||||
) -> ProxyClass:
|
) -> pydase.DataService:
|
||||||
def add_prefix_to_last_path_element(s: str, prefix: str) -> str:
|
def add_prefix_to_last_path_element(s: str, prefix: str) -> str:
|
||||||
parts = s.split(".")
|
parts = s.split(".")
|
||||||
parts[-1] = f"{prefix}_{parts[-1]}"
|
parts[-1] = f"{prefix}_{parts[-1]}"
|
||||||
@ -96,7 +120,7 @@ class ProxyClassFactory:
|
|||||||
|
|
||||||
def create_proxy_class(serialized_object: SerializedObject) -> type:
|
def create_proxy_class(serialized_object: SerializedObject) -> type:
|
||||||
class_bases = (
|
class_bases = (
|
||||||
ProxyClass,
|
ProxyBaseClass,
|
||||||
base_class,
|
base_class,
|
||||||
)
|
)
|
||||||
class_attrs: dict[str, Any] = {}
|
class_attrs: dict[str, Any] = {}
|
||||||
@ -136,20 +160,20 @@ class ProxyClassFactory:
|
|||||||
return create_proxy_class(serialized_object)()
|
return create_proxy_class(serialized_object)()
|
||||||
|
|
||||||
def _create_attr_property(self, serialized_attr: SerializedObject) -> property:
|
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(
|
return loads(
|
||||||
cast(
|
cast(
|
||||||
SerializedObject,
|
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"]
|
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(
|
result = cast(
|
||||||
SerializedObject | None,
|
SerializedObject | None,
|
||||||
self.__sio.call(
|
self._sio.call(
|
||||||
"update_value",
|
"update_value",
|
||||||
{
|
{
|
||||||
"access_path": serialized_attr["full_access_path"],
|
"access_path": serialized_attr["full_access_path"],
|
||||||
|
Loading…
x
Reference in New Issue
Block a user