ivoryos 1.0.8__py3-none-any.whl → 1.1.0__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 +19 -7
- ivoryos/routes/api/api.py +109 -0
- ivoryos/routes/auth/auth.py +5 -5
- ivoryos/routes/control/control.py +55 -353
- ivoryos/routes/control/control_file.py +36 -0
- ivoryos/routes/control/control_new_device.py +142 -0
- ivoryos/routes/control/templates/controllers.html +137 -0
- ivoryos/routes/control/templates/controllers_new.html +112 -0
- ivoryos/routes/control/utils.py +38 -0
- ivoryos/routes/data/data.py +108 -0
- ivoryos/routes/{database/templates/database → data/templates}/workflow_database.html +7 -7
- ivoryos/routes/{database/templates/database → data/templates}/workflow_view.html +3 -3
- ivoryos/routes/design/__init__.py +4 -0
- ivoryos/routes/design/design.py +96 -517
- ivoryos/routes/design/design_file.py +57 -0
- ivoryos/routes/design/design_step.py +43 -0
- ivoryos/routes/design/templates/components/action_form.html +52 -0
- ivoryos/routes/design/templates/components/action_list.html +15 -0
- ivoryos/routes/design/templates/components/autofill_toggle.html +14 -0
- ivoryos/routes/design/templates/components/canvas.html +14 -0
- ivoryos/routes/design/templates/components/canvas_footer.html +5 -0
- ivoryos/routes/design/templates/components/canvas_header.html +54 -0
- ivoryos/routes/design/templates/components/deck_selector.html +12 -0
- ivoryos/routes/design/templates/components/edit_action_form.html +29 -0
- ivoryos/routes/design/templates/components/instrument_panel.html +23 -0
- ivoryos/routes/design/templates/components/modals/drop_modal.html +19 -0
- ivoryos/routes/design/templates/components/modals/json_modal.html +22 -0
- ivoryos/routes/design/templates/components/modals/new_script_modal.html +18 -0
- ivoryos/routes/design/templates/components/modals/rename_modal.html +23 -0
- ivoryos/routes/design/templates/components/modals/saveas_modal.html +27 -0
- ivoryos/routes/design/templates/components/modals.html +6 -0
- ivoryos/routes/design/templates/components/operations_panel.html +43 -0
- ivoryos/routes/design/templates/components/python_code_overlay.html +17 -0
- ivoryos/routes/design/templates/components/script_info.html +31 -0
- ivoryos/routes/design/templates/components/scripts.html +50 -0
- ivoryos/routes/design/templates/components/sidebar.html +16 -0
- ivoryos/routes/design/templates/components/text_to_code_panel.html +20 -0
- ivoryos/routes/design/templates/experiment_builder.html +41 -0
- ivoryos/routes/execute/__init__.py +0 -0
- ivoryos/routes/execute/execute.py +173 -0
- ivoryos/routes/execute/execute_file.py +44 -0
- ivoryos/routes/execute/templates/components/error_modal.html +20 -0
- ivoryos/routes/execute/templates/components/logging_panel.html +31 -0
- ivoryos/routes/execute/templates/components/progress_panel.html +27 -0
- ivoryos/routes/execute/templates/components/run_panel.html +9 -0
- ivoryos/routes/execute/templates/components/run_tabs.html +17 -0
- ivoryos/routes/execute/templates/components/tab_bayesian.html +147 -0
- ivoryos/routes/execute/templates/components/tab_configuration.html +98 -0
- ivoryos/routes/execute/templates/components/tab_repeat.html +14 -0
- ivoryos/routes/execute/templates/experiment_run.html +294 -0
- ivoryos/routes/library/__init__.py +0 -0
- ivoryos/routes/{database/database.py → library/library.py} +10 -112
- ivoryos/routes/{database/templates/database/scripts_database.html → library/templates/library.html} +8 -8
- ivoryos/routes/main/main.py +1 -1
- ivoryos/routes/main/templates/{main/home.html → home.html} +4 -4
- ivoryos/socket_handlers.py +52 -0
- ivoryos/templates/base.html +4 -4
- ivoryos/utils/bo_campaign.py +43 -3
- ivoryos/utils/form.py +1 -0
- ivoryos/utils/py_to_json.py +225 -0
- ivoryos/utils/script_runner.py +30 -7
- ivoryos/version.py +1 -1
- {ivoryos-1.0.8.dist-info → ivoryos-1.1.0.dist-info}/METADATA +5 -8
- ivoryos-1.1.0.dist-info/RECORD +102 -0
- ivoryos/routes/control/templates/control/controllers.html +0 -78
- ivoryos/routes/control/templates/control/controllers_home.html +0 -55
- ivoryos/routes/control/templates/control/controllers_new.html +0 -89
- ivoryos/routes/design/templates/design/experiment_builder.html +0 -521
- ivoryos/routes/design/templates/design/experiment_run.html +0 -558
- ivoryos-1.0.8.dist-info/RECORD +0 -61
- /ivoryos/routes/auth/templates/{auth/login.html → login.html} +0 -0
- /ivoryos/routes/auth/templates/{auth/signup.html → signup.html} +0 -0
- /ivoryos/routes/{database → data}/__init__.py +0 -0
- /ivoryos/routes/{database/templates/database → data/templates/components}/step_card.html +0 -0
- /ivoryos/routes/main/templates/{main/help.html → help.html} +0 -0
- {ivoryos-1.0.8.dist-info → ivoryos-1.1.0.dist-info}/LICENSE +0 -0
- {ivoryos-1.0.8.dist-info → ivoryos-1.1.0.dist-info}/WHEEL +0 -0
- {ivoryos-1.0.8.dist-info → ivoryos-1.1.0.dist-info}/top_level.txt +0 -0
ivoryos/routes/design/design.py
CHANGED
|
@@ -1,81 +1,36 @@
|
|
|
1
|
-
import csv
|
|
2
|
-
import json
|
|
3
1
|
import os
|
|
4
|
-
import pickle
|
|
5
|
-
import sys
|
|
6
|
-
import time
|
|
7
2
|
|
|
8
|
-
from flask import Blueprint, redirect, url_for, flash, jsonify,
|
|
9
|
-
current_app
|
|
3
|
+
from flask import Blueprint, redirect, url_for, flash, jsonify, request, render_template, session, \
|
|
4
|
+
current_app
|
|
10
5
|
from flask_login import login_required
|
|
11
|
-
from flask_socketio import SocketIO
|
|
12
|
-
from werkzeug.utils import secure_filename
|
|
13
6
|
|
|
7
|
+
from ivoryos.routes.library.library import publish
|
|
14
8
|
from ivoryos.utils import utils
|
|
15
9
|
from ivoryos.utils.global_config import GlobalConfig
|
|
16
|
-
from ivoryos.utils.form import
|
|
10
|
+
from ivoryos.utils.form import create_action_button, format_name, create_form_from_pseudo, \
|
|
17
11
|
create_form_from_action, create_all_builtin_forms
|
|
18
|
-
from ivoryos.utils.db_models import Script
|
|
12
|
+
from ivoryos.utils.db_models import Script
|
|
13
|
+
from ivoryos.utils.py_to_json import convert_to_cards
|
|
19
14
|
from ivoryos.utils.script_runner import ScriptRunner
|
|
20
|
-
# from ivoryos.utils.utils import load_workflows
|
|
21
15
|
|
|
22
|
-
|
|
23
|
-
|
|
16
|
+
# Import the new modular components
|
|
17
|
+
from ivoryos.routes.design.design_file import files
|
|
18
|
+
from ivoryos.routes.design.design_step import steps
|
|
24
19
|
|
|
25
|
-
|
|
26
|
-
runner = ScriptRunner()
|
|
27
|
-
|
|
28
|
-
def abort_pending():
|
|
29
|
-
runner.abort_pending()
|
|
30
|
-
socketio.emit('log', {'message': "aborted pending iterations"})
|
|
31
|
-
|
|
32
|
-
def abort_current():
|
|
33
|
-
runner.stop_execution()
|
|
34
|
-
socketio.emit('log', {'message': "stopped next task"})
|
|
35
|
-
|
|
36
|
-
def pause():
|
|
37
|
-
runner.retry = False
|
|
38
|
-
msg = runner.toggle_pause()
|
|
39
|
-
socketio.emit('log', {'message': msg})
|
|
40
|
-
return msg
|
|
41
|
-
|
|
42
|
-
def retry():
|
|
43
|
-
runner.retry = True
|
|
44
|
-
msg = runner.toggle_pause()
|
|
45
|
-
socketio.emit('log', {'message': msg})
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
# ---- Socket.IO Event Handlers ----
|
|
49
|
-
|
|
50
|
-
@socketio.on('abort_pending')
|
|
51
|
-
def handle_abort_pending():
|
|
52
|
-
abort_pending()
|
|
53
|
-
|
|
54
|
-
@socketio.on('abort_current')
|
|
55
|
-
def handle_abort_current():
|
|
56
|
-
abort_current()
|
|
20
|
+
# from ...utils.py_to_json import convert_to_cards
|
|
57
21
|
|
|
58
|
-
|
|
59
|
-
def handle_pause():
|
|
60
|
-
pause()
|
|
22
|
+
design = Blueprint('design', __name__, template_folder='templates')
|
|
61
23
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
24
|
+
# Register sub-blueprints
|
|
25
|
+
design.register_blueprint(files)
|
|
26
|
+
design.register_blueprint(steps)
|
|
65
27
|
|
|
28
|
+
global_config = GlobalConfig()
|
|
66
29
|
|
|
67
|
-
|
|
68
|
-
def handle_abort_action():
|
|
69
|
-
# Fetch log messages from local file
|
|
70
|
-
filename = os.path.join(current_app.config["OUTPUT_FOLDER"], current_app.config["LOGGERS_PATH"])
|
|
71
|
-
with open(filename, 'r') as log_file:
|
|
72
|
-
log_history = log_file.readlines()
|
|
73
|
-
for message in log_history[-10:]:
|
|
74
|
-
socketio.emit('log', {'message': message})
|
|
75
|
-
|
|
30
|
+
# ---- Main Design Routes ----
|
|
76
31
|
|
|
77
|
-
@design.route("/
|
|
78
|
-
@design.route("/
|
|
32
|
+
@design.route("/script/", methods=['GET', 'POST'])
|
|
33
|
+
@design.route("/script/<instrument>/", methods=['GET', 'POST'])
|
|
79
34
|
@login_required
|
|
80
35
|
def experiment_builder(instrument=None):
|
|
81
36
|
"""
|
|
@@ -114,13 +69,10 @@ def experiment_builder(instrument=None):
|
|
|
114
69
|
"""
|
|
115
70
|
deck = global_config.deck
|
|
116
71
|
script = utils.get_script_file()
|
|
117
|
-
# load_workflows(script)
|
|
118
|
-
# registered_workflows = global_config.registered_workflows
|
|
119
72
|
|
|
120
73
|
if deck and script.deck is None:
|
|
121
74
|
script.deck = os.path.splitext(os.path.basename(deck.__file__))[
|
|
122
75
|
0] if deck.__name__ == "__main__" else deck.__name__
|
|
123
|
-
# script.sort_actions()
|
|
124
76
|
|
|
125
77
|
pseudo_deck_name = session.get('pseudo_deck', '')
|
|
126
78
|
pseudo_deck_path = os.path.join(current_app.config["DUMMY_DECK"], pseudo_deck_name)
|
|
@@ -140,24 +92,17 @@ def experiment_builder(instrument=None):
|
|
|
140
92
|
functions = {}
|
|
141
93
|
if deck:
|
|
142
94
|
deck_variables = list(global_config.deck_snapshot.keys())
|
|
143
|
-
# deck_variables.insert(0, "registered_workflows")
|
|
144
95
|
deck_variables.insert(0, "flow_control")
|
|
145
|
-
|
|
146
96
|
else:
|
|
147
97
|
deck_variables = list(pseudo_deck.keys()) if pseudo_deck else []
|
|
148
98
|
deck_variables.remove("deck_name") if len(deck_variables) > 0 else deck_variables
|
|
99
|
+
|
|
149
100
|
edit_action_info = session.get("edit_action")
|
|
150
101
|
if edit_action_info:
|
|
151
102
|
forms = create_form_from_action(edit_action_info, script=script)
|
|
152
103
|
elif instrument:
|
|
153
|
-
# if instrument in ['if', 'while', 'variable', 'wait', 'repeat']:
|
|
154
|
-
# forms = create_builtin_form(instrument, script=script)
|
|
155
104
|
if instrument == 'flow_control':
|
|
156
105
|
forms = create_all_builtin_forms(script=script)
|
|
157
|
-
# elif instrument == 'registered_workflows':
|
|
158
|
-
# functions = utils._inspect_class(registered_workflows)
|
|
159
|
-
# # forms = create_workflow_forms(script=script)
|
|
160
|
-
# forms = create_form_from_pseudo(pseudo=functions, autofill=autofill, script=script)
|
|
161
106
|
elif instrument in global_config.defined_variables.keys():
|
|
162
107
|
_object = global_config.defined_variables.get(instrument)
|
|
163
108
|
functions = utils._inspect_class(_object)
|
|
@@ -168,69 +113,71 @@ def experiment_builder(instrument=None):
|
|
|
168
113
|
elif pseudo_deck:
|
|
169
114
|
functions = pseudo_deck.get(instrument, {})
|
|
170
115
|
forms = create_form_from_pseudo(pseudo=functions, autofill=autofill, script=script)
|
|
116
|
+
|
|
171
117
|
if request.method == 'POST' and "hidden_name" in request.form:
|
|
172
|
-
# all_kwargs = request.form.copy()
|
|
173
118
|
method_name = request.form.get("hidden_name", None)
|
|
174
|
-
|
|
175
|
-
form = forms.get(method_name)
|
|
119
|
+
form = forms.get(method_name) if forms else None
|
|
176
120
|
insert_position = request.form.get("drop_target_id", None)
|
|
177
|
-
|
|
178
|
-
if form
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
121
|
+
|
|
122
|
+
if form:
|
|
123
|
+
kwargs = {field.name: field.data for field in form if field.name != 'csrf_token'}
|
|
124
|
+
if form.validate_on_submit():
|
|
125
|
+
function_name = kwargs.pop("hidden_name")
|
|
126
|
+
save_data = kwargs.pop('return', '')
|
|
127
|
+
|
|
128
|
+
primitive_arg_types = utils.get_arg_type(kwargs, functions[function_name])
|
|
129
|
+
|
|
130
|
+
script.eval_list(kwargs, primitive_arg_types)
|
|
131
|
+
kwargs = script.validate_variables(kwargs)
|
|
132
|
+
action = {"instrument": instrument, "action": function_name,
|
|
133
|
+
"args": kwargs,
|
|
134
|
+
"return": save_data,
|
|
135
|
+
'arg_types': primitive_arg_types}
|
|
136
|
+
script.add_action(action=action, insert_position=insert_position)
|
|
137
|
+
else:
|
|
138
|
+
flash(form.errors)
|
|
193
139
|
|
|
194
140
|
elif request.method == 'POST' and "builtin_name" in request.form:
|
|
195
141
|
function_name = request.form.get("builtin_name")
|
|
196
|
-
form = forms.get(function_name)
|
|
197
|
-
kwargs = {field.name: field.data for field in form if field.name != 'csrf_token'}
|
|
142
|
+
form = forms.get(function_name) if forms else None
|
|
198
143
|
insert_position = request.form.get("drop_target_id", None)
|
|
199
144
|
|
|
200
|
-
if form
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
145
|
+
if form:
|
|
146
|
+
kwargs = {field.name: field.data for field in form if field.name != 'csrf_token'}
|
|
147
|
+
if form.validate_on_submit():
|
|
148
|
+
logic_type = kwargs.pop('builtin_name')
|
|
149
|
+
if 'variable' in kwargs:
|
|
150
|
+
try:
|
|
151
|
+
script.add_variable(insert_position=insert_position, **kwargs)
|
|
152
|
+
except ValueError:
|
|
153
|
+
flash("Invalid variable type")
|
|
154
|
+
else:
|
|
155
|
+
script.add_logic_action(logic_type=logic_type, insert_position=insert_position, **kwargs)
|
|
208
156
|
else:
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
flash(form.errors)
|
|
157
|
+
flash(form.errors)
|
|
158
|
+
|
|
212
159
|
elif request.method == 'POST' and "workflow_name" in request.form:
|
|
213
160
|
workflow_name = request.form.get("workflow_name")
|
|
214
|
-
form = forms.get(workflow_name)
|
|
215
|
-
kwargs = {field.name: field.data for field in form if field.name != 'csrf_token'}
|
|
161
|
+
form = forms.get(workflow_name) if forms else None
|
|
216
162
|
insert_position = request.form.get("drop_target_id", None)
|
|
217
163
|
|
|
218
|
-
if form
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
164
|
+
if form:
|
|
165
|
+
kwargs = {field.name: field.data for field in form if field.name != 'csrf_token'}
|
|
166
|
+
if form.validate_on_submit():
|
|
167
|
+
save_data = kwargs.pop('return', '')
|
|
168
|
+
|
|
169
|
+
primitive_arg_types = utils.get_arg_type(kwargs, functions[workflow_name])
|
|
170
|
+
|
|
171
|
+
script.eval_list(kwargs, primitive_arg_types)
|
|
172
|
+
kwargs = script.validate_variables(kwargs)
|
|
173
|
+
action = {"instrument": instrument, "action": workflow_name,
|
|
174
|
+
"args": kwargs,
|
|
175
|
+
"return": save_data,
|
|
176
|
+
'arg_types': primitive_arg_types}
|
|
177
|
+
script.add_action(action=action, insert_position=insert_position)
|
|
178
|
+
script.add_workflow(**kwargs, insert_position=insert_position)
|
|
179
|
+
else:
|
|
180
|
+
flash(form.errors)
|
|
234
181
|
|
|
235
182
|
# toggle autofill, autofill doesn't apply to control flow ops
|
|
236
183
|
elif request.method == 'POST' and "autofill" in request.form:
|
|
@@ -252,7 +199,7 @@ def experiment_builder(instrument=None):
|
|
|
252
199
|
use_llm=enable_llm)
|
|
253
200
|
|
|
254
201
|
|
|
255
|
-
@design.route("/
|
|
202
|
+
@design.route("/generate_code", methods=['POST'])
|
|
256
203
|
@login_required
|
|
257
204
|
def generate_code():
|
|
258
205
|
"""
|
|
@@ -274,7 +221,6 @@ def generate_code():
|
|
|
274
221
|
if request.method == 'POST' and "gen" in request.form:
|
|
275
222
|
prompt = request.form.get("prompt")
|
|
276
223
|
session['prompt'][instrument] = prompt
|
|
277
|
-
# sdl_module = utils.parse_functions(find_instrument_by_name(f'deck.{instrument}'), doc_string=True)
|
|
278
224
|
sdl_module = global_config.deck_snapshot.get(instrument, {})
|
|
279
225
|
empty_script = Script(author=session.get('user'))
|
|
280
226
|
if enable_llm and agent is None:
|
|
@@ -300,134 +246,8 @@ def generate_code():
|
|
|
300
246
|
return redirect(url_for("design.experiment_builder", instrument=instrument, use_llm=True))
|
|
301
247
|
|
|
302
248
|
|
|
303
|
-
@design.route("/design/campaign", methods=['GET', 'POST'])
|
|
304
|
-
@login_required
|
|
305
|
-
def experiment_run():
|
|
306
|
-
"""
|
|
307
|
-
.. :quickref: Workflow Execution; Execute/iterate the workflow
|
|
308
|
-
|
|
309
|
-
.. http:get:: /design/campaign
|
|
310
|
-
|
|
311
|
-
Compile the workflow and load the experiment execution interface.
|
|
312
|
-
|
|
313
|
-
.. http:post:: /design/campaign
|
|
314
|
-
|
|
315
|
-
Start workflow execution
|
|
316
|
-
|
|
317
|
-
"""
|
|
318
|
-
deck = global_config.deck
|
|
319
|
-
script = utils.get_script_file()
|
|
320
|
-
|
|
321
|
-
# script.sort_actions() # handled in update list
|
|
322
|
-
off_line = current_app.config["OFF_LINE"]
|
|
323
|
-
deck_list = utils.import_history(os.path.join(current_app.config["OUTPUT_FOLDER"], 'deck_history.txt'))
|
|
324
|
-
# if not off_line and deck is None:
|
|
325
|
-
# # print("loading deck")
|
|
326
|
-
# module = current_app.config.get('MODULE', '')
|
|
327
|
-
# deck = sys.modules[module] if module else None
|
|
328
|
-
# script.deck = os.path.splitext(os.path.basename(deck.__file__))[0]
|
|
329
|
-
design_buttons = {stype: create_action_button(script, stype) for stype in script.stypes}
|
|
330
|
-
config_preview = []
|
|
331
|
-
config_file_list = [i for i in os.listdir(current_app.config["CSV_FOLDER"]) if not i == ".gitkeep"]
|
|
332
|
-
try:
|
|
333
|
-
# todo
|
|
334
|
-
exec_string = script.python_script if script.python_script else script.compile(current_app.config['SCRIPT_FOLDER'])
|
|
335
|
-
# exec_string = script.compile(current_app.config['SCRIPT_FOLDER'])
|
|
336
|
-
# print(exec_string)
|
|
337
|
-
except Exception as e:
|
|
338
|
-
flash(e.__str__())
|
|
339
|
-
# handle api request
|
|
340
|
-
if request.accept_mimetypes.best_match(['application/json', 'text/html']) == 'application/json':
|
|
341
|
-
return jsonify({"error": e.__str__()})
|
|
342
|
-
else:
|
|
343
|
-
return redirect(url_for("design.experiment_builder"))
|
|
344
|
-
|
|
345
|
-
config_file = request.args.get("filename")
|
|
346
|
-
config = []
|
|
347
|
-
if config_file:
|
|
348
|
-
session['config_file'] = config_file
|
|
349
|
-
filename = session.get("config_file")
|
|
350
|
-
if filename:
|
|
351
|
-
# config_preview = list(csv.DictReader(open(os.path.join(current_app.config['CSV_FOLDER'], filename))))
|
|
352
|
-
config = list(csv.DictReader(open(os.path.join(current_app.config['CSV_FOLDER'], filename))))
|
|
353
|
-
config_preview = config[1:]
|
|
354
|
-
arg_type = config.pop(0) # first entry is types
|
|
355
|
-
try:
|
|
356
|
-
for key, func_str in exec_string.items():
|
|
357
|
-
exec(func_str)
|
|
358
|
-
line_collection = script.convert_to_lines(exec_string)
|
|
359
|
-
|
|
360
|
-
except Exception:
|
|
361
|
-
flash(f"Please check {key} syntax!!")
|
|
362
|
-
return redirect(url_for("design.experiment_builder"))
|
|
363
|
-
# runner.globals_dict.update(globals())
|
|
364
|
-
run_name = script.name if script.name else "untitled"
|
|
365
|
-
|
|
366
|
-
dismiss = session.get("dismiss", None)
|
|
367
|
-
script = utils.get_script_file()
|
|
368
|
-
no_deck_warning = False
|
|
369
|
-
|
|
370
|
-
_, return_list = script.config_return()
|
|
371
|
-
config_list, config_type_list = script.config("script")
|
|
372
|
-
# config = script.config("script")
|
|
373
|
-
data_list = os.listdir(current_app.config['DATA_FOLDER'])
|
|
374
|
-
data_list.remove(".gitkeep") if ".gitkeep" in data_list else data_list
|
|
375
|
-
if deck is None:
|
|
376
|
-
no_deck_warning = True
|
|
377
|
-
flash(f"No deck is found, import {script.deck}")
|
|
378
|
-
elif script.deck:
|
|
379
|
-
is_deck_match = script.deck == deck.__name__ or script.deck == \
|
|
380
|
-
os.path.splitext(os.path.basename(deck.__file__))[0]
|
|
381
|
-
if not is_deck_match:
|
|
382
|
-
flash(f"This script is not compatible with current deck, import {script.deck}")
|
|
383
|
-
if request.method == "POST":
|
|
384
|
-
bo_args = None
|
|
385
|
-
compiled = False
|
|
386
|
-
if request.accept_mimetypes.best_match(['application/json', 'text/html']) == 'application/json':
|
|
387
|
-
payload_json = request.get_json()
|
|
388
|
-
compiled = True
|
|
389
|
-
if "kwargs" in payload_json:
|
|
390
|
-
config = payload_json["kwargs"]
|
|
391
|
-
elif "parameters" in payload_json:
|
|
392
|
-
bo_args = payload_json
|
|
393
|
-
repeat = payload_json.pop("repeat", None)
|
|
394
|
-
else:
|
|
395
|
-
if "bo" in request.form:
|
|
396
|
-
bo_args = request.form.to_dict()
|
|
397
|
-
if "online-config" in request.form:
|
|
398
|
-
config = utils.web_config_entry_wrapper(request.form.to_dict(), config_list)
|
|
399
|
-
repeat = request.form.get('repeat', None)
|
|
400
249
|
|
|
401
|
-
|
|
402
|
-
datapath = current_app.config["DATA_FOLDER"]
|
|
403
|
-
run_name = script.validate_function_name(run_name)
|
|
404
|
-
runner.run_script(script=script, run_name=run_name, config=config, bo_args=bo_args,
|
|
405
|
-
logger=g.logger, socketio=g.socketio, repeat_count=repeat,
|
|
406
|
-
output_path=datapath, compiled=compiled,
|
|
407
|
-
current_app=current_app._get_current_object()
|
|
408
|
-
)
|
|
409
|
-
if utils.check_config_duplicate(config):
|
|
410
|
-
flash(f"WARNING: Duplicate in config entries.")
|
|
411
|
-
except Exception as e:
|
|
412
|
-
if request.accept_mimetypes.best_match(['application/json', 'text/html']) == 'application/json':
|
|
413
|
-
return jsonify({"error": e.__str__()})
|
|
414
|
-
else:
|
|
415
|
-
flash(e)
|
|
416
|
-
if request.accept_mimetypes.best_match(['application/json', 'text/html']) == 'application/json':
|
|
417
|
-
# wait to get a workflow ID
|
|
418
|
-
while not global_config.runner_status:
|
|
419
|
-
time.sleep(1)
|
|
420
|
-
return jsonify({"status": "task started", "task_id": global_config.runner_status.get("id")})
|
|
421
|
-
else:
|
|
422
|
-
return render_template('experiment_run.html', script=script.script_dict, filename=filename,
|
|
423
|
-
dot_py=exec_string, line_collection=line_collection,
|
|
424
|
-
return_list=return_list, config_list=config_list, config_file_list=config_file_list,
|
|
425
|
-
config_preview=config_preview, data_list=data_list, config_type_list=config_type_list,
|
|
426
|
-
no_deck_warning=no_deck_warning, dismiss=dismiss, design_buttons=design_buttons,
|
|
427
|
-
history=deck_list, pause_status=runner.pause_status())
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
@design.route("/design/script/toggle/<stype>")
|
|
250
|
+
@design.route("/script/toggle/<stype>")
|
|
431
251
|
@login_required
|
|
432
252
|
def toggle_script_type(stype=None):
|
|
433
253
|
"""
|
|
@@ -465,7 +285,7 @@ def toggle_show_code():
|
|
|
465
285
|
|
|
466
286
|
|
|
467
287
|
# --------------------handle all the import/export and download/upload--------------------------
|
|
468
|
-
@design.route("/
|
|
288
|
+
@design.route("/clear")
|
|
469
289
|
@login_required
|
|
470
290
|
def clear():
|
|
471
291
|
"""
|
|
@@ -490,7 +310,7 @@ def clear():
|
|
|
490
310
|
return redirect(url_for("design.experiment_builder"))
|
|
491
311
|
|
|
492
312
|
|
|
493
|
-
@design.route("/
|
|
313
|
+
@design.route("/import/pseudo", methods=['POST'])
|
|
494
314
|
@login_required
|
|
495
315
|
def import_pseudo():
|
|
496
316
|
"""
|
|
@@ -513,274 +333,33 @@ def import_pseudo():
|
|
|
513
333
|
return redirect(url_for("design.experiment_builder"))
|
|
514
334
|
|
|
515
335
|
|
|
516
|
-
@design.route('/design/uploads', methods=['POST'])
|
|
517
|
-
@login_required
|
|
518
|
-
def upload():
|
|
519
|
-
"""
|
|
520
|
-
.. :quickref: Workflow Execution; upload a workflow config file (.CSV)
|
|
521
|
-
|
|
522
|
-
.. http:post:: /design/uploads
|
|
523
|
-
|
|
524
|
-
:form file: workflow CSV config file
|
|
525
|
-
:status 302: save csv file and then redirects to :http:get:`/design/campaign`
|
|
526
|
-
"""
|
|
527
|
-
if request.method == "POST":
|
|
528
|
-
f = request.files['file']
|
|
529
|
-
if 'file' not in request.files:
|
|
530
|
-
flash('No file part')
|
|
531
|
-
if f.filename.split('.')[-1] == "csv":
|
|
532
|
-
filename = secure_filename(f.filename)
|
|
533
|
-
f.save(os.path.join(current_app.config['CSV_FOLDER'], filename))
|
|
534
|
-
session['config_file'] = filename
|
|
535
|
-
return redirect(url_for("design.experiment_run"))
|
|
536
|
-
else:
|
|
537
|
-
flash("Config file is in csv format")
|
|
538
|
-
return redirect(url_for("design.experiment_run"))
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
@design.route('/design/workflow/download/<filename>')
|
|
542
|
-
@login_required
|
|
543
|
-
def download_results(filename):
|
|
544
|
-
"""
|
|
545
|
-
.. :quickref: Workflow Design; download a workflow data file
|
|
546
|
-
|
|
547
|
-
.. http:get:: /design/workflow/download/<filename>
|
|
548
|
-
|
|
549
|
-
"""
|
|
550
|
-
filepath = os.path.join(current_app.config["DATA_FOLDER"], filename)
|
|
551
|
-
return send_file(os.path.abspath(filepath), as_attachment=True)
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
@design.route('/design/load_json', methods=['POST'])
|
|
555
|
-
@login_required
|
|
556
|
-
def load_json():
|
|
557
|
-
"""
|
|
558
|
-
.. :quickref: Workflow Design Ext; upload a workflow design file (.JSON)
|
|
559
|
-
|
|
560
|
-
.. http:post:: /load_json
|
|
561
|
-
|
|
562
|
-
:form file: workflow design JSON file
|
|
563
|
-
:status 302: load pseudo deck and then redirects to :http:get:`/design/script`
|
|
564
|
-
"""
|
|
565
|
-
if request.method == "POST":
|
|
566
|
-
f = request.files['file']
|
|
567
|
-
if 'file' not in request.files:
|
|
568
|
-
flash('No file part')
|
|
569
|
-
if f.filename.endswith("json"):
|
|
570
|
-
script_dict = json.load(f)
|
|
571
|
-
utils.post_script_file(script_dict, is_dict=True)
|
|
572
|
-
else:
|
|
573
|
-
flash("Script file need to be JSON file")
|
|
574
|
-
return redirect(url_for("design.experiment_builder"))
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
@design.route('/design/script/download/<filetype>')
|
|
578
|
-
@login_required
|
|
579
|
-
def download(filetype):
|
|
580
|
-
"""
|
|
581
|
-
.. :quickref: Workflow Design Ext; download a workflow design file
|
|
582
|
-
|
|
583
|
-
.. http:get:: /design/script/download/<filetype>
|
|
584
|
-
|
|
585
|
-
"""
|
|
586
|
-
script = utils.get_script_file()
|
|
587
|
-
run_name = script.name if script.name else "untitled"
|
|
588
|
-
if filetype == "configure":
|
|
589
|
-
filepath = os.path.join(current_app.config['SCRIPT_FOLDER'], f"{run_name}_config.csv")
|
|
590
|
-
with open(filepath, 'w', newline='') as f:
|
|
591
|
-
writer = csv.writer(f)
|
|
592
|
-
cfg, cfg_types = script.config("script")
|
|
593
|
-
writer.writerow(cfg)
|
|
594
|
-
writer.writerow(list(cfg_types.values()))
|
|
595
|
-
elif filetype == "script":
|
|
596
|
-
script.sort_actions()
|
|
597
|
-
json_object = json.dumps(script.as_dict())
|
|
598
|
-
filepath = os.path.join(current_app.config['SCRIPT_FOLDER'], f"{run_name}.json")
|
|
599
|
-
with open(filepath, "w") as outfile:
|
|
600
|
-
outfile.write(json_object)
|
|
601
|
-
elif filetype == "python":
|
|
602
|
-
filepath = os.path.join(current_app.config["SCRIPT_FOLDER"], f"{run_name}.py")
|
|
603
|
-
else:
|
|
604
|
-
return "Unsupported file type", 400
|
|
605
|
-
return send_file(os.path.abspath(filepath), as_attachment=True)
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
@design.route("/design/step/edit/<uuid>", methods=['GET', 'POST'])
|
|
609
|
-
@login_required
|
|
610
|
-
def edit_action(uuid: str):
|
|
611
|
-
"""
|
|
612
|
-
.. :quickref: Workflow Design; edit parameters of an action step on canvas
|
|
613
|
-
|
|
614
|
-
.. http:get:: /design/step/edit/<uuid>
|
|
615
|
-
|
|
616
|
-
Load parameter form of an action step
|
|
617
|
-
|
|
618
|
-
.. http:post:: /design/step/edit/<uuid>
|
|
619
|
-
|
|
620
|
-
:param uuid: The step's uuid
|
|
621
|
-
:type uuid: str
|
|
622
|
-
|
|
623
|
-
:form dynamic form: workflow step dynamic inputs
|
|
624
|
-
:status 302: save changes and then redirects to :http:get:`/design/script`
|
|
625
|
-
"""
|
|
626
|
-
script = utils.get_script_file()
|
|
627
|
-
action = script.find_by_uuid(uuid)
|
|
628
|
-
session['edit_action'] = action
|
|
629
|
-
|
|
630
|
-
if request.method == "POST" and action is not None:
|
|
631
|
-
forms = create_form_from_action(action, script=script)
|
|
632
|
-
if "back" not in request.form:
|
|
633
|
-
kwargs = {field.name: field.data for field in forms if field.name != 'csrf_token'}
|
|
634
|
-
# print(kwargs)
|
|
635
|
-
if forms and forms.validate_on_submit():
|
|
636
|
-
save_as = kwargs.pop('return', '')
|
|
637
|
-
kwargs = script.validate_variables(kwargs)
|
|
638
|
-
# try:
|
|
639
|
-
script.update_by_uuid(uuid=uuid, args=kwargs, output=save_as)
|
|
640
|
-
# except Exception as e:
|
|
641
|
-
else:
|
|
642
|
-
flash(forms.errors)
|
|
643
|
-
session.pop('edit_action')
|
|
644
|
-
return redirect(url_for('design.experiment_builder'))
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
@design.route("/design/step/delete/<id>")
|
|
648
|
-
@login_required
|
|
649
|
-
def delete_action(id: int):
|
|
650
|
-
"""
|
|
651
|
-
.. :quickref: Workflow Design; delete an action step on canvas
|
|
652
|
-
|
|
653
|
-
.. http:get:: /design/step/delete/<id>
|
|
654
|
-
|
|
655
|
-
:param id: The step number id
|
|
656
|
-
:type id: int
|
|
657
|
-
|
|
658
|
-
:status 302: save changes and then redirects to :http:get:`/design/script`
|
|
659
|
-
"""
|
|
660
|
-
back = request.referrer
|
|
661
|
-
script = utils.get_script_file()
|
|
662
|
-
script.delete_action(id)
|
|
663
|
-
utils.post_script_file(script)
|
|
664
|
-
return redirect(back)
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
@design.route("/design/step/duplicate/<id>")
|
|
668
|
-
@login_required
|
|
669
|
-
def duplicate_action(id: int):
|
|
670
|
-
"""
|
|
671
|
-
.. :quickref: Workflow Design; duplicate an action step on canvas
|
|
672
|
-
|
|
673
|
-
.. http:get:: /design/step/duplicate/<id>
|
|
674
|
-
|
|
675
|
-
:param id: The step number id
|
|
676
|
-
:type id: int
|
|
677
|
-
|
|
678
|
-
:status 302: save changes and then redirects to :http:get:`/design/script`
|
|
679
|
-
"""
|
|
680
|
-
back = request.referrer
|
|
681
|
-
script = utils.get_script_file()
|
|
682
|
-
script.duplicate_action(id)
|
|
683
|
-
utils.post_script_file(script)
|
|
684
|
-
return redirect(back)
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
# ---- HTTP API Endpoints ----
|
|
688
336
|
|
|
689
|
-
@design.route("/
|
|
690
|
-
def runner_status():
|
|
691
|
-
"""
|
|
692
|
-
.. :quickref: Workflow Design; get the execution status
|
|
693
|
-
|
|
694
|
-
.. http:get:: /api/runner/status
|
|
695
|
-
|
|
696
|
-
:status 200: status
|
|
697
|
-
"""
|
|
698
|
-
runner_busy = global_config.runner_lock.locked()
|
|
699
|
-
status = {"busy": runner_busy}
|
|
700
|
-
task_status = global_config.runner_status
|
|
701
|
-
current_step = {}
|
|
702
|
-
# print(task_status)
|
|
703
|
-
if task_status is not None:
|
|
704
|
-
task_type = task_status["type"]
|
|
705
|
-
task_id = task_status["id"]
|
|
706
|
-
if task_type == "task":
|
|
707
|
-
step = SingleStep.query.get(task_id)
|
|
708
|
-
current_step = step.as_dict()
|
|
709
|
-
if task_type == "workflow":
|
|
710
|
-
workflow = WorkflowRun.query.get(task_id)
|
|
711
|
-
if workflow is not None:
|
|
712
|
-
latest_step = WorkflowStep.query.filter_by(workflow_id=workflow.id).order_by(WorkflowStep.start_time.desc()).first()
|
|
713
|
-
if latest_step is not None:
|
|
714
|
-
current_step = latest_step.as_dict()
|
|
715
|
-
status["workflow_status"] = {"workflow_info": workflow.as_dict(), "runner_status": runner.get_status()}
|
|
716
|
-
status["current_task"] = current_step
|
|
717
|
-
return jsonify(status), 200
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
@design.route("/api/runner/abort_pending", methods=["POST"])
|
|
722
|
-
def api_abort_pending():
|
|
723
|
-
"""
|
|
724
|
-
.. :quickref: Workflow Design; abort pending action(s) during execution
|
|
725
|
-
|
|
726
|
-
.. http:get:: /api/runner/abort_pending
|
|
727
|
-
|
|
728
|
-
:status 200: {"status": "ok"}
|
|
729
|
-
"""
|
|
730
|
-
abort_pending()
|
|
731
|
-
return jsonify({"status": "ok"}), 200
|
|
732
|
-
|
|
733
|
-
@design.route("/api/runner/abort_current", methods=["POST"])
|
|
734
|
-
def api_abort_current():
|
|
735
|
-
"""
|
|
736
|
-
.. :quickref: Workflow Design; abort right after current action during execution
|
|
737
|
-
|
|
738
|
-
.. http:get:: /api/runner/abort_current
|
|
739
|
-
|
|
740
|
-
:status 200: {"status": "ok"}
|
|
741
|
-
"""
|
|
742
|
-
abort_current()
|
|
743
|
-
return jsonify({"status": "ok"}), 200
|
|
744
|
-
|
|
745
|
-
@design.route("/api/runner/pause", methods=["POST"])
|
|
746
|
-
def api_pause():
|
|
747
|
-
"""
|
|
748
|
-
.. :quickref: Workflow Design; pause during execution
|
|
749
|
-
|
|
750
|
-
.. http:get:: /api/runner/pause
|
|
751
|
-
|
|
752
|
-
:status 200: {"status": "ok"}
|
|
753
|
-
"""
|
|
754
|
-
msg = pause()
|
|
755
|
-
return jsonify({"status": "ok", "pause_status": msg}), 200
|
|
756
|
-
|
|
757
|
-
@design.route("/api/runner/retry", methods=["POST"])
|
|
758
|
-
def api_retry():
|
|
759
|
-
"""
|
|
760
|
-
.. :quickref: Workflow Design; retry when error occur during execution
|
|
761
|
-
|
|
762
|
-
.. http:get:: /api/runner/retry
|
|
763
|
-
|
|
764
|
-
:status 200: {"status": "ok"}
|
|
765
|
-
"""
|
|
766
|
-
retry()
|
|
767
|
-
return jsonify({"status": "ok, retrying failed step"}), 200
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
@design.route("/api/design/submit", methods=["POST"])
|
|
337
|
+
@design.route("/submit_python", methods=["POST"])
|
|
771
338
|
def submit_script():
|
|
772
|
-
"""
|
|
773
|
-
.. :quickref: Workflow Design; submit script
|
|
774
|
-
|
|
775
|
-
.. http:get:: /api/design/submit
|
|
776
|
-
|
|
777
|
-
:status 200: {"status": "ok"}
|
|
778
|
-
"""
|
|
339
|
+
"""Submit script"""
|
|
779
340
|
deck = global_config.deck
|
|
780
341
|
deck_name = os.path.splitext(os.path.basename(deck.__file__))[0] if deck.__name__ == "__main__" else deck.__name__
|
|
781
342
|
script = Script(author=session.get('user'), deck=deck_name)
|
|
782
343
|
script_collection = request.get_json()
|
|
344
|
+
workflow_name = script_collection.pop("workflow_name")
|
|
783
345
|
script.python_script = script_collection
|
|
784
346
|
# todo check script format
|
|
347
|
+
script.name = workflow_name
|
|
348
|
+
result = {}
|
|
349
|
+
for stype, py_str in script_collection.items():
|
|
350
|
+
try:
|
|
351
|
+
card = convert_to_cards(py_str)
|
|
352
|
+
script.script_dict[stype] = card
|
|
353
|
+
result[stype] = "success"
|
|
354
|
+
except Exception as e:
|
|
355
|
+
result[
|
|
356
|
+
stype] = f"failed to transcript to ivoryos visualization, but function can still run. error: {str(e)}"
|
|
785
357
|
utils.post_script_file(script)
|
|
786
|
-
|
|
358
|
+
try:
|
|
359
|
+
publish()
|
|
360
|
+
db_status = "success"
|
|
361
|
+
except Exception as e:
|
|
362
|
+
db_status = "failed"
|
|
363
|
+
return jsonify({"script": result, "db": db_status}), 200
|
|
364
|
+
|
|
365
|
+
|