ivoryos 1.3.4__tar.gz → 1.3.5a0__tar.gz

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.

Files changed (109) hide show
  1. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/PKG-INFO +1 -1
  2. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/routes/control/control.py +2 -2
  3. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/routes/design/design.py +4 -1
  4. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/utils/db_models.py +23 -11
  5. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/utils/py_to_json.py +19 -4
  6. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/utils/script_runner.py +21 -2
  7. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/utils/task_runner.py +28 -17
  8. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/utils/utils.py +2 -1
  9. ivoryos-1.3.5a0/ivoryos/version.py +1 -0
  10. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos.egg-info/PKG-INFO +1 -1
  11. ivoryos-1.3.4/ivoryos/version.py +0 -1
  12. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/LICENSE +0 -0
  13. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/README.md +0 -0
  14. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/__init__.py +0 -0
  15. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/app.py +0 -0
  16. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/config.py +0 -0
  17. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/optimizer/ax_optimizer.py +0 -0
  18. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/optimizer/base_optimizer.py +0 -0
  19. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/optimizer/baybe_optimizer.py +0 -0
  20. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/optimizer/registry.py +0 -0
  21. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/routes/__init__.py +0 -0
  22. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/routes/api/api.py +0 -0
  23. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/routes/auth/__init__.py +0 -0
  24. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/routes/auth/auth.py +0 -0
  25. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/routes/auth/templates/login.html +0 -0
  26. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/routes/auth/templates/signup.html +0 -0
  27. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/routes/control/__init__.py +0 -0
  28. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/routes/control/control_file.py +0 -0
  29. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/routes/control/control_new_device.py +0 -0
  30. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/routes/control/templates/controllers.html +0 -0
  31. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/routes/control/templates/controllers_new.html +0 -0
  32. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/routes/control/utils.py +0 -0
  33. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/routes/data/__init__.py +0 -0
  34. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/routes/data/data.py +0 -0
  35. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/routes/data/templates/components/step_card.html +0 -0
  36. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/routes/data/templates/workflow_database.html +0 -0
  37. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/routes/data/templates/workflow_view.html +0 -0
  38. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/routes/design/__init__.py +0 -0
  39. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/routes/design/design_file.py +0 -0
  40. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/routes/design/design_step.py +0 -0
  41. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/routes/design/templates/components/action_form.html +0 -0
  42. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/routes/design/templates/components/actions_panel.html +0 -0
  43. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/routes/design/templates/components/autofill_toggle.html +0 -0
  44. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/routes/design/templates/components/canvas.html +0 -0
  45. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/routes/design/templates/components/canvas_footer.html +0 -0
  46. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/routes/design/templates/components/canvas_header.html +0 -0
  47. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/routes/design/templates/components/canvas_main.html +0 -0
  48. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/routes/design/templates/components/deck_selector.html +0 -0
  49. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/routes/design/templates/components/edit_action_form.html +0 -0
  50. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/routes/design/templates/components/instruments_panel.html +0 -0
  51. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/routes/design/templates/components/modals/drop_modal.html +0 -0
  52. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/routes/design/templates/components/modals/json_modal.html +0 -0
  53. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/routes/design/templates/components/modals/new_script_modal.html +0 -0
  54. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/routes/design/templates/components/modals/rename_modal.html +0 -0
  55. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/routes/design/templates/components/modals/saveas_modal.html +0 -0
  56. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/routes/design/templates/components/modals.html +0 -0
  57. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/routes/design/templates/components/python_code_overlay.html +0 -0
  58. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/routes/design/templates/components/sidebar.html +0 -0
  59. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/routes/design/templates/components/text_to_code_panel.html +0 -0
  60. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/routes/design/templates/experiment_builder.html +0 -0
  61. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/routes/execute/__init__.py +0 -0
  62. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/routes/execute/execute.py +0 -0
  63. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/routes/execute/execute_file.py +0 -0
  64. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/routes/execute/templates/components/error_modal.html +0 -0
  65. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/routes/execute/templates/components/logging_panel.html +0 -0
  66. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/routes/execute/templates/components/progress_panel.html +0 -0
  67. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/routes/execute/templates/components/run_panel.html +0 -0
  68. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/routes/execute/templates/components/run_tabs.html +0 -0
  69. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/routes/execute/templates/components/tab_bayesian.html +0 -0
  70. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/routes/execute/templates/components/tab_configuration.html +0 -0
  71. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/routes/execute/templates/components/tab_repeat.html +0 -0
  72. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/routes/execute/templates/experiment_run.html +0 -0
  73. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/routes/library/__init__.py +0 -0
  74. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/routes/library/library.py +0 -0
  75. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/routes/library/templates/library.html +0 -0
  76. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/routes/main/__init__.py +0 -0
  77. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/routes/main/main.py +0 -0
  78. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/routes/main/templates/help.html +0 -0
  79. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/routes/main/templates/home.html +0 -0
  80. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/server.py +0 -0
  81. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/socket_handlers.py +0 -0
  82. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/static/favicon.ico +0 -0
  83. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/static/gui_annotation/Slide1.png +0 -0
  84. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/static/gui_annotation/Slide2.PNG +0 -0
  85. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/static/js/action_handlers.js +0 -0
  86. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/static/js/db_delete.js +0 -0
  87. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/static/js/overlay.js +0 -0
  88. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/static/js/script_metadata.js +0 -0
  89. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/static/js/socket_handler.js +0 -0
  90. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/static/js/sortable_card.js +0 -0
  91. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/static/js/sortable_design.js +0 -0
  92. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/static/js/ui_state.js +0 -0
  93. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/static/logo.webp +0 -0
  94. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/static/style.css +0 -0
  95. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/templates/base.html +0 -0
  96. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/utils/__init__.py +0 -0
  97. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/utils/bo_campaign.py +0 -0
  98. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/utils/client_proxy.py +0 -0
  99. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/utils/decorators.py +0 -0
  100. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/utils/form.py +0 -0
  101. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/utils/global_config.py +0 -0
  102. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/utils/llm_agent.py +0 -0
  103. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos/utils/serilize.py +0 -0
  104. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos.egg-info/SOURCES.txt +0 -0
  105. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos.egg-info/dependency_links.txt +0 -0
  106. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos.egg-info/requires.txt +0 -0
  107. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/ivoryos.egg-info/top_level.txt +0 -0
  108. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/pyproject.toml +0 -0
  109. {ivoryos-1.3.4 → ivoryos-1.3.5a0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ivoryos
3
- Version: 1.3.4
3
+ Version: 1.3.5a0
4
4
  Summary: an open-source Python package enabling Self-Driving Labs (SDLs) interoperability
5
5
  Author-email: Ivory Zhang <ivoryzhang@chem.ubc.ca>
6
6
  License: MIT
@@ -23,7 +23,7 @@ control.register_blueprint(control_temp)
23
23
  @control.route("/", strict_slashes=False, methods=["GET", "POST"])
24
24
  @control.route("/<string:instrument>", strict_slashes=False, methods=["GET", "POST"])
25
25
  @login_required
26
- def deck_controllers(instrument: str = None):
26
+ async def deck_controllers(instrument: str = None):
27
27
  """
28
28
  .. :quickref: Direct Control; device (instruments) and methods
29
29
 
@@ -82,7 +82,7 @@ def deck_controllers(instrument: str = None):
82
82
 
83
83
  wait = str(payload.get("hidden_wait", "true")).lower() == "true"
84
84
 
85
- output = runner.run_single_step(
85
+ output = await runner.run_single_step(
86
86
  component=instrument, method=method_name, kwargs=kwargs, wait=wait,
87
87
  current_app=current_app._get_current_object()
88
88
  )
@@ -316,6 +316,7 @@ def methods_handler(instrument: str = ''):
316
316
  msg = ""
317
317
  request.form
318
318
  if "hidden_name" in request.form:
319
+ deck_snapshot = global_config.deck_snapshot
319
320
  method_name = request.form.get("hidden_name", None)
320
321
  form = forms.get(method_name) if forms else None
321
322
  insert_position = request.form.get("drop_target_id", None)
@@ -334,7 +335,9 @@ def methods_handler(instrument: str = ''):
334
335
  action = {"instrument": instrument, "action": function_name,
335
336
  "args": kwargs,
336
337
  "return": save_data,
337
- 'arg_types': primitive_arg_types}
338
+ 'arg_types': primitive_arg_types,
339
+ "coroutine": deck_snapshot[instrument][function_name].get("coroutine", False) if deck_snapshot else False,
340
+ }
338
341
  script.add_action(action=action, insert_position=insert_position)
339
342
  else:
340
343
  msg = [f"{field}: {', '.join(messages)}" for field, messages in form.errors.items()]
@@ -434,14 +434,21 @@ class Script(db.Model):
434
434
  :return: A dict containing script types as keys and lists of function body lines as values.
435
435
  """
436
436
  line_collection = {}
437
+
437
438
  for stype, func_str in exec_str_collection.items():
438
439
  if func_str:
439
440
  module = ast.parse(func_str)
440
- func_def = next(node for node in module.body if isinstance(node, ast.FunctionDef))
441
441
 
442
- # Extract function body as source lines
443
- line_collection[stype] = [ast_unparse(node) for node in func_def.body if not isinstance(node, ast.Return)]
444
- # print(line_collection[stype])
442
+ # Find the first function (regular or async)
443
+ func_def = next(
444
+ node for node in module.body
445
+ if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))
446
+ )
447
+
448
+ # Extract function body as source lines, skipping 'return' nodes
449
+ line_collection[stype] = [
450
+ ast_unparse(node) for node in func_def.body if not isinstance(node, ast.Return)
451
+ ]
445
452
  return line_collection
