ivoryos 1.3.5a0__tar.gz → 1.3.6__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.5a0 → ivoryos-1.3.6}/PKG-INFO +15 -2
  2. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/__init__.py +6 -2
  3. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/optimizer/ax_optimizer.py +3 -3
  4. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/optimizer/base_optimizer.py +2 -1
  5. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/optimizer/registry.py +3 -1
  6. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/routes/design/design.py +12 -3
  7. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/routes/design/design_step.py +38 -13
  8. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/routes/design/templates/components/canvas_main.html +6 -1
  9. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/routes/design/templates/components/edit_action_form.html +2 -0
  10. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/server.py +5 -1
  11. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/static/js/action_handlers.js +48 -11
  12. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/utils/bo_campaign.py +1 -1
  13. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/utils/db_models.py +14 -5
  14. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/utils/form.py +8 -4
  15. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/utils/script_runner.py +4 -2
  16. ivoryos-1.3.6/ivoryos/version.py +1 -0
  17. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos.egg-info/PKG-INFO +15 -2
  18. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos.egg-info/requires.txt +18 -0
  19. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/pyproject.toml +14 -1
  20. ivoryos-1.3.5a0/ivoryos/version.py +0 -1
  21. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/LICENSE +0 -0
  22. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/README.md +0 -0
  23. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/app.py +0 -0
  24. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/config.py +0 -0
  25. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/optimizer/baybe_optimizer.py +0 -0
  26. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/routes/__init__.py +0 -0
  27. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/routes/api/api.py +0 -0
  28. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/routes/auth/__init__.py +0 -0
  29. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/routes/auth/auth.py +0 -0
  30. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/routes/auth/templates/login.html +0 -0
  31. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/routes/auth/templates/signup.html +0 -0
  32. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/routes/control/__init__.py +0 -0
  33. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/routes/control/control.py +0 -0
  34. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/routes/control/control_file.py +0 -0
  35. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/routes/control/control_new_device.py +0 -0
  36. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/routes/control/templates/controllers.html +0 -0
  37. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/routes/control/templates/controllers_new.html +0 -0
  38. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/routes/control/utils.py +0 -0
  39. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/routes/data/__init__.py +0 -0
  40. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/routes/data/data.py +0 -0
  41. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/routes/data/templates/components/step_card.html +0 -0
  42. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/routes/data/templates/workflow_database.html +0 -0
  43. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/routes/data/templates/workflow_view.html +0 -0
  44. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/routes/design/__init__.py +0 -0
  45. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/routes/design/design_file.py +0 -0
  46. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/routes/design/templates/components/action_form.html +0 -0
  47. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/routes/design/templates/components/actions_panel.html +0 -0
  48. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/routes/design/templates/components/autofill_toggle.html +0 -0
  49. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/routes/design/templates/components/canvas.html +0 -0
  50. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/routes/design/templates/components/canvas_footer.html +0 -0
  51. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/routes/design/templates/components/canvas_header.html +0 -0
  52. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/routes/design/templates/components/deck_selector.html +0 -0
  53. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/routes/design/templates/components/instruments_panel.html +0 -0
  54. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/routes/design/templates/components/modals/drop_modal.html +0 -0
  55. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/routes/design/templates/components/modals/json_modal.html +0 -0
  56. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/routes/design/templates/components/modals/new_script_modal.html +0 -0
  57. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/routes/design/templates/components/modals/rename_modal.html +0 -0
  58. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/routes/design/templates/components/modals/saveas_modal.html +0 -0
  59. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/routes/design/templates/components/modals.html +0 -0
  60. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/routes/design/templates/components/python_code_overlay.html +0 -0
  61. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/routes/design/templates/components/sidebar.html +0 -0
  62. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/routes/design/templates/components/text_to_code_panel.html +0 -0
  63. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/routes/design/templates/experiment_builder.html +0 -0
  64. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/routes/execute/__init__.py +0 -0
  65. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/routes/execute/execute.py +0 -0
  66. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/routes/execute/execute_file.py +0 -0
  67. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/routes/execute/templates/components/error_modal.html +0 -0
  68. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/routes/execute/templates/components/logging_panel.html +0 -0
  69. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/routes/execute/templates/components/progress_panel.html +0 -0
  70. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/routes/execute/templates/components/run_panel.html +0 -0
  71. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/routes/execute/templates/components/run_tabs.html +0 -0
  72. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/routes/execute/templates/components/tab_bayesian.html +0 -0
  73. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/routes/execute/templates/components/tab_configuration.html +0 -0
  74. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/routes/execute/templates/components/tab_repeat.html +0 -0
  75. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/routes/execute/templates/experiment_run.html +0 -0
  76. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/routes/library/__init__.py +0 -0
  77. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/routes/library/library.py +0 -0
  78. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/routes/library/templates/library.html +0 -0
  79. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/routes/main/__init__.py +0 -0
  80. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/routes/main/main.py +0 -0
  81. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/routes/main/templates/help.html +0 -0
  82. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/routes/main/templates/home.html +0 -0
  83. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/socket_handlers.py +0 -0
  84. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/static/favicon.ico +0 -0
  85. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/static/gui_annotation/Slide1.png +0 -0
  86. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/static/gui_annotation/Slide2.PNG +0 -0
  87. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/static/js/db_delete.js +0 -0
  88. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/static/js/overlay.js +0 -0
  89. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/static/js/script_metadata.js +0 -0
  90. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/static/js/socket_handler.js +0 -0
  91. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/static/js/sortable_card.js +0 -0
  92. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/static/js/sortable_design.js +0 -0
  93. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/static/js/ui_state.js +0 -0
  94. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/static/logo.webp +0 -0
  95. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/static/style.css +0 -0
  96. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/templates/base.html +0 -0
  97. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/utils/__init__.py +0 -0
  98. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/utils/client_proxy.py +0 -0
  99. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/utils/decorators.py +0 -0
  100. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/utils/global_config.py +0 -0
  101. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/utils/llm_agent.py +0 -0
  102. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/utils/py_to_json.py +0 -0
  103. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/utils/serilize.py +0 -0
  104. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/utils/task_runner.py +0 -0
  105. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos/utils/utils.py +0 -0
  106. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos.egg-info/SOURCES.txt +0 -0
  107. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos.egg-info/dependency_links.txt +0 -0
  108. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/ivoryos.egg-info/top_level.txt +0 -0
  109. {ivoryos-1.3.5a0 → ivoryos-1.3.6}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ivoryos
