ivoryos 0.1.12__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 +50 -14
- ivoryos/routes/control/templates/control/controllers.html +3 -0
- ivoryos/routes/design/design.py +46 -40
- ivoryos/routes/design/templates/design/experiment_builder.html +23 -10
- ivoryos/routes/main/templates/main/home.html +19 -17
- ivoryos/templates/base.html +20 -10
- ivoryos/utils/db_models.py +157 -61
- ivoryos/utils/form.py +192 -81
- ivoryos/utils/script_runner.py +11 -8
- ivoryos/utils/utils.py +13 -41
- ivoryos/version.py +1 -1
- {ivoryos-0.1.12.dist-info → ivoryos-0.1.18.dist-info}/METADATA +4 -1
- {ivoryos-0.1.12.dist-info → ivoryos-0.1.18.dist-info}/RECORD +16 -17
- {ivoryos-0.1.12.dist-info → ivoryos-0.1.18.dist-info}/WHEEL +1 -1
- ivoryos/static/.DS_Store +0 -0
- {ivoryos-0.1.12.dist-info → ivoryos-0.1.18.dist-info}/LICENSE +0 -0
- {ivoryos-0.1.12.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,33 +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"}},
|
|
243
282
|
],
|
|
244
283
|
"repeat":
|
|
245
284
|
[
|
|
246
285
|
{"id": current_len + 1, "instrument": 'repeat', "action": "repeat",
|
|
247
|
-
"args":
|
|
286
|
+
"args": {"statement": 1 if statement == '' else statement}, "return": '', "uuid": uid,
|
|
287
|
+
"arg_types": {"statement": "int"}},
|
|
248
288
|
{"id": current_len + 2, "instrument": 'repeat', "action": 'endrepeat',
|
|
249
|
-
"args":
|
|
289
|
+
"args": {}, "return": '', "uuid": uid},
|
|
250
290
|
],
|
|
251
291
|
}
|
|
252
292
|
action_list = logic_dict[logic_type]
|
|
@@ -255,7 +295,9 @@ class Script(db.Model):
|
|
|
255
295
|
self.update_time_stamp()
|
|
256
296
|
|
|
257
297
|
def delete_action(self, id: int):
|
|
258
|
-
|
|
298
|
+
"""
|
|
299
|
+
Delete the action by id (step number)
|
|
300
|
+
"""
|
|
259
301
|
uid = next((action['uuid'] for action in self.currently_editing_script if action['id'] == int(id)), None)
|
|
260
302
|
id_to_be_removed = [action['id'] for action in self.currently_editing_script if action['uuid'] == uid]
|
|
261
303
|
order = self.currently_editing_order
|
|
@@ -266,7 +308,11 @@ class Script(db.Model):
|
|
|
266
308
|
self.update_time_stamp()
|
|
267
309
|
|
|
268
310
|
def duplicate_action(self, id: int):
|
|
269
|
-
|
|
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)
|
|
270
316
|
insert_id = action_to_duplicate.get("id")
|
|
271
317
|
self.add_action(action_to_duplicate)
|
|
272
318
|
# print(self.currently_editing_script)
|
|
@@ -319,7 +365,7 @@ class Script(db.Model):
|
|
|
319
365
|
:return: list of variable that require input
|
|
320
366
|
"""
|
|
321
367
|
|
|
322
|
-
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'] == ''])
|
|
323
369
|
output_str = "return {"
|
|
324
370
|
for i in return_list:
|
|
325
371
|
output_str += "'" + i + "':" + i + ","
|
|
@@ -327,15 +373,18 @@ class Script(db.Model):
|
|
|
327
373
|
return output_str, return_list
|
|
328
374
|
|
|
329
375
|
def finalize(self):
|
|
376
|
+
"""finalize script, disable editing"""
|
|
330
377
|
self.status = "finalized"
|
|
331
378
|
self.update_time_stamp()
|
|
332
379
|
|
|
333
380
|
def save_as(self, name):
|
|
381
|
+
"""resave script, enable editing"""
|
|
334
382
|
self.name = name
|
|
335
383
|
self.status = "editing"
|
|
336
384
|
self.update_time_stamp()
|
|
337
385
|
|
|
338
386
|
def indent(self, unit=0):
|
|
387
|
+
"""helper: create _ unit of indent in code string"""
|
|
339
388
|
string = "\n"
|
|
340
389
|
for _ in range(unit):
|
|
341
390
|
string += "\t"
|
|
@@ -360,6 +409,26 @@ class Script(db.Model):
|
|
|
360
409
|
|
|
361
410
|
return exec_string
|
|
362
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
|
+
|
|
363
432
|
@staticmethod
|
|
364
433
|
def validate_function_name(name):
|
|
365
434
|
"""Replace invalid characters with underscores"""
|
|
@@ -375,7 +444,8 @@ class Script(db.Model):
|
|
|
375
444
|
"""
|
|
376
445
|
configure, config_type = self.config(stype)
|
|
377
446
|
|
|
378
|
-
configure = [param + f":{param_type}" if not param_type == "any" else "" for param, param_type in
|
|
447
|
+
configure = [param + f":{param_type}" if not param_type == "any" else "" for param, param_type in
|
|
448
|
+
config_type.items()]
|
|
379
449
|
|
|
380
450
|
function_header = f"\n\ndef {run_name}_{stype}("
|
|
381
451
|
|
|
@@ -401,26 +471,43 @@ class Script(db.Model):
|
|
|
401
471
|
body += self.indent(indent_unit) + return_str
|
|
402
472
|
return body
|
|
403
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
|
+
|
|
404
489
|
def _process_action(self, indent_unit, action, index, stype):
|
|
405
490
|
"""
|
|
406
491
|
Process each action within the script dictionary.
|
|
407
492
|
"""
|
|
408
493
|
instrument = action['instrument']
|
|
494
|
+
statement = action['args'].get('statement')
|
|
409
495
|
args = self._process_args(action['args'])
|
|
496
|
+
|
|
410
497
|
save_data = action['return']
|
|
411
498
|
action_name = action['action']
|
|
412
499
|
next_action = self._get_next_action(stype, index)
|
|
500
|
+
# print(args)
|
|
413
501
|
if instrument == 'if':
|
|
414
|
-
return self._process_if(indent_unit, action_name,
|
|
502
|
+
return self._process_if(indent_unit, action_name, statement, next_action)
|
|
415
503
|
elif instrument == 'while':
|
|
416
|
-
return self._process_while(indent_unit, action_name,
|
|
504
|
+
return self._process_while(indent_unit, action_name, statement, next_action)
|
|
417
505
|
elif instrument == 'variable':
|
|
418
|
-
return self.indent(indent_unit) + f"{action_name} = {
|
|
506
|
+
return self.indent(indent_unit) + f"{action_name} = {statement}", indent_unit
|
|
419
507
|
elif instrument == 'wait':
|
|
420
|
-
return f"{self.indent(indent_unit)}time.sleep({
|
|
508
|
+
return f"{self.indent(indent_unit)}time.sleep({statement})", indent_unit
|
|
421
509
|
elif instrument == 'repeat':
|
|
422
|
-
return self._process_repeat(indent_unit, action_name,
|
|
423
|
-
|
|
510
|
+
return self._process_repeat(indent_unit, action_name, statement, next_action)
|
|
424
511
|
else:
|
|
425
512
|
return self._process_instrument_action(indent_unit, instrument, action_name, args, save_data)
|
|
426
513
|
|
|
@@ -486,6 +573,7 @@ class Script(db.Model):
|
|
|
486
573
|
"""
|
|
487
574
|
Process actions related to instruments.
|
|
488
575
|
"""
|
|
576
|
+
|
|
489
577
|
if isinstance(args, dict):
|
|
490
578
|
args_str = self._process_dict_args(args)
|
|
491
579
|
single_line = f"{instrument}.{action}(**{args_str})"
|
|
@@ -507,8 +595,16 @@ class Script(db.Model):
|
|
|
507
595
|
for arg in args:
|
|
508
596
|
if isinstance(args[arg], str) and args[arg].startswith("#"):
|
|
509
597
|
args_str = args_str.replace(f"'#{args[arg][1:]}'", args[arg][1:])
|
|
510
|
-
elif
|
|
511
|
-
|
|
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])
|
|
512
608
|
return args_str
|
|
513
609
|
|
|
514
610
|
def _get_next_action(self, stype, index):
|