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/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"
|
|
@@ -349,16 +398,17 @@ class Script(db.Model):
|
|
|
349
398
|
self.sort_actions()
|
|
350
399
|
run_name = self.name if self.name else "untitled"
|
|
351
400
|
run_name = self.validate_function_name(run_name)
|
|
352
|
-
|
|
401
|
+
exec_str_collection = {}
|
|
353
402
|
|
|
354
403
|
for i in self.stypes:
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
404
|
+
func_str = self._generate_function_header(run_name, i) + self._generate_function_body(i)
|
|
405
|
+
exec_str_collection[i] = func_str
|
|
358
406
|
if script_path:
|
|
359
|
-
self._write_to_file(script_path, run_name,
|
|
407
|
+
self._write_to_file(script_path, run_name, exec_str_collection)
|
|
408
|
+
|
|
409
|
+
return exec_str_collection
|
|
410
|
+
|
|
360
411
|
|
|
361
|
-
return exec_string
|
|
362
412
|
|
|
363
413
|
@staticmethod
|
|
364
414
|
def validate_function_name(name):
|
|
@@ -375,9 +425,10 @@ class Script(db.Model):
|
|
|
375
425
|
"""
|
|
376
426
|
configure, config_type = self.config(stype)
|
|
377
427
|
|
|
378
|
-
configure = [param + f":{param_type}" if not param_type == "any" else "" for param, param_type in
|
|
428
|
+
configure = [param + f":{param_type}" if not param_type == "any" else "" for param, param_type in
|
|
429
|
+
config_type.items()]
|
|
379
430
|
|
|
380
|
-
function_header = f"
|
|
431
|
+
function_header = f"def {run_name}_{stype}("
|
|
381
432
|
|
|
382
433
|
if stype == "script":
|
|
383
434
|
function_header += ", ".join(configure)
|
|
@@ -406,21 +457,23 @@ class Script(db.Model):
|
|
|
406
457
|
Process each action within the script dictionary.
|
|
407
458
|
"""
|
|
408
459
|
instrument = action['instrument']
|
|
460
|
+
statement = action['args'].get('statement')
|
|
409
461
|
args = self._process_args(action['args'])
|
|
462
|
+
|
|
410
463
|
save_data = action['return']
|
|
411
464
|
action_name = action['action']
|
|
412
465
|
next_action = self._get_next_action(stype, index)
|
|
466
|
+
# print(args)
|
|
413
467
|
if instrument == 'if':
|
|
414
|
-
return self._process_if(indent_unit, action_name,
|
|
468
|
+
return self._process_if(indent_unit, action_name, statement, next_action)
|
|
415
469
|
elif instrument == 'while':
|
|
416
|
-
return self._process_while(indent_unit, action_name,
|
|
470
|
+
return self._process_while(indent_unit, action_name, statement, next_action)
|
|
417
471
|
elif instrument == 'variable':
|
|
418
|
-
return self.indent(indent_unit) + f"{action_name} = {
|
|
472
|
+
return self.indent(indent_unit) + f"{action_name} = {statement}", indent_unit
|
|
419
473
|
elif instrument == 'wait':
|
|
420
|
-
return f"{self.indent(indent_unit)}time.sleep({
|
|
474
|
+
return f"{self.indent(indent_unit)}time.sleep({statement})", indent_unit
|
|
421
475
|
elif instrument == 'repeat':
|
|
422
|
-
return self._process_repeat(indent_unit, action_name,
|
|
423
|
-
|
|
476
|
+
return self._process_repeat(indent_unit, action_name, statement, next_action)
|
|
424
477
|
else:
|
|
425
478
|
return self._process_instrument_action(indent_unit, instrument, action_name, args, save_data)
|
|
426
479
|
|
|
@@ -486,6 +539,7 @@ class Script(db.Model):
|
|
|
486
539
|
"""
|
|
487
540
|
Process actions related to instruments.
|
|
488
541
|
"""
|
|
542
|
+
|
|
489
543
|
if isinstance(args, dict):
|
|
490
544
|
args_str = self._process_dict_args(args)
|
|
491
545
|
single_line = f"{instrument}.{action}(**{args_str})"
|
|
@@ -507,8 +561,16 @@ class Script(db.Model):
|
|
|
507
561
|
for arg in args:
|
|
508
562
|
if isinstance(args[arg], str) and args[arg].startswith("#"):
|
|
509
563
|
args_str = args_str.replace(f"'#{args[arg][1:]}'", args[arg][1:])
|
|
510
|
-
elif
|
|
511
|
-
|
|
564
|
+
elif isinstance(args[arg], dict):
|
|
565
|
+
# print(args[arg])
|
|
566
|
+
variables = self.get_variables()
|
|
567
|
+
value = next(iter(args[arg]))
|
|
568
|
+
if value not in variables:
|
|
569
|
+
raise ValueError(f"Variable ({value}) is not defined.")
|
|
570
|
+
args_str = args_str.replace(f"{args[arg]}", next(iter(args[arg])))
|
|
571
|
+
# elif self._is_variable(arg):
|
|
572
|
+
# print("is variable")
|
|
573
|
+
# args_str = args_str.replace(f"'{args[arg]}'", args[arg])
|
|
512
574
|
return args_str
|
|
513
575
|
|
|
514
576
|
def _get_next_action(self, stype, index):
|
|
@@ -535,7 +597,8 @@ class Script(db.Model):
|
|
|
535
597
|
else:
|
|
536
598
|
s.write("deck = None")
|
|
537
599
|
s.write("\nimport time")
|
|
538
|
-
|
|
600
|
+
for i in exec_string.values():
|
|
601
|
+
s.write(f"\n\n\n{i}")
|
|
539
602
|
|
|
540
603
|
|
|
541
604
|
if __name__ == "__main__":
|