ivoryos 0.1.9__py3-none-any.whl → 0.1.12__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of ivoryos might be problematic. Click here for more details.
- ivoryos/__init__.py +122 -99
- ivoryos/config.py +47 -47
- ivoryos/routes/auth/auth.py +100 -65
- ivoryos/routes/auth/templates/auth/login.html +25 -25
- ivoryos/routes/auth/templates/auth/signup.html +32 -32
- ivoryos/routes/control/control.py +400 -272
- ivoryos/routes/control/templates/control/controllers.html +75 -75
- ivoryos/routes/control/templates/control/controllers_home.html +50 -50
- ivoryos/routes/control/templates/control/controllers_new.html +89 -89
- ivoryos/routes/database/database.py +188 -114
- ivoryos/routes/database/templates/database/experiment_database.html +72 -72
- ivoryos/routes/design/design.py +543 -416
- ivoryos/routes/design/templates/design/experiment_builder.html +415 -415
- ivoryos/routes/design/templates/design/experiment_run.html +325 -325
- ivoryos/routes/main/main.py +42 -25
- ivoryos/routes/main/templates/main/help.html +141 -141
- ivoryos/routes/main/templates/main/home.html +68 -68
- ivoryos/static/.DS_Store +0 -0
- ivoryos/static/js/overlay.js +12 -12
- ivoryos/static/js/socket_handler.js +34 -34
- ivoryos/static/js/sortable_card.js +24 -24
- ivoryos/static/js/sortable_design.js +36 -36
- ivoryos/static/style.css +201 -201
- ivoryos/templates/base.html +143 -143
- ivoryos/utils/db_models.py +544 -518
- ivoryos/utils/form.py +328 -316
- ivoryos/utils/global_config.py +67 -67
- ivoryos/utils/llm_agent.py +183 -183
- ivoryos/utils/script_runner.py +166 -164
- ivoryos/utils/utils.py +437 -422
- ivoryos/version.py +1 -0
- {ivoryos-0.1.9.dist-info → ivoryos-0.1.12.dist-info}/LICENSE +21 -21
- {ivoryos-0.1.9.dist-info → ivoryos-0.1.12.dist-info}/METADATA +170 -169
- ivoryos-0.1.12.dist-info/RECORD +47 -0
- {ivoryos-0.1.9.dist-info → ivoryos-0.1.12.dist-info}/WHEEL +1 -1
- ivoryos-0.1.9.dist-info/RECORD +0 -45
- {ivoryos-0.1.9.dist-info → ivoryos-0.1.12.dist-info}/top_level.txt +0 -0
ivoryos/utils/form.py
CHANGED
|
@@ -1,316 +1,328 @@
|
|
|
1
|
-
from wtforms.fields.core import Field
|
|
2
|
-
from wtforms.validators import InputRequired
|
|
3
|
-
from wtforms.widgets.core import TextInput
|
|
4
|
-
|
|
5
|
-
from flask_wtf import FlaskForm
|
|
6
|
-
from wtforms import StringField, FloatField, HiddenField, BooleanField, IntegerField
|
|
7
|
-
import inspect
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def find_variable(data, script):
|
|
11
|
-
# TODO: needs to check for valid order of variables, important when editting
|
|
12
|
-
added_variables: list[dict[str, str]] = [action for action in script.currently_editing_script if
|
|
13
|
-
action["instrument"] == "variable"
|
|
14
|
-
# or action["return"] # TODO find returns
|
|
15
|
-
]
|
|
16
|
-
for added_variable in added_variables:
|
|
17
|
-
if added_variable["action"] == data:
|
|
18
|
-
return data, added_variable["args"]
|
|
19
|
-
# if added_variable["return"] == data:
|
|
20
|
-
# return data, None
|
|
21
|
-
return None, None
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
class VariableOrStringField(Field):
|
|
25
|
-
widget = TextInput()
|
|
26
|
-
|
|
27
|
-
def __init__(self, label='', validators=None, script=None, **kwargs):
|
|
28
|
-
super(VariableOrStringField, self).__init__(label, validators, **kwargs)
|
|
29
|
-
self.script = script
|
|
30
|
-
|
|
31
|
-
def process_formdata(self, valuelist):
|
|
32
|
-
if valuelist:
|
|
33
|
-
if not self.script.editing_type == "script" and valuelist[0].startswith("#"):
|
|
34
|
-
raise ValueError(self.gettext("Variable is not supported in prep/cleanup"))
|
|
35
|
-
self.data = valuelist[0]
|
|
36
|
-
|
|
37
|
-
def _value(self):
|
|
38
|
-
if self.script:
|
|
39
|
-
variable, value = find_variable(self.data, self.script)
|
|
40
|
-
if variable:
|
|
41
|
-
return variable
|
|
42
|
-
|
|
43
|
-
return str(self.data) if self.data is not None else ""
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
class VariableOrFloatField(Field):
|
|
47
|
-
widget = TextInput()
|
|
48
|
-
|
|
49
|
-
def __init__(self, label='', validators=None, script=None, **kwargs):
|
|
50
|
-
super(VariableOrFloatField, self).__init__(label, validators, **kwargs)
|
|
51
|
-
self.script = script
|
|
52
|
-
|
|
53
|
-
def _value(self):
|
|
54
|
-
if self.script:
|
|
55
|
-
variable, value = find_variable(self.data, self.script)
|
|
56
|
-
if variable:
|
|
57
|
-
return variable
|
|
58
|
-
|
|
59
|
-
if self.raw_data:
|
|
60
|
-
return self.raw_data[0]
|
|
61
|
-
if self.data is not None:
|
|
62
|
-
return str(self.data)
|
|
63
|
-
return ""
|
|
64
|
-
|
|
65
|
-
def process_formdata(self, valuelist):
|
|
66
|
-
if not valuelist:
|
|
67
|
-
return
|
|
68
|
-
elif valuelist[0].startswith("#"):
|
|
69
|
-
if not self.script.editing_type == "script":
|
|
70
|
-
raise ValueError(self.gettext("Variable is not supported in prep/cleanup"))
|
|
71
|
-
self.data = valuelist[0]
|
|
72
|
-
return
|
|
73
|
-
try:
|
|
74
|
-
if self.script:
|
|
75
|
-
try:
|
|
76
|
-
variable, value = find_variable(valuelist[0], self.script)
|
|
77
|
-
if variable:
|
|
78
|
-
float(value)
|
|
79
|
-
self.data = str(variable)
|
|
80
|
-
return
|
|
81
|
-
except ValueError:
|
|
82
|
-
pass
|
|
83
|
-
|
|
84
|
-
self.data = float(valuelist[0])
|
|
85
|
-
except ValueError as exc:
|
|
86
|
-
self.data = None
|
|
87
|
-
raise ValueError(self.gettext("Not a valid float value.")) from exc
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
# unset_value = UnsetValue()
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
class VariableOrIntField(Field):
|
|
94
|
-
widget = TextInput()
|
|
95
|
-
|
|
96
|
-
def __init__(self, label='', validators=None, script=None, **kwargs):
|
|
97
|
-
super(VariableOrIntField, self).__init__(label, validators, **kwargs)
|
|
98
|
-
self.script = script
|
|
99
|
-
|
|
100
|
-
def _value(self):
|
|
101
|
-
if self.script:
|
|
102
|
-
variable, value = find_variable(self.data, self.script)
|
|
103
|
-
if variable:
|
|
104
|
-
return variable
|
|
105
|
-
|
|
106
|
-
if self.raw_data:
|
|
107
|
-
return self.raw_data[0]
|
|
108
|
-
if self.data is not None:
|
|
109
|
-
return str(self.data)
|
|
110
|
-
return ""
|
|
111
|
-
|
|
112
|
-
# def process_data(self, value):
|
|
113
|
-
#
|
|
114
|
-
# if self.script:
|
|
115
|
-
# variable, var_value = find_variable(value, self.script)
|
|
116
|
-
# if variable:
|
|
117
|
-
# try:
|
|
118
|
-
# int(var_value)
|
|
119
|
-
# self.data = str(variable)
|
|
120
|
-
# return
|
|
121
|
-
# except ValueError:
|
|
122
|
-
# pass
|
|
123
|
-
# if value is None or value is unset_value:
|
|
124
|
-
# self.data = None
|
|
125
|
-
# return
|
|
126
|
-
# try:
|
|
127
|
-
# self.data = int(value)
|
|
128
|
-
# except (ValueError, TypeError) as exc:
|
|
129
|
-
# self.data = None
|
|
130
|
-
# raise ValueError(self.gettext("Not a valid integer value.")) from exc
|
|
131
|
-
|
|
132
|
-
def process_formdata(self, valuelist):
|
|
133
|
-
if not valuelist:
|
|
134
|
-
return
|
|
135
|
-
if self.script:
|
|
136
|
-
variable, var_value = find_variable(valuelist[0], self.script)
|
|
137
|
-
if variable:
|
|
138
|
-
try:
|
|
139
|
-
int(var_value)
|
|
140
|
-
self.data = str(variable)
|
|
141
|
-
return
|
|
142
|
-
except ValueError:
|
|
143
|
-
pass
|
|
144
|
-
if valuelist[0].startswith("#"):
|
|
145
|
-
if not self.script.editing_type == "script":
|
|
146
|
-
raise ValueError(self.gettext("Variable is not supported in prep/cleanup"))
|
|
147
|
-
self.data = valuelist[0]
|
|
148
|
-
return
|
|
149
|
-
try:
|
|
150
|
-
self.data = int(valuelist[0])
|
|
151
|
-
except ValueError as exc:
|
|
152
|
-
self.data = None
|
|
153
|
-
raise ValueError(self.gettext("Not a valid integer value.")) from exc
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
class VariableOrBoolField(BooleanField):
|
|
157
|
-
widget = TextInput()
|
|
158
|
-
|
|
159
|
-
def __init__(self, label='', validators=None, script=None, **kwargs):
|
|
160
|
-
super(VariableOrBoolField, self).__init__(label, validators, **kwargs)
|
|
161
|
-
self.script = script
|
|
162
|
-
|
|
163
|
-
def process_data(self, value):
|
|
164
|
-
|
|
165
|
-
if self.script:
|
|
166
|
-
variable, var_value = find_variable(value, self.script)
|
|
167
|
-
if variable:
|
|
168
|
-
try:
|
|
169
|
-
bool(var_value)
|
|
170
|
-
return variable
|
|
171
|
-
except ValueError:
|
|
172
|
-
return
|
|
173
|
-
|
|
174
|
-
self.data = bool(value)
|
|
175
|
-
|
|
176
|
-
def process_formdata(self, valuelist):
|
|
177
|
-
if not valuelist or type(valuelist) is list and valuelist[0] == '':
|
|
178
|
-
self.data = False
|
|
179
|
-
elif valuelist and valuelist[0].startswith("#"):
|
|
180
|
-
if not self.script.editing_type == "script":
|
|
181
|
-
raise ValueError(self.gettext("Variable is not supported in prep/cleanup"))
|
|
182
|
-
self.data = valuelist[0]
|
|
183
|
-
else:
|
|
184
|
-
self.data = True
|
|
185
|
-
|
|
186
|
-
def _value(self):
|
|
187
|
-
|
|
188
|
-
if self.script:
|
|
189
|
-
variable, value = find_variable(self.raw_data, self.script)
|
|
190
|
-
if variable:
|
|
191
|
-
return variable
|
|
192
|
-
|
|
193
|
-
if self.raw_data:
|
|
194
|
-
return str(self.raw_data[0])
|
|
195
|
-
return "y"
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
def format_name(name):
|
|
199
|
-
"""Converts 'example_name' to 'Example Name'."""
|
|
200
|
-
name = name.split(".")[-1]
|
|
201
|
-
text = ' '.join(word for word in name.split('_'))
|
|
202
|
-
return text.capitalize()
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
def create_form_for_method(method, method_name, autofill, script=None, design=True):
|
|
206
|
-
class DynamicForm(FlaskForm):
|
|
207
|
-
pass
|
|
208
|
-
|
|
209
|
-
annotation_mapping = {
|
|
210
|
-
int: (VariableOrIntField if design else IntegerField, 'Enter integer value'),
|
|
211
|
-
float: (VariableOrFloatField if design else FloatField, 'Enter numeric value'),
|
|
212
|
-
str: (VariableOrStringField if design else StringField, 'Enter text'),
|
|
213
|
-
bool: (VariableOrBoolField if design else BooleanField, 'Empty for false')
|
|
214
|
-
}
|
|
215
|
-
sig = method if type(method) is inspect.Signature else inspect.signature(method)
|
|
216
|
-
|
|
217
|
-
for param in sig.parameters.values():
|
|
218
|
-
if param.name == 'self':
|
|
219
|
-
continue
|
|
220
|
-
formatted_param_name = format_name(param.name)
|
|
221
|
-
field_kwargs = {
|
|
222
|
-
"label": formatted_param_name,
|
|
223
|
-
"default": f'#{param.name}' if autofill else (param.default if param.default is not param.empty else None),
|
|
224
|
-
"validators": [InputRequired()] if param.default is param.empty else None,
|
|
225
|
-
**({"script": script} if (autofill or design) else {})
|
|
226
|
-
}
|
|
227
|
-
field_class, placeholder_text = annotation_mapping.get(
|
|
228
|
-
param.annotation,
|
|
229
|
-
(VariableOrStringField if design else StringField, f'Enter {param.annotation} value')
|
|
230
|
-
)
|
|
231
|
-
render_kwargs = {"placeholder": placeholder_text}
|
|
232
|
-
|
|
233
|
-
# Create the field with additional rendering kwargs for placeholder text
|
|
234
|
-
field = field_class(**field_kwargs, render_kw=render_kwargs)
|
|
235
|
-
setattr(DynamicForm, param.name, field)
|
|
236
|
-
|
|
237
|
-
# setattr(DynamicForm, f'add', fname)
|
|
238
|
-
return DynamicForm
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
# Create forms for each method in DummySDLDeck
|
|
242
|
-
def create_add_form(attr, attr_name, autofill, script=None, design=True):
|
|
243
|
-
dynamic_form = create_form_for_method(attr, attr_name, autofill, script, design)
|
|
244
|
-
if design:
|
|
245
|
-
return_value = StringField(label='Save value as', render_kw={"placeholder": "Optional"})
|
|
246
|
-
setattr(dynamic_form, 'return', return_value)
|
|
247
|
-
hidden_method_name = HiddenField(name=f'hidden_name', render_kw={"value": f'{attr_name}'})
|
|
248
|
-
setattr(dynamic_form, 'hidden_name', hidden_method_name)
|
|
249
|
-
return dynamic_form
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
def create_form_from_module(sdl_module, autofill: bool, script=None, design=True):
|
|
253
|
-
# sdl_deck = DummySDLDeck(DummyPump("COM1"), DummyBalance("COM2"))
|
|
254
|
-
method_forms = {}
|
|
255
|
-
for attr_name in dir(sdl_module):
|
|
256
|
-
attr = getattr(sdl_module, attr_name)
|
|
257
|
-
if inspect.ismethod(attr) and not attr_name.startswith('_'):
|
|
258
|
-
form_class = create_add_form(attr, attr_name, autofill, script, design)
|
|
259
|
-
method_forms[attr_name] = form_class()
|
|
260
|
-
return method_forms
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
def create_form_from_pseudo(pseudo: dict, autofill: bool, script=None, design=True):
|
|
264
|
-
'''{'dose_liquid': < Signature(amount_in_ml: float, rate_ml_per_minute: float) >}'''
|
|
265
|
-
method_forms = {}
|
|
266
|
-
for attr_name, signature in pseudo.items():
|
|
267
|
-
form_class = create_add_form(signature, attr_name, autofill, script, design)
|
|
268
|
-
method_forms[attr_name] = form_class()
|
|
269
|
-
return method_forms
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
def create_builtin_form(logic_type):
|
|
273
|
-
class BuiltinFunctionForm(FlaskForm):
|
|
274
|
-
pass
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
1
|
+
from wtforms.fields.core import Field
|
|
2
|
+
from wtforms.validators import InputRequired
|
|
3
|
+
from wtforms.widgets.core import TextInput
|
|
4
|
+
|
|
5
|
+
from flask_wtf import FlaskForm
|
|
6
|
+
from wtforms import StringField, FloatField, HiddenField, BooleanField, IntegerField
|
|
7
|
+
import inspect
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def find_variable(data, script):
|
|
11
|
+
# TODO: needs to check for valid order of variables, important when editting
|
|
12
|
+
added_variables: list[dict[str, str]] = [action for action in script.currently_editing_script if
|
|
13
|
+
action["instrument"] == "variable"
|
|
14
|
+
# or action["return"] # TODO find returns
|
|
15
|
+
]
|
|
16
|
+
for added_variable in added_variables:
|
|
17
|
+
if added_variable["action"] == data:
|
|
18
|
+
return data, added_variable["args"]
|
|
19
|
+
# if added_variable["return"] == data:
|
|
20
|
+
# return data, None
|
|
21
|
+
return None, None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class VariableOrStringField(Field):
|
|
25
|
+
widget = TextInput()
|
|
26
|
+
|
|
27
|
+
def __init__(self, label='', validators=None, script=None, **kwargs):
|
|
28
|
+
super(VariableOrStringField, self).__init__(label, validators, **kwargs)
|
|
29
|
+
self.script = script
|
|
30
|
+
|
|
31
|
+
def process_formdata(self, valuelist):
|
|
32
|
+
if valuelist:
|
|
33
|
+
if not self.script.editing_type == "script" and valuelist[0].startswith("#"):
|
|
34
|
+
raise ValueError(self.gettext("Variable is not supported in prep/cleanup"))
|
|
35
|
+
self.data = valuelist[0]
|
|
36
|
+
|
|
37
|
+
def _value(self):
|
|
38
|
+
if self.script:
|
|
39
|
+
variable, value = find_variable(self.data, self.script)
|
|
40
|
+
if variable:
|
|
41
|
+
return variable
|
|
42
|
+
|
|
43
|
+
return str(self.data) if self.data is not None else ""
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class VariableOrFloatField(Field):
|
|
47
|
+
widget = TextInput()
|
|
48
|
+
|
|
49
|
+
def __init__(self, label='', validators=None, script=None, **kwargs):
|
|
50
|
+
super(VariableOrFloatField, self).__init__(label, validators, **kwargs)
|
|
51
|
+
self.script = script
|
|
52
|
+
|
|
53
|
+
def _value(self):
|
|
54
|
+
if self.script:
|
|
55
|
+
variable, value = find_variable(self.data, self.script)
|
|
56
|
+
if variable:
|
|
57
|
+
return variable
|
|
58
|
+
|
|
59
|
+
if self.raw_data:
|
|
60
|
+
return self.raw_data[0]
|
|
61
|
+
if self.data is not None:
|
|
62
|
+
return str(self.data)
|
|
63
|
+
return ""
|
|
64
|
+
|
|
65
|
+
def process_formdata(self, valuelist):
|
|
66
|
+
if not valuelist:
|
|
67
|
+
return
|
|
68
|
+
elif valuelist[0].startswith("#"):
|
|
69
|
+
if not self.script.editing_type == "script":
|
|
70
|
+
raise ValueError(self.gettext("Variable is not supported in prep/cleanup"))
|
|
71
|
+
self.data = valuelist[0]
|
|
72
|
+
return
|
|
73
|
+
try:
|
|
74
|
+
if self.script:
|
|
75
|
+
try:
|
|
76
|
+
variable, value = find_variable(valuelist[0], self.script)
|
|
77
|
+
if variable:
|
|
78
|
+
float(value)
|
|
79
|
+
self.data = str(variable)
|
|
80
|
+
return
|
|
81
|
+
except ValueError:
|
|
82
|
+
pass
|
|
83
|
+
|
|
84
|
+
self.data = float(valuelist[0])
|
|
85
|
+
except ValueError as exc:
|
|
86
|
+
self.data = None
|
|
87
|
+
raise ValueError(self.gettext("Not a valid float value.")) from exc
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# unset_value = UnsetValue()
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class VariableOrIntField(Field):
|
|
94
|
+
widget = TextInput()
|
|
95
|
+
|
|
96
|
+
def __init__(self, label='', validators=None, script=None, **kwargs):
|
|
97
|
+
super(VariableOrIntField, self).__init__(label, validators, **kwargs)
|
|
98
|
+
self.script = script
|
|
99
|
+
|
|
100
|
+
def _value(self):
|
|
101
|
+
if self.script:
|
|
102
|
+
variable, value = find_variable(self.data, self.script)
|
|
103
|
+
if variable:
|
|
104
|
+
return variable
|
|
105
|
+
|
|
106
|
+
if self.raw_data:
|
|
107
|
+
return self.raw_data[0]
|
|
108
|
+
if self.data is not None:
|
|
109
|
+
return str(self.data)
|
|
110
|
+
return ""
|
|
111
|
+
|
|
112
|
+
# def process_data(self, value):
|
|
113
|
+
#
|
|
114
|
+
# if self.script:
|
|
115
|
+
# variable, var_value = find_variable(value, self.script)
|
|
116
|
+
# if variable:
|
|
117
|
+
# try:
|
|
118
|
+
# int(var_value)
|
|
119
|
+
# self.data = str(variable)
|
|
120
|
+
# return
|
|
121
|
+
# except ValueError:
|
|
122
|
+
# pass
|
|
123
|
+
# if value is None or value is unset_value:
|
|
124
|
+
# self.data = None
|
|
125
|
+
# return
|
|
126
|
+
# try:
|
|
127
|
+
# self.data = int(value)
|
|
128
|
+
# except (ValueError, TypeError) as exc:
|
|
129
|
+
# self.data = None
|
|
130
|
+
# raise ValueError(self.gettext("Not a valid integer value.")) from exc
|
|
131
|
+
|
|
132
|
+
def process_formdata(self, valuelist):
|
|
133
|
+
if not valuelist:
|
|
134
|
+
return
|
|
135
|
+
if self.script:
|
|
136
|
+
variable, var_value = find_variable(valuelist[0], self.script)
|
|
137
|
+
if variable:
|
|
138
|
+
try:
|
|
139
|
+
int(var_value)
|
|
140
|
+
self.data = str(variable)
|
|
141
|
+
return
|
|
142
|
+
except ValueError:
|
|
143
|
+
pass
|
|
144
|
+
if valuelist[0].startswith("#"):
|
|
145
|
+
if not self.script.editing_type == "script":
|
|
146
|
+
raise ValueError(self.gettext("Variable is not supported in prep/cleanup"))
|
|
147
|
+
self.data = valuelist[0]
|
|
148
|
+
return
|
|
149
|
+
try:
|
|
150
|
+
self.data = int(valuelist[0])
|
|
151
|
+
except ValueError as exc:
|
|
152
|
+
self.data = None
|
|
153
|
+
raise ValueError(self.gettext("Not a valid integer value.")) from exc
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
class VariableOrBoolField(BooleanField):
|
|
157
|
+
widget = TextInput()
|
|
158
|
+
|
|
159
|
+
def __init__(self, label='', validators=None, script=None, **kwargs):
|
|
160
|
+
super(VariableOrBoolField, self).__init__(label, validators, **kwargs)
|
|
161
|
+
self.script = script
|
|
162
|
+
|
|
163
|
+
def process_data(self, value):
|
|
164
|
+
|
|
165
|
+
if self.script:
|
|
166
|
+
variable, var_value = find_variable(value, self.script)
|
|
167
|
+
if variable:
|
|
168
|
+
try:
|
|
169
|
+
bool(var_value)
|
|
170
|
+
return variable
|
|
171
|
+
except ValueError:
|
|
172
|
+
return
|
|
173
|
+
|
|
174
|
+
self.data = bool(value)
|
|
175
|
+
|
|
176
|
+
def process_formdata(self, valuelist):
|
|
177
|
+
if not valuelist or type(valuelist) is list and valuelist[0] == '':
|
|
178
|
+
self.data = False
|
|
179
|
+
elif valuelist and valuelist[0].startswith("#"):
|
|
180
|
+
if not self.script.editing_type == "script":
|
|
181
|
+
raise ValueError(self.gettext("Variable is not supported in prep/cleanup"))
|
|
182
|
+
self.data = valuelist[0]
|
|
183
|
+
else:
|
|
184
|
+
self.data = True
|
|
185
|
+
|
|
186
|
+
def _value(self):
|
|
187
|
+
|
|
188
|
+
if self.script:
|
|
189
|
+
variable, value = find_variable(self.raw_data, self.script)
|
|
190
|
+
if variable:
|
|
191
|
+
return variable
|
|
192
|
+
|
|
193
|
+
if self.raw_data:
|
|
194
|
+
return str(self.raw_data[0])
|
|
195
|
+
return "y"
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def format_name(name):
|
|
199
|
+
"""Converts 'example_name' to 'Example Name'."""
|
|
200
|
+
name = name.split(".")[-1]
|
|
201
|
+
text = ' '.join(word for word in name.split('_'))
|
|
202
|
+
return text.capitalize()
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def create_form_for_method(method, method_name, autofill, script=None, design=True):
|
|
206
|
+
class DynamicForm(FlaskForm):
|
|
207
|
+
pass
|
|
208
|
+
|
|
209
|
+
annotation_mapping = {
|
|
210
|
+
int: (VariableOrIntField if design else IntegerField, 'Enter integer value'),
|
|
211
|
+
float: (VariableOrFloatField if design else FloatField, 'Enter numeric value'),
|
|
212
|
+
str: (VariableOrStringField if design else StringField, 'Enter text'),
|
|
213
|
+
bool: (VariableOrBoolField if design else BooleanField, 'Empty for false')
|
|
214
|
+
}
|
|
215
|
+
sig = method if type(method) is inspect.Signature else inspect.signature(method)
|
|
216
|
+
|
|
217
|
+
for param in sig.parameters.values():
|
|
218
|
+
if param.name == 'self':
|
|
219
|
+
continue
|
|
220
|
+
formatted_param_name = format_name(param.name)
|
|
221
|
+
field_kwargs = {
|
|
222
|
+
"label": formatted_param_name,
|
|
223
|
+
"default": f'#{param.name}' if autofill else (param.default if param.default is not param.empty else None),
|
|
224
|
+
"validators": [InputRequired()] if param.default is param.empty else None,
|
|
225
|
+
**({"script": script} if (autofill or design) else {})
|
|
226
|
+
}
|
|
227
|
+
field_class, placeholder_text = annotation_mapping.get(
|
|
228
|
+
param.annotation,
|
|
229
|
+
(VariableOrStringField if design else StringField, f'Enter {param.annotation} value')
|
|
230
|
+
)
|
|
231
|
+
render_kwargs = {"placeholder": placeholder_text}
|
|
232
|
+
|
|
233
|
+
# Create the field with additional rendering kwargs for placeholder text
|
|
234
|
+
field = field_class(**field_kwargs, render_kw=render_kwargs)
|
|
235
|
+
setattr(DynamicForm, param.name, field)
|
|
236
|
+
|
|
237
|
+
# setattr(DynamicForm, f'add', fname)
|
|
238
|
+
return DynamicForm
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
# Create forms for each method in DummySDLDeck
|
|
242
|
+
def create_add_form(attr, attr_name, autofill, script=None, design=True):
|
|
243
|
+
dynamic_form = create_form_for_method(attr, attr_name, autofill, script, design)
|
|
244
|
+
if design:
|
|
245
|
+
return_value = StringField(label='Save value as', render_kw={"placeholder": "Optional"})
|
|
246
|
+
setattr(dynamic_form, 'return', return_value)
|
|
247
|
+
hidden_method_name = HiddenField(name=f'hidden_name', render_kw={"value": f'{attr_name}'})
|
|
248
|
+
setattr(dynamic_form, 'hidden_name', hidden_method_name)
|
|
249
|
+
return dynamic_form
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def create_form_from_module(sdl_module, autofill: bool, script=None, design=True):
|
|
253
|
+
# sdl_deck = DummySDLDeck(DummyPump("COM1"), DummyBalance("COM2"))
|
|
254
|
+
method_forms = {}
|
|
255
|
+
for attr_name in dir(sdl_module):
|
|
256
|
+
attr = getattr(sdl_module, attr_name)
|
|
257
|
+
if inspect.ismethod(attr) and not attr_name.startswith('_'):
|
|
258
|
+
form_class = create_add_form(attr, attr_name, autofill, script, design)
|
|
259
|
+
method_forms[attr_name] = form_class()
|
|
260
|
+
return method_forms
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def create_form_from_pseudo(pseudo: dict, autofill: bool, script=None, design=True):
|
|
264
|
+
'''{'dose_liquid': < Signature(amount_in_ml: float, rate_ml_per_minute: float) >}'''
|
|
265
|
+
method_forms = {}
|
|
266
|
+
for attr_name, signature in pseudo.items():
|
|
267
|
+
form_class = create_add_form(signature, attr_name, autofill, script, design)
|
|
268
|
+
method_forms[attr_name] = form_class()
|
|
269
|
+
return method_forms
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def create_builtin_form(logic_type, autofill, script):
|
|
273
|
+
class BuiltinFunctionForm(FlaskForm):
|
|
274
|
+
pass
|
|
275
|
+
placeholder_text = {
|
|
276
|
+
'wait': 'Enter second',
|
|
277
|
+
'repeat': 'Enter an integer'
|
|
278
|
+
}.get(logic_type, 'Enter statement')
|
|
279
|
+
description_text = {
|
|
280
|
+
'variable': 'Your variable can be numbers, boolean (True or False) or text ("text")',
|
|
281
|
+
}.get(logic_type, '')
|
|
282
|
+
field_class = {
|
|
283
|
+
'wait': VariableOrFloatField,
|
|
284
|
+
'repeat': VariableOrIntField
|
|
285
|
+
}.get(logic_type, VariableOrStringField) # Default to StringField as a fallback
|
|
286
|
+
field_kwargs = {
|
|
287
|
+
"label": f'statement',
|
|
288
|
+
"validators": [InputRequired()] if logic_type in ['wait', "variable"] else [],
|
|
289
|
+
"description": description_text,
|
|
290
|
+
"script": script
|
|
291
|
+
}
|
|
292
|
+
render_kwargs = {"placeholder": placeholder_text}
|
|
293
|
+
field = field_class(**field_kwargs, render_kw=render_kwargs)
|
|
294
|
+
setattr(BuiltinFunctionForm, "statement", field)
|
|
295
|
+
if logic_type == 'variable':
|
|
296
|
+
variable_field = StringField(label=f'variable', validators=[InputRequired()],
|
|
297
|
+
description="Your variable name cannot include space",
|
|
298
|
+
render_kw=render_kwargs)
|
|
299
|
+
setattr(BuiltinFunctionForm, "variable", variable_field)
|
|
300
|
+
hidden_field = HiddenField(name=f'builtin_name', render_kw={"value": f'{logic_type}'})
|
|
301
|
+
setattr(BuiltinFunctionForm, "builtin_name", hidden_field)
|
|
302
|
+
return BuiltinFunctionForm()
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def create_action_button(s: dict):
|
|
306
|
+
if s['instrument'] in ['if', 'while', 'repeat']:
|
|
307
|
+
text = f"{s['action']} {s['args']}"
|
|
308
|
+
elif s['instrument'] == 'variable':
|
|
309
|
+
text = f"{s['action']} = {s['args']}"
|
|
310
|
+
else:
|
|
311
|
+
# regular action button
|
|
312
|
+
prefix = f"{s['return']} = " if s['return'] else ""
|
|
313
|
+
action_text = f"{s['instrument'].split('.')[-1] if s['instrument'].startswith('deck') else s['instrument']}.{s['action']}"
|
|
314
|
+
arg_string = ""
|
|
315
|
+
if s['args']:
|
|
316
|
+
if type(s['args']) is dict:
|
|
317
|
+
arg_string = "(" + ", ".join([f"{k} = {v}" for k, v in s['args'].items()]) + ")"
|
|
318
|
+
else:
|
|
319
|
+
arg_string = f"= {s['args']}"
|
|
320
|
+
|
|
321
|
+
text = f"{prefix}{action_text} {arg_string}"
|
|
322
|
+
style = {
|
|
323
|
+
"repeat": "background-color: lightsteelblue",
|
|
324
|
+
"if": "background-color: salmon",
|
|
325
|
+
"while": "background-color: salmon",
|
|
326
|
+
|
|
327
|
+
}.get(s['instrument'], "")
|
|
328
|
+
return dict(label=text, style=style, uuid=s["uuid"], id=s["id"], instrument=s['instrument'])
|