Merge pull request #98 from tiqi-group/refactor/passing_full_serialization_dict_to_frontend

Refactor: passing full serialization dict to frontend
This commit is contained in:
Mose Müller 2024-02-01 09:27:29 +01:00 committed by GitHub
commit e60880fd30
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 43 additions and 35 deletions

View File

@ -1,10 +1,6 @@
import { useCallback, useEffect, useReducer, useState } from 'react'; import { useCallback, useEffect, useReducer, useState } from 'react';
import { Navbar, Form, Offcanvas, Container } from 'react-bootstrap'; import { Navbar, Form, Offcanvas, Container } from 'react-bootstrap';
import { hostname, port, socket } from './socket'; import { hostname, port, socket } from './socket';
import {
DataServiceComponent,
DataServiceJSON
} from './components/DataServiceComponent';
import './App.css'; import './App.css';
import { import {
Notifications, Notifications,
@ -14,6 +10,7 @@ import {
import { ConnectionToast } from './components/ConnectionToast'; import { ConnectionToast } from './components/ConnectionToast';
import { SerializedValue, setNestedValueByPath, State } from './utils/stateUtils'; import { SerializedValue, setNestedValueByPath, State } from './utils/stateUtils';
import { WebSettingsContext, WebSetting } from './WebSettings'; import { WebSettingsContext, WebSetting } from './WebSettings';
import { Attribute, GenericComponent } from './components/GenericComponent';
type Action = type Action =
| { type: 'SET_DATA'; data: State } | { type: 'SET_DATA'; data: State }
@ -35,7 +32,10 @@ const reducer = (state: State, action: Action): State => {
case 'SET_DATA': case 'SET_DATA':
return action.data; return action.data;
case 'UPDATE_ATTRIBUTE': { case 'UPDATE_ATTRIBUTE': {
return setNestedValueByPath(state, action.fullAccessPath, action.newValue); return {
...state,
value: setNestedValueByPath(state.value, action.fullAccessPath, action.newValue)
};
} }
default: default:
throw new Error(); throw new Error();
@ -184,9 +184,10 @@ const App = () => {
<div className="App navbarOffset"> <div className="App navbarOffset">
<WebSettingsContext.Provider value={webSettings}> <WebSettingsContext.Provider value={webSettings}>
<DataServiceComponent <GenericComponent
name={''} name=""
props={state as DataServiceJSON} parentPath=""
attribute={state as Attribute}
isInstantUpdate={isInstantUpdate} isInstantUpdate={isInstantUpdate}
addNotification={addNotification} addNotification={addNotification}
/> />

View File

@ -6,7 +6,12 @@ export interface SerializedValue {
async?: boolean; async?: boolean;
parameters?: unknown; parameters?: unknown;
} }
export type State = Record<string, SerializedValue> | null; export type State = {
type: string;
value: Record<string, SerializedValue> | null;
readonly: boolean;
doc: string | null;
};
export function setNestedValueByPath( export function setNestedValueByPath(
serializationDict: Record<string, SerializedValue>, serializationDict: Record<string, SerializedValue>,

View File

@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "pydase" name = "pydase"
version = "0.5.2" version = "0.6.0"
description = "A flexible and robust Python library for creating, managing, and interacting with data services, with built-in support for web and RPC servers, and customizable features for diverse use cases." description = "A flexible and robust Python library for creating, managing, and interacting with data services, with built-in support for web and RPC servers, and customizable features for diverse use cases."
authors = ["Mose Mueller <mosmuell@ethz.ch>"] authors = ["Mose Mueller <mosmuell@ethz.ch>"]
readme = "README.md" readme = "README.md"

View File

@ -196,7 +196,7 @@ class DataService(rpyc.Service, AbstractDataService):
) )
# Traverse the serialized representation and set the attributes of the class # Traverse the serialized representation and set the attributes of the class
serialized_class = self.serialize() serialized_class = self.serialize()["value"]
for path in generate_serialized_data_paths(json_dict): for path in generate_serialized_data_paths(json_dict):
nested_json_dict = get_nested_dict_by_path(json_dict, path) nested_json_dict = get_nested_dict_by_path(json_dict, path)
value = nested_json_dict["value"] value = nested_json_dict["value"]
@ -248,7 +248,7 @@ class DataService(rpyc.Service, AbstractDataService):
Returns: Returns:
dict: The serialized instance. dict: The serialized instance.
""" """
return Serializer.serialize_object(self)["value"] return Serializer.serialize_object(self)
def update_DataService_attribute( # noqa: N802 def update_DataService_attribute( # noqa: N802
self, self,

View File

@ -30,10 +30,10 @@ class DataServiceCache:
self._cache = self.service.serialize() self._cache = self.service.serialize()
def update_cache(self, full_access_path: str, value: Any) -> None: def update_cache(self, full_access_path: str, value: Any) -> None:
set_nested_value_by_path(self._cache, full_access_path, value) set_nested_value_by_path(self._cache["value"], full_access_path, value)
def get_value_dict_from_cache(self, full_access_path: str) -> dict[str, Any]: def get_value_dict_from_cache(self, full_access_path: str) -> dict[str, Any]:
try: try:
return get_nested_dict_by_path(self._cache, full_access_path) return get_nested_dict_by_path(self._cache["value"], full_access_path)
except (SerializationPathError, SerializationValueError, KeyError): except (SerializationPathError, SerializationValueError, KeyError):
return {} return {}

View File

@ -126,7 +126,7 @@ class StateManager:
if self.filename is not None: if self.filename is not None:
with open(self.filename, "w") as f: with open(self.filename, "w") as f:
json.dump(self.cache, f, indent=4) json.dump(self.cache["value"], f, indent=4)
else: else:
logger.info( logger.info(
"State manager was not initialised with a filename. Skipping " "State manager was not initialised with a filename. Skipping "
@ -191,7 +191,7 @@ class StateManager:
value: The new value to set for the attribute. value: The new value to set for the attribute.
""" """
current_value_dict = get_nested_dict_by_path(self.cache, path) current_value_dict = get_nested_dict_by_path(self.cache["value"], path)
# This will also filter out methods as they are 'read-only' # This will also filter out methods as they are 'read-only'
if current_value_dict["readonly"]: if current_value_dict["readonly"]:
@ -234,7 +234,7 @@ class StateManager:
# Update path to reflect the attribute without list indices # Update path to reflect the attribute without list indices
path = ".".join([*parent_path_list, attr_name]) path = ".".join([*parent_path_list, attr_name])
attr_cache_type = get_nested_dict_by_path(self.cache, path)["type"] attr_cache_type = get_nested_dict_by_path(self.cache["value"], path)["type"]
# Traverse the object according to the path parts # Traverse the object according to the path parts
target_obj = get_object_attr_from_path_list(self.service, parent_path_list) target_obj = get_object_attr_from_path_list(self.service, parent_path_list)
@ -273,7 +273,7 @@ class StateManager:
return has_decorator return has_decorator
cached_serialization_dict = get_nested_dict_by_path( cached_serialization_dict = get_nested_dict_by_path(
self.cache, full_access_path self.cache["value"], full_access_path
) )
if cached_serialization_dict["value"] == "method": if cached_serialization_dict["value"] == "method":

View File

@ -1,13 +1,13 @@
{ {
"files": { "files": {
"main.css": "/static/css/main.2d8458eb.css", "main.css": "/static/css/main.2d8458eb.css",
"main.js": "/static/js/main.dba067e7.js", "main.js": "/static/js/main.1b1d7066.js",
"index.html": "/index.html", "index.html": "/index.html",
"main.2d8458eb.css.map": "/static/css/main.2d8458eb.css.map", "main.2d8458eb.css.map": "/static/css/main.2d8458eb.css.map",
"main.dba067e7.js.map": "/static/js/main.dba067e7.js.map" "main.1b1d7066.js.map": "/static/js/main.1b1d7066.js.map"
}, },
"entrypoints": [ "entrypoints": [
"static/css/main.2d8458eb.css", "static/css/main.2d8458eb.css",
"static/js/main.dba067e7.js" "static/js/main.1b1d7066.js"
] ]
} }

View File

@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site displaying a pydase UI."/><link rel="apple-touch-icon" href="/logo192.png"/><link rel="manifest" href="/manifest.json"/><title>pydase App</title><script defer="defer" src="/static/js/main.dba067e7.js"></script><link href="/static/css/main.2d8458eb.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html> <!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site displaying a pydase UI."/><link rel="apple-touch-icon" href="/logo192.png"/><link rel="manifest" href="/manifest.json"/><title>pydase App</title><script defer="defer" src="/static/js/main.1b1d7066.js"></script><link href="/static/css/main.2d8458eb.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -126,7 +126,7 @@ class WebServer:
@property @property
def web_settings(self) -> dict[str, dict[str, Any]]: def web_settings(self) -> dict[str, dict[str, Any]]:
current_web_settings = self._get_web_settings_from_file() current_web_settings = self._get_web_settings_from_file()
for path in generate_serialized_data_paths(self.state_manager.cache): for path in generate_serialized_data_paths(self.state_manager.cache["value"]):
if path in current_web_settings: if path in current_web_settings:
continue continue

View File

@ -91,7 +91,7 @@ class Service(pydase.DataService):
self._property_attr = value self._property_attr = value
CURRENT_STATE = Service().serialize() CURRENT_STATE = Service().serialize()["value"]
LOAD_STATE = { LOAD_STATE = {
"list_attr": { "list_attr": {

View File

@ -323,7 +323,7 @@ def test_derived_data_service_serialization() -> None:
@pytest.fixture @pytest.fixture
def setup_dict(): def setup_dict() -> dict[str, Any]:
class MySubclass(pydase.DataService): class MySubclass(pydase.DataService):
attr3 = 1.0 attr3 = 1.0
list_attr = [1.0, 1] list_attr = [1.0, 1]
@ -333,30 +333,32 @@ def setup_dict():
attr2 = MySubclass() attr2 = MySubclass()
attr_list = [0, 1, MySubclass()] attr_list = [0, 1, MySubclass()]
return ServiceClass().serialize() return ServiceClass().serialize()["value"]
def test_update_attribute(setup_dict): def test_update_attribute(setup_dict) -> None:
set_nested_value_by_path(setup_dict, "attr1", 15) set_nested_value_by_path(setup_dict, "attr1", 15)
assert setup_dict["attr1"]["value"] == 15 assert setup_dict["attr1"]["value"] == 15
def test_update_nested_attribute(setup_dict): def test_update_nested_attribute(setup_dict) -> None:
set_nested_value_by_path(setup_dict, "attr2.attr3", 25.0) set_nested_value_by_path(setup_dict, "attr2.attr3", 25.0)
assert setup_dict["attr2"]["value"]["attr3"]["value"] == 25.0 assert setup_dict["attr2"]["value"]["attr3"]["value"] == 25.0
def test_update_list_entry(setup_dict): def test_update_list_entry(setup_dict) -> None:
set_nested_value_by_path(setup_dict, "attr_list[1]", 20) set_nested_value_by_path(setup_dict, "attr_list[1]", 20)
assert setup_dict["attr_list"]["value"][1]["value"] == 20 assert setup_dict["attr_list"]["value"][1]["value"] == 20
def test_update_list_append(setup_dict): def test_update_list_append(setup_dict) -> None:
set_nested_value_by_path(setup_dict, "attr_list[3]", 20) set_nested_value_by_path(setup_dict, "attr_list[3]", 20)
assert setup_dict["attr_list"]["value"][3]["value"] == 20 assert setup_dict["attr_list"]["value"][3]["value"] == 20
def test_update_invalid_list_index(setup_dict, caplog: pytest.LogCaptureFixture): def test_update_invalid_list_index(
setup_dict, caplog: pytest.LogCaptureFixture
) -> None:
set_nested_value_by_path(setup_dict, "attr_list[10]", 30) set_nested_value_by_path(setup_dict, "attr_list[10]", 30)
assert ( assert (
"Error occured trying to change 'attr_list[10]': list index " "Error occured trying to change 'attr_list[10]': list index "