446
453
 
447
454
  def compile(self, script_path=None):
@@ -459,7 +466,8 @@ class Script(db.Model):
459
466
 
460
467
  for i in self.stypes:
461
468
  if self.script_dict[i]:
462
- func_str = self._generate_function_header(run_name, i) + self._generate_function_body(i)
469
+ is_async = any(a.get("coroutine", False) for a in self.script_dict[i])
470
+ func_str = self._generate_function_header(run_name, i, is_async) + self._generate_function_body(i)
463
471
  exec_str_collection[i] = func_str
464
472
  if script_path:
465
473
  self._write_to_file(script_path, run_name, exec_str_collection)
@@ -477,7 +485,7 @@ class Script(db.Model):
477
485
  name += '_'
478
486
  return name
479
487
 
480
- def _generate_function_header(self, run_name, stype):
488
+ def _generate_function_header(self, run_name, stype, is_async):
481
489
  """
482
490
  Generate the function header.
483
491
  """
@@ -487,7 +495,8 @@ class Script(db.Model):
487
495
  config_type.items()]
488
496
 
489
497
  script_type = f"_{stype}" if stype != "script" else ""
490
- function_header = f"def {run_name}{script_type}("
498
+ async_str = "async " if is_async else ""
499
+ function_header = f"{async_str}def {run_name}{script_type}("
491
500
 
