ivoryos 0.1.12__py3-none-any.whl → 0.1.19__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 +50 -14
- ivoryos/routes/control/templates/control/controllers.html +3 -0
- ivoryos/routes/design/design.py +67 -47
- ivoryos/routes/design/templates/design/experiment_builder.html +23 -10
- ivoryos/routes/design/templates/design/experiment_run.html +21 -5
- ivoryos/routes/main/templates/main/home.html +19 -17
- ivoryos/static/js/socket_handler.js +31 -3
- ivoryos/templates/base.html +20 -10
- ivoryos/utils/db_models.py +132 -69
- ivoryos/utils/form.py +192 -81
- ivoryos/utils/script_runner.py +86 -37
- ivoryos/utils/utils.py +13 -41
- ivoryos/version.py +1 -1
- {ivoryos-0.1.12.dist-info → ivoryos-0.1.19.dist-info}/METADATA +13 -1
- {ivoryos-0.1.12.dist-info → ivoryos-0.1.19.dist-info}/RECORD +18 -19
- {ivoryos-0.1.12.dist-info → ivoryos-0.1.19.dist-info}/WHEEL +1 -1
- ivoryos/static/.DS_Store +0 -0
- {ivoryos-0.1.12.dist-info → ivoryos-0.1.19.dist-info}/LICENSE +0 -0
- {ivoryos-0.1.12.dist-info → ivoryos-0.1.19.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,40 +234,125 @@ 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
|
|
355
|
+
|
|
275
356
|
placeholder_text = {
|
|
276
357
|
'wait': 'Enter second',
|
|
277
358
|
'repeat': 'Enter an integer'
|
|
@@ -296,33 +377,63 @@ def create_builtin_form(logic_type, autofill, script):
|
|
|
296
377
|
variable_field = StringField(label=f'variable', validators=[InputRequired()],
|
|
297
378
|
description="Your variable name cannot include space",
|
|
298
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
|
+
)
|
|
299
385
|
setattr(BuiltinFunctionForm, "variable", variable_field)
|
|
386
|
+
setattr(BuiltinFunctionForm, "type", type_field)
|
|
300
387
|
hidden_field = HiddenField(name=f'builtin_name', render_kw={"value": f'{logic_type}'})
|
|
301
388
|
setattr(BuiltinFunctionForm, "builtin_name", hidden_field)
|
|
302
389
|
return BuiltinFunctionForm()
|
|
303
390
|
|
|
304
391
|
|
|
305
|
-
def create_action_button(
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
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']}"
|
|
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)]
|
|
320
401
|
|
|
321
|
-
|
|
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
|
+
"""
|
|
322
409
|
style = {
|
|
323
410
|
"repeat": "background-color: lightsteelblue",
|
|
324
411
|
"if": "background-color: salmon",
|
|
325
412
|
"while": "background-color: salmon",
|
|
413
|
+
}.get(action['instrument'], "")
|
|
326
414
|
|
|
327
|
-
|
|
328
|
-
|
|
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')}"
|
|
419
|
+
else:
|
|
420
|
+
# regular action button
|
|
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']}"
|
|
423
|
+
arg_string = ""
|
|
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) + ")"
|
|
436
|
+
else:
|
|
437
|
+
arg_string = f"= {action['args']}"
|
|
438
|
+
text = f"{prefix}{action_text} {arg_string}"
|
|
439
|
+
return dict(label=text, style=style, uuid=action["uuid"], id=action["id"], instrument=action['instrument'])
|
ivoryos/utils/script_runner.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import ast
|
|
1
2
|
import os
|
|
2
3
|
import csv
|
|
3
4
|
import threading
|
|
@@ -17,25 +18,40 @@ class ScriptRunner:
|
|
|
17
18
|
if globals_dict is None:
|
|
18
19
|
globals_dict = globals()
|
|
19
20
|
self.globals_dict = globals_dict
|
|
20
|
-
|
|
21
|
-
self.
|
|
21
|
+
self.pause_event = threading.Event() # A threading event to manage pause/resume
|
|
22
|
+
self.pause_event.set()
|
|
23
|
+
self.stop_pending_event = threading.Event()
|
|
24
|
+
self.stop_current_event = threading.Event()
|
|
22
25
|
self.is_running = False
|
|
23
26
|
self.lock = threading.Lock()
|
|
24
27
|
|
|
28
|
+
def toggle_pause(self):
|
|
29
|
+
"""Toggles between pausing and resuming the script"""
|
|
30
|
+
if self.pause_event.is_set():
|
|
31
|
+
self.pause_event.clear() # Pause the script
|
|
32
|
+
return "Paused"
|
|
33
|
+
else:
|
|
34
|
+
self.pause_event.set() # Resume the script
|
|
35
|
+
return "Resumed"
|
|
36
|
+
|
|
25
37
|
def reset_stop_event(self):
|
|
26
|
-
self.
|
|
38
|
+
self.stop_pending_event.clear()
|
|
39
|
+
self.stop_current_event.clear()
|
|
27
40
|
|
|
28
|
-
def
|
|
29
|
-
self.
|
|
41
|
+
def abort_pending(self):
|
|
42
|
+
self.stop_pending_event.set()
|
|
30
43
|
# print("Stop pending tasks")
|
|
31
44
|
|
|
45
|
+
def stop_execution(self):
|
|
46
|
+
"""Force stop everything, including ongoing tasks."""
|
|
47
|
+
self.stop_current_event.set()
|
|
48
|
+
self.abort_pending()
|
|
49
|
+
|
|
32
50
|
def run_script(self, script, repeat_count=1, run_name=None, logger=None, socketio=None, config=None, bo_args=None,
|
|
33
51
|
output_path=""):
|
|
34
52
|
global deck
|
|
35
53
|
if deck is None:
|
|
36
54
|
deck = global_config.deck
|
|
37
|
-
exec_string = script.compile()
|
|
38
|
-
exec(exec_string)
|
|
39
55
|
time.sleep(1)
|
|
40
56
|
with self.lock:
|
|
41
57
|
if self.is_running:
|
|
@@ -50,24 +66,61 @@ class ScriptRunner:
|
|
|
50
66
|
thread.start()
|
|
51
67
|
return thread
|
|
52
68
|
|
|
69
|
+
def execute_function_line_by_line(self, script, section_name, logger, **kwargs):
|
|
70
|
+
"""
|
|
71
|
+
Executes a function defined in a string line by line.
|
|
72
|
+
|
|
73
|
+
:param func_str: The function as a string
|
|
74
|
+
:param kwargs: Arguments to pass to the function
|
|
75
|
+
:return: The final result of the function execution
|
|
76
|
+
"""
|
|
77
|
+
global deck
|
|
78
|
+
if deck is None:
|
|
79
|
+
deck = global_config.deck
|
|
80
|
+
# func_str = script.compile()
|
|
81
|
+
# Parse function body from string
|
|
82
|
+
module = ast.parse(script)
|
|
83
|
+
func_def = next(node for node in module.body if isinstance(node, ast.FunctionDef))
|
|
84
|
+
|
|
85
|
+
# Extract function body as source lines
|
|
86
|
+
lines = [ast.unparse(node) for node in func_def.body if not isinstance(node, ast.Return)]
|
|
87
|
+
# Prepare execution environment
|
|
88
|
+
exec_globals = {"deck": deck} # Add required global objects
|
|
89
|
+
exec_locals = {} # Local execution scope
|
|
90
|
+
|
|
91
|
+
# Define function arguments manually in exec_locals
|
|
92
|
+
exec_locals.update(kwargs)
|
|
93
|
+
|
|
94
|
+
# Execute each line dynamically
|
|
95
|
+
for line in lines:
|
|
96
|
+
if self.stop_current_event.is_set():
|
|
97
|
+
logger.info(f'Stopping execution during {section_name}')
|
|
98
|
+
break
|
|
99
|
+
logger.info(f"Executing: {line}") # Debugging output
|
|
100
|
+
exec(line, exec_globals, exec_locals)
|
|
101
|
+
self.pause_event.wait()
|
|
102
|
+
|
|
103
|
+
return exec_locals # Return the 'results' variable
|
|
104
|
+
|
|
53
105
|
def _run_with_stop_check(self, script: Script, repeat_count, run_name, logger, socketio, config, bo_args,
|
|
54
106
|
output_path):
|
|
55
107
|
time.sleep(1)
|
|
108
|
+
func_str = script.compile()
|
|
56
109
|
self._emit_progress(socketio, 1)
|
|
57
110
|
try:
|
|
58
111
|
# Run "prep" section once
|
|
59
112
|
script_dict = script.script_dict
|
|
60
|
-
self._run_actions(script_dict.get("prep", []),
|
|
113
|
+
self._run_actions(script_dict.get("prep", []), func_str.get("prep", ''), section_name="prep", logger=logger)
|
|
61
114
|
output_list = []
|
|
62
115
|
_, arg_type = script.config("script")
|
|
63
116
|
_, return_list = script.config_return()
|
|
64
117
|
# Run "script" section multiple times
|
|
65
118
|
if repeat_count:
|
|
66
|
-
self._run_repeat_section(repeat_count, arg_type, bo_args, output_list,
|
|
119
|
+
self._run_repeat_section(repeat_count, arg_type, bo_args, output_list, func_str.get("script", ''), run_name, return_list, logger, socketio)
|
|
67
120
|
elif config:
|
|
68
|
-
self._run_config_section(config, arg_type, output_list,
|
|
121
|
+
self._run_config_section(config, arg_type, output_list, func_str.get("script", ''), run_name, logger, socketio)
|
|
69
122
|
# Run "cleanup" section once
|
|
70
|
-
self._run_actions(script_dict.get("cleanup", []),
|
|
123
|
+
self._run_actions(script_dict.get("cleanup", []), func_str.get("cleanup", ''), section_name="cleanup", logger=logger)
|
|
71
124
|
# Save results if necessary
|
|
72
125
|
if output_list:
|
|
73
126
|
self._save_results(run_name, arg_type, return_list, output_list, logger, output_path)
|
|
@@ -78,18 +131,14 @@ class ScriptRunner:
|
|
|
78
131
|
self.is_running = False # Reset the running flag when done
|
|
79
132
|
self._emit_progress(socketio, 100)
|
|
80
133
|
|
|
81
|
-
def _run_actions(self, actions, section_name="",
|
|
134
|
+
def _run_actions(self, actions, func_str, section_name="", logger=None):
|
|
82
135
|
logger.info(f'Executing {section_name} steps') if actions else logger.info(f'No {section_name} steps')
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
logger.info(f'Executing {action.get("action", "")} action')
|
|
88
|
-
fname = f"{run_name}_{section_name}"
|
|
89
|
-
function = self.globals_dict[fname]
|
|
90
|
-
function()
|
|
136
|
+
if self.stop_pending_event.is_set():
|
|
137
|
+
logger.info(f"Stopping execution during {section_name} section.")
|
|
138
|
+
return
|
|
139
|
+
self.execute_function_line_by_line(func_str, section_name, logger)
|
|
91
140
|
|
|
92
|
-
def _run_config_section(self, config, arg_type, output_list,
|
|
141
|
+
def _run_config_section(self, config, arg_type, output_list, func_str, run_name, logger, socketio):
|
|
93
142
|
compiled = True
|
|
94
143
|
for i in config:
|
|
95
144
|
try:
|
|
@@ -101,25 +150,25 @@ class ScriptRunner:
|
|
|
101
150
|
if compiled:
|
|
102
151
|
for i, kwargs in enumerate(config):
|
|
103
152
|
kwargs = dict(kwargs)
|
|
104
|
-
if self.
|
|
153
|
+
if self.stop_pending_event.is_set():
|
|
105
154
|
logger.info(f'Stopping execution during {run_name}: {i + 1}/{len(config)}')
|
|
106
155
|
break
|
|
107
156
|
logger.info(f'Executing {i + 1} of {len(config)} with kwargs = {kwargs}')
|
|
108
157
|
progress = (i + 1) * 100 / len(config)
|
|
109
158
|
self._emit_progress(socketio, progress)
|
|
110
|
-
fname = f"{run_name}_script"
|
|
111
|
-
function = self.globals_dict[fname]
|
|
112
|
-
output =
|
|
159
|
+
# fname = f"{run_name}_script"
|
|
160
|
+
# function = self.globals_dict[fname]
|
|
161
|
+
output = self.execute_function_line_by_line(func_str, "script", logger, **kwargs)
|
|
113
162
|
if output:
|
|
114
|
-
kwargs.update(output)
|
|
115
|
-
output_list.append(
|
|
163
|
+
# kwargs.update(output)
|
|
164
|
+
output_list.append(output)
|
|
116
165
|
|
|
117
|
-
def _run_repeat_section(self, repeat_count, arg_types, bo_args, output_list,
|
|
166
|
+
def _run_repeat_section(self, repeat_count, arg_types, bo_args, output_list, func_str, run_name, return_list, logger, socketio):
|
|
118
167
|
if bo_args:
|
|
119
168
|
logger.info('Initializing optimizer...')
|
|
120
169
|
ax_client = utils.ax_initiation(bo_args, arg_types)
|
|
121
170
|
for i in range(int(repeat_count)):
|
|
122
|
-
if self.
|
|
171
|
+
if self.stop_pending_event.is_set():
|
|
123
172
|
logger.info(f'Stopping execution during {run_name}: {i + 1}/{int(repeat_count)}')
|
|
124
173
|
break
|
|
125
174
|
logger.info(f'Executing {run_name} experiment: {i + 1}/{int(repeat_count)}')
|
|
@@ -129,20 +178,20 @@ class ScriptRunner:
|
|
|
129
178
|
try:
|
|
130
179
|
parameters, trial_index = ax_client.get_next_trial()
|
|
131
180
|
logger.info(f'Output value: {parameters}')
|
|
132
|
-
fname = f"{run_name}_script"
|
|
133
|
-
function = self.globals_dict[fname]
|
|
134
|
-
output =
|
|
135
|
-
|
|
136
|
-
_output = output.
|
|
181
|
+
# fname = f"{run_name}_script"
|
|
182
|
+
# function = self.globals_dict[fname]
|
|
183
|
+
output = self.execute_function_line_by_line(func_str, "script", logger, **parameters)
|
|
184
|
+
|
|
185
|
+
_output = {key: value for key, value in output.items() if key in return_list}
|
|
137
186
|
ax_client.complete_trial(trial_index=trial_index, raw_data=_output)
|
|
138
187
|
output.update(parameters)
|
|
139
188
|
except Exception as e:
|
|
140
189
|
logger.info(f'Optimization error: {e}')
|
|
141
190
|
break
|
|
142
191
|
else:
|
|
143
|
-
fname = f"{run_name}_script"
|
|
144
|
-
function = self.globals_dict[fname]
|
|
145
|
-
output =
|
|
192
|
+
# fname = f"{run_name}_script"
|
|
193
|
+
# function = self.globals_dict[fname]
|
|
194
|
+
output = self.execute_function_line_by_line(func_str, "script", logger)
|
|
146
195
|
|
|
147
196
|
if output:
|
|
148
197
|
output_list.append(output)
|