ivoryos 1.0.9__py3-none-any.whl → 1.4.4__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.
- docs/source/conf.py +84 -0
- ivoryos/__init__.py +17 -207
- ivoryos/app.py +154 -0
- ivoryos/config.py +1 -0
- ivoryos/optimizer/ax_optimizer.py +191 -0
- ivoryos/optimizer/base_optimizer.py +84 -0
- ivoryos/optimizer/baybe_optimizer.py +193 -0
- ivoryos/optimizer/nimo_optimizer.py +173 -0
- ivoryos/optimizer/registry.py +11 -0
- ivoryos/routes/auth/auth.py +43 -14
- ivoryos/routes/auth/templates/change_password.html +32 -0
- ivoryos/routes/control/control.py +101 -366
- ivoryos/routes/control/control_file.py +33 -0
- ivoryos/routes/control/control_new_device.py +152 -0
- ivoryos/routes/control/templates/controllers.html +193 -0
- ivoryos/routes/control/templates/controllers_new.html +112 -0
- ivoryos/routes/control/utils.py +40 -0
- ivoryos/routes/data/data.py +197 -0
- ivoryos/routes/data/templates/components/step_card.html +78 -0
- ivoryos/routes/{database/templates/database → data/templates}/workflow_database.html +14 -8
- ivoryos/routes/data/templates/workflow_view.html +360 -0
- ivoryos/routes/design/__init__.py +4 -0
- ivoryos/routes/design/design.py +348 -657
- ivoryos/routes/design/design_file.py +68 -0
- ivoryos/routes/design/design_step.py +171 -0
- ivoryos/routes/design/templates/components/action_form.html +53 -0
- ivoryos/routes/design/templates/components/actions_panel.html +25 -0
- ivoryos/routes/design/templates/components/autofill_toggle.html +10 -0
- ivoryos/routes/design/templates/components/canvas.html +5 -0
- ivoryos/routes/design/templates/components/canvas_footer.html +9 -0
- ivoryos/routes/design/templates/components/canvas_header.html +75 -0
- ivoryos/routes/design/templates/components/canvas_main.html +39 -0
- ivoryos/routes/design/templates/components/deck_selector.html +10 -0
- ivoryos/routes/design/templates/components/edit_action_form.html +53 -0
- ivoryos/routes/design/templates/components/info_modal.html +318 -0
- ivoryos/routes/design/templates/components/instruments_panel.html +88 -0
- ivoryos/routes/design/templates/components/modals/drop_modal.html +17 -0
- ivoryos/routes/design/templates/components/modals/json_modal.html +22 -0
- ivoryos/routes/design/templates/components/modals/new_script_modal.html +17 -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/python_code_overlay.html +56 -0
- ivoryos/routes/design/templates/components/sidebar.html +15 -0
- ivoryos/routes/design/templates/components/text_to_code_panel.html +20 -0
- ivoryos/routes/design/templates/experiment_builder.html +44 -0
- ivoryos/routes/execute/__init__.py +0 -0
- ivoryos/routes/execute/execute.py +377 -0
- ivoryos/routes/execute/execute_file.py +78 -0
- ivoryos/routes/execute/templates/components/error_modal.html +20 -0
- ivoryos/routes/execute/templates/components/logging_panel.html +56 -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 +60 -0
- ivoryos/routes/execute/templates/components/tab_bayesian.html +520 -0
- ivoryos/routes/execute/templates/components/tab_configuration.html +383 -0
- ivoryos/routes/execute/templates/components/tab_repeat.html +18 -0
- ivoryos/routes/execute/templates/experiment_run.html +30 -0
- ivoryos/routes/library/__init__.py +0 -0
- ivoryos/routes/library/library.py +157 -0
- ivoryos/routes/{database/templates/database/scripts_database.html → library/templates/library.html} +32 -23
- ivoryos/routes/main/main.py +31 -3
- ivoryos/routes/main/templates/{main/home.html → home.html} +4 -4
- ivoryos/server.py +180 -0
- ivoryos/socket_handlers.py +52 -0
- ivoryos/static/ivoryos_logo.png +0 -0
- ivoryos/static/js/action_handlers.js +384 -0
- ivoryos/static/js/db_delete.js +23 -0
- ivoryos/static/js/script_metadata.js +39 -0
- ivoryos/static/js/socket_handler.js +40 -5
- ivoryos/static/js/sortable_design.js +107 -56
- ivoryos/static/js/ui_state.js +114 -0
- ivoryos/templates/base.html +67 -8
- ivoryos/utils/bo_campaign.py +180 -3
- ivoryos/utils/client_proxy.py +267 -36
- ivoryos/utils/db_models.py +300 -65
- ivoryos/utils/decorators.py +34 -0
- ivoryos/utils/form.py +63 -29
- ivoryos/utils/global_config.py +34 -1
- ivoryos/utils/nest_script.py +314 -0
- ivoryos/utils/py_to_json.py +295 -0
- ivoryos/utils/script_runner.py +599 -165
- ivoryos/utils/serilize.py +201 -0
- ivoryos/utils/task_runner.py +71 -21
- ivoryos/utils/utils.py +50 -6
- ivoryos/version.py +1 -1
- ivoryos-1.4.4.dist-info/METADATA +263 -0
- ivoryos-1.4.4.dist-info/RECORD +119 -0
- {ivoryos-1.0.9.dist-info → ivoryos-1.4.4.dist-info}/WHEEL +1 -1
- {ivoryos-1.0.9.dist-info → ivoryos-1.4.4.dist-info}/top_level.txt +1 -0
- tests/unit/test_type_conversion.py +42 -0
- tests/unit/test_util.py +3 -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/database/database.py +0 -306
- ivoryos/routes/database/templates/database/step_card.html +0 -7
- ivoryos/routes/database/templates/database/workflow_view.html +0 -130
- ivoryos/routes/design/templates/design/experiment_builder.html +0 -521
- ivoryos/routes/design/templates/design/experiment_run.html +0 -558
- ivoryos-1.0.9.dist-info/METADATA +0 -218
- ivoryos-1.0.9.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/main/templates/{main/help.html → help.html} +0 -0
- {ivoryos-1.0.9.dist-info → ivoryos-1.4.4.dist-info/licenses}/LICENSE +0 -0
ivoryos/routes/design/design.py
CHANGED
|
@@ -1,786 +1,477 @@
|
|
|
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
|
-
|
|
10
|
-
from flask_login import login_required
|
|
11
|
-
from flask_socketio import SocketIO
|
|
12
|
-
from werkzeug.utils import secure_filename
|
|
3
|
+
from flask import Blueprint, redirect, url_for, flash, jsonify, request, render_template, session, current_app
|
|
4
|
+
from flask_login import login_required, current_user
|
|
13
5
|
|
|
6
|
+
from ivoryos.routes.library.library import publish
|
|
14
7
|
from ivoryos.utils import utils
|
|
15
8
|
from ivoryos.utils.global_config import GlobalConfig
|
|
16
|
-
from ivoryos.utils.form import
|
|
17
|
-
|
|
18
|
-
from ivoryos.utils.
|
|
19
|
-
from ivoryos.utils.script_runner import ScriptRunner
|
|
20
|
-
# from ivoryos.utils.utils import load_workflows
|
|
9
|
+
from ivoryos.utils.form import create_action_button, create_form_from_pseudo, create_all_builtin_forms
|
|
10
|
+
from ivoryos.utils.db_models import Script
|
|
11
|
+
from ivoryos.utils.py_to_json import convert_to_cards
|
|
21
12
|
|
|
22
|
-
|
|
23
|
-
|
|
13
|
+
# Import the new modular components
|
|
14
|
+
from ivoryos.routes.design.design_file import files
|
|
15
|
+
from ivoryos.routes.design.design_step import steps
|
|
24
16
|
|
|
25
|
-
global_config = GlobalConfig()
|
|
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
17
|
|
|
54
|
-
|
|
55
|
-
def handle_abort_current():
|
|
56
|
-
abort_current()
|
|
18
|
+
design = Blueprint('design', __name__, template_folder='templates')
|
|
57
19
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
@socketio.on('retry')
|
|
63
|
-
def handle_retry():
|
|
64
|
-
retry()
|
|
20
|
+
# Register sub-blueprints
|
|
21
|
+
design.register_blueprint(files)
|
|
22
|
+
design.register_blueprint(steps)
|
|
65
23
|
|
|
24
|
+
global_config = GlobalConfig()
|
|
66
25
|
|
|
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})
|
|
26
|
+
# ---- Main Design Routes ----
|
|
75
27
|
|
|
76
28
|
|
|
77
|
-
|
|
78
|
-
|
|
29
|
+
def _create_forms(instrument, script, autofill, pseudo_deck = None):
|
|
30
|
+
deck = global_config.deck
|
|
31
|
+
functions = {}
|
|
32
|
+
if instrument == 'flow_control':
|
|
33
|
+
forms = create_all_builtin_forms(script=script)
|
|
34
|
+
elif instrument in global_config.defined_variables.keys():
|
|
35
|
+
_object = global_config.defined_variables.get(instrument)
|
|
36
|
+
functions = utils._inspect_class(_object)
|
|
37
|
+
forms = create_form_from_pseudo(pseudo=functions, autofill=autofill, script=script)
|
|
38
|
+
elif instrument.startswith("blocks"):
|
|
39
|
+
forms = create_form_from_pseudo(pseudo=global_config.building_blocks[instrument], autofill=autofill, script=script)
|
|
40
|
+
functions = global_config.building_blocks[instrument]
|
|
41
|
+
else:
|
|
42
|
+
if deck:
|
|
43
|
+
functions = global_config.deck_snapshot.get(instrument, {})
|
|
44
|
+
elif pseudo_deck:
|
|
45
|
+
functions = pseudo_deck.get(instrument, {})
|
|
46
|
+
forms = create_form_from_pseudo(pseudo=functions, autofill=autofill, script=script)
|
|
47
|
+
return functions, forms
|
|
48
|
+
|
|
49
|
+
@design.route("/draft")
|
|
79
50
|
@login_required
|
|
80
|
-
def experiment_builder(
|
|
51
|
+
def experiment_builder():
|
|
81
52
|
"""
|
|
82
53
|
.. :quickref: Workflow Design; Build experiment workflow
|
|
83
54
|
|
|
84
55
|
**Experiment Builder**
|
|
85
56
|
|
|
86
|
-
|
|
87
|
-
define variables, and manage experiment scripts.
|
|
88
|
-
|
|
89
|
-
.. http:get:: /design/script
|
|
57
|
+
.. http:get:: /draft
|
|
90
58
|
|
|
91
|
-
Load the experiment builder
|
|
59
|
+
Load the experiment builder page where users can design their workflow by adding actions, instruments, and logic.
|
|
92
60
|
|
|
93
|
-
:param instrument: The specific instrument for which to load functions and forms.
|
|
94
|
-
:type instrument: str
|
|
95
61
|
:status 200: Experiment builder loaded successfully.
|
|
96
62
|
|
|
97
|
-
.. http:post:: /design/script
|
|
98
|
-
|
|
99
|
-
Submit form data to add or modify actions in the experiment script.
|
|
100
|
-
|
|
101
|
-
**Adding action to canvas**
|
|
102
|
-
|
|
103
|
-
:form return: (optional) The name of the function or method to add to the script.
|
|
104
|
-
:form dynamic: depend on the selected instrument and its metadata.
|
|
105
|
-
|
|
106
|
-
:status 200: Action added or modified successfully.
|
|
107
|
-
:status 400: Validation errors in submitted form data.
|
|
108
|
-
:status 302: Toggles autofill or redirects to refresh the page.
|
|
109
|
-
|
|
110
|
-
**Toggle auto parameter name fill**:
|
|
111
|
-
|
|
112
|
-
:status 200: autofill toggled successfully
|
|
113
|
-
|
|
114
63
|
"""
|
|
115
64
|
deck = global_config.deck
|
|
116
65
|
script = utils.get_script_file()
|
|
117
|
-
# load_workflows(script)
|
|
118
|
-
# registered_workflows = global_config.registered_workflows
|
|
119
66
|
|
|
120
67
|
if deck and script.deck is None:
|
|
121
68
|
script.deck = os.path.splitext(os.path.basename(deck.__file__))[
|
|
122
69
|
0] if deck.__name__ == "__main__" else deck.__name__
|
|
123
|
-
|
|
124
|
-
|
|
70
|
+
utils.post_script_file(script)
|
|
125
71
|
pseudo_deck_name = session.get('pseudo_deck', '')
|
|
126
72
|
pseudo_deck_path = os.path.join(current_app.config["DUMMY_DECK"], pseudo_deck_name)
|
|
127
73
|
off_line = current_app.config["OFF_LINE"]
|
|
128
|
-
enable_llm = current_app.config["ENABLE_LLM"]
|
|
129
|
-
autofill = session.get('autofill')
|
|
130
74
|
|
|
131
|
-
# autofill is not allowed for prep and cleanup
|
|
132
|
-
autofill = autofill if script.editing_type == "script" else False
|
|
133
|
-
forms = None
|
|
134
75
|
pseudo_deck = utils.load_deck(pseudo_deck_path) if off_line and pseudo_deck_name else None
|
|
135
76
|
if off_line and pseudo_deck is None:
|
|
136
77
|
flash("Choose available deck below.")
|
|
137
78
|
|
|
138
79
|
deck_list = utils.available_pseudo_deck(current_app.config["DUMMY_DECK"])
|
|
139
80
|
|
|
140
|
-
functions = {}
|
|
141
81
|
if deck:
|
|
142
82
|
deck_variables = list(global_config.deck_snapshot.keys())
|
|
143
|
-
# deck_variables.insert(0, "
|
|
144
|
-
deck_variables.insert(0, "flow_control")
|
|
145
|
-
|
|
83
|
+
# deck_variables.insert(0, "flow_control")
|
|
146
84
|
else:
|
|
147
85
|
deck_variables = list(pseudo_deck.keys()) if pseudo_deck else []
|
|
148
86
|
deck_variables.remove("deck_name") if len(deck_variables) > 0 else deck_variables
|
|
149
|
-
edit_action_info = session.get("edit_action")
|
|
150
|
-
if edit_action_info:
|
|
151
|
-
forms = create_form_from_action(edit_action_info, script=script)
|
|
152
|
-
elif instrument:
|
|
153
|
-
# if instrument in ['if', 'while', 'variable', 'wait', 'repeat']:
|
|
154
|
-
# forms = create_builtin_form(instrument, script=script)
|
|
155
|
-
if instrument == 'flow_control':
|
|
156
|
-
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
|
-
elif instrument in global_config.defined_variables.keys():
|
|
162
|
-
_object = global_config.defined_variables.get(instrument)
|
|
163
|
-
functions = utils._inspect_class(_object)
|
|
164
|
-
forms = create_form_from_pseudo(pseudo=functions, autofill=autofill, script=script)
|
|
165
|
-
else:
|
|
166
|
-
if deck:
|
|
167
|
-
functions = global_config.deck_snapshot.get(instrument, {})
|
|
168
|
-
elif pseudo_deck:
|
|
169
|
-
functions = pseudo_deck.get(instrument, {})
|
|
170
|
-
forms = create_form_from_pseudo(pseudo=functions, autofill=autofill, script=script)
|
|
171
|
-
if request.method == 'POST' and "hidden_name" in request.form:
|
|
172
|
-
# all_kwargs = request.form.copy()
|
|
173
|
-
method_name = request.form.get("hidden_name", None)
|
|
174
|
-
# if method_name is not None:
|
|
175
|
-
form = forms.get(method_name)
|
|
176
|
-
insert_position = request.form.get("drop_target_id", None)
|
|
177
|
-
kwargs = {field.name: field.data for field in form if field.name != 'csrf_token'}
|
|
178
|
-
if form and form.validate_on_submit():
|
|
179
|
-
function_name = kwargs.pop("hidden_name")
|
|
180
|
-
save_data = kwargs.pop('return', '')
|
|
181
|
-
|
|
182
|
-
primitive_arg_types = utils.get_arg_type(kwargs, functions[function_name])
|
|
183
|
-
|
|
184
|
-
script.eval_list(kwargs, primitive_arg_types)
|
|
185
|
-
kwargs = script.validate_variables(kwargs)
|
|
186
|
-
action = {"instrument": instrument, "action": function_name,
|
|
187
|
-
"args": kwargs,
|
|
188
|
-
"return": save_data,
|
|
189
|
-
'arg_types': primitive_arg_types}
|
|
190
|
-
script.add_action(action=action, insert_position=insert_position)
|
|
191
|
-
else:
|
|
192
|
-
flash(form.errors)
|
|
193
|
-
|
|
194
|
-
elif request.method == 'POST' and "builtin_name" in request.form:
|
|
195
|
-
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'}
|
|
198
|
-
insert_position = request.form.get("drop_target_id", None)
|
|
199
|
-
|
|
200
|
-
if form.validate_on_submit():
|
|
201
|
-
# print(kwargs)
|
|
202
|
-
logic_type = kwargs.pop('builtin_name')
|
|
203
|
-
if 'variable' in kwargs:
|
|
204
|
-
try:
|
|
205
|
-
script.add_variable(insert_position=insert_position, **kwargs)
|
|
206
|
-
except ValueError:
|
|
207
|
-
flash("Invalid variable type")
|
|
208
|
-
else:
|
|
209
|
-
script.add_logic_action(logic_type=logic_type, insert_position=insert_position, **kwargs)
|
|
210
|
-
else:
|
|
211
|
-
flash(form.errors)
|
|
212
|
-
elif request.method == 'POST' and "workflow_name" in request.form:
|
|
213
|
-
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'}
|
|
216
|
-
insert_position = request.form.get("drop_target_id", None)
|
|
217
|
-
|
|
218
|
-
if form.validate_on_submit():
|
|
219
|
-
# workflow_name = kwargs.pop('workflow_name')
|
|
220
|
-
save_data = kwargs.pop('return', '')
|
|
221
|
-
|
|
222
|
-
primitive_arg_types = utils.get_arg_type(kwargs, functions[workflow_name])
|
|
223
|
-
|
|
224
|
-
script.eval_list(kwargs, primitive_arg_types)
|
|
225
|
-
kwargs = script.validate_variables(kwargs)
|
|
226
|
-
action = {"instrument": instrument, "action": workflow_name,
|
|
227
|
-
"args": kwargs,
|
|
228
|
-
"return": save_data,
|
|
229
|
-
'arg_types': primitive_arg_types}
|
|
230
|
-
script.add_action(action=action, insert_position=insert_position)
|
|
231
|
-
script.add_workflow(**kwargs, insert_position=insert_position)
|
|
232
|
-
else:
|
|
233
|
-
flash(form.errors)
|
|
234
|
-
|
|
235
|
-
# toggle autofill, autofill doesn't apply to control flow ops
|
|
236
|
-
elif request.method == 'POST' and "autofill" in request.form:
|
|
237
|
-
autofill = not autofill
|
|
238
|
-
session['autofill'] = autofill
|
|
239
|
-
if not instrument == 'flow_control':
|
|
240
|
-
forms = create_form_from_pseudo(functions, autofill=autofill, script=script)
|
|
241
87
|
|
|
242
|
-
|
|
88
|
+
# edit_action_info = session.get("edit_action")
|
|
243
89
|
|
|
244
|
-
|
|
90
|
+
try:
|
|
91
|
+
exec_string = script.python_script if script.python_script else script.compile(current_app.config['SCRIPT_FOLDER'])
|
|
92
|
+
except Exception as e:
|
|
93
|
+
exec_string = {}
|
|
94
|
+
flash(f"Error in Python script: {e}")
|
|
245
95
|
session['python_code'] = exec_string
|
|
246
96
|
|
|
247
|
-
design_buttons = create_action_button(script)
|
|
248
|
-
return render_template('experiment_builder.html', off_line=off_line, instrument=instrument, history=deck_list,
|
|
249
|
-
script=script, defined_variables=deck_variables,
|
|
250
|
-
local_variables=global_config.defined_variables,
|
|
251
|
-
forms=forms, buttons=design_buttons, format_name=format_name,
|
|
252
|
-
use_llm=enable_llm)
|
|
97
|
+
design_buttons = {stype: create_action_button(script, stype) for stype in script.stypes}
|
|
253
98
|
|
|
99
|
+
return render_template('experiment_builder.html', off_line=off_line, history=deck_list,
|
|
100
|
+
script=script, defined_variables=deck_variables, buttons_dict=design_buttons,
|
|
101
|
+
local_variables=global_config.defined_variables, block_variables=global_config.building_blocks)
|
|
254
102
|
|
|
255
|
-
@design.route("/
|
|
103
|
+
@design.route("/draft/code_preview", methods=["GET"])
|
|
256
104
|
@login_required
|
|
257
|
-
def
|
|
258
|
-
|
|
259
|
-
|
|
105
|
+
def compile_preview():
|
|
106
|
+
# Get mode and batch from query parameters
|
|
107
|
+
script = utils.get_script_file()
|
|
108
|
+
mode = request.args.get("mode", "single") # default to "single"
|
|
109
|
+
batch = request.args.get("batch", "sample") # default to "sample"
|
|
260
110
|
|
|
261
|
-
|
|
111
|
+
try:
|
|
112
|
+
# Example: decide which code to return based on mode/batch
|
|
113
|
+
if mode == "single":
|
|
114
|
+
code = script.compile(current_app.config['SCRIPT_FOLDER'])
|
|
115
|
+
elif mode == "batch":
|
|
116
|
+
code = script.compile(current_app.config['SCRIPT_FOLDER'], batch=True, mode=batch)
|
|
117
|
+
else:
|
|
118
|
+
code = "Invalid mode. Please select 'single' or 'batch'."
|
|
119
|
+
except Exception as e:
|
|
120
|
+
code = f"Error compiling: {e}"
|
|
121
|
+
# print(code)
|
|
122
|
+
return jsonify(code=code)
|
|
262
123
|
|
|
263
|
-
:form prompt: user's prompt
|
|
264
|
-
:status 200: and then redirects to :http:get:`/experiment/build`
|
|
265
|
-
:status 400: failed to initialize the AI agent redirects to :http:get:`/design/script`
|
|
266
124
|
|
|
267
|
-
|
|
268
|
-
agent = global_config.agent
|
|
269
|
-
enable_llm = current_app.config["ENABLE_LLM"]
|
|
270
|
-
instrument = request.form.get("instrument")
|
|
271
|
-
|
|
272
|
-
if request.method == 'POST' and "clear" in request.form:
|
|
273
|
-
session['prompt'][instrument] = ''
|
|
274
|
-
if request.method == 'POST' and "gen" in request.form:
|
|
275
|
-
prompt = request.form.get("prompt")
|
|
276
|
-
session['prompt'][instrument] = prompt
|
|
277
|
-
# sdl_module = utils.parse_functions(find_instrument_by_name(f'deck.{instrument}'), doc_string=True)
|
|
278
|
-
sdl_module = global_config.deck_snapshot.get(instrument, {})
|
|
279
|
-
empty_script = Script(author=session.get('user'))
|
|
280
|
-
if enable_llm and agent is None:
|
|
281
|
-
try:
|
|
282
|
-
model = current_app.config["LLM_MODEL"]
|
|
283
|
-
server = current_app.config["LLM_SERVER"]
|
|
284
|
-
module = current_app.config["MODULE"]
|
|
285
|
-
from ivoryos.utils.llm_agent import LlmAgent
|
|
286
|
-
agent = LlmAgent(host=server, model=model, output_path=os.path.dirname(os.path.abspath(module)))
|
|
287
|
-
except Exception as e:
|
|
288
|
-
flash(e.__str__())
|
|
289
|
-
return redirect(url_for("design.experiment_builder", instrument=instrument, use_llm=True)), 400
|
|
290
|
-
action_list = agent.generate_code(sdl_module, prompt)
|
|
291
|
-
for action in action_list:
|
|
292
|
-
action['instrument'] = instrument
|
|
293
|
-
action['return'] = ''
|
|
294
|
-
if "args" not in action:
|
|
295
|
-
action['args'] = {}
|
|
296
|
-
if "arg_types" not in action:
|
|
297
|
-
action['arg_types'] = {}
|
|
298
|
-
empty_script.add_action(action)
|
|
299
|
-
utils.post_script_file(empty_script)
|
|
300
|
-
return redirect(url_for("design.experiment_builder", instrument=instrument, use_llm=True))
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
@design.route("/design/campaign", methods=['GET', 'POST'])
|
|
125
|
+
@design.route("/draft/meta", methods=["PATCH"])
|
|
304
126
|
@login_required
|
|
305
|
-
def
|
|
127
|
+
def update_script_meta():
|
|
306
128
|
"""
|
|
307
|
-
.. :quickref: Workflow
|
|
308
|
-
|
|
309
|
-
.. http:get:: /design/campaign
|
|
129
|
+
.. :quickref: Workflow Design; update the script metadata.
|
|
310
130
|
|
|
311
|
-
|
|
131
|
+
.. http:patch:: /draft/meta
|
|
312
132
|
|
|
313
|
-
|
|
133
|
+
Update the script metadata, including the script name and status. If the script name is provided,
|
|
134
|
+
it saves the script with that name. If the status is "finished", it finalizes the script.
|
|
314
135
|
|
|
315
|
-
|
|
136
|
+
:form name: The name to save the script as.
|
|
137
|
+
:form status: The status of the script (e.g., "finished").
|
|
316
138
|
|
|
139
|
+
:status 200: Successfully updated the script metadata.
|
|
317
140
|
"""
|
|
318
|
-
|
|
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)
|
|
141
|
+
data = request.get_json()
|
|
367
142
|
script = utils.get_script_file()
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
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)
|
|
143
|
+
if 'name' in data:
|
|
144
|
+
run_name = data.get("name")
|
|
145
|
+
exist_script = Script.query.get(run_name)
|
|
146
|
+
if exist_script is None:
|
|
147
|
+
script.save_as(run_name)
|
|
148
|
+
utils.post_script_file(script)
|
|
149
|
+
return jsonify(success=True)
|
|
394
150
|
else:
|
|
395
|
-
|
|
396
|
-
|
|
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)
|
|
151
|
+
flash("Script name is already exist in database")
|
|
152
|
+
return jsonify(success=False)
|
|
400
153
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
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())
|
|
154
|
+
if 'status' in data:
|
|
155
|
+
if data['status'] == "finished":
|
|
156
|
+
script.finalize()
|
|
157
|
+
utils.post_script_file(script)
|
|
158
|
+
return jsonify(success=True)
|
|
159
|
+
return jsonify(success=False)
|
|
428
160
|
|
|
429
161
|
|
|
430
|
-
@design.route("/
|
|
162
|
+
@design.route("/draft/ui-state", methods=["PATCH"])
|
|
431
163
|
@login_required
|
|
432
|
-
def
|
|
164
|
+
def update_ui_state():
|
|
433
165
|
"""
|
|
434
|
-
.. :quickref: Workflow Design;
|
|
435
|
-
|
|
436
|
-
.. http:get:: /design/script/toggle/<stype>
|
|
166
|
+
.. :quickref: Workflow Design; update the UI state for the design canvas.
|
|
437
167
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
"""
|
|
441
|
-
script = utils.get_script_file()
|
|
442
|
-
script.editing_type = stype
|
|
443
|
-
utils.post_script_file(script)
|
|
444
|
-
return redirect(url_for('design.experiment_builder'))
|
|
168
|
+
.. http:patch:: /draft/ui-state
|
|
445
169
|
|
|
170
|
+
Update the UI state for the design canvas, including showing code overlays, setting editing types,
|
|
171
|
+
and handling deck selection.
|
|
446
172
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
script = utils.get_script_file()
|
|
452
|
-
script.currently_editing_order = order.split(",", len(script.currently_editing_script))
|
|
453
|
-
script.sort_actions()
|
|
454
|
-
exec_string = script.compile(current_app.config['SCRIPT_FOLDER'])
|
|
455
|
-
utils.post_script_file(script)
|
|
456
|
-
session['python_code'] = exec_string
|
|
457
|
-
|
|
458
|
-
return jsonify({'success': True})
|
|
173
|
+
:form show_code: Whether to show the code overlay (true/false).
|
|
174
|
+
:form editing_type: The type of editing to set (prep, script, cleanup).
|
|
175
|
+
:form autofill: Whether to enable autofill for the instrument panel (true/false).
|
|
176
|
+
:form deck_name: The name of the deck to select.
|
|
459
177
|
|
|
178
|
+
:status 200: Updates the UI state and returns a success message.
|
|
179
|
+
"""
|
|
180
|
+
data = request.get_json()
|
|
460
181
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
182
|
+
if "show_code" in data:
|
|
183
|
+
session["show_code"] = bool(data["show_code"])
|
|
184
|
+
return jsonify({"success": True})
|
|
185
|
+
if "editing_type" in data:
|
|
186
|
+
stype = data.get("editing_type")
|
|
465
187
|
|
|
188
|
+
script = utils.get_script_file()
|
|
189
|
+
script.editing_type = stype
|
|
190
|
+
utils.post_script_file(script)
|
|
466
191
|
|
|
467
|
-
#
|
|
468
|
-
|
|
192
|
+
# Re-render only the part of the page you want to update
|
|
193
|
+
design_buttons = {stype: create_action_button(script, stype) for stype in script.stypes}
|
|
194
|
+
rendered_html = render_template("components/canvas.html", script=script, buttons_dict=design_buttons)
|
|
195
|
+
return jsonify({"html": rendered_html})
|
|
196
|
+
|
|
197
|
+
if "autofill" in data:
|
|
198
|
+
script = utils.get_script_file()
|
|
199
|
+
instrument = data.get("instrument", '')
|
|
200
|
+
autofill = data.get("autofill", False)
|
|
201
|
+
session['autofill'] = autofill
|
|
202
|
+
_, forms = _create_forms(instrument, script, autofill)
|
|
203
|
+
rendered_html = render_template("components/actions_panel.html", forms=forms, script=script, instrument=instrument)
|
|
204
|
+
return jsonify({"html": rendered_html})
|
|
205
|
+
|
|
206
|
+
if "deck_name" in data:
|
|
207
|
+
pkl_name = data.get('deck_name', "")
|
|
208
|
+
script = utils.get_script_file()
|
|
209
|
+
session['pseudo_deck'] = pkl_name
|
|
210
|
+
deck_list = utils.available_pseudo_deck(current_app.config["DUMMY_DECK"])
|
|
211
|
+
|
|
212
|
+
if script.deck is None or script.isEmpty():
|
|
213
|
+
script.deck = pkl_name.split('.')[0]
|
|
214
|
+
utils.post_script_file(script)
|
|
215
|
+
elif script.deck and not script.deck == pkl_name.split('.')[0]:
|
|
216
|
+
flash(f"Choose the deck with name {script.deck}")
|
|
217
|
+
pseudo_deck_path = os.path.join(current_app.config["DUMMY_DECK"], pkl_name)
|
|
218
|
+
pseudo_deck = utils.load_deck(pseudo_deck_path)
|
|
219
|
+
deck_variables = list(pseudo_deck.keys()) if pseudo_deck else []
|
|
220
|
+
deck_variables.remove("deck_name") if len(deck_variables) > 0 else deck_variables
|
|
221
|
+
html = render_template("components/sidebar.html", history=deck_list,
|
|
222
|
+
defined_variables=deck_variables, local_variables = global_config.defined_variables,
|
|
223
|
+
block_variables=global_config.building_blocks)
|
|
224
|
+
return jsonify({"html": html})
|
|
225
|
+
return jsonify({"error": "Invalid request"}), 400
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
# @design.route("/draft/steps/order", methods=['POST'])
|
|
229
|
+
# @login_required
|
|
230
|
+
# def update_list():
|
|
231
|
+
# """
|
|
232
|
+
# .. :quickref: Workflow Design Steps; update the order of steps in the design canvas when reordering steps.
|
|
233
|
+
#
|
|
234
|
+
# .. http:post:: /draft/steps/order
|
|
235
|
+
#
|
|
236
|
+
# Update the order of steps in the design canvas when reordering steps.
|
|
237
|
+
#
|
|
238
|
+
# :form order: A comma-separated string representing the new order of steps.
|
|
239
|
+
# :status 200: Successfully updated the order of steps.
|
|
240
|
+
# """
|
|
241
|
+
# order = request.form['order']
|
|
242
|
+
# script = utils.get_script_file()
|
|
243
|
+
# script.currently_editing_order = order.split(",", len(script.currently_editing_script))
|
|
244
|
+
# script.sort_actions()
|
|
245
|
+
# exec_string = script.compile(current_app.config['SCRIPT_FOLDER'])
|
|
246
|
+
# utils.post_script_file(script)
|
|
247
|
+
# session['python_code'] = exec_string
|
|
248
|
+
#
|
|
249
|
+
# return jsonify({'success': True})
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
@design.route("/draft", methods=['DELETE'])
|
|
469
254
|
@login_required
|
|
470
|
-
def
|
|
255
|
+
def clear_draft():
|
|
471
256
|
"""
|
|
472
257
|
.. :quickref: Workflow Design; clear the design canvas.
|
|
473
258
|
|
|
474
|
-
.. http:
|
|
259
|
+
.. http:delete:: /draft
|
|
475
260
|
|
|
476
|
-
:
|
|
477
|
-
:status 200: clear canvas and then redirects to :http:get:`/design/script`
|
|
261
|
+
:status 200: clear canvas
|
|
478
262
|
"""
|
|
479
263
|
deck = global_config.deck
|
|
480
|
-
pseudo_name = session.get("pseudo_deck", "")
|
|
481
264
|
if deck:
|
|
482
265
|
deck_name = os.path.splitext(os.path.basename(deck.__file__))[
|
|
483
266
|
0] if deck.__name__ == "__main__" else deck.__name__
|
|
484
|
-
elif pseudo_name:
|
|
485
|
-
deck_name = pseudo_name
|
|
486
267
|
else:
|
|
487
|
-
deck_name =
|
|
488
|
-
script = Script(deck=deck_name, author=
|
|
268
|
+
deck_name = session.get("pseudo_deck", "")
|
|
269
|
+
script = Script(deck=deck_name, author=current_user.get_id())
|
|
489
270
|
utils.post_script_file(script)
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
@design.route("/design/import/pseudo", methods=['POST'])
|
|
494
|
-
@login_required
|
|
495
|
-
def import_pseudo():
|
|
496
|
-
"""
|
|
497
|
-
.. :quickref: Workflow Design; Import pseudo deck from deck history
|
|
271
|
+
exec_string = script.compile(current_app.config['SCRIPT_FOLDER'])
|
|
272
|
+
session['python_code'] = exec_string
|
|
273
|
+
return jsonify({'success': True})
|
|
498
274
|
|
|
499
|
-
.. http:post:: /design/import/pseudo
|
|
500
275
|
|
|
501
|
-
:form pkl_name: pseudo deck name
|
|
502
|
-
:status 302: load pseudo deck and then redirects to :http:get:`/design/script`
|
|
503
|
-
"""
|
|
504
|
-
pkl_name = request.form.get('pkl_name')
|
|
505
|
-
script = utils.get_script_file()
|
|
506
|
-
session['pseudo_deck'] = pkl_name
|
|
507
276
|
|
|
508
|
-
if script.deck is None or script.isEmpty():
|
|
509
|
-
script.deck = pkl_name.split('.')[0]
|
|
510
|
-
utils.post_script_file(script)
|
|
511
|
-
elif script.deck and not script.deck == pkl_name.split('.')[0]:
|
|
512
|
-
flash(f"Choose the deck with name {script.deck}")
|
|
513
|
-
return redirect(url_for("design.experiment_builder"))
|
|
514
277
|
|
|
515
278
|
|
|
516
|
-
@design.route(
|
|
517
|
-
|
|
518
|
-
def upload():
|
|
279
|
+
@design.route("/draft/submit_python", methods=["POST"])
|
|
280
|
+
def submit_script():
|
|
519
281
|
"""
|
|
520
|
-
.. :quickref: Workflow
|
|
282
|
+
.. :quickref: Workflow Design; convert Python to workflow script
|
|
521
283
|
|
|
522
|
-
.. http:post:: /
|
|
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"))
|
|
284
|
+
.. http:post:: /draft/submit_python
|
|
539
285
|
|
|
286
|
+
Convert a Python script to a workflow script and save it in the database.
|
|
540
287
|
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
288
|
+
:form workflow_name: workflow name
|
|
289
|
+
:form script: main script
|
|
290
|
+
:form prep: prep script
|
|
291
|
+
:form cleanup: post script
|
|
292
|
+
:status 200: clear canvas
|
|
544
293
|
"""
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
294
|
+
deck = global_config.deck
|
|
295
|
+
deck_name = os.path.splitext(os.path.basename(deck.__file__))[0] if deck.__name__ == "__main__" else deck.__name__
|
|
296
|
+
script = Script(author=current_user.get_id(), deck=deck_name)
|
|
297
|
+
script_collection = request.get_json()
|
|
298
|
+
workflow_name = script_collection.pop("workflow_name")
|
|
299
|
+
script.python_script = script_collection
|
|
300
|
+
# todo check script format
|
|
301
|
+
script.name = workflow_name
|
|
302
|
+
result = {}
|
|
303
|
+
for stype, py_str in script_collection.items():
|
|
304
|
+
try:
|
|
305
|
+
card = convert_to_cards(py_str)
|
|
306
|
+
script.script_dict[stype] = card
|
|
307
|
+
result[stype] = "success"
|
|
308
|
+
except Exception as e:
|
|
309
|
+
result[stype] = f"failed to transcript, but function can still run. error: {str(e)}"
|
|
310
|
+
utils.post_script_file(script)
|
|
311
|
+
status = publish()
|
|
312
|
+
return jsonify({"script": result, "db": status}), 200
|
|
548
313
|
|
|
549
|
-
"""
|
|
550
|
-
filepath = os.path.join(current_app.config["DATA_FOLDER"], filename)
|
|
551
|
-
return send_file(os.path.abspath(filepath), as_attachment=True)
|
|
552
314
|
|
|
553
315
|
|
|
554
|
-
@design.
|
|
316
|
+
@design.post("/draft/instruments/<string:instrument>")
|
|
555
317
|
@login_required
|
|
556
|
-
def
|
|
318
|
+
def methods_handler(instrument: str = ''):
|
|
557
319
|
"""
|
|
558
|
-
.. :quickref: Workflow Design
|
|
320
|
+
.. :quickref: Workflow Design; handle methods of a specific instrument
|
|
559
321
|
|
|
560
|
-
.. http:post:: /
|
|
322
|
+
.. http:post:: /draft/instruments/<string:instrument>
|
|
561
323
|
|
|
562
|
-
|
|
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>
|
|
324
|
+
Add methods for a specific instrument in the workflow design.
|
|
584
325
|
|
|
326
|
+
:param instrument: The name of the instrument to handle methods for.
|
|
327
|
+
:type instrument: str
|
|
328
|
+
:status 200: Render the methods for the specified instrument.
|
|
585
329
|
"""
|
|
586
330
|
script = utils.get_script_file()
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
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
|
|
331
|
+
pseudo_deck_name = session.get('pseudo_deck', '')
|
|
332
|
+
pseudo_deck_path = os.path.join(current_app.config["DUMMY_DECK"], pseudo_deck_name)
|
|
333
|
+
off_line = current_app.config["OFF_LINE"]
|
|
334
|
+
pseudo_deck = utils.load_deck(pseudo_deck_path) if off_line and pseudo_deck_name else None
|
|
335
|
+
autofill = session.get('autofill', False)
|
|
613
336
|
|
|
614
|
-
|
|
337
|
+
functions, forms = _create_forms(instrument, script, autofill, pseudo_deck)
|
|
615
338
|
|
|
616
|
-
|
|
339
|
+
success = True
|
|
340
|
+
msg = ""
|
|
341
|
+
request.form
|
|
342
|
+
if "hidden_name" in request.form:
|
|
343
|
+
deck_snapshot = global_config.deck_snapshot
|
|
344
|
+
block_snapshot = global_config.building_blocks
|
|
345
|
+
method_name = request.form.get("hidden_name", None)
|
|
346
|
+
form = forms.get(method_name) if forms else None
|
|
347
|
+
insert_position = request.form.get("drop_target_id", None)
|
|
617
348
|
|
|
618
|
-
|
|
349
|
+
if form:
|
|
350
|
+
kwargs = {field.name: field.data for field in form if field.name != 'csrf_token'}
|
|
351
|
+
if form.validate_on_submit():
|
|
352
|
+
function_name = kwargs.pop("hidden_name")
|
|
353
|
+
batch_action = kwargs.pop("batch_action", False)
|
|
354
|
+
save_data = kwargs.pop('return', '')
|
|
355
|
+
primitive_arg_types = utils.get_arg_type(kwargs, functions[function_name])
|
|
619
356
|
|
|
620
|
-
|
|
621
|
-
|
|
357
|
+
# todo
|
|
358
|
+
# print(primitive_arg_types)
|
|
622
359
|
|
|
623
|
-
|
|
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', '')
|
|
360
|
+
script.eval_list(kwargs, primitive_arg_types)
|
|
637
361
|
kwargs = script.validate_variables(kwargs)
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
362
|
+
coroutine = False
|
|
363
|
+
if instrument.startswith("deck") and deck_snapshot:
|
|
364
|
+
coroutine = deck_snapshot[instrument][function_name].get("coroutine", False)
|
|
365
|
+
elif instrument.startswith("blocks") and block_snapshot:
|
|
366
|
+
coroutine = block_snapshot[instrument][function_name].get("coroutine", False)
|
|
367
|
+
action = {"instrument": instrument, "action": function_name,
|
|
368
|
+
"args": kwargs,
|
|
369
|
+
"return": save_data,
|
|
370
|
+
'arg_types': primitive_arg_types,
|
|
371
|
+
"coroutine": coroutine,
|
|
372
|
+
"batch_action": batch_action,
|
|
373
|
+
}
|
|
374
|
+
script.add_action(action=action, insert_position=insert_position)
|
|
641
375
|
else:
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
376
|
+
msg = [f"{field}: {', '.join(messages)}" for field, messages in form.errors.items()]
|
|
377
|
+
success = False
|
|
378
|
+
elif "builtin_name" in request.form:
|
|
379
|
+
function_name = request.form.get("builtin_name")
|
|
380
|
+
form = forms.get(function_name) if forms else None
|
|
381
|
+
insert_position = request.form.get("drop_target_id", None)
|
|
382
|
+
if form:
|
|
383
|
+
kwargs = {field.name: field.data for field in form if field.name != 'csrf_token'}
|
|
384
|
+
if form.validate_on_submit():
|
|
385
|
+
logic_type = kwargs.pop('builtin_name')
|
|
386
|
+
if 'variable' in kwargs:
|
|
387
|
+
try:
|
|
388
|
+
script.add_variable(insert_position=insert_position, **kwargs)
|
|
389
|
+
except ValueError:
|
|
390
|
+
success = False
|
|
391
|
+
msg = [f"{field}: {', '.join(messages)}" for field, messages in form.errors.items()]
|
|
392
|
+
else:
|
|
393
|
+
script.add_logic_action(logic_type=logic_type, insert_position=insert_position, **kwargs)
|
|
394
|
+
else:
|
|
395
|
+
success = False
|
|
396
|
+
msg = [f"{field}: {', '.join(messages)}" for field, messages in form.errors.items()]
|
|
397
|
+
elif "workflow_name" in request.form:
|
|
398
|
+
workflow_name = request.form.get("workflow_name")
|
|
399
|
+
form = forms.get(workflow_name) if forms else None
|
|
400
|
+
insert_position = request.form.get("drop_target_id", None)
|
|
401
|
+
if form:
|
|
402
|
+
kwargs = {field.name: field.data for field in form if field.name != 'csrf_token'}
|
|
403
|
+
if form.validate_on_submit():
|
|
404
|
+
save_data = kwargs.pop('return', '')
|
|
405
|
+
primitive_arg_types = utils.get_arg_type(kwargs, functions[workflow_name])
|
|
406
|
+
script.eval_list(kwargs, primitive_arg_types)
|
|
407
|
+
kwargs = script.validate_variables(kwargs)
|
|
408
|
+
action = {"instrument": instrument, "action": workflow_name,
|
|
409
|
+
"args": kwargs,
|
|
410
|
+
"return": save_data,
|
|
411
|
+
'arg_types': primitive_arg_types}
|
|
412
|
+
script.add_action(action=action, insert_position=insert_position)
|
|
413
|
+
script.add_workflow(**kwargs, insert_position=insert_position)
|
|
414
|
+
else:
|
|
415
|
+
success = False
|
|
416
|
+
msg = [f"{field}: {', '.join(messages)}" for field, messages in form.errors.items()]
|
|
663
417
|
utils.post_script_file(script)
|
|
664
|
-
|
|
418
|
+
#TODO
|
|
419
|
+
try:
|
|
420
|
+
exec_string = script.compile(current_app.config['SCRIPT_FOLDER'])
|
|
421
|
+
except Exception as e:
|
|
422
|
+
exec_string = {}
|
|
423
|
+
msg = f"Compilation failed: {str(e)}"
|
|
424
|
+
# exec_string = script.compile(current_app.config['SCRIPT_FOLDER'])
|
|
425
|
+
session['python_code'] = exec_string
|
|
426
|
+
design_buttons = {stype: create_action_button(script, stype) for stype in script.stypes}
|
|
427
|
+
html = render_template("components/canvas_main.html", script=script, buttons_dict=design_buttons)
|
|
428
|
+
return jsonify({"html": html, "success": success, "error": msg})
|
|
665
429
|
|
|
666
430
|
|
|
667
|
-
@design.
|
|
431
|
+
@design.get("/draft/instruments", strict_slashes=False)
|
|
432
|
+
@design.get("/draft/instruments/<string:instrument>")
|
|
668
433
|
@login_required
|
|
669
|
-
def
|
|
434
|
+
def get_operation_sidebar(instrument: str = ''):
|
|
670
435
|
"""
|
|
671
|
-
.. :quickref: Workflow Design;
|
|
436
|
+
.. :quickref: Workflow Design; handle methods of a specific instrument
|
|
672
437
|
|
|
673
|
-
.. http:get:: /
|
|
438
|
+
.. http:get:: /draft/instruments/<string:instrument>
|
|
674
439
|
|
|
675
|
-
:param
|
|
676
|
-
:type
|
|
440
|
+
:param instrument: The name of the instrument to handle methods for.
|
|
441
|
+
:type instrument: str
|
|
677
442
|
|
|
678
|
-
:status
|
|
443
|
+
:status 200: Render the methods for the specified instrument.
|
|
679
444
|
"""
|
|
680
|
-
back = request.referrer
|
|
681
445
|
script = utils.get_script_file()
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
# ---- HTTP API Endpoints ----
|
|
688
|
-
|
|
689
|
-
@design.route("/api/runner/status", methods=["GET"])
|
|
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
|
|
446
|
+
pseudo_deck_name = session.get('pseudo_deck', '')
|
|
447
|
+
pseudo_deck_path = os.path.join(current_app.config["DUMMY_DECK"], pseudo_deck_name)
|
|
448
|
+
off_line = current_app.config["OFF_LINE"]
|
|
449
|
+
pseudo_deck = utils.load_deck(pseudo_deck_path) if off_line and pseudo_deck_name else None
|
|
450
|
+
autofill = session.get('autofill', False)
|
|
768
451
|
|
|
452
|
+
functions, forms = _create_forms(instrument, script, autofill, pseudo_deck)
|
|
769
453
|
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
454
|
+
if instrument:
|
|
455
|
+
html = render_template("components/sidebar.html", forms=forms, instrument=instrument, script=script)
|
|
456
|
+
else:
|
|
457
|
+
pseudo_deck_name = session.get('pseudo_deck', '')
|
|
458
|
+
pseudo_deck_path = os.path.join(current_app.config["DUMMY_DECK"], pseudo_deck_name)
|
|
459
|
+
off_line = current_app.config["OFF_LINE"]
|
|
460
|
+
pseudo_deck = utils.load_deck(pseudo_deck_path) if off_line and pseudo_deck_name else None
|
|
461
|
+
if off_line and pseudo_deck is None:
|
|
462
|
+
flash("Choose available deck below.")
|
|
463
|
+
deck_list = utils.available_pseudo_deck(current_app.config["DUMMY_DECK"])
|
|
464
|
+
if not off_line:
|
|
465
|
+
deck_variables = list(global_config.deck_snapshot.keys())
|
|
466
|
+
else:
|
|
467
|
+
deck_variables = list(pseudo_deck.keys()) if pseudo_deck else []
|
|
468
|
+
deck_variables.remove("deck_name") if len(deck_variables) > 0 else deck_variables
|
|
469
|
+
# edit_action_info = session.get("edit_action")
|
|
470
|
+
html = render_template("components/sidebar.html", off_line=off_line, history=deck_list,
|
|
471
|
+
defined_variables=deck_variables,
|
|
472
|
+
local_variables=global_config.defined_variables,
|
|
473
|
+
block_variables=global_config.building_blocks,
|
|
474
|
+
)
|
|
475
|
+
return jsonify({"html": html})
|
|
774
476
|
|
|
775
|
-
.. http:get:: /api/design/submit
|
|
776
477
|
|
|
777
|
-
:status 200: {"status": "ok"}
|
|
778
|
-
"""
|
|
779
|
-
deck = global_config.deck
|
|
780
|
-
deck_name = os.path.splitext(os.path.basename(deck.__file__))[0] if deck.__name__ == "__main__" else deck.__name__
|
|
781
|
-
script = Script(author=session.get('user'), deck=deck_name)
|
|
782
|
-
script_collection = request.get_json()
|
|
783
|
-
script.python_script = script_collection
|
|
784
|
-
# todo check script format
|
|
785
|
-
utils.post_script_file(script)
|
|
786
|
-
return jsonify({"status": "ok"}), 200
|