3
- Version: 1.3.5a0
3
+ Version: 1.3.6
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
@@ -9,6 +9,7 @@ Requires-Python: >=3.7
9
9
  Description-Content-Type: text/markdown
10
10
  License-File: LICENSE
11
11
  Requires-Dist: bcrypt
12
+ Requires-Dist: Flask[async]
12
13
  Requires-Dist: Flask-Login
13
14
  Requires-Dist: Flask-Session
14
15
  Requires-Dist: Flask-SocketIO
@@ -17,9 +18,21 @@ Requires-Dist: Flask-WTF
17
18
  Requires-Dist: SQLAlchemy-Utils
18
19
  Requires-Dist: python-dotenv
19
20
  Requires-Dist: astor; python_version < "3.9"
21
+ Provides-Extra: optimizer-ax
22
+ Requires-Dist: ax-platform; extra == "optimizer-ax"
23
+ Provides-Extra: optimizer-baybe
24
+ Requires-Dist: baybe; extra == "optimizer-baybe"
25
+ Provides-Extra: optimizer-nimo
26
+ Requires-Dist: nimo; extra == "optimizer-nimo"
20
27
  Provides-Extra: optimizer
21
- Requires-Dist: ax-platform; extra == "optimizer"
28
+ Requires-Dist: ax-platform>=1.1.2; extra == "optimizer"
22
29
  Requires-Dist: baybe; extra == "optimizer"
30
+ Provides-Extra: doc
31
+ Requires-Dist: sphinx; extra == "doc"
32
+ Requires-Dist: sphinx-rtd-theme; extra == "doc"
33
+ Requires-Dist: sphinxcontrib-httpdomain; extra == "doc"
34
+ Provides-Extra: dev
35
+ Requires-Dist: pytest; extra == "dev"
23
36
  Dynamic: license-file
24
37
 
