ivoryos 1.0.1__py3-none-any.whl → 1.0.3__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.

@@ -9,7 +9,7 @@ login_manager = LoginManager()
9
9
  auth = Blueprint('auth', __name__, template_folder='templates/auth')
10
10
 
11
11
 
12
- @auth.route('/login', methods=['GET', 'POST'])
12
+ @auth.route('/auth/login', methods=['GET', 'POST'])
13
13
  def login():
14
14
  """
15
15
  .. :quickref: User; login user
@@ -50,7 +50,7 @@ def login():
50
50
  return render_template('login.html')
51
51
 
52
52
 
53
- @auth.route('/signup', methods=['GET', 'POST'])
53
+ @auth.route('/auth/signup', methods=['GET', 'POST'])
54
54
  def signup():
55
55
  """
56
56
  .. :quickref: User; signup for a new account
@@ -82,7 +82,7 @@ def signup():
82
82
  return render_template('signup.html')
83
83
 
84
84
 
85
- @auth.route("/logout")
85
+ @auth.route("/auth/logout")
86
86
  @login_required
87
87
  def logout():
88
88
  """
@@ -1,8 +1,10 @@
1
1
  import os
2
2
 
3
- from flask import Blueprint, redirect, url_for, flash, request, render_template, session, current_app, jsonify
3
+ from flask import Blueprint, redirect, url_for, flash, request, render_template, session, current_app, jsonify, \
4
+ send_file
4
5
  from flask_login import login_required
5
6
 
7
+ from ivoryos.utils.client_proxy import export_to_python, create_function
6
8
  from ivoryos.utils.global_config import GlobalConfig
7
9
  from ivoryos.utils import utils
8
10
  from ivoryos.utils.form import create_form_from_module, format_name
@@ -14,7 +16,7 @@ runner = TaskRunner()
14
16
  control = Blueprint('control', __name__, template_folder='templates/control')
15
17
 
16
18
 
17
- @control.route("/my_deck")
19
+ @control.route("/control/home/deck", strict_slashes=False)
18
20
  @login_required
19
21
  def deck_controllers():
20
22
  """
@@ -22,15 +24,15 @@ def deck_controllers():
22
24
 
23
25
  deck control home interface for listing all deck instruments
24
26
 
25
- .. http:get:: /my_deck
27
+ .. http:get:: /control/home/deck
26
28
  """
27
29
  deck_variables = global_config.deck_snapshot.keys()
28
30
  deck_list = utils.import_history(os.path.join(current_app.config["OUTPUT_FOLDER"], 'deck_history.txt'))
29
31
  return render_template('controllers_home.html', defined_variables=deck_variables, deck=True, history=deck_list)
30
32
 
31
33
 
32
- @control.route("/new_controller/")
33
- @control.route("/new_controller/<instrument>", methods=['GET', 'POST'])
34
+ @control.route("/control/new/", strict_slashes=False)
35
+ @control.route("/control/new/<instrument>", methods=['GET', 'POST'])
34
36
  @login_required
35
37
  def new_controller(instrument=None):
36
38
  """
@@ -38,12 +40,12 @@ def new_controller(instrument=None):
38
40
 
39
41
  interface for connecting a new <instrument>
40
42
 
41
- .. http:get:: /new_controller
43
+ .. http:get:: /control/new/
42
44
 
43
45
  :param instrument: instrument name
44
46
  :type instrument: str
45
47
 
46
- .. http:post:: /new_controller
48
+ .. http:post:: /control/new/
47
49
 
48
50
  :form device_name: module instance name (e.g. my_instance = MyClass())
49
51
  :form kwargs: dynamic module initialization kwargs fields
@@ -88,7 +90,7 @@ def new_controller(instrument=None):
88
90
  device=device, args=args, defined_variables=global_config.defined_variables)
89
91
 
90
92
 
91
- @control.route("/controllers")
93
+ @control.route("/control/home/temp", strict_slashes=False)
92
94
  @login_required
93
95
  def controllers_home():
94
96
  """
@@ -96,14 +98,15 @@ def controllers_home():
96
98
 
97
99
  temporarily connected devices home interface for listing all instruments
98
100
 
99
- .. http:get:: /controllers
101
+ .. http:get:: /control/home/temp
100
102
 
101
103
  """
102
104
  # defined_variables = parse_deck(deck)
103
- return render_template('controllers_home.html', defined_variables=global_config.defined_variables)
105
+ defined_variables = global_config.defined_variables.keys()
106
+ return render_template('controllers_home.html', defined_variables=defined_variables)
104
107
 
105
108
 
106
- @control.route("/controllers/<instrument>", methods=['GET', 'POST'])
109
+ @control.route("/control/<instrument>/methods", methods=['GET', 'POST'])
107
110
  @login_required
108
111
  def controllers(instrument: str):
109
112
  """
@@ -111,12 +114,12 @@ def controllers(instrument: str):
111
114
 
112
115
  control interface for selected <instrument>
113
116
 
114
- .. http:get:: /controllers
117
+ .. http:get:: /control/<instrument>/methods
115
118
 
116
119
  :param instrument: instrument name
117
120
  :type instrument: str
118
121
 
119
- .. http:post:: /controllers
122
+ .. http:post:: /control/<instrument>/methods
120
123
 
121
124
  :form hidden_name: function name (hidden field)
122
125
  :form kwargs: dynamic kwargs field
@@ -154,24 +157,50 @@ def controllers(instrument: str):
154
157
  flash(form.errors)
155
158
  return render_template('controllers.html', instrument=instrument, forms=forms, format_name=format_name)
156
159
 
160
+ @control.route("/control/download", strict_slashes=False)
161
+ @login_required
162
+ def download_proxy():
163
+ """
164
+ .. :quickref: Direct Control; download proxy interface
165
+
166
+ download proxy interface
157
167
 
158
- @control.route("/backend_control/<instrument>", methods=['POST'])
168
+ .. http:get:: /control/download
169
+ """
170
+ snapshot = global_config.deck_snapshot.copy()
171
+ class_definitions = {}
172
+ # Iterate through each instrument in the snapshot
173
+ for instrument_key, instrument_data in snapshot.items():
174
+ # Iterate through each function associated with the current instrument
175
+ for function_key, function_data in instrument_data.items():
176
+ # Convert the function signature to a string representation
177
+ function_data['signature'] = str(function_data['signature'])
178
+ class_name = instrument_key.split('.')[-1] # Extracting the class name from the path
179
+ class_definitions[class_name.capitalize()] = create_function(request.url_root, class_name, instrument_data)
180
+ # Export the generated class definitions to a .py script
181
+ export_to_python(class_definitions, current_app.config["OUTPUT_FOLDER"])
182
+ filepath = os.path.join(current_app.config["OUTPUT_FOLDER"], "generated_proxy.py")
183
+ return send_file(os.path.abspath(filepath), as_attachment=True)
184
+
185
+ @control.route("/api/control/", strict_slashes=False, methods=['GET'])
186
+ @control.route("/api/control/<instrument>", methods=['POST'])
159
187
  def backend_control(instrument: str=None):
160
188
  """
161
189
  .. :quickref: Backend Control; backend control
162
190
 
163
191
  backend control through http requests
164
192
 
165
- .. http:get:: /backend_control
193
+ .. http:get:: /api/control/
166
194
 
167
195
  :param instrument: instrument name
168
196
  :type instrument: str
169
197
 
170
- .. http:post:: /backend_control
198
+ .. http:post:: /api/control/
171
199
 
