mirror of
https://github.com/tiqi-group/pydase.git
synced 2025-12-20 13:11:20 +01:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c7d452d7db | ||
|
|
b7926b730d | ||
|
|
0c175fc706 | ||
|
|
7d21bca8b1 | ||
|
|
d1628ae8c9 | ||
|
|
441658ebc1 | ||
|
|
99c7ad0ec8 | ||
|
|
24a01c0982 | ||
|
|
b8a52c2e6a | ||
|
|
7aacc21010 | ||
|
|
8787cb0509 | ||
|
|
8971cebfcd | ||
|
|
f2cf0d9c1a | ||
|
|
36c863e845 | ||
|
|
836c1e14df | ||
|
|
dba036c6b3 | ||
|
|
8b1f1ef1b1 | ||
|
|
698db4881b | ||
|
|
d709d43d75 | ||
|
|
691bf809cb |
@@ -32,6 +32,9 @@ const reducer = (state: State, action: Action): State => {
|
||||
case 'SET_DATA':
|
||||
return action.data;
|
||||
case 'UPDATE_ATTRIBUTE': {
|
||||
if (state === null) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
...state,
|
||||
value: setNestedValueByPath(state.value, action.fullAccessPath, action.newValue)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "pydase"
|
||||
version = "0.7.1"
|
||||
version = "0.7.3"
|
||||
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>"]
|
||||
readme = "README.md"
|
||||
|
||||
@@ -44,6 +44,12 @@ class DataServiceObserver(PropertyObserver):
|
||||
|
||||
self._update_cache_value(full_access_path, value, cached_value_dict)
|
||||
|
||||
cached_value_dict = deepcopy(
|
||||
self.state_manager._data_service_cache.get_value_dict_from_cache(
|
||||
full_access_path
|
||||
)
|
||||
)
|
||||
|
||||
for callback in self._notification_callbacks:
|
||||
callback(full_access_path, value, cached_value_dict)
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"files": {
|
||||
"main.css": "/static/css/main.7ef670d5.css",
|
||||
"main.js": "/static/js/main.6d1d080e.js",
|
||||
"main.js": "/static/js/main.ca0b4800.js",
|
||||
"index.html": "/index.html",
|
||||
"main.7ef670d5.css.map": "/static/css/main.7ef670d5.css.map",
|
||||
"main.6d1d080e.js.map": "/static/js/main.6d1d080e.js.map"
|
||||
"main.ca0b4800.js.map": "/static/js/main.ca0b4800.js.map"
|
||||
},
|
||||
"entrypoints": [
|
||||
"static/css/main.7ef670d5.css",
|
||||
"static/js/main.6d1d080e.js"
|
||||
"static/js/main.ca0b4800.js"
|
||||
]
|
||||
}
|
||||
@@ -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.6d1d080e.js"></script><link href="/static/css/main.7ef670d5.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.ca0b4800.js"></script><link href="/static/css/main.7ef670d5.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
1
src/pydase/frontend/static/js/main.ca0b4800.js.map
Normal file
1
src/pydase/frontend/static/js/main.ca0b4800.js.map
Normal file
File diff suppressed because one or more lines are too long
@@ -9,7 +9,6 @@ from pydase.data_service.data_service_observer import DataServiceObserver
|
||||
from pydase.data_service.state_manager import StateManager
|
||||
from pydase.utils.helpers import get_object_attr_from_path_list
|
||||
from pydase.utils.logging import SocketIOHandler
|
||||
from pydase.utils.serializer import dump
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -97,11 +96,6 @@ def setup_sio_server(
|
||||
full_access_path: str, value: Any, cached_value_dict: dict[str, Any]
|
||||
) -> None:
|
||||
if cached_value_dict != {}:
|
||||
serialized_value = dump(value)
|
||||
if cached_value_dict["type"] != "method":
|
||||
cached_value_dict["type"] = serialized_value["type"]
|
||||
|
||||
cached_value_dict["value"] = serialized_value["value"]
|
||||
|
||||
async def notify() -> None:
|
||||
try:
|
||||
|
||||
@@ -267,15 +267,28 @@ def set_nested_value_by_path(
|
||||
logger.error(e)
|
||||
return
|
||||
|
||||
# setting the new value
|
||||
serialized_value = dump(value)
|
||||
if "readonly" in current_dict:
|
||||
if current_dict["type"] != "method":
|
||||
current_dict["type"] = serialized_value["type"]
|
||||
current_dict["value"] = serialized_value["value"]
|
||||
keys_to_keep = set(serialized_value.keys())
|
||||
|
||||
if current_dict == {}: # adding an attribute / element to a list or dict
|
||||
pass
|
||||
elif current_dict["type"] == "method": # state change of task
|
||||
keys_to_keep = set(current_dict.keys())
|
||||
|
||||
serialized_value = current_dict
|
||||
serialized_value["value"] = value.name if isinstance(value, Enum) else None
|
||||
else:
|
||||
# attribute-specific information should not be overwritten by new value
|
||||
serialized_value.pop("readonly")
|
||||
serialized_value.pop("doc")
|
||||
|
||||
current_dict.update(serialized_value)
|
||||
|
||||
# removes keys that are not present in the serialized new value
|
||||
for key in list(current_dict.keys()):
|
||||
if key not in keys_to_keep:
|
||||
current_dict.pop(key, None)
|
||||
|
||||
|
||||
def get_nested_dict_by_path(
|
||||
serialization_dict: dict[str, Any],
|
||||
|
||||
141
tests/components/test_image.py
Normal file
141
tests/components/test_image.py
Normal file
@@ -0,0 +1,141 @@
|
||||
import logging
|
||||
|
||||
import pydase
|
||||
import pydase.components
|
||||
from pydase.data_service.data_service_observer import DataServiceObserver
|
||||
from pydase.data_service.state_manager import StateManager
|
||||
from pydase.utils.serializer import dump
|
||||
from pytest import LogCaptureFixture
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def test_image_functions(caplog: LogCaptureFixture) -> None:
|
||||
class MyService(pydase.DataService):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.my_image = pydase.components.Image()
|
||||
|
||||
service_instance = MyService()
|
||||
state_manager = StateManager(service_instance)
|
||||
DataServiceObserver(state_manager)
|
||||
|
||||
service_instance.my_image.load_from_url("https://cataas.com/cat")
|
||||
|
||||
caplog.clear()
|
||||
|
||||
|
||||
def test_image_serialization() -> None:
|
||||
class MyService(pydase.DataService):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.my_image = pydase.components.Image()
|
||||
|
||||
assert dump(MyService()) == {
|
||||
"name": "MyService",
|
||||
"type": "DataService",
|
||||
"value": {
|
||||
"my_image": {
|
||||
"name": "Image",
|
||||
"type": "Image",
|
||||
"value": {
|
||||
"format": {
|
||||
"type": "str",
|
||||
"value": "",
|
||||
"readonly": True,
|
||||
"doc": None,
|
||||
},
|
||||
"load_from_base64": {
|
||||
"type": "method",
|
||||
"value": None,
|
||||
"readonly": True,
|
||||
"doc": None,
|
||||
"async": False,
|
||||
"signature": {
|
||||
"parameters": {
|
||||
"value_": {
|
||||
"annotation": "<class 'bytes'>",
|
||||
"default": {},
|
||||
},
|
||||
"format_": {
|
||||
"annotation": "str | None",
|
||||
"default": {
|
||||
"type": "NoneType",
|
||||
"value": None,
|
||||
"readonly": False,
|
||||
"doc": None,
|
||||
},
|
||||
},
|
||||
},
|
||||
"return_annotation": {},
|
||||
},
|
||||
"frontend_render": False,
|
||||
},
|
||||
"load_from_matplotlib_figure": {
|
||||
"type": "method",
|
||||
"value": None,
|
||||
"readonly": True,
|
||||
"doc": None,
|
||||
"async": False,
|
||||
"signature": {
|
||||
"parameters": {
|
||||
"fig": {"annotation": "Figure", "default": {}},
|
||||
"format_": {
|
||||
"annotation": "<class 'str'>",
|
||||
"default": {
|
||||
"type": "str",
|
||||
"value": "png",
|
||||
"readonly": False,
|
||||
"doc": None,
|
||||
},
|
||||
},
|
||||
},
|
||||
"return_annotation": {},
|
||||
},
|
||||
"frontend_render": False,
|
||||
},
|
||||
"load_from_path": {
|
||||
"type": "method",
|
||||
"value": None,
|
||||
"readonly": True,
|
||||
"doc": None,
|
||||
"async": False,
|
||||
"signature": {
|
||||
"parameters": {
|
||||
"path": {
|
||||
"annotation": "pathlib.Path | str",
|
||||
"default": {},
|
||||
}
|
||||
},
|
||||
"return_annotation": {},
|
||||
},
|
||||
"frontend_render": False,
|
||||
},
|
||||
"load_from_url": {
|
||||
"type": "method",
|
||||
"value": None,
|
||||
"readonly": True,
|
||||
"doc": None,
|
||||
"async": False,
|
||||
"signature": {
|
||||
"parameters": {
|
||||
"url": {"annotation": "<class 'str'>", "default": {}}
|
||||
},
|
||||
"return_annotation": {},
|
||||
},
|
||||
"frontend_render": False,
|
||||
},
|
||||
"value": {
|
||||
"type": "str",
|
||||
"value": "",
|
||||
"readonly": True,
|
||||
"doc": None,
|
||||
},
|
||||
},
|
||||
"readonly": False,
|
||||
"doc": None,
|
||||
}
|
||||
},
|
||||
"readonly": False,
|
||||
"doc": None,
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import asyncio
|
||||
import enum
|
||||
from enum import Enum
|
||||
from typing import Any
|
||||
|
||||
@@ -18,6 +19,11 @@ from pydase.utils.serializer import (
|
||||
)
|
||||
|
||||
|
||||
class MyEnum(enum.Enum):
|
||||
RUNNING = "running"
|
||||
FINISHED = "finished"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"test_input, expected",
|
||||
[
|
||||
@@ -396,33 +402,80 @@ def setup_dict() -> dict[str, Any]:
|
||||
class ServiceClass(pydase.DataService):
|
||||
attr1 = 1.0
|
||||
attr2 = MySubclass()
|
||||
enum_attr = MyEnum.RUNNING
|
||||
attr_list = [0, 1, MySubclass()]
|
||||
|
||||
def my_task(self) -> None:
|
||||
pass
|
||||
|
||||
return ServiceClass().serialize()["value"]
|
||||
|
||||
|
||||
def test_update_attribute(setup_dict) -> None:
|
||||
def test_update_attribute(setup_dict: dict[str, Any]) -> None:
|
||||
set_nested_value_by_path(setup_dict, "attr1", 15)
|
||||
assert setup_dict["attr1"]["value"] == 15
|
||||
|
||||
|
||||
def test_update_nested_attribute(setup_dict) -> None:
|
||||
def test_update_nested_attribute(setup_dict: dict[str, Any]) -> None:
|
||||
set_nested_value_by_path(setup_dict, "attr2.attr3", 25.0)
|
||||
assert setup_dict["attr2"]["value"]["attr3"]["value"] == 25.0
|
||||
|
||||
|
||||
def test_update_list_entry(setup_dict) -> None:
|
||||
def test_update_float_attribute_to_enum(setup_dict: dict[str, Any]) -> None:
|
||||
set_nested_value_by_path(setup_dict, "attr2.attr3", MyEnum.RUNNING)
|
||||
assert setup_dict["attr2"]["value"]["attr3"] == {
|
||||
"doc": None,
|
||||
"enum": {"FINISHED": "finished", "RUNNING": "running"},
|
||||
"readonly": False,
|
||||
"type": "Enum",
|
||||
"value": "RUNNING",
|
||||
}
|
||||
|
||||
|
||||
def test_update_enum_attribute_to_float(setup_dict: dict[str, Any]) -> None:
|
||||
set_nested_value_by_path(setup_dict, "enum_attr", 1.01)
|
||||
assert setup_dict["enum_attr"] == {
|
||||
"doc": None,
|
||||
"readonly": False,
|
||||
"type": "float",
|
||||
"value": 1.01,
|
||||
}
|
||||
|
||||
|
||||
def test_update_task_state(setup_dict: dict[str, Any]) -> None:
|
||||
assert setup_dict["my_task"] == {
|
||||
"async": False,
|
||||
"doc": None,
|
||||
"frontend_render": False,
|
||||
"readonly": True,
|
||||
"signature": {"parameters": {}, "return_annotation": {}},
|
||||
"type": "method",
|
||||
"value": None,
|
||||
}
|
||||
set_nested_value_by_path(setup_dict, "my_task", TaskStatus.RUNNING)
|
||||
assert setup_dict["my_task"] == {
|
||||
"async": False,
|
||||
"doc": None,
|
||||
"frontend_render": False,
|
||||
"readonly": True,
|
||||
"signature": {"parameters": {}, "return_annotation": {}},
|
||||
"type": "method",
|
||||
"value": "RUNNING",
|
||||
}
|
||||
|
||||
|
||||
def test_update_list_entry(setup_dict: dict[str, Any]) -> None:
|
||||
set_nested_value_by_path(setup_dict, "attr_list[1]", 20)
|
||||
assert setup_dict["attr_list"]["value"][1]["value"] == 20
|
||||
|
||||
|
||||
def test_update_list_append(setup_dict) -> None:
|
||||
def test_update_list_append(setup_dict: dict[str, Any]) -> None:
|
||||
set_nested_value_by_path(setup_dict, "attr_list[3]", 20)
|
||||
assert setup_dict["attr_list"]["value"][3]["value"] == 20
|
||||
|
||||
|
||||
def test_update_invalid_list_index(
|
||||
setup_dict, caplog: pytest.LogCaptureFixture
|
||||
setup_dict: dict[str, Any], caplog: pytest.LogCaptureFixture
|
||||
) -> None:
|
||||
set_nested_value_by_path(setup_dict, "attr_list[10]", 30)
|
||||
assert (
|
||||
|
||||
Reference in New Issue
Block a user