Merge pull request #249 from tiqi-group/feat/improve-client-proxy-serialization

chore: improve client proxy serialization
This commit is contained in:
Mose Müller
2025-06-23 14:11:31 +02:00
committed by GitHub
3 changed files with 45 additions and 12 deletions

View File

@@ -240,12 +240,11 @@ class Client:
self.proxy, serialized_object=serialized_object self.proxy, serialized_object=serialized_object
) )
serialized_object["type"] = "DeviceConnection" serialized_object["type"] = "DeviceConnection"
if self.proxy._service_representation is not None: # need to use object.__setattr__ to not trigger an observer notification
# need to use object.__setattr__ to not trigger an observer notification object.__setattr__(self.proxy, "_service_representation", serialized_object)
object.__setattr__(self.proxy, "_service_representation", serialized_object)
if TYPE_CHECKING: if TYPE_CHECKING:
self.proxy._service_representation = serialized_object # type: ignore self.proxy._service_representation = serialized_object # type: ignore
self.proxy._notify_changed("", self.proxy) self.proxy._notify_changed("", self.proxy)
self.proxy._connected = True self.proxy._connected = True

View File

@@ -65,19 +65,31 @@ class ProxyClass(ProxyClassMixin, pydase.components.DeviceConnection):
self.reconnect = reconnect self.reconnect = reconnect
def serialize(self) -> SerializedObject: def serialize(self) -> SerializedObject:
if self._service_representation is None: current_loop = asyncio.get_event_loop()
serialization_future = cast(
if not self.connected or current_loop == self._loop:
logger.debug(
"Client not connected, or called from within client event loop - using "
"fallback serialization"
)
if self._service_representation is None:
serialized_service = pydase.components.DeviceConnection().serialize()
else:
serialized_service = self._service_representation
else:
future = cast(
"asyncio.Future[SerializedDataService]", "asyncio.Future[SerializedDataService]",
asyncio.run_coroutine_threadsafe( asyncio.run_coroutine_threadsafe(
self._sio.call("service_serialization"), self._loop self._sio.call("service_serialization"), self._loop
), ),
) )
result = future.result()
# need to use object.__setattr__ to not trigger an observer notification # need to use object.__setattr__ to not trigger an observer notification
object.__setattr__( object.__setattr__(self, "_service_representation", result)
self, "_service_representation", serialization_future.result()
)
if TYPE_CHECKING: if TYPE_CHECKING:
self._service_representation = serialization_future.result() self._service_representation = result
serialized_service = result
device_connection_value = cast( device_connection_value = cast(
"dict[str, SerializedObject]", "dict[str, SerializedObject]",
@@ -93,7 +105,7 @@ class ProxyClass(ProxyClassMixin, pydase.components.DeviceConnection):
"dict[str, SerializedObject]", "dict[str, SerializedObject]",
# need to deepcopy to not overwrite the _service_representation dict # need to deepcopy to not overwrite the _service_representation dict
# when adding a prefix with add_prefix_to_full_access_path # when adding a prefix with add_prefix_to_full_access_path
deepcopy(self._service_representation["value"]), deepcopy(serialized_service["value"]),
), ),
**device_connection_value, **device_connection_value,
} }

View File

@@ -0,0 +1,22 @@
import asyncio
from unittest.mock import AsyncMock, call, patch
import pytest
from pydase import components
from pydase.client.proxy_class import ProxyClass
@pytest.mark.asyncio
async def test_serialize_fallback_inside_event_loop() -> None:
loop = asyncio.get_running_loop()
mock_sio = AsyncMock()
proxy = ProxyClass(sio_client=mock_sio, loop=loop, reconnect=lambda: None)
with patch.object(
components.DeviceConnection, "serialize", return_value={"value": {}}
) as mock_fallback:
result = proxy.serialize()
mock_fallback.assert_has_calls(calls=[call(), call()])
assert isinstance(result, dict)