25
38
  [![Documentation Status](https://readthedocs.org/projects/ivoryos/badge/?version=latest)](https://ivoryos.readthedocs.io/en/latest/?badge=latest)
@@ -1,8 +1,8 @@
1
- from ivoryos.server import run
1
+ from ivoryos.server import run, global_config
2
2
  from ivoryos.optimizer.registry import OPTIMIZER_REGISTRY
3
3
  from ivoryos.version import __version__ as ivoryos_version
4
4
  from ivoryos.utils.decorators import block, BUILDING_BLOCKS
5
- from ivoryos.app import app
5
+ from ivoryos.app import app, create_app, socketio, db
6
6
 
7
7
  __all__ = [
8
8
  "block",
@@ -11,4 +11,8 @@ __all__ = [
11
11
  "run",
12
12
  "app",
13
13
  "ivoryos_version",
14
+ "create_app",
15
+ "socketio",
16
+ "global_config",
17
+ "db"
14
18
  ]
@@ -30,7 +30,7 @@ class AxOptimizer(OptimizerBase):
30
30
  @staticmethod
31
31
  def _create_generator_mapping():
32
32
  """Create a mapping from string values to Generator enum members."""
33
- from ax.modelbridge import Generators
33
+ from ax.adapter import Generators
34
34
  return {member.value: member for member in Generators}
35
35
 
36
36
  def _convert_parameter_to_ax_format(self, parameter_space):
@@ -96,8 +96,8 @@ class AxOptimizer(OptimizerBase):
96
96
  step_2 = optimizer_config.get("step_2", {})
97
97
  step_1_generator = step_1.get("model", "Sobol")
98
98
  step_2_generator = step_2.get("model", "BOTorch")
99
- generator_1 = GenerationStep(model=generators.get(step_1_generator), num_trials=step_1.get("num_samples", 5))
100
- generator_2 = GenerationStep(model=generators.get(step_2_generator), num_trials=step_2.get("num_samples", -1))
99
+ generator_1 = GenerationStep(generator=generators.get(step_1_generator), num_trials=step_1.get("num_samples", 5))
100
+ generator_2 = GenerationStep(generator=generators.get(step_2_generator), num_trials=step_2.get("num_samples", -1))
101
101
  return GenerationStrategy(steps=[generator_1, generator_2])
102
102
 
103
103
  def suggest(self, n=1):
@@ -5,7 +5,7 @@ from abc import ABC, abstractmethod
5
5
 
6
6
 
7
7
  class OptimizerBase(ABC):
8
- def __init__(self, experiment_name:str, parameter_space: list, objective_config: dict, optimizer_config: dict):
8
+ def __init__(self, experiment_name:str, parameter_space: list, objective_config: dict, optimizer_config: dict, datapath:str=None):
9
9
  """
10
10
  :param experiment_name: arbitrary name
11
11
  :param parameter_space: list of parameter names
@@ -29,6 +29,7 @@ class OptimizerBase(ABC):
29
29
  self.parameter_space = parameter_space
30
30
  self.objective_config = objective_config
31
31
  self.optimizer_config = optimizer_config
32
+ self.datapath = datapath
32
33
 
33
34
  @abstractmethod
34
35
  def suggest(self, n=1):
@@ -2,8 +2,10 @@
2
2
 
3
3
  from ivoryos.optimizer.ax_optimizer import AxOptimizer
4
4
  from ivoryos.optimizer.baybe_optimizer import BaybeOptimizer
5
+ from ivoryos.optimizer.nimo_optimizer import NIMOOptimizer
5
6
 
6
7
  OPTIMIZER_REGISTRY = {
7
8
  "ax": AxOptimizer,
8
- "baybe": BaybeOptimizer
9
+ "baybe": BaybeOptimizer,
10
+ "nimo": NIMOOptimizer,
9
11
  }
@@ -87,8 +87,11 @@ def experiment_builder():
87
87
 
88
88
  # edit_action_info = session.get("edit_action")
89
89
 
90
-
91
- exec_string = script.python_script if script.python_script else script.compile(current_app.config['SCRIPT_FOLDER'])
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}")
92
95
  session['python_code'] = exec_string
93
96
 
94
97
  design_buttons = {stype: create_action_button(script, stype) for stype in script.stypes}
@@ -382,7 +385,13 @@ def methods_handler(instrument: str = ''):
382
385
  success = False
383
386
  msg = [f"{field}: {', '.join(messages)}" for field, messages in form.errors.items()]
384
387
  utils.post_script_file(script)
385
- exec_string = script.compile(current_app.config['SCRIPT_FOLDER'])
388
+ #TODO
389
+ try:
390
+ exec_string = script.compile(current_app.config['SCRIPT_FOLDER'])
391
+ except Exception as e:
392
+ exec_string = {}
393
+ msg = f"Compilation failed: {str(e)}"
394
+ # exec_string = script.compile(current_app.config['SCRIPT_FOLDER'])
386
395
  session['python_code'] = exec_string
387
396
  design_buttons = {stype: create_action_button(script, stype) for stype in script.stypes}
388
397
  html = render_template("components/canvas_main.html", script=script, buttons_dict=design_buttons)
@@ -23,12 +23,16 @@ def get_step(uuid: int):
23
23
  """
24
24
  script = utils.get_script_file()
25
25
  action = script.find_by_uuid(uuid)
26
- if request.method == 'GET':
27
- # forms = create_form_from_action(action, script=script)
26
+ if action is None:
27
+ return jsonify({"warning": "Step not found, please refresh the page."}), 404
28
+
29
+ elif request.method == 'GET':
30
+ forms = create_form_from_action(action, script=script)
28
31
  # session['edit_action'] = action
29
32
  return render_template("components/edit_action_form.html",
30
33
  action=action,
31
- forms=create_form_from_action(action, script=script))
34
+ forms=forms)
35
+
32
36
 
33
37
 
34
38
  @steps.post("/draft/steps/<int:uuid>")
@@ -47,6 +51,7 @@ def save_step(uuid: int):
47
51
  """
48
52
  script = utils.get_script_file()
49
53
  action = script.find_by_uuid(uuid)
54
+ warning = None
50
55
  if action is not None:
51
56
  forms = create_form_from_action(action, script=script)
52
57
  kwargs = {field.name: field.data for field in forms if field.name != 'csrf_token'}
@@ -55,14 +60,19 @@ def save_step(uuid: int):
55
60
  kwargs = script.validate_variables(kwargs)
56
61
  script.update_by_uuid(uuid=uuid, args=kwargs, output=save_as)
57
62
  else:
58
- flash(forms.errors)
63
+ warning = f"Compilation failed: {str(forms.errors)}"
59
64
  utils.post_script_file(script)
60
- exec_string = script.compile(current_app.config['SCRIPT_FOLDER'])
65
+ try:
66
+ exec_string = script.compile(current_app.config['SCRIPT_FOLDER'])
67
+ except Exception as e:
68
+ exec_string = {}
69
+ warning = f"Compilation failed: {str(e)}"
61
70
  session['python_code'] = exec_string
62
71
  design_buttons = {stype: create_action_button(script, stype) for stype in script.stypes}
63
72
  return render_template("components/canvas_main.html",
64
- script=script,
65
- buttons_dict=design_buttons)
73
+ script=script,
74
+ buttons_dict=design_buttons,
75
+ warning=warning)
66
76
 
67
77
  @steps.delete("/draft/steps/<int:uuid>")
68
78
  def delete_step(uuid: int):
@@ -82,12 +92,17 @@ def delete_step(uuid: int):
82
92
  if request.method == 'DELETE':
83
93
  script.delete_action(uuid)
84
94
  utils.post_script_file(script)
85
- exec_string = script.compile(current_app.config['SCRIPT_FOLDER'])
95
+ warning = None
96
+ try:
97
+ exec_string = script.compile(current_app.config['SCRIPT_FOLDER'])
98
+ except Exception as e:
99
+ exec_string = {}
100
+ warning = f"Compilation failed: {str(e)}"
86
101
  session['python_code'] = exec_string
87
102
  design_buttons = {stype: create_action_button(script, stype) for stype in script.stypes}
88
103
  return render_template("components/canvas_main.html",
89
104
  script=script,
90
- buttons_dict=design_buttons)
105
+ buttons_dict=design_buttons, warning=warning)
91
106
 
92
107
 
93
108
  @steps.route("/draft/steps/<int:uuid>/duplicate", methods=["POST"], strict_slashes=False,)
@@ -107,13 +122,18 @@ def duplicate_action(uuid: int):
107
122
  script = utils.get_script_file()
108
123
  script.duplicate_action(uuid)
109
124
  utils.post_script_file(script)
110
- exec_string = script.compile(current_app.config['SCRIPT_FOLDER'])
125
+ warning = None
126
+ try:
127
+ exec_string = script.compile(current_app.config['SCRIPT_FOLDER'])
128
+ except Exception as e:
129
+ exec_string = {}
130
+ warning = f"Compilation failed: {str(e)}"
111
131
  session['python_code'] = exec_string
112
132
  design_buttons = {stype: create_action_button(script, stype) for stype in script.stypes}
113
133
 
114
134
  return render_template("components/canvas_main.html",
115
135
  script=script,
116
- buttons_dict=design_buttons)
136
+ buttons_dict=design_buttons, warning=warning)
117
137
 
118
138
 
119
139
  @steps.route("/draft/steps/order", methods=['POST'])
@@ -133,13 +153,18 @@ def update_list():
133
153
  script = utils.get_script_file()
134
154
  script.currently_editing_order = order.split(",", len(script.currently_editing_script))
135
155
  script.sort_actions()
156
+ warning = None
136
157
 
137
158
  utils.post_script_file(script)
138
- exec_string = script.compile(current_app.config['SCRIPT_FOLDER'])
159
+ try:
160
+ exec_string = script.compile(current_app.config['SCRIPT_FOLDER'])
161
+ except Exception as e:
162
+ exec_string = {}
163
+ warning = f"Compilation failed: {str(e)}"
139
164
  session['python_code'] = exec_string
140
165
 
141
166
  # Return the updated canvas HTML instead of JSON
142
167
  design_buttons = {stype: create_action_button(script, stype) for stype in script.stypes}
143
168
  return render_template("components/canvas_main.html",
144
169
  script=script,
145
- buttons_dict=design_buttons)
170
+ buttons_dict=design_buttons, warning=warning)
@@ -31,4 +31,9 @@
31
31
  </div>
32
32
  <div class="python-code-wrapper" id="python-code-wrapper">
33
33
  {% include 'components/python_code_overlay.html' %}
34
- </div>
34
+ </div>
35
+
36
+
37
+ {% if warning %}
38
+ <div id="warning" style="display:none;">{{ warning }}</div>
39
+ {% endif %}
@@ -6,6 +6,7 @@
6
6
  <i class="bi bi-arrow-return-left"></i>
7
7
  </a>
8
8
  </div>
9
+ {% if action %}
9
10
  <h5> {{ action['action'] | format_name }} </h5>
10
11
  <form role="form" method='POST' name="{{instrument}}"
11
12
  action="{{ url_for('design.design_steps.get_step', uuid=action['uuid']) }}"
@@ -36,3 +37,4 @@
36
37
  <button type="submit" class="btn btn-primary">Save</button>
37
38
  <button type="button" class="btn btn-primary" id="back">Back</button>
38
39
  </form>
40
+ {% endif %}
@@ -50,6 +50,7 @@ def run(module=None, host="0.0.0.0", port=None, debug=None, llm_server=None, mod
50
50
  blueprint_plugins: Union[list, Blueprint] = [],
51
51
  exclude_names: list = [],
52
52
  notification_handler=None,
53
+ optimizer_registry: dict = None,
53
54
  ):
54
55
  """
55
56
  Start ivoryOS app server.
@@ -91,7 +92,10 @@ def run(module=None, host="0.0.0.0", port=None, debug=None, llm_server=None, mod
91
92
  app.config["LOGGERS_PATH"] = logger_output_name or app.config["LOGGERS_PATH"] # default.log
92
93
  logger_path = os.path.join(app.config["OUTPUT_FOLDER"], app.config["LOGGERS_PATH"])
93
94
  dummy_deck_path = os.path.join(app.config["OUTPUT_FOLDER"], app.config["DUMMY_DECK"])
94
- global_config.optimizers = OPTIMIZER_REGISTRY
95
+ if optimizer_registry:
96
+ global_config.optimizers = optimizer_registry
97
+ else:
98
+ global_config.optimizers = OPTIMIZER_REGISTRY
95
99
  if module:
96
100
  app.config["MODULE"] = module
97
101
  app.config["OFF_LINE"] = False
@@ -103,6 +103,12 @@ function submitEditForm(event) {
103
103
  document.getElementById('instrument-panel').innerHTML = previousHtmlState;
104
104
  previousHtmlState = null; // Clear the stored state
105
105
  }
106
+ const parser = new DOMParser();
107
+ const doc = parser.parseFromString(html, 'text/html');
108
+ const warningDiv = doc.querySelector('#warning');
109
+ if (warningDiv && warningDiv.textContent.trim()) {
110
+ alert(warningDiv.textContent.trim()); // or use a nicer toast
111
+ }
106
112
  }
107
113
  })
