ivoryos 0.1.10__py3-none-any.whl → 0.1.18__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 +54 -14
- ivoryos/routes/control/control.py +1 -1
- ivoryos/routes/control/templates/control/controllers.html +3 -0
- ivoryos/routes/design/design.py +49 -41
- ivoryos/routes/design/templates/design/experiment_builder.html +27 -14
- ivoryos/routes/design/templates/design/experiment_run.html +2 -2
- ivoryos/routes/main/templates/main/home.html +19 -17
- ivoryos/templates/base.html +20 -10
- ivoryos/utils/db_models.py +181 -59
- ivoryos/utils/form.py +204 -81
- ivoryos/utils/script_runner.py +13 -9
- ivoryos/utils/utils.py +27 -43
- ivoryos/version.py +1 -1
- {ivoryos-0.1.10.dist-info → ivoryos-0.1.18.dist-info}/METADATA +4 -1
- {ivoryos-0.1.10.dist-info → ivoryos-0.1.18.dist-info}/RECORD +18 -19
- {ivoryos-0.1.10.dist-info → ivoryos-0.1.18.dist-info}/WHEEL +1 -1
- ivoryos/static/.DS_Store +0 -0
- {ivoryos-0.1.10.dist-info → ivoryos-0.1.18.dist-info}/LICENSE +0 -0
- {ivoryos-0.1.10.dist-info → ivoryos-0.1.18.dist-info}/top_level.txt +0 -0
ivoryos/utils/form.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from wtforms.fields.choices import SelectField
|
|
1
2
|
from wtforms.fields.core import Field
|
|
2
3
|
from wtforms.validators import InputRequired
|
|
3
4
|
from wtforms.widgets.core import TextInput
|
|
@@ -8,16 +9,15 @@ import inspect
|
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
def find_variable(data, script):
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
# return data, None
|
|
12
|
+
"""
|
|
13
|
+
find user defined variables and return values in the script:Script
|
|
14
|
+
:param data: string of input variable name
|
|
15
|
+
:param script:Script object
|
|
16
|
+
"""
|
|
17
|
+
variables: dict[str, str] = script.get_variables()
|
|
18
|
+
for variable_name, variable_type in variables.items():
|
|
19
|
+
if variable_name == data:
|
|
20
|
+
return data, variable_type # variable_type int float str or "function_output"
|
|
21
21
|
return None, None
|
|
22
22
|
|
|
23
23
|
|
|
@@ -36,7 +36,7 @@ class VariableOrStringField(Field):
|
|
|
36
36
|
|
|
37
37
|
def _value(self):
|
|
38
38
|
if self.script:
|
|
39
|
-
variable,
|
|
39
|
+
variable, variable_type = find_variable(self.data, self.script)
|
|
40
40
|
if variable:
|
|
41
41
|
return variable
|
|
42
42
|
|
|
@@ -52,7 +52,7 @@ class VariableOrFloatField(Field):
|
|
|
52
52
|
|
|
53
53
|
def _value(self):
|
|
54
54
|
if self.script:
|
|
55
|
-
variable,
|
|
55
|
+
variable, variable_type = find_variable(self.data, self.script)
|
|
56
56
|
if variable:
|
|
57
57
|
return variable
|
|
58
58
|
|
|
@@ -73,14 +73,15 @@ class VariableOrFloatField(Field):
|
|
|
73
73
|
try:
|
|
74
74
|
if self.script:
|
|
75
75
|
try:
|
|
76
|
-
variable,
|
|
76
|
+
variable, variable_type = find_variable(valuelist[0], self.script)
|
|
77
77
|
if variable:
|
|
78
|
-
|
|
79
|
-
|
|
78
|
+
if not variable_type == "function_output":
|
|
79
|
+
if variable_type not in ["float", "int"]:
|
|
80
|
+
raise ValueError("Variable is not a valid float")
|
|
81
|
+
self.data = variable
|
|
80
82
|
return
|
|
81
83
|
except ValueError:
|
|
82
84
|
pass
|
|
83
|
-
|
|
84
85
|
self.data = float(valuelist[0])
|
|
85
86
|
except ValueError as exc:
|
|
86
87
|
self.data = None
|
|
@@ -99,7 +100,7 @@ class VariableOrIntField(Field):
|
|
|
99
100
|
|
|
100
101
|
def _value(self):
|
|
101
102
|
if self.script:
|
|
102
|
-
variable,
|
|
103
|
+
variable, variable_type = find_variable(self.data, self.script)
|
|
103
104
|
if variable:
|
|
104
105
|
return variable
|
|
105
106
|
|
|
@@ -109,34 +110,16 @@ class VariableOrIntField(Field):
|
|
|
109
110
|
return str(self.data)
|
|
110
111
|
return ""
|
|
111
112
|
|
|
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
113
|
def process_formdata(self, valuelist):
|
|
133
114
|
if not valuelist:
|
|
134
115
|
return
|
|
135
116
|
if self.script:
|
|
136
|
-
variable,
|
|
117
|
+
variable, variable_type = find_variable(valuelist[0], self.script)
|
|
137
118
|
if variable:
|
|
138
119
|
try:
|
|
139
|
-
|
|
120
|
+
if not variable_type == "function_output":
|
|
121
|
+
if not variable_type == "int":
|
|
122
|
+
raise ValueError("Not a valid integer value")
|
|
140
123
|
self.data = str(variable)
|
|
141
124
|
return
|
|
142
125
|
except ValueError:
|
|
@@ -155,6 +138,7 @@ class VariableOrIntField(Field):
|
|
|
155
138
|
|
|
156
139
|
class VariableOrBoolField(BooleanField):
|
|
157
140
|
widget = TextInput()
|
|
141
|
+
false_values = (False, "false", "", "False", "f", "F")
|
|
158
142
|
|
|
159
143
|
def __init__(self, label='', validators=None, script=None, **kwargs):
|
|
160
144
|
super(VariableOrBoolField, self).__init__(label, validators, **kwargs)
|
|
@@ -163,30 +147,34 @@ class VariableOrBoolField(BooleanField):
|
|
|
163
147
|
def process_data(self, value):
|
|
164
148
|
|
|
165
149
|
if self.script:
|
|
166
|
-
variable,
|
|
150
|
+
variable, variable_type = find_variable(value, self.script)
|
|
167
151
|
if variable:
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
except ValueError:
|
|
172
|
-
return
|
|
152
|
+
if not variable_type == "function_output":
|
|
153
|
+
raise ValueError("Not accepting boolean variables")
|
|
154
|
+
return variable
|
|
173
155
|
|
|
174
156
|
self.data = bool(value)
|
|
175
157
|
|
|
176
158
|
def process_formdata(self, valuelist):
|
|
177
|
-
|
|
159
|
+
# todo
|
|
160
|
+
# print(valuelist)
|
|
161
|
+
if not valuelist or not type(valuelist) is list:
|
|
178
162
|
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
163
|
else:
|
|
184
|
-
|
|
164
|
+
value = valuelist[0] if type(valuelist) is list else valuelist
|
|
165
|
+
if value.startswith("#"):
|
|
166
|
+
if not self.script.editing_type == "script":
|
|
167
|
+
raise ValueError(self.gettext("Variable is not supported in prep/cleanup"))
|
|
168
|
+
self.data = valuelist[0]
|
|
169
|
+
elif value in self.false_values:
|
|
170
|
+
self.data = False
|
|
171
|
+
else:
|
|
172
|
+
self.data = True
|
|
185
173
|
|
|
186
174
|
def _value(self):
|
|
187
175
|
|
|
188
176
|
if self.script:
|
|
189
|
-
variable,
|
|
177
|
+
variable, variable_type = find_variable(self.raw_data, self.script)
|
|
190
178
|
if variable:
|
|
191
179
|
return variable
|
|
192
180
|
|
|
@@ -202,7 +190,15 @@ def format_name(name):
|
|
|
202
190
|
return text.capitalize()
|
|
203
191
|
|
|
204
192
|
|
|
205
|
-
def create_form_for_method(method,
|
|
193
|
+
def create_form_for_method(method, autofill, script=None, design=True):
|
|
194
|
+
"""
|
|
195
|
+
Create forms for each method or signature
|
|
196
|
+
:param method: dict(docstring, signature)
|
|
197
|
+
:param autofill:bool if autofill is enabled
|
|
198
|
+
:param script:Script object
|
|
199
|
+
:param design: if design is enabled
|
|
200
|
+
"""
|
|
201
|
+
|
|
206
202
|
class DynamicForm(FlaskForm):
|
|
207
203
|
pass
|
|
208
204
|
|
|
@@ -238,48 +234,141 @@ def create_form_for_method(method, method_name, autofill, script=None, design=Tr
|
|
|
238
234
|
return DynamicForm
|
|
239
235
|
|
|
240
236
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
237
|
+
def create_add_form(attr, attr_name, autofill: bool, script=None, design: bool = True):
|
|
238
|
+
"""
|
|
239
|
+
Create forms for each method or signature
|
|
240
|
+
:param attr: dict(docstring, signature)
|
|
241
|
+
:param attr_name: method name
|
|
242
|
+
:param autofill:bool if autofill is enabled
|
|
243
|
+
:param script:Script object
|
|
244
|
+
:param design: if design is enabled. Design allows string input for parameter names ("#param") for all fields
|
|
245
|
+
"""
|
|
246
|
+
signature = attr.get('signature', {})
|
|
247
|
+
docstring = attr.get('docstring', "")
|
|
248
|
+
dynamic_form = create_form_for_method(signature, autofill, script, design)
|
|
244
249
|
if design:
|
|
245
250
|
return_value = StringField(label='Save value as', render_kw={"placeholder": "Optional"})
|
|
246
251
|
setattr(dynamic_form, 'return', return_value)
|
|
247
|
-
hidden_method_name = HiddenField(name=f'hidden_name', render_kw={"value": f'{attr_name}'})
|
|
252
|
+
hidden_method_name = HiddenField(name=f'hidden_name', description=docstring, render_kw={"value": f'{attr_name}'})
|
|
248
253
|
setattr(dynamic_form, 'hidden_name', hidden_method_name)
|
|
249
254
|
return dynamic_form
|
|
250
255
|
|
|
251
256
|
|
|
252
|
-
def create_form_from_module(sdl_module, autofill: bool, script=None, design=
|
|
253
|
-
|
|
257
|
+
def create_form_from_module(sdl_module, autofill: bool = False, script=None, design: bool = False):
|
|
258
|
+
"""
|
|
259
|
+
Create forms for each method, used for control routes
|
|
260
|
+
:param sdl_module: method module
|
|
261
|
+
:param autofill:bool if autofill is enabled
|
|
262
|
+
:param script:Script object
|
|
263
|
+
:param design: if design is enabled
|
|
264
|
+
"""
|
|
254
265
|
method_forms = {}
|
|
255
266
|
for attr_name in dir(sdl_module):
|
|
256
|
-
|
|
257
|
-
if inspect.ismethod(
|
|
267
|
+
method = getattr(sdl_module, attr_name)
|
|
268
|
+
if inspect.ismethod(method) and not attr_name.startswith('_'):
|
|
269
|
+
signature = inspect.signature(method)
|
|
270
|
+
docstring = inspect.getdoc(method)
|
|
271
|
+
attr = dict(signature=signature, docstring=docstring)
|
|
258
272
|
form_class = create_add_form(attr, attr_name, autofill, script, design)
|
|
259
273
|
method_forms[attr_name] = form_class()
|
|
260
274
|
return method_forms
|
|
261
275
|
|
|
262
276
|
|
|
263
277
|
def create_form_from_pseudo(pseudo: dict, autofill: bool, script=None, design=True):
|
|
264
|
-
|
|
278
|
+
"""
|
|
279
|
+
Create forms for pseudo method, used for design routes
|
|
280
|
+
:param pseudo:{'dose_liquid': {
|
|
281
|
+
"docstring": "some docstring",
|
|
282
|
+
"signature": Signature(amount_in_ml: float, rate_ml_per_minute: float) }
|
|
283
|
+
}
|
|
284
|
+
:param autofill:bool if autofill is enabled
|
|
285
|
+
:param script:Script object
|
|
286
|
+
:param design: if design is enabled
|
|
287
|
+
"""
|
|
265
288
|
method_forms = {}
|
|
266
289
|
for attr_name, signature in pseudo.items():
|
|
290
|
+
# signature = info.get('signature', {})
|
|
267
291
|
form_class = create_add_form(signature, attr_name, autofill, script, design)
|
|
268
292
|
method_forms[attr_name] = form_class()
|
|
269
293
|
return method_forms
|
|
270
294
|
|
|
271
295
|
|
|
272
|
-
def
|
|
296
|
+
def create_form_from_action(action: dict, script=None, design=True):
|
|
297
|
+
'''
|
|
298
|
+
Create forms for single action, used for design routes
|
|
299
|
+
:param action: {'action': 'dose_solid', 'arg_types': {'amount_in_mg': 'float', 'bring_in': 'bool'},
|
|
300
|
+
'args': {'amount_in_mg': 5.0, 'bring_in': False}, 'id': 9,
|
|
301
|
+
'instrument': 'deck.sdl', 'return': '', 'uuid': 266929188668995}
|
|
302
|
+
:param script:Script object
|
|
303
|
+
:param design: if design is enabled
|
|
304
|
+
|
|
305
|
+
'''
|
|
306
|
+
|
|
307
|
+
arg_types = action.get("arg_types", {})
|
|
308
|
+
args = action.get("args", {})
|
|
309
|
+
save_as = action.get("return")
|
|
310
|
+
|
|
311
|
+
class DynamicForm(FlaskForm):
|
|
312
|
+
pass
|
|
313
|
+
|
|
314
|
+
annotation_mapping = {
|
|
315
|
+
"int": (VariableOrIntField if design else IntegerField, 'Enter integer value'),
|
|
316
|
+
"float": (VariableOrFloatField if design else FloatField, 'Enter numeric value'),
|
|
317
|
+
"str": (VariableOrStringField if design else StringField, 'Enter text'),
|
|
318
|
+
"bool": (VariableOrBoolField if design else BooleanField, 'Empty for false')
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
for name, param_type in arg_types.items():
|
|
322
|
+
formatted_param_name = format_name(name)
|
|
323
|
+
value = args.get(name, "")
|
|
324
|
+
if type(value) is dict:
|
|
325
|
+
value = next(iter(value))
|
|
326
|
+
field_kwargs = {
|
|
327
|
+
"label": formatted_param_name,
|
|
328
|
+
"default": f'{value}',
|
|
329
|
+
"validators": [InputRequired()],
|
|
330
|
+
**({"script": script})
|
|
331
|
+
}
|
|
332
|
+
param_type = param_type if type(param_type) is str else f"{param_type}"
|
|
333
|
+
field_class, placeholder_text = annotation_mapping.get(
|
|
334
|
+
param_type,
|
|
335
|
+
(VariableOrStringField if design else StringField, f'Enter {param_type} value')
|
|
336
|
+
)
|
|
337
|
+
render_kwargs = {"placeholder": placeholder_text}
|
|
338
|
+
|
|
339
|
+
# Create the field with additional rendering kwargs for placeholder text
|
|
340
|
+
field = field_class(**field_kwargs, render_kw=render_kwargs)
|
|
341
|
+
setattr(DynamicForm, name, field)
|
|
342
|
+
|
|
343
|
+
if design:
|
|
344
|
+
return_value = StringField(label='Save value as', default=f"{save_as}", render_kw={"placeholder": "Optional"})
|
|
345
|
+
setattr(DynamicForm, 'return', return_value)
|
|
346
|
+
return DynamicForm()
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def create_builtin_form(logic_type, script):
|
|
350
|
+
"""
|
|
351
|
+
Create a builtin form {if, while, variable, repeat, wait}
|
|
352
|
+
"""
|
|
273
353
|
class BuiltinFunctionForm(FlaskForm):
|
|
274
354
|
pass
|
|
275
355
|
|
|
276
|
-
placeholder_text =
|
|
277
|
-
|
|
278
|
-
|
|
356
|
+
placeholder_text = {
|
|
357
|
+
'wait': 'Enter second',
|
|
358
|
+
'repeat': 'Enter an integer'
|
|
359
|
+
}.get(logic_type, 'Enter statement')
|
|
360
|
+
description_text = {
|
|
361
|
+
'variable': 'Your variable can be numbers, boolean (True or False) or text ("text")',
|
|
362
|
+
}.get(logic_type, '')
|
|
363
|
+
field_class = {
|
|
364
|
+
'wait': VariableOrFloatField,
|
|
365
|
+
'repeat': VariableOrIntField
|
|
366
|
+
}.get(logic_type, VariableOrStringField) # Default to StringField as a fallback
|
|
279
367
|
field_kwargs = {
|
|
280
368
|
"label": f'statement',
|
|
281
369
|
"validators": [InputRequired()] if logic_type in ['wait', "variable"] else [],
|
|
282
370
|
"description": description_text,
|
|
371
|
+
"script": script
|
|
283
372
|
}
|
|
284
373
|
render_kwargs = {"placeholder": placeholder_text}
|
|
285
374
|
field = field_class(**field_kwargs, render_kw=render_kwargs)
|
|
@@ -288,29 +377,63 @@ def create_builtin_form(logic_type):
|
|
|
288
377
|
variable_field = StringField(label=f'variable', validators=[InputRequired()],
|
|
289
378
|
description="Your variable name cannot include space",
|
|
290
379
|
render_kw=render_kwargs)
|
|
380
|
+
type_field = SelectField(
|
|
381
|
+
'Select Input Type',
|
|
382
|
+
choices=[('int', 'Integer'), ('float', 'Float'), ('str', 'String')],
|
|
383
|
+
default='str' # Optional default value
|
|
384
|
+
)
|
|
291
385
|
setattr(BuiltinFunctionForm, "variable", variable_field)
|
|
386
|
+
setattr(BuiltinFunctionForm, "type", type_field)
|
|
292
387
|
hidden_field = HiddenField(name=f'builtin_name', render_kw={"value": f'{logic_type}'})
|
|
293
388
|
setattr(BuiltinFunctionForm, "builtin_name", hidden_field)
|
|
294
389
|
return BuiltinFunctionForm()
|
|
295
390
|
|
|
296
391
|
|
|
297
|
-
def create_action_button(
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
392
|
+
def create_action_button(script, stype=None):
|
|
393
|
+
"""
|
|
394
|
+
Creates action buttons for design route (design canvas)
|
|
395
|
+
:param script: Script object
|
|
396
|
+
:param stype: script type (script, prep, cleanup)
|
|
397
|
+
"""
|
|
398
|
+
stype = stype or script.editing_type
|
|
399
|
+
variables = script.get_variables()
|
|
400
|
+
return [_action_button(i, variables) for i in script.get_script(stype)]
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
def _action_button(action: dict, variables: dict):
|
|
404
|
+
"""
|
|
405
|
+
Creates action button for one action
|
|
406
|
+
:param action: Action dict
|
|
407
|
+
:param variables: created variable dict
|
|
408
|
+
"""
|
|
409
|
+
style = {
|
|
410
|
+
"repeat": "background-color: lightsteelblue",
|
|
411
|
+
"if": "background-color: salmon",
|
|
412
|
+
"while": "background-color: salmon",
|
|
413
|
+
}.get(action['instrument'], "")
|
|
414
|
+
|
|
415
|
+
if action['instrument'] in ['if', 'while', 'repeat']:
|
|
416
|
+
text = f"{action['action']} {action['args'].get('statement', '')}"
|
|
417
|
+
elif action['instrument'] == 'variable':
|
|
418
|
+
text = f"{action['action']} = {action['args'].get('statement')}"
|
|
304
419
|
else:
|
|
305
420
|
# regular action button
|
|
306
|
-
prefix = f"{
|
|
307
|
-
action_text = f"{
|
|
421
|
+
prefix = f"{action['return']} = " if action['return'] else ""
|
|
422
|
+
action_text = f"{action['instrument'].split('.')[-1] if action['instrument'].startswith('deck') else action['instrument']}.{action['action']}"
|
|
308
423
|
arg_string = ""
|
|
309
|
-
if
|
|
310
|
-
if type(
|
|
311
|
-
|
|
424
|
+
if action['args']:
|
|
425
|
+
if type(action['args']) is dict:
|
|
426
|
+
arg_list = []
|
|
427
|
+
for k, v in action['args'].items():
|
|
428
|
+
if isinstance(v, dict):
|
|
429
|
+
value = next(iter(v)) # Extract the first key if it's a dict
|
|
430
|
+
# show warning color for variable calling when there is no definition
|
|
431
|
+
style = "background-color: khaki" if value not in variables.keys() else ""
|
|
432
|
+
else:
|
|
433
|
+
value = v # Keep the original value if not a dict
|
|
434
|
+
arg_list.append(f"{k} = {value}") # Format the key-value pair
|
|
435
|
+
arg_string = "(" + ", ".join(arg_list) + ")"
|
|
312
436
|
else:
|
|
313
|
-
arg_string = f"= {
|
|
314
|
-
|
|
437
|
+
arg_string = f"= {action['args']}"
|
|
315
438
|
text = f"{prefix}{action_text} {arg_string}"
|
|
316
|
-
return dict(label=text, style=style, uuid=
|
|
439
|
+
return dict(label=text, style=style, uuid=action["uuid"], id=action["id"], instrument=action['instrument'])
|
ivoryos/utils/script_runner.py
CHANGED
|
@@ -80,14 +80,17 @@ class ScriptRunner:
|
|
|
80
80
|
|
|
81
81
|
def _run_actions(self, actions, section_name="", run_name=None, logger=None):
|
|
82
82
|
logger.info(f'Executing {section_name} steps') if actions else logger.info(f'No {section_name} steps')
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
83
|
+
if self.stop_event.is_set():
|
|
84
|
+
logger.info(f"Stopping execution during {section_name} section.")
|
|
85
|
+
return
|
|
86
|
+
# for action in actions:
|
|
87
|
+
# if self.stop_event.is_set():
|
|
88
|
+
# logger.info(f"Stopping execution during {section_name} section.")
|
|
89
|
+
# break
|
|
90
|
+
# logger.info(f'Executing {action.get("action", "")} action')
|
|
91
|
+
fname = f"{run_name}_{section_name}"
|
|
92
|
+
function = self.globals_dict[fname]
|
|
93
|
+
function()
|
|
91
94
|
|
|
92
95
|
def _run_config_section(self, config, arg_type, output_list, return_list, run_name, logger, socketio):
|
|
93
96
|
compiled = True
|
|
@@ -133,7 +136,8 @@ class ScriptRunner:
|
|
|
133
136
|
function = self.globals_dict[fname]
|
|
134
137
|
output = function(**parameters)
|
|
135
138
|
# output = eval(f"{run_name}_script(**{parameters})")
|
|
136
|
-
|
|
139
|
+
_output = output.copy()
|
|
140
|
+
ax_client.complete_trial(trial_index=trial_index, raw_data=_output)
|
|
137
141
|
output.update(parameters)
|
|
138
142
|
except Exception as e:
|
|
139
143
|
logger.info(f'Optimization error: {e}')
|
ivoryos/utils/utils.py
CHANGED
|
@@ -6,7 +6,7 @@ import os
|
|
|
6
6
|
import pickle
|
|
7
7
|
import subprocess
|
|
8
8
|
import sys
|
|
9
|
-
from
|
|
9
|
+
from collections import Counter
|
|
10
10
|
|
|
11
11
|
from flask import session
|
|
12
12
|
from flask_socketio import SocketIO
|
|
@@ -83,7 +83,6 @@ def available_pseudo_deck(path):
|
|
|
83
83
|
"""
|
|
84
84
|
load pseudo deck (snapshot) from connection history
|
|
85
85
|
"""
|
|
86
|
-
import os
|
|
87
86
|
return os.listdir(path)
|
|
88
87
|
|
|
89
88
|
|
|
@@ -102,20 +101,9 @@ def _inspect_class(class_object=None, debug=False):
|
|
|
102
101
|
if not function.startswith(under_score) and not function.isupper():
|
|
103
102
|
try:
|
|
104
103
|
annotation = inspect.signature(method)
|
|
105
|
-
# if doc_string:
|
|
106
104
|
docstring = inspect.getdoc(method)
|
|
107
105
|
functions[function] = dict(signature=annotation, docstring=docstring)
|
|
108
106
|
|
|
109
|
-
# handle getter setters todo
|
|
110
|
-
# if callable(att):
|
|
111
|
-
# functions[function] = inspect.signature(att)
|
|
112
|
-
# else:
|
|
113
|
-
# att = getattr(class_object.__class__, function)
|
|
114
|
-
# if isinstance(att, property) and att.fset is not None:
|
|
115
|
-
# setter = att.fset.__annotations__
|
|
116
|
-
# setter.pop('return', None)
|
|
117
|
-
# if setter:
|
|
118
|
-
# functions[function] = setter
|
|
119
107
|
except Exception:
|
|
120
108
|
pass
|
|
121
109
|
return functions
|
|
@@ -131,36 +119,22 @@ def _get_type_from_parameters(arg, parameters):
|
|
|
131
119
|
if annotation is not inspect._empty:
|
|
132
120
|
# print(p[arg].annotation)
|
|
133
121
|
if annotation.__module__ == 'typing':
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
122
|
+
|
|
123
|
+
if hasattr(annotation, '__origin__'):
|
|
124
|
+
origin = annotation.__origin__
|
|
125
|
+
if hasattr(origin, '_name') and origin._name in ["Optional", "Union"]:
|
|
126
|
+
arg_type = [i.__name__ for i in annotation.__args__]
|
|
127
|
+
elif hasattr(origin, '__name__'):
|
|
128
|
+
arg_type = origin.__name__
|
|
129
|
+
# todo other types
|
|
130
|
+
elif annotation.__module__ == 'types':
|
|
131
|
+
arg_type = [i.__name__ for i in annotation.__args__]
|
|
132
|
+
|
|
142
133
|
else:
|
|
143
134
|
arg_type = annotation.__name__
|
|
144
135
|
return arg_type
|
|
145
136
|
|
|
146
137
|
|
|
147
|
-
def find_variable_in_script(script: Script, args: Dict[str, str]) -> Optional[Tuple[Dict[str, str], Dict[str, str]]]:
|
|
148
|
-
# TODO: need to search for if the variable exists
|
|
149
|
-
added_variables: list[Dict[str, str]] = [action for action in script.currently_editing_script if
|
|
150
|
-
action["instrument"] == "variable"]
|
|
151
|
-
|
|
152
|
-
possible_variable_arguments = {}
|
|
153
|
-
possible_variable_types = {}
|
|
154
|
-
|
|
155
|
-
for arg_name, arg_val in args.items():
|
|
156
|
-
for added_variable in added_variables:
|
|
157
|
-
if added_variable["action"] == arg_val:
|
|
158
|
-
possible_variable_arguments[arg_name] = added_variable["action"]
|
|
159
|
-
possible_variable_types[arg_name] = "variable"
|
|
160
|
-
|
|
161
|
-
return possible_variable_arguments, possible_variable_types
|
|
162
|
-
|
|
163
|
-
|
|
164
138
|
def _convert_by_str(args, arg_types):
|
|
165
139
|
"""
|
|
166
140
|
Converts a value to type through eval(f'{type}("{args}")')
|
|
@@ -200,9 +174,6 @@ def convert_config_type(args, arg_types, is_class: bool = False):
|
|
|
200
174
|
"""
|
|
201
175
|
Converts an argument from str to an arg type
|
|
202
176
|
"""
|
|
203
|
-
bool_dict = {"True": True, "False": False}
|
|
204
|
-
# print(args, arg_types)
|
|
205
|
-
# print(globals())
|
|
206
177
|
if args:
|
|
207
178
|
for arg in args:
|
|
208
179
|
if arg not in arg_types.keys():
|
|
@@ -317,8 +288,9 @@ def ax_wrapper(data: dict, arg_types:list):
|
|
|
317
288
|
is_min = True if value == "minimize" else False
|
|
318
289
|
|
|
319
290
|
threshold = None if f"{obj_name}_threshold" not in data else data[f"{obj_name}_threshold"]
|
|
320
|
-
properties = ObjectiveProperties(minimize=is_min
|
|
291
|
+
properties = ObjectiveProperties(minimize=is_min)
|
|
321
292
|
objectives[obj_name] = properties
|
|
293
|
+
|
|
322
294
|
return parameter, objectives
|
|
323
295
|
|
|
324
296
|
|
|
@@ -331,13 +303,15 @@ def ax_initiation(data, arg_types):
|
|
|
331
303
|
parameter, objectives = ax_wrapper(data, arg_types)
|
|
332
304
|
from ax.service.ax_client import AxClient
|
|
333
305
|
ax_client = AxClient()
|
|
334
|
-
ax_client.create_experiment(parameter, objectives)
|
|
306
|
+
ax_client.create_experiment(parameter, objectives=objectives)
|
|
335
307
|
return ax_client
|
|
336
308
|
|
|
337
309
|
|
|
338
310
|
def get_arg_type(args, parameters):
|
|
311
|
+
"""get argument type from signature"""
|
|
339
312
|
arg_types = {}
|
|
340
313
|
# print(args, parameters)
|
|
314
|
+
parameters = parameters.get("signature")
|
|
341
315
|
if args:
|
|
342
316
|
for arg in args:
|
|
343
317
|
arg_types[arg] = _get_type_from_parameters(arg, parameters)
|
|
@@ -423,3 +397,13 @@ def load_deck(pkl_name: str):
|
|
|
423
397
|
return pseudo_deck
|
|
424
398
|
except FileNotFoundError:
|
|
425
399
|
return None
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def check_config_duplicate(config):
|
|
403
|
+
"""
|
|
404
|
+
Checks if the config entry has any duplicate
|
|
405
|
+
:param config: [{"arg": 1}, {"arg": 1}, {"arg": 1}]
|
|
406
|
+
:return: [True, False]
|
|
407
|
+
"""
|
|
408
|
+
hashable_data = [tuple(sorted(d.items())) for d in config]
|
|
409
|
+
return any(count > 1 for count in Counter(hashable_data).values())
|
ivoryos/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.1.
|
|
1
|
+
__version__ = "0.1.18"
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: ivoryos
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.18
|
|
4
4
|
Summary: an open-source Python package enabling Self-Driving Labs (SDLs) interoperability
|
|
5
5
|
Home-page: https://gitlab.com/heingroup/ivoryos
|
|
6
6
|
Author: Ivory Zhang
|
|
7
7
|
Author-email: ivoryzhang@chem.ubc.ca
|
|
8
8
|
License: MIT
|
|
9
|
+
Platform: UNKNOWN
|
|
9
10
|
Description-Content-Type: text/markdown
|
|
10
11
|
License-File: LICENSE
|
|
11
12
|
Requires-Dist: bcrypt
|
|
@@ -168,3 +169,5 @@ https://youtu.be/dFfJv9I2-1g
|
|
|
168
169
|
Ivory Zhang, Lucy Hao
|
|
169
170
|
|
|
170
171
|
Authors acknowledge all former and current Hein Lab members for their valuable suggestions.
|
|
172
|
+
|
|
173
|
+
|