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/db_models.py
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import ast
|
|
2
|
+
import builtins
|
|
1
3
|
import json
|
|
2
4
|
import keyword
|
|
3
5
|
import re
|
|
4
6
|
import uuid
|
|
5
7
|
from datetime import datetime
|
|
8
|
+
from typing import Dict
|
|
6
9
|
|
|
7
10
|
from flask_login import UserMixin
|
|
8
11
|
from flask_sqlalchemy import SQLAlchemy
|
|
@@ -89,52 +92,58 @@ class Script(db.Model):
|
|
|
89
92
|
return action
|
|
90
93
|
|
|
91
94
|
def _convert_type(self, args, arg_types):
|
|
95
|
+
if arg_types in ["list", "tuple", "set"]:
|
|
96
|
+
try:
|
|
97
|
+
args = ast.literal_eval(args)
|
|
98
|
+
return args
|
|
99
|
+
except Exception:
|
|
100
|
+
pass
|
|
92
101
|
if type(arg_types) is not list:
|
|
93
102
|
arg_types = [arg_types]
|
|
94
103
|
for arg_type in arg_types:
|
|
95
104
|
try:
|
|
105
|
+
# print(arg_type)
|
|
96
106
|
args = eval(f"{arg_type}('{args}')")
|
|
97
107
|
return
|
|
98
108
|
except Exception:
|
|
109
|
+
|
|
99
110
|
pass
|
|
100
111
|
raise TypeError(f"Input type error: cannot convert '{args}' to {arg_type}.")
|
|
101
112
|
|
|
102
113
|
def update_by_uuid(self, uuid, args, output):
|
|
103
|
-
bool_dict = {"True": True, "False": False}
|
|
104
114
|
action = self.find_by_uuid(uuid)
|
|
115
|
+
if not action:
|
|
116
|
+
return
|
|
117
|
+
arg_types = action['arg_types']
|
|
105
118
|
if type(action['args']) is dict:
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
if args[arg] in bool_dict.keys():
|
|
110
|
-
args[arg] = bool_dict[args[arg]]
|
|
111
|
-
elif args[arg] == "None" or args[arg] == "":
|
|
112
|
-
args[arg] = None
|
|
113
|
-
else:
|
|
114
|
-
if arg in action['arg_types']:
|
|
115
|
-
arg_types = action['arg_types'][arg]
|
|
116
|
-
self._convert_type(args[arg], arg_types)
|
|
117
|
-
else:
|
|
118
|
-
try:
|
|
119
|
-
args[arg] = eval(args[arg])
|
|
120
|
-
except Exception:
|
|
121
|
-
pass
|
|
119
|
+
# pass
|
|
120
|
+
self.eval_list(args, arg_types)
|
|
122
121
|
else:
|
|
123
|
-
|
|
124
|
-
if not args.startswith("#"):
|
|
125
|
-
if args in bool_dict.keys():
|
|
126
|
-
args = bool_dict[args]
|
|
127
|
-
|
|
128
|
-
else:
|
|
129
|
-
if 'arg_types' in action:
|
|
130
|
-
arg_types = action['arg_types']
|
|
131
|
-
self._convert_type(args, arg_types)
|
|
132
|
-
|
|
133
|
-
# print(args)
|
|
122
|
+
pass
|
|
134
123
|
action['args'] = args
|
|
135
|
-
# print(action)
|
|
136
124
|
action['return'] = output
|
|
137
125
|
|
|
126
|
+
@staticmethod
|
|
127
|
+
def eval_list(args, arg_types):
|
|
128
|
+
for arg in args:
|
|
129
|
+
arg_type = arg_types[arg]
|
|
130
|
+
if arg_type in ["list", "tuple", "set"]:
|
|
131
|
+
|
|
132
|
+
if type(arg) is str and not args[arg].startswith("#"):
|
|
133
|
+
# arg_types = arg_types[arg]
|
|
134
|
+
# if arg_types in ["list", "tuple", "set"]:
|
|
135
|
+
convert_type = getattr(builtins, arg_type) # Handle unknown types s
|
|
136
|
+
try:
|
|
137
|
+
output = ast.literal_eval(args[arg])
|
|
138
|
+
if type(output) not in [list, tuple, set]:
|
|
139
|
+
output = [output]
|
|
140
|
+
args[arg] = convert_type(output)
|
|
141
|
+
# return args
|
|
142
|
+
except ValueError:
|
|
143
|
+
_list = ''.join(args[arg]).split(',')
|
|
144
|
+
# convert_type = getattr(builtins, arg_types) # Handle unknown types s
|
|
145
|
+
args[arg] = convert_type([s.strip() for s in _list])
|
|
146
|
+
|
|
138
147
|
@property
|
|
139
148
|
def stypes(self):
|
|
140
149
|
return list(self.script_dict.keys())
|
|
@@ -155,14 +164,6 @@ class Script(db.Model):
|
|
|
155
164
|
def currently_editing_order(self, script):
|
|
156
165
|
self.id_order[self.editing_type] = script
|
|
157
166
|
|
|
158
|
-
# @property
|
|
159
|
-
# def editing_type(self):
|
|
160
|
-
# return self.editing_type
|
|
161
|
-
|
|
162
|
-
# @editing_type.setter
|
|
163
|
-
# def editing_type(self, change_type):
|
|
164
|
-
# self.editing_type = change_type
|
|
165
|
-
|
|
166
167
|
def update_time_stamp(self):
|
|
167
168
|
self.last_modified = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
168
169
|
|
|
@@ -204,15 +205,52 @@ class Script(db.Model):
|
|
|
204
205
|
self.currently_editing_order.append(str(current_len + 1))
|
|
205
206
|
self.update_time_stamp()
|
|
206
207
|
|
|
207
|
-
def add_variable(self, statement, variable):
|
|
208
|
+
def add_variable(self, statement, variable, type):
|
|
209
|
+
variable = self.validate_function_name(variable)
|
|
210
|
+
convert_type = getattr(builtins, type)
|
|
211
|
+
statement = convert_type(statement)
|
|
208
212
|
current_len = len(self.currently_editing_script)
|
|
209
213
|
uid = uuid.uuid4().fields[-1]
|
|
210
214
|
action_list = [{"id": current_len + 1, "instrument": 'variable', "action": variable,
|
|
211
|
-
"args": 'None' if statement == '' else statement, "return": '', "uuid": uid,
|
|
215
|
+
"args": {"statement": 'None' if statement == '' else statement}, "return": '', "uuid": uid,
|
|
216
|
+
"arg_types": {"statement": type}}]
|
|
212
217
|
self.currently_editing_script.extend(action_list)
|
|
213
218
|
self.currently_editing_order.extend([str(current_len + i + 1) for i in range(len(action_list))])
|
|
214
219
|
self.update_time_stamp()
|
|
215
220
|
|
|
221
|
+
def get_added_variables(self):
|
|
222
|
+
added_variables: Dict[str, str] = {action["action"]: action["arg_types"]["statement"] for action in
|
|
223
|
+
self.currently_editing_script if action["instrument"] == "variable"}
|
|
224
|
+
|
|
225
|
+
return added_variables
|
|
226
|
+
|
|
227
|
+
def get_output_variables(self):
|
|
228
|
+
output_variables: Dict[str, str] = {action["return"]: "function_output" for action in
|
|
229
|
+
self.currently_editing_script if action["return"]}
|
|
230
|
+
|
|
231
|
+
return output_variables
|
|
232
|
+
|
|
233
|
+
def get_variables(self):
|
|
234
|
+
output_variables: Dict[str, str] = self.get_output_variables()
|
|
235
|
+
added_variables = self.get_added_variables()
|
|
236
|
+
output_variables.update(added_variables)
|
|
237
|
+
|
|
238
|
+
return output_variables
|
|
239
|
+
|
|
240
|
+
def validate_variables(self, kwargs):
|
|
241
|
+
"""
|
|
242
|
+
Validates the kwargs passed to the Script
|
|
243
|
+
"""
|
|
244
|
+
output_variables: Dict[str, str] = self.get_variables()
|
|
245
|
+
# print(output_variables)
|
|
246
|
+
for key, value in kwargs.items():
|
|
247
|
+
if type(value) is str and value in output_variables:
|
|
248
|
+
var_type = output_variables[value]
|
|
249
|
+
kwargs[key] = {value: var_type}
|
|
250
|
+
if isinstance(value, str) and value.startswith("#"):
|
|
251
|
+
kwargs[key] = f"#{self.validate_function_name(value[1:])}"
|
|
252
|
+
return kwargs
|
|
253
|
+
|
|
216
254
|
def add_logic_action(self, logic_type: str, statement):
|
|
217
255
|
current_len = len(self.currently_editing_script)
|
|
218
256
|
uid = uuid.uuid4().fields[-1]
|
|
@@ -220,26 +258,35 @@ class Script(db.Model):
|
|
|
220
258
|
"if":
|
|
221
259
|
[
|
|
222
260
|
{"id": current_len + 1, "instrument": 'if', "action": 'if',
|
|
223
|
-
"args": 'True' if statement == '' else statement,
|
|
224
|
-
"return": '', "uuid": uid, "arg_types": ''},
|
|
225
|
-
{"id": current_len + 2, "instrument": 'if', "action": 'else', "args":
|
|
261
|
+
"args": {"statement": 'True' if statement == '' else statement},
|
|
262
|
+
"return": '', "uuid": uid, "arg_types": {"statement": ''}},
|
|
263
|
+
{"id": current_len + 2, "instrument": 'if', "action": 'else', "args": {}, "return": '',
|
|
226
264
|
"uuid": uid},
|
|
227
|
-
{"id": current_len + 3, "instrument": 'if', "action": 'endif', "args":
|
|
265
|
+
{"id": current_len + 3, "instrument": 'if', "action": 'endif', "args": {}, "return": '',
|
|
228
266
|
"uuid": uid},
|
|
229
267
|
],
|
|
230
268
|
"while":
|
|
231
269
|
[
|
|
232
270
|
{"id": current_len + 1, "instrument": 'while', "action": 'while',
|
|
233
|
-
"args": 'False' if statement == '' else statement, "return": '', "uuid": uid,
|
|
234
|
-
|
|
271
|
+
"args": {"statement": 'False' if statement == '' else statement}, "return": '', "uuid": uid,
|
|
272
|
+
"arg_types": {"statement": ''}},
|
|
273
|
+
{"id": current_len + 2, "instrument": 'while', "action": 'endwhile', "args": {}, "return": '',
|
|
235
274
|
"uuid": uid},
|
|
236
275
|
],
|
|
237
276
|
|
|
238
277
|
"wait":
|
|
239
278
|
[
|
|
240
279
|
{"id": current_len + 1, "instrument": 'wait', "action": "wait",
|
|
241
|
-
"args":
|
|
242
|
-
"return": '', "uuid": uid, "arg_types": "float"},
|
|
280
|
+
"args": {"statement": 1 if statement == '' else statement},
|
|
281
|
+
"return": '', "uuid": uid, "arg_types": {"statement": "float"}},
|
|
282
|
+
],
|
|
283
|
+
"repeat":
|
|
284
|
+
[
|
|
285
|
+
{"id": current_len + 1, "instrument": 'repeat', "action": "repeat",
|
|
286
|
+
"args": {"statement": 1 if statement == '' else statement}, "return": '', "uuid": uid,
|
|
287
|
+
"arg_types": {"statement": "int"}},
|
|
288
|
+
{"id": current_len + 2, "instrument": 'repeat', "action": 'endrepeat',
|
|
289
|
+
"args": {}, "return": '', "uuid": uid},
|
|
243
290
|
],
|
|
244
291
|
}
|
|
245
292
|
action_list = logic_dict[logic_type]
|
|
@@ -248,7 +295,9 @@ class Script(db.Model):
|
|
|
248
295
|
self.update_time_stamp()
|
|
249
296
|
|
|
250
297
|
def delete_action(self, id: int):
|
|
251
|
-
|
|
298
|
+
"""
|
|
299
|
+
Delete the action by id (step number)
|
|
300
|
+
"""
|
|
252
301
|
uid = next((action['uuid'] for action in self.currently_editing_script if action['id'] == int(id)), None)
|
|
253
302
|
id_to_be_removed = [action['id'] for action in self.currently_editing_script if action['uuid'] == uid]
|
|
254
303
|
order = self.currently_editing_order
|
|
@@ -259,7 +308,11 @@ class Script(db.Model):
|
|
|
259
308
|
self.update_time_stamp()
|
|
260
309
|
|
|
261
310
|
def duplicate_action(self, id: int):
|
|
262
|
-
|
|
311
|
+
"""
|
|
312
|
+
duplicate action by id (step number), available only for non logic actions
|
|
313
|
+
"""
|
|
314
|
+
action_to_duplicate = next((action for action in self.currently_editing_script if action['id'] == int(id)),
|
|
315
|
+
None)
|
|
263
316
|
insert_id = action_to_duplicate.get("id")
|
|
264
317
|
self.add_action(action_to_duplicate)
|
|
265
318
|
# print(self.currently_editing_script)
|
|
@@ -312,7 +365,7 @@ class Script(db.Model):
|
|
|
312
365
|
:return: list of variable that require input
|
|
313
366
|
"""
|
|
314
367
|
|
|
315
|
-
return_list = [action['return'] for action in self.script_dict['script'] if not action['return'] == '']
|
|
368
|
+
return_list = set([action['return'] for action in self.script_dict['script'] if not action['return'] == ''])
|
|
316
369
|
output_str = "return {"
|
|
317
370
|
for i in return_list:
|
|
318
371
|
output_str += "'" + i + "':" + i + ","
|
|
@@ -320,15 +373,18 @@ class Script(db.Model):
|
|
|
320
373
|
return output_str, return_list
|
|
321
374
|
|
|
322
375
|
def finalize(self):
|
|
376
|
+
"""finalize script, disable editing"""
|
|
323
377
|
self.status = "finalized"
|
|
324
378
|
self.update_time_stamp()
|
|
325
379
|
|
|
326
380
|
def save_as(self, name):
|
|
381
|
+
"""resave script, enable editing"""
|
|
327
382
|
self.name = name
|
|
328
383
|
self.status = "editing"
|
|
329
384
|
self.update_time_stamp()
|
|
330
385
|
|
|
331
386
|
def indent(self, unit=0):
|
|
387
|
+
"""helper: create _ unit of indent in code string"""
|
|
332
388
|
string = "\n"
|
|
333
389
|
for _ in range(unit):
|
|
334
390
|
string += "\t"
|
|
@@ -353,6 +409,26 @@ class Script(db.Model):
|
|
|
353
409
|
|
|
354
410
|
return exec_string
|
|
355
411
|
|
|
412
|
+
def compile_steps(self, script_path=None):
|
|
413
|
+
"""
|
|
414
|
+
Compile the current script to steps.
|
|
415
|
+
:return: {"prep":[], "script":[], "cleanup":[],}.
|
|
416
|
+
"""
|
|
417
|
+
self.sort_actions()
|
|
418
|
+
run_name = self.name if self.name else "untitled"
|
|
419
|
+
run_name = self.validate_function_name(run_name)
|
|
420
|
+
exec_string = ''
|
|
421
|
+
steps = {}
|
|
422
|
+
for i in self.stypes:
|
|
423
|
+
# exec_string += self._generate_function_header(run_name, i)
|
|
424
|
+
exec_string += self._generate_function_body(i)
|
|
425
|
+
|
|
426
|
+
if script_path:
|
|
427
|
+
self._write_to_file(script_path, run_name, exec_string)
|
|
428
|
+
|
|
429
|
+
return exec_string
|
|
430
|
+
|
|
431
|
+
|
|
356
432
|
@staticmethod
|
|
357
433
|
def validate_function_name(name):
|
|
358
434
|
"""Replace invalid characters with underscores"""
|
|
@@ -366,11 +442,15 @@ class Script(db.Model):
|
|
|
366
442
|
"""
|
|
367
443
|
Generate the function header.
|
|
368
444
|
"""
|
|
369
|
-
configure,
|
|
445
|
+
configure, config_type = self.config(stype)
|
|
446
|
+
|
|
447
|
+
configure = [param + f":{param_type}" if not param_type == "any" else "" for param, param_type in
|
|
448
|
+
config_type.items()]
|
|
449
|
+
|
|
370
450
|
function_header = f"\n\ndef {run_name}_{stype}("
|
|
371
451
|
|
|
372
452
|
if stype == "script":
|
|
373
|
-
function_header += ",".join(configure)
|
|
453
|
+
function_header += ", ".join(configure)
|
|
374
454
|
|
|
375
455
|
function_header += "):"
|
|
376
456
|
function_header += self.indent(1) + f"global {run_name}_{stype}"
|
|
@@ -389,26 +469,45 @@ class Script(db.Model):
|
|
|
389
469
|
return_str, return_list = self.config_return()
|
|
390
470
|
if return_list and stype == "script":
|
|
391
471
|
body += self.indent(indent_unit) + return_str
|
|
392
|
-
|
|
393
472
|
return body
|
|
394
473
|
|
|
474
|
+
# def _generate_function_body(self, stype):
|
|
475
|
+
# """
|
|
476
|
+
# Generate the function body for each type in stypes.
|
|
477
|
+
# """
|
|
478
|
+
# steps = []
|
|
479
|
+
# indent_unit = 1
|
|
480
|
+
#
|
|
481
|
+
# for index, action in enumerate(self.script_dict[stype]):
|
|
482
|
+
# text, indent_unit = self._process_action(indent_unit, action, index, stype)
|
|
483
|
+
# body += text
|
|
484
|
+
# return_str, return_list = self.config_return()
|
|
485
|
+
# if return_list and stype == "script":
|
|
486
|
+
# body += self.indent(indent_unit) + return_str
|
|
487
|
+
# return body
|
|
488
|
+
|
|
395
489
|
def _process_action(self, indent_unit, action, index, stype):
|
|
396
490
|
"""
|
|
397
491
|
Process each action within the script dictionary.
|
|
398
492
|
"""
|
|
399
493
|
instrument = action['instrument']
|
|
494
|
+
statement = action['args'].get('statement')
|
|
400
495
|
args = self._process_args(action['args'])
|
|
496
|
+
|
|
401
497
|
save_data = action['return']
|
|
402
498
|
action_name = action['action']
|
|
403
499
|
next_action = self._get_next_action(stype, index)
|
|
500
|
+
# print(args)
|
|
404
501
|
if instrument == 'if':
|
|
405
|
-
return self._process_if(indent_unit, action_name,
|
|
502
|
+
return self._process_if(indent_unit, action_name, statement, next_action)
|
|
406
503
|
elif instrument == 'while':
|
|
407
|
-
return self._process_while(indent_unit, action_name,
|
|
504
|
+
return self._process_while(indent_unit, action_name, statement, next_action)
|
|
408
505
|
elif instrument == 'variable':
|
|
409
|
-
return self.indent(indent_unit) + f"{action_name} = {
|
|
506
|
+
return self.indent(indent_unit) + f"{action_name} = {statement}", indent_unit
|
|
410
507
|
elif instrument == 'wait':
|
|
411
|
-
return f"{self.indent(indent_unit)}time.sleep({
|
|
508
|
+
return f"{self.indent(indent_unit)}time.sleep({statement})", indent_unit
|
|
509
|
+
elif instrument == 'repeat':
|
|
510
|
+
return self._process_repeat(indent_unit, action_name, statement, next_action)
|
|
412
511
|
else:
|
|
413
512
|
return self._process_instrument_action(indent_unit, instrument, action_name, args, save_data)
|
|
414
513
|
|
|
@@ -456,10 +555,25 @@ class Script(db.Model):
|
|
|
456
555
|
indent_unit -= 1
|
|
457
556
|
return exec_string, indent_unit
|
|
458
557
|
|
|
558
|
+
def _process_repeat(self, indent_unit, action, args, next_action):
|
|
559
|
+
"""
|
|
560
|
+
Process 'while' and 'endwhile' actions.
|
|
561
|
+
"""
|
|
562
|
+
exec_string = ""
|
|
563
|
+
if action == 'repeat':
|
|
564
|
+
exec_string += self.indent(indent_unit) + f"for _ in range({args}):"
|
|
565
|
+
indent_unit += 1
|
|
566
|
+
if next_action and next_action['instrument'] == 'repeat':
|
|
567
|
+
exec_string += self.indent(indent_unit) + "pass"
|
|
568
|
+
elif action == 'endrepeat':
|
|
569
|
+
indent_unit -= 1
|
|
570
|
+
return exec_string, indent_unit
|
|
571
|
+
|
|
459
572
|
def _process_instrument_action(self, indent_unit, instrument, action, args, save_data):
|
|
460
573
|
"""
|
|
461
574
|
Process actions related to instruments.
|
|
462
575
|
"""
|
|
576
|
+
|
|
463
577
|
if isinstance(args, dict):
|
|
464
578
|
args_str = self._process_dict_args(args)
|
|
465
579
|
single_line = f"{instrument}.{action}(**{args_str})"
|
|
@@ -481,8 +595,16 @@ class Script(db.Model):
|
|
|
481
595
|
for arg in args:
|
|
482
596
|
if isinstance(args[arg], str) and args[arg].startswith("#"):
|
|
483
597
|
args_str = args_str.replace(f"'#{args[arg][1:]}'", args[arg][1:])
|
|
484
|
-
elif
|
|
485
|
-
|
|
598
|
+
elif isinstance(args[arg], dict):
|
|
599
|
+
# print(args[arg])
|
|
600
|
+
variables = self.get_variables()
|
|
601
|
+
value = next(iter(args[arg]))
|
|
602
|
+
if value not in variables:
|
|
603
|
+
raise ValueError(f"Variable ({value}) is not defined.")
|
|
604
|
+
args_str = args_str.replace(f"{args[arg]}", next(iter(args[arg])))
|
|
605
|
+
# elif self._is_variable(arg):
|
|
606
|
+
# print("is variable")
|
|
607
|
+
# args_str = args_str.replace(f"'{args[arg]}'", args[arg])
|
|
486
608
|
return args_str
|
|
487
609
|
|
|
488
610
|
def _get_next_action(self, stype, index):
|