mirror of
https://github.com/tiqi-group/pydase.git
synced 2025-12-20 05:01:19 +01:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c45f1bd489 | ||
|
|
5784818e5a | ||
|
|
64a7097568 | ||
|
|
5ef382728c | ||
|
|
51d6189002 | ||
|
|
71e29c890e | ||
|
|
6e407ba1d6 | ||
|
|
4fb5e56aa8 | ||
|
|
d55ba3a85f | ||
|
|
265d9a7ef5 | ||
|
|
4cd36b4a2b | ||
|
|
1b2ff38aff | ||
|
|
4b243985e8 | ||
|
|
8615bdeadc | ||
|
|
d24893a989 | ||
|
|
661603ef71 | ||
|
|
d6947b0f43 |
2
.github/workflows/python-package.yml
vendored
2
.github/workflows/python-package.yml
vendored
@@ -2,6 +2,8 @@
|
|||||||
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python
|
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python
|
||||||
|
|
||||||
name: Python package
|
name: Python package
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ const createDisplayNameFromAccessPath = (fullAccessPath: string): string => {
|
|||||||
|
|
||||||
function changeCallback(
|
function changeCallback(
|
||||||
value: SerializedObject,
|
value: SerializedObject,
|
||||||
callback: (ack: unknown) => void = () => {},
|
callback: (ack: undefined | SerializedObject) => void = () => {},
|
||||||
) {
|
) {
|
||||||
updateValue(value, callback);
|
updateValue(value, callback);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,10 @@ interface NumberComponentProps {
|
|||||||
isInstantUpdate: boolean;
|
isInstantUpdate: boolean;
|
||||||
unit?: string;
|
unit?: string;
|
||||||
addNotification: (message: string, levelname?: LevelName) => void;
|
addNotification: (message: string, levelname?: LevelName) => void;
|
||||||
changeCallback?: (value: SerializedObject, callback?: (ack: unknown) => void) => void;
|
changeCallback?: (
|
||||||
|
value: SerializedObject,
|
||||||
|
callback?: (ack: undefined | SerializedObject) => void,
|
||||||
|
) => void;
|
||||||
displayName?: string;
|
displayName?: string;
|
||||||
id: string;
|
id: string;
|
||||||
}
|
}
|
||||||
@@ -217,6 +220,15 @@ export const NumberComponent = React.memo((props: NumberComponentProps) => {
|
|||||||
id,
|
id,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
const handleChange = (newValue: SerializedObject) => {
|
||||||
|
changeCallback(newValue, (result: undefined | SerializedObject) => {
|
||||||
|
if (result === undefined) return;
|
||||||
|
if (result.type == "Exception") {
|
||||||
|
setInputString(value.toString());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// Create a state for the cursor position
|
// Create a state for the cursor position
|
||||||
const cursorPositionRef = useRef<number | null>(null);
|
const cursorPositionRef = useRef<number | null>(null);
|
||||||
|
|
||||||
@@ -319,7 +331,7 @@ export const NumberComponent = React.memo((props: NumberComponentProps) => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
changeCallback(serializedObject);
|
handleChange(serializedObject);
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
console.debug(key);
|
console.debug(key);
|
||||||
@@ -350,7 +362,7 @@ export const NumberComponent = React.memo((props: NumberComponentProps) => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
changeCallback(serializedObject);
|
handleChange(serializedObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
setInputString(newValue);
|
setInputString(newValue);
|
||||||
@@ -384,7 +396,7 @@ export const NumberComponent = React.memo((props: NumberComponentProps) => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
changeCallback(serializedObject);
|
handleChange(serializedObject);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -19,7 +19,10 @@ interface SliderComponentProps {
|
|||||||
stepSize: NumberObject;
|
stepSize: NumberObject;
|
||||||
isInstantUpdate: boolean;
|
isInstantUpdate: boolean;
|
||||||
addNotification: (message: string, levelname?: LevelName) => void;
|
addNotification: (message: string, levelname?: LevelName) => void;
|
||||||
changeCallback?: (value: SerializedObject, callback?: (ack: unknown) => void) => void;
|
changeCallback?: (
|
||||||
|
value: SerializedObject,
|
||||||
|
callback?: (ack: undefined | SerializedObject) => void,
|
||||||
|
) => void;
|
||||||
displayName: string;
|
displayName: string;
|
||||||
id: string;
|
id: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export const socket = io(URL, {
|
|||||||
|
|
||||||
export const updateValue = (
|
export const updateValue = (
|
||||||
serializedObject: SerializedObject,
|
serializedObject: SerializedObject,
|
||||||
callback?: (ack: unknown) => void,
|
callback?: (ack: undefined | SerializedObject) => void,
|
||||||
) => {
|
) => {
|
||||||
if (callback) {
|
if (callback) {
|
||||||
socket.emit(
|
socket.emit(
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "pydase"
|
name = "pydase"
|
||||||
version = "0.10.18"
|
version = "0.10.21"
|
||||||
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 = [
|
authors = [
|
||||||
{name = "Mose Müller",email = "mosemueller@gmail.com"}
|
{name = "Mose Müller",email = "mosemueller@gmail.com"}
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ from pydase.observer_pattern.observable.observable import (
|
|||||||
from pydase.utils.helpers import (
|
from pydase.utils.helpers import (
|
||||||
get_class_and_instance_attributes,
|
get_class_and_instance_attributes,
|
||||||
is_descriptor,
|
is_descriptor,
|
||||||
is_property_attribute,
|
|
||||||
)
|
)
|
||||||
from pydase.utils.serialization.serializer import (
|
from pydase.utils.serialization.serializer import (
|
||||||
Serializer,
|
Serializer,
|
||||||
@@ -28,9 +27,6 @@ class DataService(AbstractDataService):
|
|||||||
self.__check_instance_classes()
|
self.__check_instance_classes()
|
||||||
|
|
||||||
def __setattr__(self, name: str, value: Any, /) -> None:
|
def __setattr__(self, name: str, value: Any, /) -> None:
|
||||||
# Check and warn for unexpected type changes in attributes
|
|
||||||
self._warn_on_type_change(name, value)
|
|
||||||
|
|
||||||
# every class defined by the user should inherit from DataService if it is
|
# every class defined by the user should inherit from DataService if it is
|
||||||
# assigned to a public attribute
|
# assigned to a public attribute
|
||||||
if not name.startswith("_") and not inspect.isfunction(value):
|
if not name.startswith("_") and not inspect.isfunction(value):
|
||||||
@@ -39,21 +35,6 @@ class DataService(AbstractDataService):
|
|||||||
# Set the attribute
|
# Set the attribute
|
||||||
super().__setattr__(name, value)
|
super().__setattr__(name, value)
|
||||||
|
|
||||||
def _warn_on_type_change(self, attr_name: str, new_value: Any) -> None:
|
|
||||||
if is_property_attribute(self, attr_name):
|
|
||||||
return
|
|
||||||
|
|
||||||
current_value = getattr(self, attr_name, None)
|
|
||||||
if self._is_unexpected_type_change(current_value, new_value):
|
|
||||||
logger.warning(
|
|
||||||
"Type of '%s' changed from '%s' to '%s'. This may have unwanted "
|
|
||||||
"side effects! Consider setting it to '%s' directly.",
|
|
||||||
attr_name,
|
|
||||||
type(current_value).__name__,
|
|
||||||
type(new_value).__name__,
|
|
||||||
type(current_value).__name__,
|
|
||||||
)
|
|
||||||
|
|
||||||
def _is_unexpected_type_change(self, current_value: Any, new_value: Any) -> bool:
|
def _is_unexpected_type_change(self, current_value: Any, new_value: Any) -> bool:
|
||||||
return (
|
return (
|
||||||
isinstance(current_value, float) and not isinstance(new_value, float)
|
isinstance(current_value, float) and not isinstance(new_value, float)
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -7,7 +7,7 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<meta name="theme-color" content="#000000" />
|
<meta name="theme-color" content="#000000" />
|
||||||
<meta name="description" content="Web site displaying a pydase UI." />
|
<meta name="description" content="Web site displaying a pydase UI." />
|
||||||
<script type="module" crossorigin src="/assets/index-XZbNXHJp.js"></script>
|
<script type="module" crossorigin src="/assets/index-CKS_bS2p.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-Cs09d5Pk.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-Cs09d5Pk.css">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ def get_property_dependencies(prop: property, prefix: str = "") -> list[str]:
|
|||||||
|
|
||||||
class PropertyObserver(Observer):
|
class PropertyObserver(Observer):
|
||||||
def __init__(self, observable: Observable) -> None:
|
def __init__(self, observable: Observable) -> None:
|
||||||
|
self.property_deps_dict: dict[str, list[str]] = {}
|
||||||
super().__init__(observable)
|
super().__init__(observable)
|
||||||
self._update_property_deps_dict()
|
self._update_property_deps_dict()
|
||||||
|
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ class Serializer:
|
|||||||
"doc": None,
|
"doc": None,
|
||||||
"readonly": True,
|
"readonly": True,
|
||||||
"type": "Exception",
|
"type": "Exception",
|
||||||
"value": obj.args[0],
|
"value": obj.args[0] if len(obj.args) > 0 else "",
|
||||||
"name": obj.__class__.__name__,
|
"name": obj.__class__.__name__,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,38 +1,13 @@
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import pydase
|
|
||||||
import pydase.units as u
|
|
||||||
import pytest
|
import pytest
|
||||||
from pydase import DataService
|
|
||||||
from pydase.data_service.data_service_observer import DataServiceObserver
|
|
||||||
from pydase.data_service.state_manager import StateManager
|
|
||||||
from pydase.utils.decorators import FunctionDefinitionError, frontend
|
|
||||||
from pytest import LogCaptureFixture
|
from pytest import LogCaptureFixture
|
||||||
|
|
||||||
|
import pydase
|
||||||
def test_unexpected_type_change_warning(caplog: LogCaptureFixture) -> None:
|
import pydase.units as u
|
||||||
class ServiceClass(DataService):
|
from pydase import DataService
|
||||||
attr_1 = 1.0
|
from pydase.utils.decorators import FunctionDefinitionError, frontend
|
||||||
current = 1.0 * u.units.A
|
|
||||||
|
|
||||||
service_instance = ServiceClass()
|
|
||||||
state_manager = StateManager(service_instance)
|
|
||||||
DataServiceObserver(state_manager)
|
|
||||||
service_instance.attr_1 = 2
|
|
||||||
|
|
||||||
assert "'attr_1' changed to '2'" in caplog.text
|
|
||||||
assert (
|
|
||||||
"Type of 'attr_1' changed from 'float' to 'int'. This may have unwanted "
|
|
||||||
"side effects! Consider setting it to 'float' directly." in caplog.text
|
|
||||||
)
|
|
||||||
|
|
||||||
service_instance.current = 2
|
|
||||||
assert "'current' changed to '2'" in caplog.text
|
|
||||||
assert (
|
|
||||||
"Type of 'current' changed from 'Quantity' to 'int'. This may have unwanted "
|
|
||||||
"side effects! Consider setting it to 'Quantity' directly." in caplog.text
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_basic_inheritance_warning(caplog: LogCaptureFixture) -> None:
|
def test_basic_inheritance_warning(caplog: LogCaptureFixture) -> None:
|
||||||
|
|||||||
@@ -1225,3 +1225,22 @@ def test_add_prefix_to_full_access_path(
|
|||||||
serialized_obj: SerializedObject, prefix: str, expected: SerializedObject
|
serialized_obj: SerializedObject, prefix: str, expected: SerializedObject
|
||||||
) -> None:
|
) -> None:
|
||||||
assert add_prefix_to_full_access_path(serialized_obj, prefix) == expected
|
assert add_prefix_to_full_access_path(serialized_obj, prefix) == expected
|
||||||
|
|
||||||
|
|
||||||
|
def test_serialize_exception() -> None:
|
||||||
|
assert dump(Exception()) == {
|
||||||
|
"doc": None,
|
||||||
|
"full_access_path": "",
|
||||||
|
"name": "Exception",
|
||||||
|
"readonly": True,
|
||||||
|
"type": "Exception",
|
||||||
|
"value": "",
|
||||||
|
}
|
||||||
|
assert dump(Exception("Exception message")) == {
|
||||||
|
"doc": None,
|
||||||
|
"full_access_path": "",
|
||||||
|
"name": "Exception",
|
||||||
|
"readonly": True,
|
||||||
|
"type": "Exception",
|
||||||
|
"value": "Exception message",
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user