172
200
  """
173
- inst_object = find_instrument_by_name(instrument)
174
- forms = create_form_from_module(sdl_module=inst_object, autofill=False, design=False)
201
+ if instrument:
202
+ inst_object = find_instrument_by_name(instrument)
203
+ forms = create_form_from_module(sdl_module=inst_object, autofill=False, design=False)
175
204
 
176
205
  if request.method == 'POST':
177
206
  method_name = request.form.get("hidden_name", None)
@@ -183,42 +212,48 @@ def backend_control(instrument: str=None):
183
212
  current_app=current_app._get_current_object())
184
213
  return jsonify(output), 200
185
214
 
186
-
187
- @control.route("/backend_control", methods=['GET'])
188
- def backend_client():
189
- """
190
- .. :quickref: Backend Control; get snapshot
191
-
192
- backend control through http requests
193
-
194
- .. http:get:: /backend_control
195
- """
196
- # Create a snapshot of the current deck configuration
197
215
  snapshot = global_config.deck_snapshot.copy()
198
-
199
216
  # Iterate through each instrument in the snapshot
200
217
  for instrument_key, instrument_data in snapshot.items():
201
218
  # Iterate through each function associated with the current instrument
202
219
  for function_key, function_data in instrument_data.items():
203
220
  # Convert the function signature to a string representation
204
221
  function_data['signature'] = str(function_data['signature'])
222
+ return jsonify(snapshot), 200
205
223
 
206
- json_output = jsonify(snapshot)
207
- return json_output, 200
224
+ # @control.route("/api/control", strict_slashes=False, methods=['GET'])
225
+ # def backend_client():
226
+ # """
227
+ # .. :quickref: Backend Control; get snapshot
228
+ #
229
+ # backend control through http requests
230
+ #
231
+ # .. http:get:: /api/control/summary
232
+ # """
233
+ # # Create a snapshot of the current deck configuration
234
+ # snapshot = global_config.deck_snapshot.copy()
235
+ #
236
+ # # Iterate through each instrument in the snapshot
237
+ # for instrument_key, instrument_data in snapshot.items():
238
+ # # Iterate through each function associated with the current instrument
239
+ # for function_key, function_data in instrument_data.items():
240
+ # # Convert the function signature to a string representation
241
+ # function_data['signature'] = str(function_data['signature'])
242
+ # return jsonify(snapshot), 200
208
243
 
209
244
 
210
- @control.route("/import_api", methods=['POST'])
245
+ @control.route("/control/import/module", methods=['POST'])
211
246
  def import_api():
212
247
  """
213
248
  .. :quickref: Advanced Features; Manually import API module(s)
214
249
 
215
250
  importing other Python modules
216
251
 
217
- .. http:post:: /import_api
252
+ .. http:post:: /control/import/module
218
253
 
219
254
  :form filepath: API (Python class) module filepath
220
255
 
221
- import the module and redirect to :http:get:`/ivoryos/new_controller/`
256
+ import the module and redirect to :http:get:`/ivoryos/control/new/`
222
257
 
223
258
  """
224
259
  filepath = request.form.get('filepath')
@@ -266,12 +301,12 @@ def import_api():
266
301
  # return redirect(url_for('control.deck_controllers'))
267
302
 
268
303
 
269
- @control.route("/import_deck", methods=['POST'])
304
+ @control.route("/control/import/deck", methods=['POST'])
270
305
  def import_deck():
271
306
  """
272
307
  .. :quickref: Advanced Features; Manually import a deck
273
308
 
274
- .. http:post:: /import_deck
309
+ .. http:post:: /control/import_deck
275
310
 
276
311
  :form filepath: deck module filepath
277
312
 
@@ -304,12 +339,12 @@ def import_deck():
304
339
  return redirect(back)
305
340
 
306
341
 
307
- @control.route('/save-order/<instrument>', methods=['POST'])
342
+ @control.route('/control/<instrument>/save-order', methods=['POST'])
308
343
  def save_order(instrument: str):
309
344
  """
310
345
  .. :quickref: Control Customization; Save functions' order
311
346
 
312
- .. http:post:: /save-order
347
+ .. http:post:: /control/save-order
313
348
 
314
349
  save function drag and drop order for the given <instrument>
315
350
 
@@ -320,12 +355,12 @@ def save_order(instrument: str):
320
355
  return '', 204
321
356
 
322
357
 
323
- @control.route('/hide_function/<instrument>/<function>')
358
+ @control.route('/control/<instrument>/<function>/hide')
324
359
  def hide_function(instrument, function):
325
360
  """
326
361
  .. :quickref: Control Customization; Hide function
327
362
 
328
- .. http:get:: /hide_function
363
+ .. http:get:: //control/<instrument>/<function>/hide
329
364
 
330
365
  Hide the given <instrument> and <function>
331
366
 
@@ -341,12 +376,12 @@ def hide_function(instrument, function):
341
376
  return redirect(back)
342
377
 
343
378
 
344
- @control.route('/remove_hidden/<instrument>/<function>')
379
+ @control.route('/control/<instrument>/<function>/unhide')
345
380
  def remove_hidden(instrument: str, function: str):
346
381
  """
347
382
  .. :quickref: Control Customization; Remove a hidden function
348
383
 
349
- .. http:get:: /remove_hidden
384
+ .. http:get:: /control/<instrument>/<function>/unhide
350
385
 
351
386
  Un-hide the given <instrument> and <function>
352
387
 
@@ -6,21 +6,21 @@
6
6
  {% for instrument in defined_variables %}
7
7
  <div class="col-xl-3 col-lg-4 col-md-6 mb-4 ">
8
8
  <div class="bg-white rounded shadow-sm position-relative">