108
114
  .catch(error => {
@@ -149,6 +155,13 @@ function duplicateAction(uuid) {
149
155
  .then(response => response.text())
150
156
  .then(html => {
151
157
  updateActionCanvas(html);
158
+
159
+ const parser = new DOMParser();
160
+ const doc = parser.parseFromString(html, 'text/html');
161
+ const warningDiv = doc.querySelector('#warning');
162
+ if (warningDiv && warningDiv.textContent.trim()) {
163
+ alert(warningDiv.textContent.trim()); // or use a nicer toast
164
+ }
152
165
  })
153
166
  .catch(error => console.error('Error:', error));
154
167
  }
@@ -159,33 +172,50 @@ function editAction(uuid) {
159
172
  return;
160
173
  }
161
174
 
162
- // Save current state before fetching new content
163
175
  previousHtmlState = document.getElementById('instrument-panel').innerHTML;
164
176
 
165
177
  fetch(scriptStepUrl.replace('0', uuid), {
166
- method: 'GET',
178
+ method: 'GET', // no need for Content-Type on GET
167
179
  headers: {
168
180
  'Content-Type': 'application/json'
169
181
  }
170
182
  })
171
- .then(response => response.text())
183
+ .then(response => {
184
+ if (!response.ok) {
185
+ return response.json().then(err => {
186
+ if (err.warning) {
187
+ alert(err.warning); // <-- should fire now
188
+ }
189
+ // restore panel so user isn't stuck
190
+ if (previousHtmlState) {
191
+ document.getElementById('instrument-panel').innerHTML = previousHtmlState;
192
+ previousHtmlState = null;
193
+ }
194
+ throw new Error("Step fetch failed: " + response.status);
195
+ });
196
+ }
197
+ return response.text();
198
+ })
172
199
  .then(html => {
173
200
  document.getElementById('instrument-panel').innerHTML = html;
174
201
 
175
- // Add click handler for back button
176
- document.getElementById('back').addEventListener('click', function(e) {
177
- e.preventDefault();
178
- if (previousHtmlState) {
179
- document.getElementById('instrument-panel').innerHTML = previousHtmlState;
180
- previousHtmlState = null; // Clear the stored state
181
- }
182
- });
202
+ const backButton = document.getElementById('back');
203
+ if (backButton) {
204
+ backButton.addEventListener('click', function(e) {
205
+ e.preventDefault();
206
+ if (previousHtmlState) {
207
+ document.getElementById('instrument-panel').innerHTML = previousHtmlState;
208
+ previousHtmlState = null;
209
+ }
210
+ });
211
+ }
183
212
  })
184
213
  .catch(error => console.error('Error:', error));
185
214
  }
186
215
 
187
216
 
188
217
 
218
+
189
219
  function deleteAction(uuid) {
190
220
  if (!uuid) {
191
221
  console.error('Invalid UUID');
@@ -202,6 +232,13 @@ function deleteAction(uuid) {
202
232
  .then(html => {
203
233
  // Find the first list element's content and replace it
204
234
  updateActionCanvas(html);
235
+ // Optionally, check if a warning element exists
236
+ const parser = new DOMParser();
237
+ const doc = parser.parseFromString(html, 'text/html');
238
+ const warningDiv = doc.querySelector('#warning');
239
+ if (warningDiv && warningDiv.textContent.trim()) {
240
+ alert(warningDiv.textContent.trim()); // or use a nicer toast
241
+ }
205
242
  })
206
243
  .catch(error => console.error('Error:', error));
207
244
  }
@@ -209,7 +209,7 @@ def parse_optimization_form(form_data: Dict[str, str]):
209
209
  parameter["bounds"] = bounds
210
210
 
211
211
  elif value == "choice":
212
- choices_field = f"{param_name}_choices"
212
+ choices_field = f"{param_name}_value"
213
213
  if choices_field in form_data and form_data[choices_field]:
214
214
  # Split choices by comma and clean whitespace
215
215
  choices = [choice.strip() for choice in form_data[choices_field].split(',')]
@@ -644,12 +644,21 @@ class Script(db.Model):
644
644
  args_str = args_str.replace(f"'#{args[arg][1:]}'", args[arg][1:])
645
645
  elif isinstance(args[arg], dict):
646
646
  # print(args[arg])
647
- variables = self.get_variables()
647
+ if not args[arg]:
648
+ continue
649
+ # Extract the variable name (first key in the dict)
648
650
  value = next(iter(args[arg]))
649
- if value not in variables:
650
- raise ValueError(f"Variable ({value}) is not defined.")
651
- args_str = args_str.replace(f"{args[arg]}", next(iter(args[arg])))
652
- # elif self._is_variable(arg):
651
+ var_type = args[arg].get(value)
652
+
653
+ # Only process if it's a function_output variable reference
654
+ if var_type == "function_output":
655
+ variables = self.get_variables()
656
+ if value not in variables:
657
+ raise ValueError(f"Variable ({value}) is not defined.")
658
+ # Replace the dict string representation with just the variable name
659
+ args_str = args_str.replace(f"{args[arg]}", value)
660
+
661
+ # elif self._is_variable(arg):
653
662
  # print("is variable")
654
663
  # args_str = args_str.replace(f"'{args[arg]}'", args[arg])
655
664
  return args_str
@@ -405,7 +405,7 @@ def create_form_from_action(action: dict, script=None, design=True):
405
405
  for name, param_type in arg_types.items():
406
406
  # formatted_param_name = format_name(name)
407
407
  value = args.get(name, "")
408
- if type(value) is dict:
408
+ if type(value) is dict and value:
409
409
  value = next(iter(value))
410
410
  field_kwargs = {
411
411
  "label": name,
@@ -556,9 +556,13 @@ def _action_button(action: dict, variables: dict):
556
556
  arg_list = []
557
557
  for k, v in action['args'].items():
558
558
  if isinstance(v, dict):
559
- value = next(iter(v)) # Extract the first key if it's a dict
560
- # show warning color for variable calling when there is no definition
561
- style = "background-color: khaki" if value not in variables.keys() else ""
559
+ if not v:
560
+ value = v # Keep the original value if not a dict
561
+ else:
562
+ value = next(iter(v)) # Extract the first key if it's a dict
563
+ # show warning color for variable calling when there is no definition
564
+
565
+ style = "background-color: khaki" if v.get(value) == "function_output" and value not in variables.keys() else ""
562
566
  else:
563
567
  value = v # Keep the original value if not a dict
564
568
  arg_list.append(f"{k} = {value}") # Format the key-value pair
@@ -195,11 +195,12 @@ class ScriptRunner:
195
195
 
196
196
  # Update exec_locals with the returned locals
197
197
  exec_locals.update(result_locals)
198
- exec_locals.pop("__async_exec_wrapper", None)
198
+
199
199
 
200
200
  else:
201
- print("just exec synchronously")
201
+ # print("just exec synchronously")
202
202
  exec(line, exec_globals, exec_locals)
203
+ exec_globals.update(exec_locals)
203
204
  # return locals_dict
204
205
  # exec(line, exec_globals, exec_locals)
205
206
  # step.run_error = False
@@ -217,6 +218,7 @@ class ScriptRunner:
217
218
 
218
219
  step.run_error = True
219
220
  self.toggle_pause()
221
+ exec_locals.pop("__async_exec_wrapper", None)
220
222
  step.end_time = datetime.now()
221
223
  step.output = exec_locals
222
224
  db.session.commit()
@@ -0,0 +1 @@
1
+ __version__ = "1.3.6"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ivoryos
3
- Version: 1.3.5a0
3
+ Version: 1.3.6
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
@@ -9,6 +9,7 @@ Requires-Python: >=3.7
9
9
  Description-Content-Type: text/markdown
10
10
  License-File: LICENSE
11
11
  Requires-Dist: bcrypt
12
+ Requires-Dist: Flask[async]
12
13
  Requires-Dist: Flask-Login
13
14
  Requires-Dist: Flask-Session
14
15
  Requires-Dist: Flask-SocketIO
@@ -17,9 +18,21 @@ Requires-Dist: Flask-WTF
17
18
  Requires-Dist: SQLAlchemy-Utils
18
19
  Requires-Dist: python-dotenv
19
20
  Requires-Dist: astor; python_version < "3.9"
21
+ Provides-Extra: optimizer-ax
22
+ Requires-Dist: ax-platform; extra == "optimizer-ax"
23
+ Provides-Extra: optimizer-baybe
24
+ Requires-Dist: baybe; extra == "optimizer-baybe"
25
+ Provides-Extra: optimizer-nimo
26
+ Requires-Dist: nimo; extra == "optimizer-nimo"
20
27
  Provides-Extra: optimizer
21
- Requires-Dist: ax-platform; extra == "optimizer"
28
+ Requires-Dist: ax-platform>=1.1.2; extra == "optimizer"
22
29
  Requires-Dist: baybe; extra == "optimizer"
30
+ Provides-Extra: doc
31
+ Requires-Dist: sphinx; extra == "doc"
32
+ Requires-Dist: sphinx-rtd-theme; extra == "doc"
33
+ Requires-Dist: sphinxcontrib-httpdomain; extra == "doc"
34
+ Provides-Extra: dev
35
+ Requires-Dist: pytest; extra == "dev"
23
36
  Dynamic: license-file
24
37
 
25
38
  [![Documentation Status](https://readthedocs.org/projects/ivoryos/badge/?version=latest)](https://ivoryos.readthedocs.io/en/latest/?badge=latest)
@@ -1,4 +1,5 @@
1
1
  bcrypt
2
+ Flask[async]
2
3
  Flask-Login
3
4
  Flask-Session
4
5
  Flask-SocketIO
@@ -10,6 +11,23 @@ python-dotenv
10
11
  [:python_version < "3.9"]
11
12
  astor
12
13
 
14
+ [dev]
15
+ pytest
16
+
17
+ [doc]
18
+ sphinx
19
+ sphinx-rtd-theme
20
+ sphinxcontrib-httpdomain
21
+
13
22
  [optimizer]
23
+ ax-platform>=1.1.2
24
+ baybe
25
+
26
+ [optimizer-ax]
14
27
  ax-platform
28
+
29
+ [optimizer-baybe]
15
30
  baybe
31
+
32
+ [optimizer-nimo]
33
+ nimo
@@ -14,6 +14,7 @@ authors = [
14
14
  ]
15
15
  dependencies = [
16
16
  "bcrypt",
17
+ "Flask[async]",
17
18
  "Flask-Login",
18
19
  "Flask-Session",
19
20
  "Flask-SocketIO",
@@ -25,7 +26,19 @@ dependencies = [
25
26
  ]
26
27
 
27
28
  [project.optional-dependencies]
28
- optimizer = ["ax-platform", "baybe"]
29
+ optimizer-ax = ["ax-platform"]
30
+ optimizer-baybe = ["baybe"]
31
+ optimizer-nimo = ["nimo"]
32
+ optimizer = [
33
+ "ax-platform>=1.1.2",
34
+ "baybe"
35
+ ]
36
+ doc = [
37
+ "sphinx",
38
+ "sphinx-rtd-theme",
39
+ "sphinxcontrib-httpdomain"
40
+ ]
41
+ dev = ["pytest"]
29
42
 
30
43
  [project.urls]
31
44
  Homepage = "https://gitlab.com/heingroup/ivoryos"
@@ -1 +0,0 @@
1
- __version__ = "1.3.5a0"
File without changes
File without changes
File without changes
File without changes
File without changes