492
501
  if stype == "script":
493
502
  function_header += ", ".join(configure)
@@ -540,7 +549,8 @@ class Script(db.Model):
540
549
  # elif instrument == 'registered_workflows':
541
550
  # return inspect.getsource(my_function)
542
551
  else:
543
- return self._process_instrument_action(indent_unit, instrument, action_name, args, save_data)
552
+ is_async = action.get("coroutine", False)
553
+ return self._process_instrument_action(indent_unit, instrument, action_name, args, save_data, is_async)
544
554
 
545
555
  def _process_args(self, args):
546
556
  """
@@ -600,10 +610,12 @@ class Script(db.Model):
600
610
  indent_unit -= 1
601
611
  return exec_string, indent_unit
602
612
 
603
- def _process_instrument_action(self, indent_unit, instrument, action, args, save_data):
613
+ def _process_instrument_action(self, indent_unit, instrument, action, args, save_data, is_async=False):
604
614
  """
605
615
  Process actions related to instruments.
606
616
  """
617
+ async_str = "await " if is_async else ""
618
+
607
619
  function_call = f"{instrument}.{action}"
608
620
  if instrument.startswith("blocks"):
609
621
  self.blocks_included = True
@@ -611,11 +623,11 @@ class Script(db.Model):
611
623
 
612
624
  if isinstance(args, dict) and args != {}:
613
625
  args_str = self._process_dict_args(args)
614
- single_line = f"{function_call}(**{args_str})"
626
+ single_line = f"{async_str}{function_call}(**{args_str})"
615
627
  elif isinstance(args, str):
616
628
  single_line = f"{function_call} = {args}"
617
629
  else:
618
- single_line = f"{function_call}()"
630
+ single_line = f"{async_str}{function_call}()"
619
631
 
620
632
  if save_data:
621
633
  save_data += " = "
@@ -55,6 +55,10 @@ def convert_to_cards(source_code: str):
55
55
  )
56
56
 
57
57
  class CardVisitor(ast.NodeVisitor):
58
+ def __init__(self):
59
+ self.defined_types = {} # <-- always exists
60
+
61
+
58
62
  def visit_FunctionDef(self, node):
59
63
  self.defined_types = {
60
64
  arg.arg: ast.unparse(arg.annotation) if arg.annotation else "float"
@@ -142,14 +146,20 @@ def convert_to_cards(source_code: str):
142
146
  "return": "",
143
147
  "uuid": generate_uuid()
144
148
  })
149
+ elif isinstance(node.value, ast.Await):
150
+ self.handle_call(node.value.value, ret_var=node.targets[0].id, awaited=True)
151
+
145
152
  elif isinstance(node.value, ast.Call):
146
153
  self.handle_call(node.value, ret_var=node.targets[0].id)
147
154
 
148
155
  def visit_Expr(self, node):
149
- if isinstance(node.value, ast.Call):
156
+ if isinstance(node.value, ast.Await):
157
+ # node.value is ast.Await
158
+ self.handle_call(node.value.value, awaited=True)
159
+ elif isinstance(node.value, ast.Call):
150
160
  self.handle_call(node.value)
151
161
 
152
- def handle_call(self, node, ret_var=""):
162
+ def handle_call(self, node, ret_var="", awaited=False):
153
163
  func_parts = []
154
164
  f = node.func
155
165
  while isinstance(f, ast.Attribute):
@@ -229,7 +239,7 @@ def convert_to_cards(source_code: str):
229
239
  else infer_type(value)
230
240
  )
231
241
 
232
- add_card({
242
+ card = {
233
243
  "action": action,
234
244
  "arg_types": arg_types,
235
245
  "args": args,
@@ -237,7 +247,12 @@ def convert_to_cards(source_code: str):
237
247
  "instrument": instrument,
238
248
  "return": ret_var,
239
249
  "uuid": generate_uuid()
240
- })
250
+ }
251
+
252
+ if awaited:
253
+ card["coroutine"] = True # mark as coroutine if awaited
254
+
255
+ add_card(card)
241
256
 
242
257
  CardVisitor().visit(tree)
243
258
  return cards
@@ -1,4 +1,5 @@
1
1
  import ast
2
+ import asyncio
2
3
  import os
3
4
  import csv
4
5
  import threading
@@ -182,7 +183,25 @@ class ScriptRunner:
182
183
  duration = float(duration_str)
183
184
  self.safe_sleep(duration)
184
185
  else:
185
- exec(line, exec_globals, exec_locals)
186
+ if "await " in line:
187
+ async_code = f"async def __async_exec_wrapper():\n"
188
+ # indent all code lines by 4 spaces
189
+ async_code += "\n".join(" " + line for line in line.splitlines())
190
+ async_code += f"\n return locals()"
191
+ exec(async_code, exec_globals, exec_locals)
192
+ func = exec_locals.get("__async_exec_wrapper") or exec_globals.get("__async_exec_wrapper")
193
+ # Capture the return value from asyncio.run
194
+ result_locals = asyncio.run(func())
195
+
196
+ # Update exec_locals with the returned locals
197
+ exec_locals.update(result_locals)
198
+ exec_locals.pop("__async_exec_wrapper", None)
199
+
200
+ else:
201
+ print("just exec synchronously")
202
+ exec(line, exec_globals, exec_locals)
203
+ # return locals_dict
204
+ # exec(line, exec_globals, exec_locals)
186
205
  # step.run_error = False
187
206
 
188
207
  except HumanInterventionRequired as e:
@@ -249,7 +268,7 @@ class ScriptRunner:
249
268
  self._run_actions(script, section_name="cleanup", logger=logger, socketio=socketio,run_id=run_id)
250
269
  # Reset the running flag when done
251
270
  # Save results if necessary
252
- if not script.python_script and output_list:
271
+ if not script.python_script and return_list:
253
272
  filename = self._save_results(run_name, arg_type, return_list, output_list, logger, output_path)
254
273
  self._emit_progress(socketio, 100)
255
274
 
@@ -1,3 +1,5 @@
1
+ import inspect
2
+ import asyncio
1
3
  import threading
2
4
  import time
3
5
  from datetime import datetime
@@ -19,8 +21,7 @@ class TaskRunner:
19
21
  self.globals_dict = globals_dict
20
22
  self.lock = global_config.runner_lock
21
23
 
22
-
23
- def run_single_step(self, component, method, kwargs, wait=True, current_app=None):
24
+ async def run_single_step(self, component, method, kwargs, wait=True, current_app=None):
24
25
  global deck
25
26
  if deck is None:
26
27
  deck = global_config.deck
@@ -32,16 +33,15 @@ class TaskRunner:
32
33
  current_status["output"] = "busy"
33
34
  return current_status
34
35
 
35
-
36
36
  if wait:
37
- output = self._run_single_step(component, method, kwargs, current_app)
37
+ output = await self._run_single_step(component, method, kwargs, current_app)
38
38
  else:
39
- print("running with thread")
40
- thread = threading.Thread(
41
- target=self._run_single_step, args=(component, method, kwargs, current_app)
42
- )
43
- thread.start()
44
- time.sleep(0.1)
39
+ # Create background task properly
40
+ async def background_runner():
41
+ await self._run_single_step(component, method, kwargs, current_app)
42
+
43
+ asyncio.create_task(background_runner())
44
+ await asyncio.sleep(0.1) # Change time.sleep to await asyncio.sleep
45
45
  output = {"status": "task started", "task_id": global_config.runner_status.get("id")}
46
46
 
47
47
  return output
@@ -60,22 +60,32 @@ class TaskRunner:
60
60
  function_executable = getattr(instrument, method)
61
61
  return function_executable
62
62
 
63
- def _run_single_step(self, component, method, kwargs, current_app=None):
63
+ async def _run_single_step(self, component, method, kwargs, current_app=None):
64
64
  try:
65
65
  function_executable = self._get_executable(component, deck, method)
66
66
  method_name = f"{component}.{method}"
67
67
  except Exception as e:
68
68
  self.lock.release()
69
- return {"status": "error", "msg": e.__str__()}
69
+ return {"status": "error", "msg": str(e)}
70
70
 
71
- # with self.lock:
71
+ # Flask context is NOT async → just use normal "with"
72
72
  with current_app.app_context():
73
- step = SingleStep(method_name=method_name, kwargs=kwargs, run_error=None, start_time=datetime.now())
73
+ step = SingleStep(
74
+ method_name=method_name,
75
+ kwargs=kwargs,
76
+ run_error=None,
77
+ start_time=datetime.now()
78
+ )
74
79
  db.session.add(step)
75
80
  db.session.flush()
76
- global_config.runner_status = {"id":step.id, "type": "task"}
81
+ global_config.runner_status = {"id": step.id, "type": "task"}
82
+
77
83
  try:
78
- output = function_executable(**kwargs)
84
+ if inspect.iscoroutinefunction(function_executable):
85
+ output = await function_executable(**kwargs)
86
+ else:
87
+ output = function_executable(**kwargs)
88
+
79
89
  step.output = output
80
90
  step.end_time = datetime.now()
81
91
  success = True
@@ -87,4 +97,5 @@ class TaskRunner:
87
97
  finally:
88
98
  db.session.commit()
89
99
  self.lock.release()
90
- return dict(success=success, output=output)
100
+
101
+ return dict(success=success, output=output)
@@ -105,7 +105,8 @@ def _inspect_class(class_object=None, debug=False):
105
105
  try:
106
106
  annotation = inspect.signature(method)
107
107
  docstring = inspect.getdoc(method)
108
- functions[function] = dict(signature=annotation, docstring=docstring)
108
+ coroutine = inspect.iscoroutinefunction(method)
109
+ functions[function] = dict(signature=annotation, docstring=docstring, coroutine=coroutine,)
109
110
 
110
111
  except Exception:
111
112
  pass
@@ -0,0 +1 @@
1
+ __version__ = "1.3.5a0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ivoryos
3
- Version: 1.3.4
3
+ Version: 1.3.5a0
4
4
  Summary: an open-source Python package enabling Self-Driving Labs (SDLs) interoperability
5
5
  Author-email: Ivory Zhang <ivoryzhang@chem.ubc.ca>
6
6
  License: MIT
@@ -1 +0,0 @@
1
- __version__ = "1.3.4"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes