14 Commits

Author SHA1 Message Date
Mose Müller
bcabd2dc48 Merge pull request #95 from tiqi-group/fix/service_configuration
Fix/service configuration
2024-01-29 15:26:27 +01:00
Mose Müller
7ac9c557c2 updates version to v0.5.2 2024-01-29 15:24:13 +01:00
Mose Müller
656529d1fb fixes service configuration (allow all environment variables) 2024-01-29 15:23:27 +01:00
Mose Müller
14601105a7 Merge pull request #93 from tiqi-group/45-placing-the-explanation-question-mark-next-to-the-variable-instead-of-above
feat: placing the explanation question mark next to the variable instead of above
2024-01-16 14:16:38 +01:00
Mose Müller
484b5131e9 fixing enum serialization for python 3.10 2024-01-16 14:13:36 +01:00
Mose Müller
616a5cea21 npm run build 2024-01-16 13:44:37 +01:00
Mose Müller
300bd6ca9a updates Enum serialization 2024-01-16 13:37:39 +01:00
Mose Müller
3e1517e905 udpates dev-guide for adding components 2024-01-16 13:00:01 +01:00
Mose Müller
0ecaeac3fb replaces js interfaces with types 2024-01-16 12:57:35 +01:00
Mose Müller
0e9832e2f1 updates DocStringComponent placement 2024-01-16 12:55:18 +01:00
Mose Müller
0343abd0b0 Merge pull request #91 from tiqi-group/fix/load_from_file
Fix/load from file
2024-01-09 16:39:59 +01:00
Mose Müller
0c149b85b5 updates version to v0.5.1 2024-01-09 16:39:12 +01:00
Mose Müller
0e331e58ff adds tests for server to check if loading from file is working 2024-01-09 16:36:35 +01:00
Mose Müller
45135927e6 initialises observer before loading state from json file 2024-01-09 16:21:57 +01:00
25 changed files with 148 additions and 63 deletions

View File

