ivoryos 1.3.3__tar.gz → 1.3.4__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.
- {ivoryos-1.3.3 → ivoryos-1.3.4}/PKG-INFO +31 -6
- {ivoryos-1.3.3 → ivoryos-1.3.4}/README.md +30 -5
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/app.py +2 -1
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/server.py +29 -22
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/utils/global_config.py +11 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/utils/script_runner.py +13 -5
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/utils/task_runner.py +2 -1
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/utils/utils.py +1 -0
- ivoryos-1.3.4/ivoryos/version.py +1 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos.egg-info/PKG-INFO +31 -6
- ivoryos-1.3.3/ivoryos/version.py +0 -1
- {ivoryos-1.3.3 → ivoryos-1.3.4}/LICENSE +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/__init__.py +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/config.py +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/optimizer/ax_optimizer.py +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/optimizer/base_optimizer.py +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/optimizer/baybe_optimizer.py +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/optimizer/registry.py +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/__init__.py +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/api/api.py +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/auth/__init__.py +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/auth/auth.py +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/auth/templates/login.html +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/auth/templates/signup.html +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/control/__init__.py +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/control/control.py +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/control/control_file.py +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/control/control_new_device.py +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/control/templates/controllers.html +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/control/templates/controllers_new.html +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/control/utils.py +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/data/__init__.py +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/data/data.py +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/data/templates/components/step_card.html +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/data/templates/workflow_database.html +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/data/templates/workflow_view.html +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/design/__init__.py +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/design/design.py +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/design/design_file.py +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/design/design_step.py +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/design/templates/components/action_form.html +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/design/templates/components/actions_panel.html +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/design/templates/components/autofill_toggle.html +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/design/templates/components/canvas.html +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/design/templates/components/canvas_footer.html +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/design/templates/components/canvas_header.html +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/design/templates/components/canvas_main.html +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/design/templates/components/deck_selector.html +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/design/templates/components/edit_action_form.html +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/design/templates/components/instruments_panel.html +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/design/templates/components/modals/drop_modal.html +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/design/templates/components/modals/json_modal.html +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/design/templates/components/modals/new_script_modal.html +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/design/templates/components/modals/rename_modal.html +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/design/templates/components/modals/saveas_modal.html +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/design/templates/components/modals.html +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/design/templates/components/python_code_overlay.html +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/design/templates/components/sidebar.html +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/design/templates/components/text_to_code_panel.html +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/design/templates/experiment_builder.html +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/execute/__init__.py +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/execute/execute.py +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/execute/execute_file.py +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/execute/templates/components/error_modal.html +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/execute/templates/components/logging_panel.html +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/execute/templates/components/progress_panel.html +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/execute/templates/components/run_panel.html +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/execute/templates/components/run_tabs.html +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/execute/templates/components/tab_bayesian.html +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/execute/templates/components/tab_configuration.html +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/execute/templates/components/tab_repeat.html +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/execute/templates/experiment_run.html +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/library/__init__.py +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/library/library.py +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/library/templates/library.html +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/main/__init__.py +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/main/main.py +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/main/templates/help.html +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/main/templates/home.html +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/socket_handlers.py +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/static/favicon.ico +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/static/gui_annotation/Slide1.png +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/static/gui_annotation/Slide2.PNG +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/static/js/action_handlers.js +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/static/js/db_delete.js +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/static/js/overlay.js +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/static/js/script_metadata.js +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/static/js/socket_handler.js +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/static/js/sortable_card.js +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/static/js/sortable_design.js +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/static/js/ui_state.js +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/static/logo.webp +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/static/style.css +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/templates/base.html +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/utils/__init__.py +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/utils/bo_campaign.py +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/utils/client_proxy.py +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/utils/db_models.py +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/utils/decorators.py +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/utils/form.py +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/utils/llm_agent.py +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/utils/py_to_json.py +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/utils/serilize.py +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos.egg-info/SOURCES.txt +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos.egg-info/dependency_links.txt +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos.egg-info/requires.txt +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos.egg-info/top_level.txt +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/pyproject.toml +0 -0
- {ivoryos-1.3.3 → ivoryos-1.3.4}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ivoryos
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.4
|
|
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
|
|
@@ -28,7 +28,8 @@ Dynamic: license-file
|
|
|
28
28
|
[](https://youtu.be/dFfJv9I2-1g)
|
|
29
29
|
[](https://youtu.be/flr5ydiE96s)
|
|
30
30
|
[](https://www.nature.com/articles/s41467-025-60514-w)
|
|
31
|
-
|
|
31
|
+
|
|
32
|
+
[//]: # ([](https://discord.gg/AX5P9EdGVX))
|
|
32
33
|
|
|
33
34
|

|
|
34
35
|
# ivoryOS: interoperable Web UI for self-driving laboratories (SDLs)
|
|
@@ -101,11 +102,14 @@ pip install -e .
|
|
|
101
102
|
## Quick start
|
|
102
103
|
In your SDL script,
|
|
103
104
|
```python
|
|
105
|
+
my_robot = Robot()
|
|
106
|
+
|
|
104
107
|
import ivoryos
|
|
105
108
|
|
|
106
109
|
ivoryos.run(__name__)
|
|
107
110
|
```
|
|
108
|
-
|
|
111
|
+
You can now access the web UI at http://127.0.0.1:8000,
|
|
112
|
+
create an account, login, and start designing workflows!
|
|
109
113
|
|
|
110
114
|
----
|
|
111
115
|
## Features
|
|
@@ -127,6 +131,28 @@ Add single or multiple loggers:
|
|
|
127
131
|
ivoryos.run(__name__, logger="logger name")
|
|
128
132
|
ivoryos.run(__name__, logger=["logger 1", "logger 2"])
|
|
129
133
|
```
|
|
134
|
+
### Human-in-the-loop
|
|
135
|
+
Add single or multiple notification handlers for `pause` feature in flow control:
|
|
136
|
+
```python
|
|
137
|
+
|
|
138
|
+
def slack_bot(msg: str = "Hi"):
|
|
139
|
+
"""
|
|
140
|
+
a function that can be used as a notification handler function("msg")
|
|
141
|
+
:param msg: message to send
|
|
142
|
+
"""
|
|
143
|
+
from slack_sdk import WebClient
|
|
144
|
+
|
|
145
|
+
slack_token = "your slack token"
|
|
146
|
+
client = WebClient(token=slack_token)
|
|
147
|
+
|
|
148
|
+
my_user_id = "your user id" # replace with your actual Slack user ID
|
|
149
|
+
|
|
150
|
+
client.chat_postMessage(channel=my_user_id, text=msg)
|
|
151
|
+
|
|
152
|
+
import ivoryos
|
|
153
|
+
ivoryos.run(__name__, notification_handler=slack_bot)
|
|
154
|
+
```
|
|
155
|
+
|
|
130
156
|
### Directory Structure
|
|
131
157
|
|
|
132
158
|
Created automatically on first run:
|
|
@@ -152,11 +178,10 @@ ivoryos.run(__name__)
|
|
|
152
178
|
|
|
153
179
|
## Roadmap
|
|
154
180
|
|
|
155
|
-
- [x] Allow plugin pages ✅
|
|
156
|
-
- [x] pause, resume, abort current and pending workflows ✅
|
|
157
181
|
- [ ] dropdown input
|
|
158
182
|
- [ ] snapshot version control
|
|
159
183
|
- [ ] optimizer-agnostic
|
|
184
|
+
- [ ] prefect compatibility
|
|
160
185
|
- [ ] check batch-config file compatibility
|
|
161
186
|
|
|
162
187
|
---
|
|
@@ -198,4 +223,4 @@ For an additional perspective related to the development of the tool, please see
|
|
|
198
223
|
```
|
|
199
224
|
---
|
|
200
225
|
## Acknowledgements
|
|
201
|
-
Authors acknowledge Telescope Innovations Corp., Hein Lab members for their valuable suggestions and contributions.
|
|
226
|
+
Authors acknowledge Telescope Innovations Corp., UBC Hein Lab, and Acceleration Consortium members for their valuable suggestions and contributions.
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
[](https://youtu.be/dFfJv9I2-1g)
|
|
5
5
|
[](https://youtu.be/flr5ydiE96s)
|
|
6
6
|
[](https://www.nature.com/articles/s41467-025-60514-w)
|
|
7
|
-
|
|
7
|
+
|
|
8
|
+
[//]: # ([](https://discord.gg/AX5P9EdGVX))
|
|
8
9
|
|
|
9
10
|

|
|
10
11
|
# ivoryOS: interoperable Web UI for self-driving laboratories (SDLs)
|
|
@@ -77,11 +78,14 @@ pip install -e .
|
|
|
77
78
|
## Quick start
|
|
78
79
|
In your SDL script,
|
|
79
80
|
```python
|
|
81
|
+
my_robot = Robot()
|
|
82
|
+
|
|
80
83
|
import ivoryos
|
|
81
84
|
|
|
82
85
|
ivoryos.run(__name__)
|
|
83
86
|
```
|
|
84
|
-
|
|
87
|
+
You can now access the web UI at http://127.0.0.1:8000,
|
|
88
|
+
create an account, login, and start designing workflows!
|
|
85
89
|
|
|
86
90
|
----
|
|
87
91
|
## Features
|
|
@@ -103,6 +107,28 @@ Add single or multiple loggers:
|
|
|
103
107
|
ivoryos.run(__name__, logger="logger name")
|
|
104
108
|
ivoryos.run(__name__, logger=["logger 1", "logger 2"])
|
|
105
109
|
```
|
|
110
|
+
### Human-in-the-loop
|
|
111
|
+
Add single or multiple notification handlers for `pause` feature in flow control:
|
|
112
|
+
```python
|
|
113
|
+
|
|
114
|
+
def slack_bot(msg: str = "Hi"):
|
|
115
|
+
"""
|
|
116
|
+
a function that can be used as a notification handler function("msg")
|
|
117
|
+
:param msg: message to send
|
|
118
|
+
"""
|
|
119
|
+
from slack_sdk import WebClient
|
|
120
|
+
|
|
121
|
+
slack_token = "your slack token"
|
|
122
|
+
client = WebClient(token=slack_token)
|
|
123
|
+
|
|
124
|
+
my_user_id = "your user id" # replace with your actual Slack user ID
|
|
125
|
+
|
|
126
|
+
client.chat_postMessage(channel=my_user_id, text=msg)
|
|
127
|
+
|
|
128
|
+
import ivoryos
|
|
129
|
+
ivoryos.run(__name__, notification_handler=slack_bot)
|
|
130
|
+
```
|
|
131
|
+
|
|
106
132
|
### Directory Structure
|
|
107
133
|
|
|
108
134
|
Created automatically on first run:
|
|
@@ -128,11 +154,10 @@ ivoryos.run(__name__)
|
|
|
128
154
|
|
|
129
155
|
## Roadmap
|
|
130
156
|
|
|
131
|
-
- [x] Allow plugin pages ✅
|
|
132
|
-
- [x] pause, resume, abort current and pending workflows ✅
|
|
133
157
|
- [ ] dropdown input
|
|
134
158
|
- [ ] snapshot version control
|
|
135
159
|
- [ ] optimizer-agnostic
|
|
160
|
+
- [ ] prefect compatibility
|
|
136
161
|
- [ ] check batch-config file compatibility
|
|
137
162
|
|
|
138
163
|
---
|
|
@@ -174,4 +199,4 @@ For an additional perspective related to the development of the tool, please see
|
|
|
174
199
|
```
|
|
175
200
|
---
|
|
176
201
|
## Acknowledgements
|
|
177
|
-
Authors acknowledge Telescope Innovations Corp., Hein Lab members for their valuable suggestions and contributions.
|
|
202
|
+
Authors acknowledge Telescope Innovations Corp., UBC Hein Lab, and Acceleration Consortium members for their valuable suggestions and contributions.
|
|
@@ -41,7 +41,8 @@ def reset_old_schema(engine, db_dir):
|
|
|
41
41
|
old_workflow_run = 'workflow_runs' in tables
|
|
42
42
|
old_workflow_step = 'workflow_steps' in tables
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
# v1.3.4 only delete and backup when there is runs but no phases
|
|
45
|
+
if not has_workflow_phase and old_workflow_run:
|
|
45
46
|
print("⚠️ Old workflow database detected! All previous workflows have been reset to support the new schema.")
|
|
46
47
|
# Backup old DB
|
|
47
48
|
db_path = os.path.join(db_dir, "ivoryos.db")
|
|
@@ -49,6 +49,7 @@ def run(module=None, host="0.0.0.0", port=None, debug=None, llm_server=None, mod
|
|
|
49
49
|
enable_design: bool = True,
|
|
50
50
|
blueprint_plugins: Union[list, Blueprint] = [],
|
|
51
51
|
exclude_names: list = [],
|
|
52
|
+
notification_handler=None,
|
|
52
53
|
):
|
|
53
54
|
"""
|
|
54
55
|
Start ivoryOS app server.
|
|
@@ -65,6 +66,7 @@ def run(module=None, host="0.0.0.0", port=None, debug=None, llm_server=None, mod
|
|
|
65
66
|
:param enable_design: enable design canvas, database and workflow execution
|
|
66
67
|
:param blueprint_plugins: Union[list[Blueprint], Blueprint] custom Blueprint pages
|
|
67
68
|
:param exclude_names: list[str] module names to exclude from parsing
|
|
69
|
+
:param notification_handler: notification handler function
|
|
68
70
|
"""
|
|
69
71
|
app = create_app(config_class=config or get_config()) # Create app instance using factory function
|
|
70
72
|
|
|
@@ -113,11 +115,33 @@ def run(module=None, host="0.0.0.0", port=None, debug=None, llm_server=None, mod
|
|
|
113
115
|
output_path=app.config["OUTPUT_FOLDER"] if module is not None else None)
|
|
114
116
|
else:
|
|
115
117
|
app.config["ENABLE_LLM"] = False
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
# --- Logger registration ---
|
|
121
|
+
if logger:
|
|
122
|
+
if isinstance(logger, str):
|
|
123
|
+
logger = [logger] # convert single logger to list
|
|
124
|
+
elif not isinstance(logger, list):
|
|
125
|
+
raise TypeError("logger must be a string or a list of strings.")
|
|
126
|
+
|
|
127
|
+
for log_name in logger:
|
|
128
|
+
utils.start_logger(socketio, log_filename=logger_path, logger_name=log_name)
|
|
129
|
+
|
|
130
|
+
# --- Notification handler registration ---
|
|
131
|
+
if notification_handler:
|
|
132
|
+
|
|
133
|
+
# make it a list if a single function is passed
|
|
134
|
+
if callable(notification_handler):
|
|
135
|
+
notification_handler = [notification_handler]
|
|
136
|
+
|
|
137
|
+
if not isinstance(notification_handler, list):
|
|
138
|
+
raise ValueError("notification_handlers must be a callable or a list of callables.")
|
|
139
|
+
|
|
140
|
+
# validate all items are callable
|
|
141
|
+
for handler in notification_handler:
|
|
142
|
+
if not callable(handler):
|
|
143
|
+
raise TypeError(f"Handler {handler} is not callable.")
|
|
144
|
+
global_config.register_notification(handler)
|
|
121
145
|
|
|
122
146
|
# TODO in case Python 3.12 or higher doesn't log URL
|
|
123
147
|
# if sys.version_info >= (3, 12):
|
|
@@ -129,23 +153,6 @@ def run(module=None, host="0.0.0.0", port=None, debug=None, llm_server=None, mod
|
|
|
129
153
|
# return app
|
|
130
154
|
|
|
131
155
|
|
|
132
|
-
# def load_installed_plugins(app, socketio):
|
|
133
|
-
# """
|
|
134
|
-
# Dynamically load installed plugins and attach Flask-SocketIO.
|
|
135
|
-
# """
|
|
136
|
-
# plugin_names = []
|
|
137
|
-
# for entry_point in entry_points().get("ivoryos.plugins", []):
|
|
138
|
-
# plugin = entry_point.load()
|
|
139
|
-
#
|
|
140
|
-
# # If the plugin has an `init_socketio()` function, pass socketio
|
|
141
|
-
# if hasattr(plugin, 'init_socketio'):
|
|
142
|
-
# plugin.init_socketio(socketio)
|
|
143
|
-
#
|
|
144
|
-
# plugin_names.append(entry_point.name)
|
|
145
|
-
# app.register_blueprint(getattr(plugin, entry_point.name), url_prefix=f"{url_prefix}/{entry_point.name}")
|
|
146
|
-
#
|
|
147
|
-
# return plugin_names
|
|
148
|
-
|
|
149
156
|
|
|
150
157
|
def load_plugins(blueprints: Union[list, Blueprint], app, socketio):
|
|
151
158
|
"""
|
|
@@ -17,6 +17,8 @@ class GlobalConfig:
|
|
|
17
17
|
cls._instance._runner_lock = threading.Lock()
|
|
18
18
|
cls._instance._runner_status = None
|
|
19
19
|
cls._instance._optimizers = {}
|
|
20
|
+
cls._instance._notification_handlers = []
|
|
21
|
+
|
|
20
22
|
return cls._instance
|
|
21
23
|
|
|
22
24
|
@property
|
|
@@ -28,6 +30,15 @@ class GlobalConfig:
|
|
|
28
30
|
if self._deck is None:
|
|
29
31
|
self._deck = value
|
|
30
32
|
|
|
33
|
+
def register_notification(self, handler):
|
|
34
|
+
if not callable(handler):
|
|
35
|
+
raise ValueError("Handler must be callable")
|
|
36
|
+
self._notification_handlers.append(handler)
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def notification_handlers(self):
|
|
40
|
+
return self._notification_handlers
|
|
41
|
+
|
|
31
42
|
@property
|
|
32
43
|
def building_blocks(self):
|
|
33
44
|
return self._building_blocks
|
|
@@ -19,6 +19,14 @@ class HumanInterventionRequired(Exception):
|
|
|
19
19
|
pass
|
|
20
20
|
|
|
21
21
|
def pause(reason="Human intervention required"):
|
|
22
|
+
handlers = global_config.notification_handlers
|
|
23
|
+
if handlers:
|
|
24
|
+
for handler in handlers:
|
|
25
|
+
try:
|
|
26
|
+
handler(reason)
|
|
27
|
+
except Exception as e:
|
|
28
|
+
print(f"[notify] handler {handler} failed: {e}")
|
|
29
|
+
# raise error to pause workflow in gui
|
|
22
30
|
raise HumanInterventionRequired(reason)
|
|
23
31
|
|
|
24
32
|
class ScriptRunner:
|
|
@@ -161,7 +169,7 @@ class ScriptRunner:
|
|
|
161
169
|
start_time=datetime.now(),
|
|
162
170
|
)
|
|
163
171
|
db.session.add(step)
|
|
164
|
-
db.session.
|
|
172
|
+
db.session.flush()
|
|
165
173
|
|
|
166
174
|
logger.info(f"Executing: {line}")
|
|
167
175
|
socketio.emit('execution', {'section': f"{section_name}-{index}"})
|
|
@@ -219,7 +227,7 @@ class ScriptRunner:
|
|
|
219
227
|
repeat_mode=repeat_mode
|
|
220
228
|
)
|
|
221
229
|
db.session.add(run)
|
|
222
|
-
db.session.
|
|
230
|
+
db.session.flush()
|
|
223
231
|
run_id = run.id # Save the ID
|
|
224
232
|
try:
|
|
225
233
|
|
|
@@ -277,7 +285,7 @@ class ScriptRunner:
|
|
|
277
285
|
start_time=datetime.now()
|
|
278
286
|
)
|
|
279
287
|
db.session.add(phase)
|
|
280
|
-
db.session.
|
|
288
|
+
db.session.flush()
|
|
281
289
|
phase_id = phase.id
|
|
282
290
|
|
|
283
291
|
step_outputs = self.exec_steps(script, section_name, logger, socketio, phase_id=phase_id)
|
|
@@ -317,7 +325,7 @@ class ScriptRunner:
|
|
|
317
325
|
start_time=datetime.now()
|
|
318
326
|
)
|
|
319
327
|
db.session.add(phase)
|
|
320
|
-
db.session.
|
|
328
|
+
db.session.flush()
|
|
321
329
|
|
|
322
330
|
phase_id = phase.id
|
|
323
331
|
output = self.exec_steps(script, "script", logger, socketio, phase_id, **kwargs)
|
|
@@ -371,7 +379,7 @@ class ScriptRunner:
|
|
|
371
379
|
start_time=datetime.now()
|
|
372
380
|
)
|
|
373
381
|
db.session.add(phase)
|
|
374
|
-
db.session.
|
|
382
|
+
db.session.flush()
|
|
375
383
|
phase_id = phase.id
|
|
376
384
|
|
|
377
385
|
logger.info(f'Executing {run_name} experiment: {i_progress + 1}/{int(repeat_count)}')
|
|
@@ -29,6 +29,7 @@ class TaskRunner:
|
|
|
29
29
|
if not self.lock.acquire(blocking=False):
|
|
30
30
|
current_status = global_config.runner_status
|
|
31
31
|
current_status["status"] = "busy"
|
|
32
|
+
current_status["output"] = "busy"
|
|
32
33
|
return current_status
|
|
33
34
|
|
|
34
35
|
|
|
@@ -71,7 +72,7 @@ class TaskRunner:
|
|
|
71
72
|
with current_app.app_context():
|
|
72
73
|
step = SingleStep(method_name=method_name, kwargs=kwargs, run_error=None, start_time=datetime.now())
|
|
73
74
|
db.session.add(step)
|
|
74
|
-
db.session.
|
|
75
|
+
db.session.flush()
|
|
75
76
|
global_config.runner_status = {"id":step.id, "type": "task"}
|
|
76
77
|
try:
|
|
77
78
|
output = function_executable(**kwargs)
|
|
@@ -141,6 +141,7 @@ def _get_type_from_parameters(arg, parameters):
|
|
|
141
141
|
def _convert_by_str(args, arg_types):
|
|
142
142
|
"""
|
|
143
143
|
Converts a value to type through eval(f'{type}("{args}")')
|
|
144
|
+
v1.3.4 TODO try str lastly, otherwise it's always converted to str
|
|
144
145
|
"""
|
|
145
146
|
if type(arg_types) is not list:
|
|
146
147
|
arg_types = [arg_types]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.3.4"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ivoryos
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.4
|
|
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
|
|
@@ -28,7 +28,8 @@ Dynamic: license-file
|
|
|
28
28
|
[](https://youtu.be/dFfJv9I2-1g)
|
|
29
29
|
[](https://youtu.be/flr5ydiE96s)
|
|
30
30
|
[](https://www.nature.com/articles/s41467-025-60514-w)
|
|
31
|
-
|
|
31
|
+
|
|
32
|
+
[//]: # ([](https://discord.gg/AX5P9EdGVX))
|
|
32
33
|
|
|
33
34
|

|
|
34
35
|
# ivoryOS: interoperable Web UI for self-driving laboratories (SDLs)
|
|
@@ -101,11 +102,14 @@ pip install -e .
|
|
|
101
102
|
## Quick start
|
|
102
103
|
In your SDL script,
|
|
103
104
|
```python
|
|
105
|
+
my_robot = Robot()
|
|
106
|
+
|
|
104
107
|
import ivoryos
|
|
105
108
|
|
|
106
109
|
ivoryos.run(__name__)
|
|
107
110
|
```
|
|
108
|
-
|
|
111
|
+
You can now access the web UI at http://127.0.0.1:8000,
|
|
112
|
+
create an account, login, and start designing workflows!
|
|
109
113
|
|
|
110
114
|
----
|
|
111
115
|
## Features
|
|
@@ -127,6 +131,28 @@ Add single or multiple loggers:
|
|
|
127
131
|
ivoryos.run(__name__, logger="logger name")
|
|
128
132
|
ivoryos.run(__name__, logger=["logger 1", "logger 2"])
|
|
129
133
|
```
|
|
134
|
+
### Human-in-the-loop
|
|
135
|
+
Add single or multiple notification handlers for `pause` feature in flow control:
|
|
136
|
+
```python
|
|
137
|
+
|
|
138
|
+
def slack_bot(msg: str = "Hi"):
|
|
139
|
+
"""
|
|
140
|
+
a function that can be used as a notification handler function("msg")
|
|
141
|
+
:param msg: message to send
|
|
142
|
+
"""
|
|
143
|
+
from slack_sdk import WebClient
|
|
144
|
+
|
|
145
|
+
slack_token = "your slack token"
|
|
146
|
+
client = WebClient(token=slack_token)
|
|
147
|
+
|
|
148
|
+
my_user_id = "your user id" # replace with your actual Slack user ID
|
|
149
|
+
|
|
150
|
+
client.chat_postMessage(channel=my_user_id, text=msg)
|
|
151
|
+
|
|
152
|
+
import ivoryos
|
|
153
|
+
ivoryos.run(__name__, notification_handler=slack_bot)
|
|
154
|
+
```
|
|
155
|
+
|
|
130
156
|
### Directory Structure
|
|
131
157
|
|
|
132
158
|
Created automatically on first run:
|
|
@@ -152,11 +178,10 @@ ivoryos.run(__name__)
|
|
|
152
178
|
|
|
153
179
|
## Roadmap
|
|
154
180
|
|
|
155
|
-
- [x] Allow plugin pages ✅
|
|
156
|
-
- [x] pause, resume, abort current and pending workflows ✅
|
|
157
181
|
- [ ] dropdown input
|
|
158
182
|
- [ ] snapshot version control
|
|
159
183
|
- [ ] optimizer-agnostic
|
|
184
|
+
- [ ] prefect compatibility
|
|
160
185
|
- [ ] check batch-config file compatibility
|
|
161
186
|
|
|
162
187
|
---
|
|
@@ -198,4 +223,4 @@ For an additional perspective related to the development of the tool, please see
|
|
|
198
223
|
```
|
|
199
224
|
---
|
|
200
225
|
## Acknowledgements
|
|
201
|
-
Authors acknowledge Telescope Innovations Corp., Hein Lab members for their valuable suggestions and contributions.
|
|
226
|
+
Authors acknowledge Telescope Innovations Corp., UBC Hein Lab, and Acceleration Consortium members for their valuable suggestions and contributions.
|
ivoryos-1.3.3/ivoryos/version.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "1.3.3"
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/design/templates/components/actions_panel.html
RENAMED
|
File without changes
|
{ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/design/templates/components/autofill_toggle.html
RENAMED
|
File without changes
|
|
File without changes
|
{ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/design/templates/components/canvas_footer.html
RENAMED
|
File without changes
|
{ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/design/templates/components/canvas_header.html
RENAMED
|
File without changes
|
|
File without changes
|
{ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/design/templates/components/deck_selector.html
RENAMED
|
File without changes
|
{ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/design/templates/components/edit_action_form.html
RENAMED
|
File without changes
|
{ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/design/templates/components/instruments_panel.html
RENAMED
|
File without changes
|
{ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/design/templates/components/modals/drop_modal.html
RENAMED
|
File without changes
|
{ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/design/templates/components/modals/json_modal.html
RENAMED
|
File without changes
|
|
File without changes
|
{ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/design/templates/components/modals/rename_modal.html
RENAMED
|
File without changes
|
{ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/design/templates/components/modals/saveas_modal.html
RENAMED
|
File without changes
|
|
File without changes
|
{ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/design/templates/components/python_code_overlay.html
RENAMED
|
File without changes
|
|
File without changes
|
{ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/design/templates/components/text_to_code_panel.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/execute/templates/components/error_modal.html
RENAMED
|
File without changes
|
{ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/execute/templates/components/logging_panel.html
RENAMED
|
File without changes
|
{ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/execute/templates/components/progress_panel.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/execute/templates/components/tab_bayesian.html
RENAMED
|
File without changes
|
{ivoryos-1.3.3 → ivoryos-1.3.4}/ivoryos/routes/execute/templates/components/tab_configuration.html
RENAMED
|
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
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|