9
- {# {% if not deck %}#}
9
+ {% if deck %}
10
10
  {# <a href="{{ url_for('control.disconnect', instrument=instrument) }}" class="stretched-link controller-card" style="float: right;color: red; position: relative;">Disconnect <i class="bi bi-x-square"></i></a>#}
11
11
  <div class="p-4 controller-card">
12
12
  <h5 class=""><a href="{{ url_for('control.controllers', instrument=instrument) }}" class="text-dark stretched-link">{{instrument.split(".")[1]}}</a></h5>
13
13
  </div>
14
- {# {% else %}#}
15
- {# <div class="p-4 controller-card">#}
16
- {# <h5 class=""><a href="{{ url_for('control.controllers', instrument=instrument) }}" class="text-dark stretched-link">{{instrument}}</a></h5>#}
17
- {# </div>#}
18
- {# {% endif %}#}
14
+ {% else %}
15
+ <div class="p-4 controller-card">
16
+ <h5 class=""><a href="{{ url_for('control.controllers', instrument=instrument) }}" class="text-dark stretched-link">{{instrument}}</a></h5>
17
+ </div>
18
+ {% endif %}
19
19
  </div>
20
20
  </div>
21
21
  {% endfor %}
22
22
  <div class="d-flex mb-3">
23
- <a href="{{ url_for('design.download', filetype='proxy') }}" class="btn btn-outline-primary">
23
+ <a href="{{ url_for('control.download_proxy', filetype='proxy') }}" class="btn btn-outline-primary">
24
24
  <i class="bi bi-download"></i> Download remote control script
25
25
  </a>
26
26
  </div>
@@ -8,21 +8,21 @@ database = Blueprint('database', __name__, template_folder='templates/database')
8
8
 
9
9
 
10
10
 
11
- @database.route("/edit_workflow/<workflow_name>")
11
+ @database.route("/database/scripts/edit/<script_name>")
12
12
  @login_required
13
- def edit_workflow(workflow_name):
13
+ def edit_workflow(script_name:str):
14
14
  """
15
- .. :quickref: Database; load workflow to canvas
15
+ .. :quickref: Database; load workflow script to canvas
16
16
 
17
17
  load the selected workflow to the design canvas
18
18
 
19
- .. http:get:: /edit_workflow/<workflow_name>
19
+ .. http:get:: /database/scripts/edit/<script_name>
20
20
 
21
- :param workflow_name: workflow name
22
- :type workflow_name: str
21
+ :param script_name: script name
22
+ :type script_name: str
23
23
  :status 302: redirect to :http:get:`/ivoryos/experiment/build/`
24
24
  """
25
- row = Script.query.get(workflow_name)
25
+ row = Script.query.get(script_name)
26
26
  script = Script(**row.as_dict())
27
27
  post_script_file(script)
28
28
  pseudo_name = session.get("pseudo_deck", "")
@@ -37,27 +37,27 @@ def edit_workflow(workflow_name):
37
37
  return redirect(url_for('design.experiment_builder'))
38
38
 
39
39
 
40
- @database.route("/delete_workflow/<workflow_name>")
40
+ @database.route("/database/scripts/delete/<script_name>")
41
41
  @login_required
42
- def delete_workflow(workflow_name: str):
42
+ def delete_workflow(script_name: str):
43
43
  """
44
44
  .. :quickref: Database; delete workflow
45
45
 
46
46
  delete workflow from database
47
47
 
48
- .. http:get:: /delete_workflow/<workflow_name>
48
+ .. http:get:: /database/scripts/delete/<script_name>
49
49
 
50
- :param workflow_name: workflow name
51
- :type workflow_name: str
52
- :status 302: redirect to :http:get:`/ivoryos/database/`
50
+ :param script_name: workflow name
51
+ :type script_name: str
52
+ :status 302: redirect to :http:get:`/ivoryos/database/scripts/`
53
53
 
54
54
  """
55
- Script.query.filter(Script.name == workflow_name).delete()
55
+ Script.query.filter(Script.name == script_name).delete()
56
56
  db.session.commit()
57
57
  return redirect(url_for('database.load_from_database'))
58
58
 
59
59
 
60
- @database.route("/publish")
60
+ @database.route("/database/scripts/save")
61
61
  @login_required
62
62
  def publish():
63
63
  """
@@ -65,7 +65,7 @@ def publish():
65
65
 
66
66
  save workflow to database
67
67
 
68
- .. http:get:: /publish
68
+ .. http:get:: /database/scripts/save
69
69
 
70
70
  :status 302: redirect to :http:get:`/ivoryos/experiment/build/`
71
71
  """
@@ -84,7 +84,7 @@ def publish():
84
84
  return redirect(url_for('design.experiment_builder'))
85
85
 
86
86
 
87
- @database.route("/finalize")
87
+ @database.route("/database/scripts/finalize")
88
88
  @login_required
89
89
  def finalize():
90
90
  """
@@ -106,8 +106,8 @@ def finalize():
106
106
  return redirect(url_for('design.experiment_builder'))
107
107
 
108
108
 
109
- @database.route("/database/", strict_slashes=False)
110
- @database.route("/database/<deck_name>")
109
+ @database.route("/database/scripts/", strict_slashes=False)
110
+ @database.route("/database/scripts/<deck_name>")
111
111
  @login_required
112
112
  def load_from_database(deck_name=None):
113
113
  """
@@ -115,7 +115,7 @@ def load_from_database(deck_name=None):
115
115
 
116
116
  backend control through http requests
117
117
 
118
- .. http:get:: /database/<deck_name>
118
+ .. http:get:: /database/scripts/<deck_name>
119
119
 
120
120
  :param deck_name: filter for deck name
121
121
  :type deck_name: str
@@ -135,19 +135,19 @@ def load_from_database(deck_name=None):
135
135
  page = request.args.get('page', default=1, type=int)
136
136
  per_page = 10
137
137
 
138
- workflows = query.paginate(page=page, per_page=per_page, error_out=False)
138
+ scripts = query.paginate(page=page, per_page=per_page, error_out=False)
139
139
  if request.accept_mimetypes.best_match(['application/json', 'text/html']) == 'application/json':
140
- workflows = query.all()
141
- workflow_names = [w.name for w in workflows]
140
+ scripts = query.all()
141
+ script_names = [script.name for script in scripts]
142
142
  return jsonify({
143
- "workflows": workflow_names,
143
+ "workflows": script_names,
144
144
  })
145
145
  else:
146
146
  # return HTML
147
- return render_template("experiment_database.html", workflows=workflows, deck_list=deck_list, deck_name=deck_name)
147
+ return render_template("scripts_database.html", scripts=scripts, deck_list=deck_list, deck_name=deck_name)
148
148
 
149
149
 
150
- @database.route("/edit_run_name", methods=['POST'])
150
+ @database.route("/database/scripts/rename", methods=['POST'])
151
151
  @login_required
152
152
  def edit_run_name():
153
153
  """
@@ -155,7 +155,7 @@ def edit_run_name():
155
155
 
156
156
  edit the name of the current workflow, won't save to the database
157
157
 
158
- .. http:post:: /edit_run_name
158
+ .. http:post:: database/scripts/rename
159
159
 
160
160
  : form run_name: new workflow name
161
161
  :status 302: redirect to :http:get:`/ivoryos/experiment/build/`
@@ -173,15 +173,15 @@ def edit_run_name():
173
173
  return redirect(url_for("design.experiment_builder"))
174
174
 
175
175
 
176
- @database.route("/save_as", methods=['POST'])
176
+ @database.route("/database/scripts/save_as", methods=['POST'])
177
177
  @login_required
178
178
  def save_as():
179
179
  """
180
180
  .. :quickref: Database; save the run name as
181
181
 
182
- save the workflow name as
182
+ save the current workflow script as
183
183
 
184
- .. http:post:: /save_as
184
+ .. http:post:: /database/scripts/save_as
185
185
 
186
186
  : form run_name: new workflow name
187
187
  :status 302: redirect to :http:get:`/ivoryos/experiment/build/`
@@ -203,9 +203,20 @@ def save_as():
203
203
  return redirect(url_for("design.experiment_builder"))
204
204
 
205
205
 
206
- @database.route('/workflow_runs')
206
+ # -----------------------------------------------------------
207
+ # ------------------ Workflow logs -----------------------
208
+ # -----------------------------------------------------------
209
+ @database.route('/database/workflows/')
207
210
  def list_workflows():
208
- query = WorkflowRun.query
211
+ """
212
+ .. :quickref: Database; list all workflow logs
213
+
214
+ list all workflow logs
215
+
216
+ .. http:get:: /database/workflows/
217
+
218
+ """
219
+ query = WorkflowRun.query.order_by(WorkflowRun.id.desc())
209
220
  search_term = request.args.get("keyword", None)
210
221
  if search_term:
211
222
  query = query.filter(WorkflowRun.name.like(f'%{search_term}%'))
@@ -219,11 +230,20 @@ def list_workflows():
219
230
  return jsonify({
220
231
  "workflow_data": workflow_data,
221
232
  })
222
- return render_template('workflow_run_database.html', workflows=workflows)
233
+ else:
234
+ return render_template('workflow_database.html', workflows=workflows)
223
235
 
224
236
 
225
- @database.route("/workflow_steps/<int:workflow_id>")
226
- def get_workflow_steps(workflow_id):
237
+ @database.route("/database/workflows/<int:workflow_id>")
238
+ def get_workflow_steps(workflow_id:int):
239
+ """
240
+ .. :quickref: Database; list all workflow logs
241
+
242
+ list all workflow logs
243
+
244
+ .. http:get:: /database/workflows/<int:workflow_id>
245
+
246
+ """
227
247
  workflow = WorkflowRun.query.get_or_404(workflow_id)
228
248
  steps = WorkflowStep.query.filter_by(workflow_id=workflow_id).order_by(WorkflowStep.start_time).all()
229
249
 
@@ -261,23 +281,23 @@ def get_workflow_steps(workflow_id):
261
281
  "workflow_info": workflow.as_dict(),
262
282
  "steps": grouped_json,
263
283
  })
264
-
265
- return render_template("experiment_step_view.html", workflow=workflow, grouped=grouped)
284
+ else:
285
+ return render_template("workflow_view.html", workflow=workflow, grouped=grouped)
266
286
 
267
287
 
268
- @database.route("/delete_workflow_data/<workflow_id>")
288
+ @database.route("/database/workflows/delete/<int:workflow_id>")
269
289
  @login_required
270
- def delete_workflow_data(workflow_id: str):
290
+ def delete_workflow_data(workflow_id: int):
271
291
  """
272
292
  .. :quickref: Database; delete experiment data from database
273
293
 
274
294
  delete workflow data from database
275
295
 
276
- .. http:get:: /delete_workflow_data/<workflow_id>
296
+ .. http:get:: /database/workflows/delete/<int:workflow_id>
277
297
 
278
298
  :param workflow_id: workflow id
279
- :type workflow_id: str
280
- :status 302: redirect to :http:get:`/ivoryos/workflow_runs/`
299
+ :type workflow_id: int
300
+ :status 302: redirect to :http:get:`/ivoryos/database/workflows/`
281
301
 
282
302
  """
283
303
  run = WorkflowRun.query.get(workflow_id)
@@ -35,19 +35,19 @@
35
35
  </tr>
36
36
  </thead>
37
37
  <tbody>
38
- {% for workflow in workflows %}
38
+ {% for script in scripts %}
39
39
  <tr>
40
- <td><a href="{{ url_for('database.edit_workflow', workflow_name=workflow.name) }}">{{ workflow.name }}</a></td>
41
- <td>{{ workflow.deck }}</td>
42
- <td>{{ workflow.status }}</td>
43
- <td>{{ workflow.time_created }}</td>
44
- <td>{{ workflow.last_modified }}</td>
45
- <td>{{ workflow.author }}</td>
40
+ <td><a href="{{ url_for('database.edit_workflow', script_name=script.name) }}">{{ script.name }}</a></td>
41
+ <td>{{ script.deck }}</td>
42
+ <td>{{ script.status }}</td>
43
+ <td>{{ script.time_created }}</td>
44
+ <td>{{ script.last_modified }}</td>
45
+ <td>{{ script.author }}</td>
46
46
  {# <td>{{ workflow.registered }}</td>#}
47
47
  <td>
48
48
  {#not workflow.status == "finalized" or#}
49
- {% if session['user'] == 'admin' or session['user'] == workflow.author %}
50
- <a href="{{ url_for('database.delete_workflow', workflow_name=workflow.name) }}">delete</a>
49
+ {% if session['user'] == 'admin' or session['user'] == script.author %}
50
+ <a href="{{ url_for('database.delete_workflow', script_name=script.name) }}">delete</a>
51
51
  {% else %}
52
52
  <a class="disabled-link">delete</a>
53
53
  {% endif %}
@@ -57,18 +57,27 @@
57
57
  </tbody>
58
58
  </table>
59
59
 
60
- {# paging#}
60
+ {# paging#}
61
61
  <div class="pagination justify-content-center">
62
- <div class="page-item {{ 'disabled' if not workflows.has_prev else '' }}">
63
- <a class="page-link" href="{{ url_for('database.load_from_database', page=workflows.prev_num) }}">Previous</a>
62
+ <div class="page-item {{ 'disabled' if not scripts.has_prev else '' }}">
63
+ <a class="page-link" href="{{ url_for('database.load_from_database', page=scripts.prev_num) }}">Previous</a>
64
64
  </div>
65
- {% for num in workflows.iter_pages() %}
66
- <div class="page-item">
67
- <a class="page-link {{ 'active' if num == workflows.page else '' }}" href="{{ url_for('database.load_from_database', page=num) }}">{{ num }}</a>
68
- </div>
65
+
66
+ {% for num in scripts.iter_pages() %}
67
+ {% if num %}
68
+ <div class="page-item {{ 'active' if num == scripts.page else '' }}">
69
+ <a class="page-link" href="{{ url_for('database.load_from_database', page=num) }}">{{ num }}</a>
70
+ </div>
71
+ {% else %}
72
+ <div class="page-item disabled">
73
+ <span class="page-link">…</span>
74
+ </div>
75
+ {% endif %}
69
76
  {% endfor %}
70
- <div class="page-item {{ 'disabled' if not workflows.has_next else '' }}">
71
- <a class="page-link" href="{{ url_for('database.load_from_database', page=workflows.next_num) }}">Next</a>
77
+
78
+ <div class="page-item {{ 'disabled' if not scripts.has_next else '' }}">
79
+ <a class="page-link" href="{{ url_for('database.load_from_database', page=scripts.next_num) }}">Next</a>
72
80
  </div>
73
81
  </div>
82
+
74
83
  {% endblock %}
@@ -2,11 +2,24 @@
2
2
 
3
3
  {% block title %}IvoryOS | Design Database{% endblock %}
4
4
  {% block body %}
5
+ <div class="div">
6
+ <form id="search" style="display: inline-block;float: right;" action="{{url_for('database.list_workflows',deck_name=deck_name)}}" method="GET">
7
+ <div class="input-group">
8
+ <div class="form-outline">
9
+ <input type="search" name="keyword" id="keyword" class="form-control" placeholder="Search workflows...">
10
+ </div>
11
+ <button type="submit" class="btn btn-primary">
12
+ <i class="bi bi-search"></i>
13
+ </button>
14
+ </div>
15
+ </form>
16
+ </div>
5
17
 
6
- <table class="table table-hover" id="workflowResultLibrary">
18
+ <table class="table table-hover" id="workflowResultLibrary">
7
19
  <thead>
8
20
  <tr>
9
21
  <th scope="col">Workflow name</th>
22
+ <th scope="col">Workflow ID</th>
10
23
  <th scope="col">Start time</th>
11
24
  <th scope="col">End time</th>
12
25
  <th scope="col">Data</th>
@@ -16,6 +29,7 @@
16
29
  {% for workflow in workflows %}
17
30
  <tr>
18
31
  <td><a href="{{ url_for('database.get_workflow_steps', workflow_id=workflow.id) }}">{{ workflow.name }}</a></td>
32
+ <td>{{ workflow.id }}</td>
19
33
  <td>{{ workflow.start_time.strftime("%Y-%m-%d %H:%M:%S") if workflow.start_time else '' }}</td>
20
34
  <td>{{ workflow.end_time.strftime("%Y-%m-%d %H:%M:%S") if workflow.end_time else '' }}</td>
21
35
 
@@ -36,16 +50,24 @@
36
50
  </tbody>
37
51
  </table>
38
52
 
39
- {# paging#}
53
+ {# paging#}
40
54
  <div class="pagination justify-content-center">
41
55
  <div class="page-item {{ 'disabled' if not workflows.has_prev else '' }}">
42
56
  <a class="page-link" href="{{ url_for('database.list_workflows', page=workflows.prev_num) }}">Previous</a>
43
57
  </div>
58
+
44
59
  {% for num in workflows.iter_pages() %}
45
- <div class="page-item">
46
- <a class="page-link {{ 'active' if num == workflows.page else '' }}" href="{{ url_for('database.list_workflows', page=num) }}">{{ num }}</a>
47
- </div>
60
+ {% if num %}
61
+ <div class="page-item {{ 'active' if num == workflows.page else '' }}">
62
+ <a class="page-link" href="{{ url_for('database.list_workflows', page=num) }}">{{ num }}</a>
63
+ </div>
64
+ {% else %}
65
+ <div class="page-item disabled">
66
+ <span class="page-link">…</span>
67
+ </div>
68
+ {% endif %}
48
69
  {% endfor %}
70
+
49
71
  <div class="page-item {{ 'disabled' if not workflows.has_next else '' }}">
50
72
  <a class="page-link" href="{{ url_for('database.list_workflows', page=workflows.next_num) }}">Next</a>
51
73
  </div>
@@ -3,6 +3,7 @@ import json
3
3
  import os
4
4
  import pickle
5
5
  import sys
6
+ import time
6
7
 
7
8
  from flask import Blueprint, redirect, url_for, flash, jsonify, send_file, request, render_template, session, \
8
9
  current_app, g
@@ -11,7 +12,6 @@ from flask_socketio import SocketIO
11
12
  from werkzeug.utils import secure_filename
12
13
 
13
14
  from ivoryos.utils import utils
14
- from ivoryos.utils.client_proxy import create_function, export_to_python
15
15
  from ivoryos.utils.global_config import GlobalConfig
16
16
  from ivoryos.utils.form import create_builtin_form, create_action_button, format_name, create_form_from_pseudo, \
17
17
  create_form_from_action, create_all_builtin_forms
@@ -74,8 +74,8 @@ def handle_abort_action():
74
74
  socketio.emit('log', {'message': message})
75
75
 
76
76
 
77
- @design.route("/experiment/build/", methods=['GET', 'POST'])
78
- @design.route("/experiment/build/<instrument>/", methods=['GET', 'POST'])
77
+ @design.route("/design/script/", methods=['GET', 'POST'])
78
+ @design.route("/design/script/<instrument>/", methods=['GET', 'POST'])
79
79
  @login_required
80
80
  def experiment_builder(instrument=None):
81
81
  """
@@ -86,7 +86,7 @@ def experiment_builder(instrument=None):
86
86
  This route allows users to build and edit experiment workflows. Users can interact with available instruments,
87
87
  define variables, and manage experiment scripts.
88
88
 
89
- .. http:get:: /experiment/build
89
+ .. http:get:: /design/script
90
90
 
91
91
  Load the experiment builder interface.
92
92
 
@@ -94,7 +94,7 @@ def experiment_builder(instrument=None):
94
94
  :type instrument: str
95
95
  :status 200: Experiment builder loaded successfully.
96
96
 
97
- .. http:post:: /experiment/build
97
+ .. http:post:: /design/script
98
98
 
99
99
  Submit form data to add or modify actions in the experiment script.
100
100
 
@@ -248,17 +248,17 @@ def experiment_builder(instrument=None):
248
248
  use_llm=enable_llm)
249
249
 
250
250
 
251
- @design.route("/generate_code", methods=['POST'])
251
+ @design.route("/design/generate_code", methods=['POST'])
252
252
  @login_required
253
253
  def generate_code():
254
254
  """
255
255
  .. :quickref: Text to Code; Generate code from user input and update the design canvas.
256
256
 
257
- .. http:post:: /generate_code
257
+ .. http:post:: /design/generate_code
258
258
 
259
259
  :form prompt: user's prompt
260
260
  :status 200: and then redirects to :http:get:`/experiment/build`
261
- :status 400: failed to initialize the AI agent redirects to :http:get:`/experiment/build`
261
+ :status 400: failed to initialize the AI agent redirects to :http:get:`/design/script`
262
262
 
263
263
  """
264
264
  agent = global_config.agent
@@ -296,17 +296,17 @@ def generate_code():
296
296
  return redirect(url_for("design.experiment_builder", instrument=instrument, use_llm=True))
297
297
 
298
298
 
299
- @design.route("/experiment", methods=['GET', 'POST'])
299
+ @design.route("/design/campaign", methods=['GET', 'POST'])
300
300
  @login_required
301
301
  def experiment_run():
302
302
  """
303
303
  .. :quickref: Workflow Execution; Execute/iterate the workflow
304
304
 
305
- .. http:get:: /experiment
305
+ .. http:get:: /design/campaign
306
306
 
307
307
  Compile the workflow and load the experiment execution interface.
308
308
 
309
- .. http:post:: /experiment
309
+ .. http:post:: /design/campaign
310
310
 
311
311
  Start workflow execution
312
312
 
@@ -328,7 +328,7 @@ def experiment_run():
328
328
  # todo
329
329
  exec_string = script.python_script if script.python_script else script.compile(current_app.config['SCRIPT_FOLDER'])
330
330
  # exec_string = script.compile(current_app.config['SCRIPT_FOLDER'])
331
-
331
+ # print(exec_string)
332
332
  except Exception as e:
333
333
  flash(e.__str__())
334
334
  # handle api request
@@ -351,6 +351,7 @@ def experiment_run():
351
351
  for key, func_str in exec_string.items():
352
352
  exec(func_str)
353
353
  line_collection = script.convert_to_lines(exec_string)
354
+
354
355
  except Exception:
355
356
  flash(f"Please check {key} syntax!!")
356
357
  return redirect(url_for("design.experiment_builder"))
@@ -408,6 +409,9 @@ def experiment_run():
408
409
  else:
409
410
  flash(e)
410
411
  if request.accept_mimetypes.best_match(['application/json', 'text/html']) == 'application/json':
412
+ # wait to get a workflow ID
413
+ while not global_config.runner_status:
414
+ time.sleep(1)
411
415
  return jsonify({"status": "task started", "task_id": global_config.runner_status.get("id")})
412
416
  else:
413
417
  return render_template('experiment_run.html', script=script.script_dict, filename=filename,
@@ -418,15 +422,15 @@ def experiment_run():
418
422
  history=deck_list, pause_status=runner.pause_status())
419
423
 
420
424
 
421
- @design.route("/toggle_script_type/<stype>")
425
+ @design.route("/design/script/toggle/<stype>")
422
426
  @login_required
423
427
  def toggle_script_type(stype=None):
424
428
  """
425
429
  .. :quickref: Workflow Design; toggle the experimental phase for design canvas.
426
430
 
427
- .. http:get:: /toggle_script_type
431
+ .. http:get:: /design/script/toggle/<stype>
428
432
 
429
- :status 200: and then redirects to :http:get:`/experiment/build`
433
+ :status 200: and then redirects to :http:get:`/design/script`
430
434
 
431
435
  """
432
436
  script = utils.get_script_file()
@@ -446,16 +450,16 @@ def update_list():
446
450
 
447
451
 
448
452
  # --------------------handle all the import/export and download/upload--------------------------
449
- @design.route("/clear")
453
+ @design.route("/design/clear")
450
454
  @login_required
451
455
  def clear():
452
456
  """
453
457
  .. :quickref: Workflow Design; clear the design canvas.
454
458
 
455
- .. http:get:: /clear
459
+ .. http:get:: /design/clear
456
460
 
457
461
  :form prompt: user's prompt
458
- :status 200: clear canvas and then redirects to :http:get:`/experiment/build`
462
+ :status 200: clear canvas and then redirects to :http:get:`/design/script`
459
463
  """
460
464
  deck = global_config.deck
461
465
  pseudo_name = session.get("pseudo_deck", "")
@@ -471,16 +475,16 @@ def clear():
471
475
  return redirect(url_for("design.experiment_builder"))
472
476
 
473
477
 
474
- @design.route("/import_pseudo", methods=['POST'])
478
+ @design.route("/design/import/pseudo", methods=['POST'])
475
479
  @login_required
476
480
  def import_pseudo():
477
481
  """
478
482
  .. :quickref: Workflow Design; Import pseudo deck from deck history
479
483
 
480
- .. http:post:: /import_pseudo
484
+ .. http:post:: /design/import/pseudo
481
485
 
482
486
  :form pkl_name: pseudo deck name
483
- :status 302: load pseudo deck and then redirects to :http:get:`/experiment/build`
487
+ :status 302: load pseudo deck and then redirects to :http:get:`/design/script`
484
488
  """
485
489
  pkl_name = request.form.get('pkl_name')
486
490
  script = utils.get_script_file()
@@ -494,16 +498,16 @@ def import_pseudo():
494
498
  return redirect(url_for("design.experiment_builder"))
495
499
 
496
500
 
497
- @design.route('/uploads', methods=['POST'])
501
+ @design.route('/design/uploads', methods=['POST'])
498
502
  @login_required
499
503
  def upload():
500
504
  """
501
505
  .. :quickref: Workflow Execution; upload a workflow config file (.CSV)
502
506
 
503
- .. http:post:: /uploads
507
+ .. http:post:: /design/uploads
504
508
 
505
509
  :form file: workflow CSV config file
506
- :status 302: save csv file and then redirects to :http:get:`/experiment`
510
+ :status 302: save csv file and then redirects to :http:get:`/design/campaign`
507
511
  """
508
512
  if request.method == "POST":
509
513
  f = request.files['file']
@@ -519,14 +523,20 @@ def upload():
519
523
  return redirect(url_for("design.experiment_run"))
520
524
 
521
525
 
522
- @design.route('/download_results/<filename>')
526
+ @design.route('/design/workflow/download/<filename>')
523
527
  @login_required
524
528
  def download_results(filename):
529
+ """
530
+ .. :quickref: Workflow Design; download a workflow data file
531
+
532
+ .. http:get:: /design/workflow/download/<filename>
533
+
534
+ """
525
535
  filepath = os.path.join(current_app.config["DATA_FOLDER"], filename)
526
536
  return send_file(os.path.abspath(filepath), as_attachment=True)
527
537
 
528
538
 
529
- @design.route('/load_json', methods=['POST'])
539
+ @design.route('/design/load_json', methods=['POST'])
530
540
  @login_required
531
541
  def load_json():
532
542
  """
@@ -535,7 +545,7 @@ def load_json():
535
545
  .. http:post:: /load_json
536
546
 
537
547
  :form file: workflow design JSON file
538
- :status 302: load pseudo deck and then redirects to :http:get:`/experiment/build`
548
+ :status 302: load pseudo deck and then redirects to :http:get:`/design/script`
539
549
  """
540
550
  if request.method == "POST":
541
551
  f = request.files['file']
@@ -549,9 +559,15 @@ def load_json():
549
559
  return redirect(url_for("design.experiment_builder"))
550
560
 
551
561
 
552
- @design.route('/download/<filetype>')
562
+ @design.route('/design/script/download/<filetype>')
553
563
  @login_required
554
564
  def download(filetype):
565
+ """
566
+ .. :quickref: Workflow Design Ext; download a workflow design file
567
+
568
+ .. http:get:: /design/script/download/<filetype>
569
+
570
+ """
555
571
  script = utils.get_script_file()
556
572
  run_name = script.name if script.name else "untitled"
557
573
  if filetype == "configure":
@@ -569,40 +585,28 @@ def download(filetype):
569
585
  outfile.write(json_object)
570
586
  elif filetype == "python":
571
587
  filepath = os.path.join(current_app.config["SCRIPT_FOLDER"], f"{run_name}.py")
572
- elif filetype == "proxy":
573
- snapshot = global_config.deck_snapshot.copy()
574
- class_definitions = {}
575
- # Iterate through each instrument in the snapshot
576
- for instrument_key, instrument_data in snapshot.items():
577
- # Iterate through each function associated with the current instrument
578
- for function_key, function_data in instrument_data.items():
579
- # Convert the function signature to a string representation
580
- function_data['signature'] = str(function_data['signature'])
581
- class_name = instrument_key.split('.')[-1] # Extracting the class name from the path
582
- class_definitions[class_name.capitalize()] = create_function(request.url_root, class_name, instrument_data)
583
- # Export the generated class definitions to a .py script
584
- export_to_python(class_definitions, current_app.config["OUTPUT_FOLDER"])
585
- filepath = os.path.join(current_app.config["OUTPUT_FOLDER"], "generated_proxy.py")
588
+ else:
589
+ return "Unsupported file type", 400
586
590
  return send_file(os.path.abspath(filepath), as_attachment=True)
587
591
 
588
592
 
589
- @design.route("/edit/<uuid>", methods=['GET', 'POST'])
593
+ @design.route("/design/step/edit/<uuid>", methods=['GET', 'POST'])
590
594
  @login_required
591
595
  def edit_action(uuid: str):
592
596
  """
593
597
  .. :quickref: Workflow Design; edit parameters of an action step on canvas
594
598
 
595
- .. http:get:: /edit
599
+ .. http:get:: /design/step/edit/<uuid>
596
600
 
597
601
  Load parameter form of an action step
598
602
 
599
- .. http:post:: /edit
603
+ .. http:post:: /design/step/edit/<uuid>
600
604
 
601
605
  :param uuid: The step's uuid
602
606
  :type uuid: str
603
607
 
604
608
  :form dynamic form: workflow step dynamic inputs
605
- :status 302: save changes and then redirects to :http:get:`/experiment/build`
609
+ :status 302: save changes and then redirects to :http:get:`/design/script`
606
610
  """
607
611
  script = utils.get_script_file()
608
612
  action = script.find_by_uuid(uuid)
@@ -625,18 +629,18 @@ def edit_action(uuid: str):
625
629
  return redirect(url_for('design.experiment_builder'))
626
630
 
627
631
 
628
- @design.route("/delete/<id>")
632
+ @design.route("/design/step/delete/<id>")
629
633
  @login_required
630
634
  def delete_action(id: int):
631
635
  """
632
636
  .. :quickref: Workflow Design; delete an action step on canvas
633
637
 
634
- .. http:get:: /delete
638
+ .. http:get:: /design/step/delete/<id>
635
639
 
636
640
  :param id: The step number id
637
641
  :type id: int
638
642
 
639
- :status 302: save changes and then redirects to :http:get:`/experiment/build`
643
+ :status 302: save changes and then redirects to :http:get:`/design/script`
640
644
  """
641
645
  back = request.referrer
642
646
  script = utils.get_script_file()
@@ -645,18 +649,18 @@ def delete_action(id: int):
645
649
  return redirect(back)
646
650
 
647
651
 
648
- @design.route("/duplicate/<id>")
652
+ @design.route("/design/step/duplicate/<id>")
649
653
  @login_required
650
654
  def duplicate_action(id: int):
651
655
  """
652
656
  .. :quickref: Workflow Design; duplicate an action step on canvas
653
657
 
654
- .. http:get:: /duplicate
658
+ .. http:get:: /design/step/duplicate/<id>
655
659
 
656
660
  :param id: The step number id
657
661
  :type id: int
658
662
 
659
- :status 302: save changes and then redirects to :http:get:`/experiment/build`
663
+ :status 302: save changes and then redirects to :http:get:`/design/script`
660
664
  """
661
665
  back = request.referrer
662
666
  script = utils.get_script_file()
@@ -667,8 +671,15 @@ def duplicate_action(id: int):
667
671
 
668
672
  # ---- HTTP API Endpoints ----
669
673
 
670
- @design.route("/api/status", methods=["GET"])
674
+ @design.route("/api/runner/status", methods=["GET"])
671
675
  def runner_status():
676
+ """
677
+ .. :quickref: Workflow Design; get the execution status
678
+
679
+ .. http:get:: /api/runner/status
680
+
681
+ :status 200: status
682
+ """
672
683
  runner_busy = global_config.runner_lock.locked()
673
684
  status = {"busy": runner_busy}
674
685
  task_status = global_config.runner_status
@@ -692,39 +703,69 @@ def runner_status():
692
703
 
693
704
 
694
705
 
695
- @design.route("/api/abort_pending", methods=["POST"])
706
+ @design.route("/api/runner/abort_pending", methods=["POST"])
696
707
  def api_abort_pending():
708
+ """
709
+ .. :quickref: Workflow Design; abort pending action(s) during execution
710
+
711
+ .. http:get:: /api/runner/abort_pending
712
+
713
+ :status 200: {"status": "ok"}
714
+ """
697
715
  abort_pending()
698
716
  return jsonify({"status": "ok"}), 200
699
717
 
700
- @design.route("/api/abort_current", methods=["POST"])
718
+ @design.route("/api/runner/abort_current", methods=["POST"])
701
719
  def api_abort_current():
720
+ """
721
+ .. :quickref: Workflow Design; abort right after current action during execution
722
+
723
+ .. http:get:: /api/runner/abort_current
724
+
725
+ :status 200: {"status": "ok"}
726
+ """
702
727
  abort_current()
703
728
  return jsonify({"status": "ok"}), 200
704
729
 
705
- @design.route("/api/pause", methods=["POST"])
730
+ @design.route("/api/runner/pause", methods=["POST"])
706
731
  def api_pause():
732
+ """
733
+ .. :quickref: Workflow Design; pause during execution
734
+
735
+ .. http:get:: /api/runner/pause
736
+
737
+ :status 200: {"status": "ok"}
738
+ """
707
739
  msg = pause()
708
740
  return jsonify({"status": "ok", "pause_status": msg}), 200
709
741
 
710
- @design.route("/api/retry", methods=["POST"])
742
+ @design.route("/api/runner/retry", methods=["POST"])
711
743
  def api_retry():
744
+ """
745
+ .. :quickref: Workflow Design; retry when error occur during execution
746
+
747
+ .. http:get:: /api/runner/retry
748
+
749
+ :status 200: {"status": "ok"}
750
+ """
712
751
  retry()
713
752
  return jsonify({"status": "ok, retrying failed step"}), 200
714
753
 
715
754
 
716
- @design.route("/api/get_script", methods=["GET", "POST"])
717
- def get_script():
718
- script = utils.get_script_file()
719
- script.sort_actions()
720
- script_collection = script.compile()
721
- if request.method == "POST":
722
- # create a brand-new script
723
- deck = global_config.deck
724
- deck_name = os.path.splitext(os.path.basename(deck.__file__))[0] if deck.__name__ == "__main__" else deck.__name__
725
- script = Script(author=session.get('user'), deck=deck_name)
726
- script_collection = request.get_json()
727
- script.python_script = script_collection
728
- utils.post_script_file(script)
729
- return jsonify({"status": "ok"}), 200
730
- return jsonify(script_collection), 200
755
+ @design.route("/api/design/submit", methods=["POST"])
756
+ def submit_script():
757
+ """
758
+ .. :quickref: Workflow Design; submit script
759
+
760
+ .. http:get:: /api/design/submit
761
+
762
+ :status 200: {"status": "ok"}
763
+ """
764
+ deck = global_config.deck
765
+ deck_name = os.path.splitext(os.path.basename(deck.__file__))[0] if deck.__name__ == "__main__" else deck.__name__
766
+ script = Script(author=session.get('user'), deck=deck_name)
767
+ script_collection = request.get_json()
768
+ script.python_script = script_collection
769
+ # todo check script format
770
+ utils.post_script_file(script)
771
+ return jsonify({"status": "ok"}), 200
@@ -8,7 +8,7 @@ import os
8
8
 
9
9
  # Function to create class and methods dynamically
10
10
  def create_function(url, class_name, functions):
11
- class_template = f'class {class_name.capitalize()}:\n url = "{url}ivoryos/backend_control/deck.{class_name}"\n'
11
+ class_template = f'class {class_name.capitalize()}:\n url = "{url}ivoryos/api/control/deck.{class_name}"\n'
12
12
 
13
13
  for function_name, details in functions.items():
14
14
  signature = details['signature']
@@ -69,7 +69,7 @@ class ScriptRunner:
69
69
 
70
70
  if self.current_app is None:
71
71
  self.current_app = current_app
72
- time.sleep(1) # Optional: may help ensure deck readiness
72
+ # time.sleep(1) # Optional: may help ensure deck readiness
73
73
 
74
74
  # Try to acquire lock without blocking
75
75
  if not self.lock.acquire(blocking=False):
@@ -240,6 +240,7 @@ class ScriptRunner:
240
240
  for i in config:
241
241
  try:
242
242
  i = utils.convert_config_type(i, arg_type)
243
+ compiled = True
243
244
  except Exception as e:
244
245
  logger.info(e)
245
246
  compiled = False
ivoryos/version.py CHANGED
@@ -1 +1 @@
1
- __version__ = "1.0.1"
1
+ __version__ = "1.0.3"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ivoryos
3
- Version: 1.0.1
3
+ Version: 1.0.3
4
4
  Summary: an open-source Python package enabling Self-Driving Labs (SDLs) interoperability
5
5
  Home-page: https://gitlab.com/heingroup/ivoryos
6
6
  Author: Ivory Zhang
@@ -1,24 +1,24 @@
1
1
  ivoryos/__init__.py,sha256=1v6LwuIao8WbmSskifzdfde7E_gH3PWCYaNpLaRtYZk,7341
2
2
  ivoryos/config.py,sha256=3FPBYTIBhQTKDvsEoR8ZeTmg65D-CSFEdGmOuIL4pSI,1311
3
- ivoryos/version.py,sha256=d4QHYmS_30j0hPN8NmNPnQ_Z0TphDRbu4MtQj9cT9e8,22
3
+ ivoryos/version.py,sha256=2plzdEEb24FLjE2I2XyBBcJEPYWHccNL4SgtLC_6erg,22
4
4
  ivoryos/routes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  ivoryos/routes/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
- ivoryos/routes/auth/auth.py,sha256=7CdXjGAr1B_xsmwweakTWOoROgsOJf0MNTzlMP_5Nus,3240
6
+ ivoryos/routes/auth/auth.py,sha256=rvqMf4oeYXIjcsNWPbHoxK4QJRbM4YSMCl3IDRMnYtM,3255
7
7
  ivoryos/routes/auth/templates/auth/login.html,sha256=WSRrKbdM_oobqSXFRTo-j9UlOgp6sYzS9tm7TqqPULI,1207
8
8
  ivoryos/routes/auth/templates/auth/signup.html,sha256=b5LTXtpfTSkSS7X8u1ldwQbbgEFTk6UNMAediA5BwBY,1465
9
9
  ivoryos/routes/control/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
- ivoryos/routes/control/control.py,sha256=AbJA1dwWAWZi3eGnsbNObPASVsN7pZbL6la25xB52hI,14209
10
+ ivoryos/routes/control/control.py,sha256=AaXqXqaIIed9xBVQsk52CNnNDTSofje5qQAfDGWd1Q0,16404
11
11
  ivoryos/routes/control/templates/control/controllers.html,sha256=iIp0h6WA68gQj9OsoiB7dU1BqH8CGomTueR73F4C8eY,4274
12
- ivoryos/routes/control/templates/control/controllers_home.html,sha256=VQ77HRvBlyBrQ3al5fcKF5Y6_vKtU8WeAhilqQQltAo,2997
12
+ ivoryos/routes/control/templates/control/controllers_home.html,sha256=qAM4iZBEuXvSgGUWWVVIe2E9MPJOeG7U214hYM84jIE,2976
13
13
  ivoryos/routes/control/templates/control/controllers_new.html,sha256=uOQo9kYmwX2jk3KZDkMUF_ylfNUIs_oIWb_kk_MMVDM,4921
14
14
  ivoryos/routes/database/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
- ivoryos/routes/database/database.py,sha256=97387fF6tQIfb-BF6krILRP4a4_8jOJmGcE7BoPgjmk,9127
16
- ivoryos/routes/database/templates/database/experiment_database.html,sha256=edlCcKfrS91gGG1dPFQjC9xD7F7nWNNqS3S6Oa7apzs,3460
17
- ivoryos/routes/database/templates/database/experiment_step_view.html,sha256=u8_XYhiZ98PzglMzFEkuM1Tk9hVWf79xXIrpHVDxKa0,3618
15
+ ivoryos/routes/database/database.py,sha256=kP5dEb5TvX3GlePk2sxlpwer8AsTM36kdeCkzAiFkR8,9837
16
+ ivoryos/routes/database/templates/database/scripts_database.html,sha256=tEpKOj1UGz7mcDq11guwP48XJxwawjxjPvxRhKiIG2I,3640
18
17
  ivoryos/routes/database/templates/database/step_card.html,sha256=F4JRfacrEQfk2rrEbcI_i7G84nzKKDmCrMSmStLb4W4,290
19
- ivoryos/routes/database/templates/database/workflow_run_database.html,sha256=MczK9my9u0SyQsMFLbc6CXeZqKaBo5vk1SpwjkcZdqk,3571
18
+ ivoryos/routes/database/templates/database/workflow_database.html,sha256=fsJHrYeEHGBKRn1pIxWITE6e93tdZsXH3zRs9Ob5FX0,4467
19
+ ivoryos/routes/database/templates/database/workflow_view.html,sha256=u8_XYhiZ98PzglMzFEkuM1Tk9hVWf79xXIrpHVDxKa0,3618
20
20
  ivoryos/routes/design/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
- ivoryos/routes/design/design.py,sha256=ameAcgTLlrfOENUa48xxp64psuh2aZ0JuVfIj7zj-LM,28968
21
+ ivoryos/routes/design/design.py,sha256=Ju9Hz0khhAk6HmnZd3Er3PglFkvZBNPAJ-fPJMfrrIM,29328
22
22
  ivoryos/routes/design/templates/design/experiment_builder.html,sha256=rEdcHj5onJG_4MejdFBPnJVzsvCMp1KDteqNkpx24kQ,29430
23
23
  ivoryos/routes/design/templates/design/experiment_run.html,sha256=7VP0Vo98phcYnFennd5vqaMK1M1QBwDmM-b9aZb8jOw,26282
24
24
  ivoryos/routes/main/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -37,16 +37,16 @@ ivoryos/static/js/sortable_design.js,sha256=wwpKfIzZGDxfX3moNz0cvPvm9YyHmopZK3wm
37
37
  ivoryos/templates/base.html,sha256=sDdwqOIUP2Get-py4E59PkieoGWLFpX6wAJe93s4aRo,8518
38
38
  ivoryos/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
39
39
  ivoryos/utils/bo_campaign.py,sha256=CVs7q15Pm2SRuJNaCvZKIxOFuv1xibM2yymtpAMAWOk,3285
40
- ivoryos/utils/client_proxy.py,sha256=AzcSQGMqeCqVULP1a7vEKNe135NZYryVX63ke0wgK04,2099
40
+ ivoryos/utils/client_proxy.py,sha256=0OT2xTMkqh_2ybgCxMV_71ZVUThWwrsnAhTIBY5vDR8,2095
41
41
  ivoryos/utils/db_models.py,sha256=zlmmD2600CYyn79gQq8k0Vra7BDBKJBAyNLYclIWdvs,27382
42
42
  ivoryos/utils/form.py,sha256=b3JKxRc1jN45-bXyfzSJT1lcssUuxT86FhRmNUDv5-U,20973
43
43
  ivoryos/utils/global_config.py,sha256=OqfDrPgOzRdIUMD4V3pA9t6b-BATMjGZl8Jn7nkI56k,2138
44
44
  ivoryos/utils/llm_agent.py,sha256=-lVCkjPlpLues9sNTmaT7bT4sdhWvV2DiojNwzB2Lcw,6422
45
- ivoryos/utils/script_runner.py,sha256=k7bH9AeFGhx2tns-81q0JZFLCM9cvXCpZF7bH80RVzo,14504
45
+ ivoryos/utils/script_runner.py,sha256=0b5hLKAF2o0SQKiArhUsG8-4MA-eniAcjwi8gCNVwtY,14542
46
46
  ivoryos/utils/task_runner.py,sha256=u4nF0wOADu_HVlGYVTOXnUm1woWGgYAccr-ZCzgtb6Q,2899
47
47
  ivoryos/utils/utils.py,sha256=OBwrRu02yh7pqG_lyl10zWr_RYes3xhMporxIz8lGYI,13579
48
- ivoryos-1.0.1.dist-info/LICENSE,sha256=p2c8S8i-8YqMpZCJnadLz1-ofxnRMILzz6NCMIypRag,1084
49
- ivoryos-1.0.1.dist-info/METADATA,sha256=KEh9s448whM6VP-Knc5UUjl7DpoME989pKBfwLxFcHM,6992
50
- ivoryos-1.0.1.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
51
- ivoryos-1.0.1.dist-info/top_level.txt,sha256=FRIWWdiEvRKqw-XfF_UK3XV0CrnNb6EmVbEgjaVazRM,8
52
- ivoryos-1.0.1.dist-info/RECORD,,
48
+ ivoryos-1.0.3.dist-info/LICENSE,sha256=p2c8S8i-8YqMpZCJnadLz1-ofxnRMILzz6NCMIypRag,1084
49
+ ivoryos-1.0.3.dist-info/METADATA,sha256=XE0dH-qpGIUYCXwkzDULsuTLOJPlGj0AnUwhtjeiQPY,6992
50
+ ivoryos-1.0.3.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
51
+ ivoryos-1.0.3.dist-info/top_level.txt,sha256=FRIWWdiEvRKqw-XfF_UK3XV0CrnNb6EmVbEgjaVazRM,8
52
+ ivoryos-1.0.3.dist-info/RECORD,,