@@ -118,7 +118,7 @@ import { ChevronDown, ChevronRight } from 'react-bootstrap-icons';
import { getIdFromFullAccessPath } from '../utils/stringUtils'; import { getIdFromFullAccessPath } from '../utils/stringUtils';
import { LevelName } from './NotificationsComponent'; import { LevelName } from './NotificationsComponent';
interface ImageComponentProps { type ImageComponentProps = {
name: string; name: string;
parentPath?: string; parentPath?: string;
readOnly: boolean; readOnly: boolean;
@@ -165,14 +165,15 @@ export const ImageComponent = React.memo((props: ImageComponentProps) => {
onClick={() => setOpen(!open)} onClick={() => setOpen(!open)}
style={{ cursor: 'pointer' }} // Change cursor style on hover style={{ cursor: 'pointer' }} // Change cursor style on hover
> >
{displayName} {open ? <ChevronDown /> : <ChevronRight />} {displayName}
<DocStringComponent docString={docString} />
{open ? <ChevronDown /> : <ChevronRight />}
</Card.Header> </Card.Header>
<Collapse in={open}> <Collapse in={open}>
<Card.Body> <Card.Body>
{process.env.NODE_ENV === 'development' && ( {process.env.NODE_ENV === 'development' && (
<p>Render count: {renderCount.current}</p> <p>Render count: {renderCount.current}</p>
)} )}
<DocStringComponent docString={docString} />
{/* Your component TSX here */} {/* Your component TSX here */}
</Card.Body> </Card.Body>
</Collapse> </Collapse>

View File

@@ -6,7 +6,7 @@ import { getIdFromFullAccessPath } from '../utils/stringUtils';
import { LevelName } from './NotificationsComponent'; import { LevelName } from './NotificationsComponent';
import { WebSettingsContext } from '../WebSettings'; import { WebSettingsContext } from '../WebSettings';
interface AsyncMethodProps { type AsyncMethodProps = {
name: string; name: string;
parentPath: string; parentPath: string;
parameters: Record<string, string>; parameters: Record<string, string>;
@@ -14,7 +14,7 @@ interface AsyncMethodProps {
docString?: string; docString?: string;
hideOutput?: boolean; hideOutput?: boolean;
addNotification: (message: string, levelname?: LevelName) => void; addNotification: (message: string, levelname?: LevelName) => void;
} };
export const AsyncMethodComponent = React.memo((props: AsyncMethodProps) => { export const AsyncMethodComponent = React.memo((props: AsyncMethodProps) => {
const { name, parentPath, docString, value: runningTask, addNotification } = props; const { name, parentPath, docString, value: runningTask, addNotification } = props;
@@ -102,14 +102,12 @@ export const AsyncMethodComponent = React.memo((props: AsyncMethodProps) => {
{process.env.NODE_ENV === 'development' && ( {process.env.NODE_ENV === 'development' && (
<div>Render count: {renderCount.current}</div> <div>Render count: {renderCount.current}</div>
)} )}
<h5> <h5>Function: {displayName}</h5>
Function: {displayName}
<DocStringComponent docString={docString} />
</h5>
<Form onSubmit={execute} ref={formRef}> <Form onSubmit={execute} ref={formRef}>
{args} {args}
<Button id={`button-${id}`} name={name} value={parentPath} type="submit"> <Button id={`button-${id}`} name={name} value={parentPath} type="submit">
{runningTask ? 'Stop' : 'Start'} {runningTask ? 'Stop ' : 'Start '}
<DocStringComponent docString={docString} />
</Button> </Button>
</Form> </Form>
</div> </div>

View File

@@ -6,7 +6,7 @@ import { DocStringComponent } from './DocStringComponent';
import { getIdFromFullAccessPath } from '../utils/stringUtils'; import { getIdFromFullAccessPath } from '../utils/stringUtils';
import { LevelName } from './NotificationsComponent'; import { LevelName } from './NotificationsComponent';
interface ButtonComponentProps { type ButtonComponentProps = {
name: string; name: string;
parentPath?: string; parentPath?: string;
value: boolean; value: boolean;
@@ -14,7 +14,7 @@ interface ButtonComponentProps {
docString: string; docString: string;
mapping?: [string, string]; // Enforce a tuple of two strings mapping?: [string, string]; // Enforce a tuple of two strings
addNotification: (message: string, levelname?: LevelName) => void; addNotification: (message: string, levelname?: LevelName) => void;
} };
export const ButtonComponent = React.memo((props: ButtonComponentProps) => { export const ButtonComponent = React.memo((props: ButtonComponentProps) => {
const { name, parentPath, value, readOnly, docString, addNotification } = props; const { name, parentPath, value, readOnly, docString, addNotification } = props;
@@ -48,7 +48,6 @@ export const ButtonComponent = React.memo((props: ButtonComponentProps) => {
<div>Render count: {renderCount.current}</div> <div>Render count: {renderCount.current}</div>
)} )}
<DocStringComponent docString={docString} />
<ToggleButton <ToggleButton
id={`toggle-check-${id}`} id={`toggle-check-${id}`}
type="checkbox" type="checkbox"
@@ -58,6 +57,7 @@ export const ButtonComponent = React.memo((props: ButtonComponentProps) => {
disabled={readOnly} disabled={readOnly}
onChange={(e) => setChecked(e.currentTarget.checked)}> onChange={(e) => setChecked(e.currentTarget.checked)}>
{displayName} {displayName}
<DocStringComponent docString={docString} />
</ToggleButton> </ToggleButton>
</div> </div>
); );

View File

@@ -6,7 +6,7 @@ import { DocStringComponent } from './DocStringComponent';
import { getIdFromFullAccessPath } from '../utils/stringUtils'; import { getIdFromFullAccessPath } from '../utils/stringUtils';
import { LevelName } from './NotificationsComponent'; import { LevelName } from './NotificationsComponent';
interface ColouredEnumComponentProps { type ColouredEnumComponentProps = {
name: string; name: string;
parentPath: string; parentPath: string;
value: string; value: string;
@@ -14,7 +14,7 @@ interface ColouredEnumComponentProps {
readOnly: boolean; readOnly: boolean;
enumDict: Record<string, string>; enumDict: Record<string, string>;
addNotification: (message: string, levelname?: LevelName) => void; addNotification: (message: string, levelname?: LevelName) => void;
} };
export const ColouredEnumComponent = React.memo((props: ColouredEnumComponentProps) => { export const ColouredEnumComponent = React.memo((props: ColouredEnumComponentProps) => {
const { const {
@@ -53,10 +53,12 @@ export const ColouredEnumComponent = React.memo((props: ColouredEnumComponentPro
{process.env.NODE_ENV === 'development' && ( {process.env.NODE_ENV === 'development' && (
<div>Render count: {renderCount.current}</div> <div>Render count: {renderCount.current}</div>
)} )}
<DocStringComponent docString={docString} />
<Row> <Row>
<Col className="d-flex align-items-center"> <Col className="d-flex align-items-center">
<InputGroup.Text>{displayName}</InputGroup.Text> <InputGroup.Text>
{displayName}
<DocStringComponent docString={docString} />
</InputGroup.Text>
{readOnly ? ( {readOnly ? (
// Display the Form.Control when readOnly is true // Display the Form.Control when readOnly is true
<Form.Control <Form.Control

View File

@@ -1,9 +1,9 @@
import { Badge, Tooltip, OverlayTrigger } from 'react-bootstrap'; import { Badge, Tooltip, OverlayTrigger } from 'react-bootstrap';
import React from 'react'; import React from 'react';
interface DocStringProps { type DocStringProps = {
docString?: string; docString?: string;
} };
export const DocStringComponent = React.memo((props: DocStringProps) => { export const DocStringComponent = React.memo((props: DocStringProps) => {
const { docString } = props; const { docString } = props;

View File

@@ -6,14 +6,14 @@ import { getIdFromFullAccessPath } from '../utils/stringUtils';
import { DocStringComponent } from './DocStringComponent'; import { DocStringComponent } from './DocStringComponent';
import { LevelName } from './NotificationsComponent'; import { LevelName } from './NotificationsComponent';
interface EnumComponentProps { type EnumComponentProps = {
name: string; name: string;
parentPath: string; parentPath: string;
value: string; value: string;
docString?: string; docString?: string;
enumDict: Record<string, string>; enumDict: Record<string, string>;
addNotification: (message: string, levelname?: LevelName) => void; addNotification: (message: string, levelname?: LevelName) => void;
} };
export const EnumComponent = React.memo((props: EnumComponentProps) => { export const EnumComponent = React.memo((props: EnumComponentProps) => {
const { const {
@@ -52,10 +52,12 @@ export const EnumComponent = React.memo((props: EnumComponentProps) => {
{process.env.NODE_ENV === 'development' && ( {process.env.NODE_ENV === 'development' && (
<div>Render count: {renderCount.current}</div> <div>Render count: {renderCount.current}</div>
)} )}
<DocStringComponent docString={docString} />
<Row> <Row>
<Col className="d-flex align-items-center"> <Col className="d-flex align-items-center">
<InputGroup.Text>{displayName}</InputGroup.Text> <InputGroup.Text>
{displayName}
<DocStringComponent docString={docString} />
</InputGroup.Text>
<Form.Select <Form.Select
aria-label="Default select example" aria-label="Default select example"
value={value} value={value}

View File

@@ -27,7 +27,7 @@ type AttributeType =
| 'ColouredEnum'; | 'ColouredEnum';
type ValueType = boolean | string | number | object; type ValueType = boolean | string | number | object;
export interface Attribute { export type Attribute = {
type: AttributeType; type: AttributeType;
value?: ValueType | ValueType[]; value?: ValueType | ValueType[];
readonly: boolean; readonly: boolean;
@@ -35,7 +35,7 @@ export interface Attribute {
parameters?: Record<string, string>; parameters?: Record<string, string>;
async?: boolean; async?: boolean;
enum?: Record<string, string>; enum?: Record<string, string>;
} };
type GenericComponentProps = { type GenericComponentProps = {
attribute: Attribute; attribute: Attribute;
name: string; name: string;
@@ -95,7 +95,7 @@ export const GenericComponent = React.memo(
<SliderComponent <SliderComponent
name={name} name={name}
parentPath={parentPath} parentPath={parentPath}
docString={attribute.doc} docString={attribute.value['value'].doc}
readOnly={attribute.readonly} readOnly={attribute.readonly}
value={attribute.value['value']} value={attribute.value['value']}
min={attribute.value['min']} min={attribute.value['min']}
@@ -179,7 +179,7 @@ export const GenericComponent = React.memo(
parentPath={parentPath} parentPath={parentPath}
value={attribute.value['value']['value'] as string} value={attribute.value['value']['value'] as string}
readOnly={attribute.readonly} readOnly={attribute.readonly}
docString={attribute.doc} docString={attribute.value['value'].doc}
// Add any other specific props for the ImageComponent here // Add any other specific props for the ImageComponent here
format={attribute.value['format']['value'] as string} format={attribute.value['format']['value'] as string}
addNotification={addNotification} addNotification={addNotification}

View File

@@ -6,7 +6,7 @@ import { ChevronDown, ChevronRight } from 'react-bootstrap-icons';
import { getIdFromFullAccessPath } from '../utils/stringUtils'; import { getIdFromFullAccessPath } from '../utils/stringUtils';
import { LevelName } from './NotificationsComponent'; import { LevelName } from './NotificationsComponent';
interface ImageComponentProps { type ImageComponentProps = {
name: string; name: string;
parentPath: string; parentPath: string;
value: string; value: string;
@@ -14,7 +14,7 @@ interface ImageComponentProps {
docString: string; docString: string;
format: string; format: string;
addNotification: (message: string, levelname?: LevelName) => void; addNotification: (message: string, levelname?: LevelName) => void;
} };
export const ImageComponent = React.memo((props: ImageComponentProps) => { export const ImageComponent = React.memo((props: ImageComponentProps) => {
const { name, parentPath, value, docString, format, addNotification } = props; const { name, parentPath, value, docString, format, addNotification } = props;
@@ -48,14 +48,15 @@ export const ImageComponent = React.memo((props: ImageComponentProps) => {
onClick={() => setOpen(!open)} onClick={() => setOpen(!open)}
style={{ cursor: 'pointer' }} // Change cursor style on hover style={{ cursor: 'pointer' }} // Change cursor style on hover
> >
{displayName} {open ? <ChevronDown /> : <ChevronRight />} {displayName}
<DocStringComponent docString={docString} />
{open ? <ChevronDown /> : <ChevronRight />}
</Card.Header> </Card.Header>
<Collapse in={open}> <Collapse in={open}>
<Card.Body> <Card.Body>
{process.env.NODE_ENV === 'development' && ( {process.env.NODE_ENV === 'development' && (
<p>Render count: {renderCount.current}</p> <p>Render count: {renderCount.current}</p>
)} )}
<DocStringComponent docString={docString} />
{/* Your component JSX here */} {/* Your component JSX here */}
{format === '' && value === '' ? ( {format === '' && value === '' ? (
<p>No image set in the backend.</p> <p>No image set in the backend.</p>

View File

@@ -4,14 +4,14 @@ import { Attribute, GenericComponent } from './GenericComponent';
import { getIdFromFullAccessPath } from '../utils/stringUtils'; import { getIdFromFullAccessPath } from '../utils/stringUtils';
import { LevelName } from './NotificationsComponent'; import { LevelName } from './NotificationsComponent';
interface ListComponentProps { type ListComponentProps = {
name: string; name: string;
parentPath?: string; parentPath?: string;
value: Attribute[]; value: Attribute[];
docString: string; docString: string;
isInstantUpdate: boolean; isInstantUpdate: boolean;
addNotification: (message: string, levelname?: LevelName) => void; addNotification: (message: string, levelname?: LevelName) => void;
} };
export const ListComponent = React.memo((props: ListComponentProps) => { export const ListComponent = React.memo((props: ListComponentProps) => {
const { name, parentPath, value, docString, isInstantUpdate, addNotification } = const { name, parentPath, value, docString, isInstantUpdate, addNotification } =

View File

@@ -6,14 +6,14 @@ import { DocStringComponent } from './DocStringComponent';
import { getIdFromFullAccessPath } from '../utils/stringUtils'; import { getIdFromFullAccessPath } from '../utils/stringUtils';
import { LevelName } from './NotificationsComponent'; import { LevelName } from './NotificationsComponent';
interface MethodProps { type MethodProps = {
name: string; name: string;
parentPath: string; parentPath: string;
parameters: Record<string, string>; parameters: Record<string, string>;
docString?: string; docString?: string;
hideOutput?: boolean; hideOutput?: boolean;
addNotification: (message: string, levelname?: LevelName) => void; addNotification: (message: string, levelname?: LevelName) => void;
} };
export const MethodComponent = React.memo((props: MethodProps) => { export const MethodComponent = React.memo((props: MethodProps) => {
const { name, parentPath, docString, addNotification } = props; const { name, parentPath, docString, addNotification } = props;
@@ -89,12 +89,12 @@ export const MethodComponent = React.memo((props: MethodProps) => {
)} )}
<h5 onClick={() => setHideOutput(!hideOutput)} style={{ cursor: 'pointer' }}> <h5 onClick={() => setHideOutput(!hideOutput)} style={{ cursor: 'pointer' }}>
Function: {displayName} Function: {displayName}
<DocStringComponent docString={docString} />
</h5> </h5>
<Form onSubmit={execute}> <Form onSubmit={execute}>
{args} {args}
<Button variant="primary" type="submit"> <Button variant="primary" type="submit">
Execute Execute
<DocStringComponent docString={docString} />
</Button> </Button>
</Form> </Form>

View File

@@ -32,7 +32,7 @@ export type FloatObject = {
}; };
export type NumberObject = IntObject | FloatObject | QuantityObject; export type NumberObject = IntObject | FloatObject | QuantityObject;
interface NumberComponentProps { type NumberComponentProps = {
name: string; name: string;
type: 'float' | 'int'; type: 'float' | 'int';
parentPath?: string; parentPath?: string;
@@ -43,7 +43,7 @@ interface NumberComponentProps {
unit?: string; unit?: string;
showName?: boolean; showName?: boolean;
addNotification: (message: string, levelname?: LevelName) => void; addNotification: (message: string, levelname?: LevelName) => void;
} };
// TODO: highlight the digit that is being changed by setting both selectionStart and // TODO: highlight the digit that is being changed by setting both selectionStart and
// selectionEnd // selectionEnd
@@ -313,10 +313,14 @@ export const NumberComponent = React.memo((props: NumberComponentProps) => {
{process.env.NODE_ENV === 'development' && ( {process.env.NODE_ENV === 'development' && (
<div>Render count: {renderCount.current}</div> <div>Render count: {renderCount.current}</div>
)} )}
<DocStringComponent docString={docString} />
<div className="d-flex"> <div className="d-flex">
<InputGroup> <InputGroup>
{showName && <InputGroup.Text>{displayName}</InputGroup.Text>} {showName && (
<InputGroup.Text>
{displayName}
<DocStringComponent docString={docString} />
</InputGroup.Text>
)}
<Form.Control <Form.Control
type="text" type="text"
value={inputString} value={inputString}

View File

@@ -8,7 +8,7 @@ import { NumberComponent, NumberObject } from './NumberComponent';
import { getIdFromFullAccessPath } from '../utils/stringUtils'; import { getIdFromFullAccessPath } from '../utils/stringUtils';
import { LevelName } from './NotificationsComponent'; import { LevelName } from './NotificationsComponent';
interface SliderComponentProps { type SliderComponentProps = {
name: string; name: string;
min: NumberObject; min: NumberObject;
max: NumberObject; max: NumberObject;
@@ -19,7 +19,7 @@ interface SliderComponentProps {
stepSize: NumberObject; stepSize: NumberObject;
isInstantUpdate: boolean; isInstantUpdate: boolean;
addNotification: (message: string, levelname?: LevelName) => void; addNotification: (message: string, levelname?: LevelName) => void;
} };
export const SliderComponent = React.memo((props: SliderComponentProps) => { export const SliderComponent = React.memo((props: SliderComponentProps) => {
const renderCount = useRef(0); const renderCount = useRef(0);
@@ -105,10 +105,12 @@ export const SliderComponent = React.memo((props: SliderComponentProps) => {
<div>Render count: {renderCount.current}</div> <div>Render count: {renderCount.current}</div>
)} )}
<DocStringComponent docString={docString} />
<Row> <Row>
<Col xs="auto" xl="auto"> <Col xs="auto" xl="auto">
<InputGroup.Text>{displayName}</InputGroup.Text> <InputGroup.Text>
{displayName}
<DocStringComponent docString={docString} />
</InputGroup.Text>
</Col> </Col>
<Col xs="5" xl> <Col xs="5" xl>
<Slider <Slider

View File

@@ -9,7 +9,7 @@ import { WebSettingsContext } from '../WebSettings';
// TODO: add button functionality // TODO: add button functionality
interface StringComponentProps { type StringComponentProps = {
name: string; name: string;
parentPath?: string; parentPath?: string;
value: string; value: string;
@@ -17,7 +17,7 @@ interface StringComponentProps {
docString: string; docString: string;
isInstantUpdate: boolean; isInstantUpdate: boolean;
addNotification: (message: string, levelname?: LevelName) => void; addNotification: (message: string, levelname?: LevelName) => void;
} };
export const StringComponent = React.memo((props: StringComponentProps) => { export const StringComponent = React.memo((props: StringComponentProps) => {
const { name, parentPath, readOnly, docString, isInstantUpdate, addNotification } = const { name, parentPath, readOnly, docString, isInstantUpdate, addNotification } =
@@ -70,9 +70,11 @@ export const StringComponent = React.memo((props: StringComponentProps) => {
{process.env.NODE_ENV === 'development' && ( {process.env.NODE_ENV === 'development' && (
<div>Render count: {renderCount.current}</div> <div>Render count: {renderCount.current}</div>
)} )}
<DocStringComponent docString={docString} />
<InputGroup> <InputGroup>
<InputGroup.Text>{displayName}</InputGroup.Text> <InputGroup.Text>
{displayName}
<DocStringComponent docString={docString} />
</InputGroup.Text>
<Form.Control <Form.Control
type="text" type="text"
value={inputString} value={inputString}

View File

@@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "pydase" name = "pydase"
version = "0.5.0" version = "0.5.2"
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

@@ -15,7 +15,7 @@ class ServiceConfig(BaseConfig): # type: ignore[misc]
web_port: int = 8001 web_port: int = 8001
rpc_port: int = 18871 rpc_port: int = 18871
CONFIG_SOURCES = EnvSource(prefix="SERVICE_") CONFIG_SOURCES = EnvSource(allow_all=True, prefix="SERVICE_")
class WebServerConfig(BaseConfig): # type: ignore[misc] class WebServerConfig(BaseConfig): # type: ignore[misc]

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.ea55bba6.js", "main.js": "/static/js/main.dba067e7.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.ea55bba6.js.map": "/static/js/main.ea55bba6.js.map" "main.dba067e7.js.map": "/static/js/main.dba067e7.js.map"
}, },
"entrypoints": [ "entrypoints": [
"static/css/main.2d8458eb.css", "static/css/main.2d8458eb.css",
"static/js/main.ea55bba6.js" "static/js/main.dba067e7.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.ea55bba6.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.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>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -179,8 +179,8 @@ class Server:
self._state_manager = StateManager(self._service, filename) self._state_manager = StateManager(self._service, filename)
if getattr(self._service, "_filename", None) is not None: if getattr(self._service, "_filename", None) is not None:
self._service._state_manager = self._state_manager self._service._state_manager = self._state_manager
self._state_manager.load_state()
self._observer = DataServiceObserver(self._state_manager) self._observer = DataServiceObserver(self._state_manager)
self._state_manager.load_state()
def run(self) -> None: def run(self) -> None:
""" """

View File

@@ -1,5 +1,6 @@
import inspect import inspect
import logging import logging
import sys
from collections.abc import Callable from collections.abc import Callable
from enum import Enum from enum import Enum
from typing import Any from typing import Any
@@ -67,7 +68,9 @@ class Serializer:
def _serialize_enum(obj: Enum) -> dict[str, Any]: def _serialize_enum(obj: Enum) -> dict[str, Any]:
value = obj.name value = obj.name
readonly = False readonly = False
doc = get_attribute_doc(obj) doc = obj.__doc__
if sys.version_info < (3, 11) and doc == "An enumeration.":
doc = None
if type(obj).__base__.__name__ == "ColouredEnum": if type(obj).__base__.__name__ == "ColouredEnum":
obj_type = "ColouredEnum" obj_type = "ColouredEnum"
else: else:

View File

@@ -1,8 +1,15 @@
import json
import signal import signal
from pathlib import Path
from pytest_mock import MockerFixture from typing import Any
import pydase import pydase
import pydase.components
import pydase.units as u
from pydase.data_service.state_manager import load_state
from pydase.server.server import Server
from pytest import LogCaptureFixture
from pytest_mock import MockerFixture
def test_signal_handling(mocker: MockerFixture): def test_signal_handling(mocker: MockerFixture):
@@ -33,3 +40,64 @@ def test_signal_handling(mocker: MockerFixture):
# Simulate receiving a SIGINT signal for the second time # Simulate receiving a SIGINT signal for the second time
server.handle_exit(signal.SIGINT, None) server.handle_exit(signal.SIGINT, None)
mock_exit.assert_called_once_with(1) mock_exit.assert_called_once_with(1)
class Service(pydase.DataService):
def __init__(self, **kwargs: Any) -> None:
super().__init__(**kwargs)
self.some_unit: u.Quantity = 1.2 * u.units.A
self.some_float = 1.0
self._property_attr = 1337.0
@property
def property_attr(self) -> float:
return self._property_attr
@property_attr.setter
@load_state
def property_attr(self, value: float) -> None:
self._property_attr = value
CURRENT_STATE = Service().serialize()
LOAD_STATE = {
"some_float": {
"type": "float",
"value": 10.0,
"readonly": False,
"doc": None,
},
"property_attr": {
"type": "float",
"value": 1337.1,
"readonly": False,
"doc": None,
},
"some_unit": {
"type": "Quantity",
"value": {"magnitude": 12.0, "unit": "A"},
"readonly": False,
"doc": None,
},
}
def test_load_state(tmp_path: Path, caplog: LogCaptureFixture) -> None:
# Create a StateManager instance with a temporary file
file = tmp_path / "test_state.json"
# Write a temporary JSON file to read back
with open(file, "w") as f:
json.dump(LOAD_STATE, f, indent=4)
service = Service()
Server(service, filename=str(file))
assert service.some_unit == u.Quantity(12, "A")
assert service.property_attr == 1337.1
assert service.some_float == 10.0
assert "'some_unit' changed to '12.0 A'" in caplog.text
assert "'some_float' changed to '10.0'" in caplog.text
assert "'property_attr' changed to '1337.1'" in caplog.text

View File

@@ -100,6 +100,8 @@ def test_enum_serialize() -> None:
def test_ColouredEnum_serialize() -> None: def test_ColouredEnum_serialize() -> None:
class Status(ColouredEnum): class Status(ColouredEnum):
"""Status description."""
PENDING = "#FFA500" PENDING = "#FFA500"
RUNNING = "#0000FF80" RUNNING = "#0000FF80"
PAUSED = "rgb(169, 169, 169)" PAUSED = "rgb(169, 169, 169)"
@@ -121,7 +123,7 @@ def test_ColouredEnum_serialize() -> None:
"RUNNING": "#0000FF80", "RUNNING": "#0000FF80",
}, },
"readonly": False, "readonly": False,
"doc": None, "doc": "Status description.",
} }