ivoryos 1.2.5__py3-none-any.whl → 1.4.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- docs/source/conf.py +84 -0
- ivoryos/__init__.py +16 -246
- ivoryos/app.py +154 -0
- ivoryos/optimizer/ax_optimizer.py +55 -28
- ivoryos/optimizer/base_optimizer.py +20 -1
- ivoryos/optimizer/baybe_optimizer.py +27 -17
- ivoryos/optimizer/nimo_optimizer.py +173 -0
- ivoryos/optimizer/registry.py +3 -1
- ivoryos/routes/auth/auth.py +35 -8
- ivoryos/routes/auth/templates/change_password.html +32 -0
- ivoryos/routes/control/control.py +58 -28
- ivoryos/routes/control/control_file.py +12 -15
- ivoryos/routes/control/control_new_device.py +21 -11
- ivoryos/routes/control/templates/controllers.html +27 -0
- ivoryos/routes/control/utils.py +2 -0
- ivoryos/routes/data/data.py +110 -44
- ivoryos/routes/data/templates/components/step_card.html +78 -13
- ivoryos/routes/data/templates/workflow_view.html +343 -113
- ivoryos/routes/design/design.py +59 -10
- ivoryos/routes/design/design_file.py +3 -3
- ivoryos/routes/design/design_step.py +43 -17
- ivoryos/routes/design/templates/components/action_form.html +2 -2
- ivoryos/routes/design/templates/components/canvas_main.html +6 -1
- ivoryos/routes/design/templates/components/edit_action_form.html +18 -3
- ivoryos/routes/design/templates/components/info_modal.html +318 -0
- ivoryos/routes/design/templates/components/instruments_panel.html +23 -1
- ivoryos/routes/design/templates/components/python_code_overlay.html +27 -10
- ivoryos/routes/design/templates/experiment_builder.html +3 -0
- ivoryos/routes/execute/execute.py +82 -22
- ivoryos/routes/execute/templates/components/logging_panel.html +50 -25
- ivoryos/routes/execute/templates/components/run_tabs.html +45 -2
- ivoryos/routes/execute/templates/components/tab_bayesian.html +447 -325
- ivoryos/routes/execute/templates/components/tab_configuration.html +303 -18
- ivoryos/routes/execute/templates/components/tab_repeat.html +6 -2
- ivoryos/routes/execute/templates/experiment_run.html +0 -264
- ivoryos/routes/library/library.py +9 -11
- ivoryos/routes/main/main.py +30 -2
- ivoryos/server.py +180 -0
- ivoryos/socket_handlers.py +1 -1
- ivoryos/static/ivoryos_logo.png +0 -0
- ivoryos/static/js/action_handlers.js +259 -88
- ivoryos/static/js/socket_handler.js +40 -5
- ivoryos/static/js/sortable_design.js +29 -11
- ivoryos/templates/base.html +61 -2
- ivoryos/utils/bo_campaign.py +18 -17
- ivoryos/utils/client_proxy.py +267 -36
- ivoryos/utils/db_models.py +286 -60
- ivoryos/utils/decorators.py +34 -0
- ivoryos/utils/form.py +52 -19
- ivoryos/utils/global_config.py +21 -0
- ivoryos/utils/nest_script.py +314 -0
- ivoryos/utils/py_to_json.py +80 -10
- ivoryos/utils/script_runner.py +573 -189
- ivoryos/utils/task_runner.py +69 -22
- ivoryos/utils/utils.py +48 -5
- ivoryos/version.py +1 -1
- {ivoryos-1.2.5.dist-info → ivoryos-1.4.4.dist-info}/METADATA +109 -47
- ivoryos-1.4.4.dist-info/RECORD +119 -0
- ivoryos-1.4.4.dist-info/top_level.txt +3 -0
- tests/__init__.py +0 -0
- tests/conftest.py +133 -0
- tests/integration/__init__.py +0 -0
- tests/integration/test_route_auth.py +80 -0
- tests/integration/test_route_control.py +94 -0
- tests/integration/test_route_database.py +61 -0
- tests/integration/test_route_design.py +36 -0
- tests/integration/test_route_main.py +35 -0
- tests/integration/test_sockets.py +26 -0
- tests/unit/test_type_conversion.py +42 -0
- tests/unit/test_util.py +3 -0
- ivoryos/routes/api/api.py +0 -56
- ivoryos-1.2.5.dist-info/RECORD +0 -100
- ivoryos-1.2.5.dist-info/top_level.txt +0 -1
- {ivoryos-1.2.5.dist-info → ivoryos-1.4.4.dist-info}/WHEEL +0 -0
- {ivoryos-1.2.5.dist-info → ivoryos-1.4.4.dist-info}/licenses/LICENSE +0 -0
ivoryos/utils/task_runner.py
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import asyncio
|
|
1
3
|
import threading
|
|
2
4
|
import time
|
|
3
5
|
from datetime import datetime
|
|
4
6
|
|
|
7
|
+
from ivoryos.utils.decorators import BUILDING_BLOCKS
|
|
5
8
|
from ivoryos.utils.db_models import db, SingleStep
|
|
6
9
|
from ivoryos.utils.global_config import GlobalConfig
|
|
7
10
|
|
|
@@ -18,8 +21,7 @@ class TaskRunner:
|
|
|
18
21
|
self.globals_dict = globals_dict
|
|
19
22
|
self.lock = global_config.runner_lock
|
|
20
23
|
|
|
21
|
-
|
|
22
|
-
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):
|
|
23
25
|
global deck
|
|
24
26
|
if deck is None:
|
|
25
27
|
deck = global_config.deck
|
|
@@ -28,18 +30,18 @@ class TaskRunner:
|
|
|
28
30
|
if not self.lock.acquire(blocking=False):
|
|
29
31
|
current_status = global_config.runner_status
|
|
30
32
|
current_status["status"] = "busy"
|
|
33
|
+
current_status["output"] = "busy"
|
|
31
34
|
return current_status
|
|
32
35
|
|
|
33
|
-
|
|
34
36
|
if wait:
|
|
35
|
-
output = self._run_single_step(component, method, kwargs, current_app)
|
|
37
|
+
output = await self._run_single_step(component, method, kwargs, current_app)
|
|
36
38
|
else:
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
|
43
45
|
output = {"status": "task started", "task_id": global_config.runner_status.get("id")}
|
|
44
46
|
|
|
45
47
|
return output
|
|
@@ -48,37 +50,82 @@ class TaskRunner:
|
|
|
48
50
|
if component.startswith("deck."):
|
|
49
51
|
component = component.split(".")[1]
|
|
50
52
|
instrument = getattr(deck, component)
|
|
53
|
+
function_executable = getattr(instrument, method)
|
|
54
|
+
elif component.startswith("blocks."):
|
|
55
|
+
component = component.split(".")[1]
|
|
56
|
+
function_executable = BUILDING_BLOCKS[component][method]["func"]
|
|
51
57
|
else:
|
|
52
58
|
temp_connections = global_config.defined_variables
|
|
53
59
|
instrument = temp_connections.get(component)
|
|
54
|
-
|
|
60
|
+
function_executable = getattr(instrument, method)
|
|
55
61
|
return function_executable
|
|
56
62
|
|
|
57
|
-
def _run_single_step(self, component, method, kwargs, current_app=None):
|
|
63
|
+
async def _run_single_step(self, component, method, kwargs, current_app=None):
|
|
58
64
|
try:
|
|
59
65
|
function_executable = self._get_executable(component, deck, method)
|
|
60
|
-
method_name = f"{
|
|
66
|
+
method_name = f"{component}.{method}"
|
|
61
67
|
except Exception as e:
|
|
62
68
|
self.lock.release()
|
|
63
|
-
return {"status": "error", "msg": e
|
|
69
|
+
return {"status": "error", "msg": str(e)}
|
|
64
70
|
|
|
65
|
-
# with
|
|
71
|
+
# Flask context is NOT async → just use normal "with"
|
|
66
72
|
with current_app.app_context():
|
|
67
|
-
step = SingleStep(
|
|
73
|
+
step = SingleStep(
|
|
74
|
+
method_name=method_name,
|
|
75
|
+
kwargs=kwargs,
|
|
76
|
+
run_error=None,
|
|
77
|
+
start_time=datetime.now()
|
|
78
|
+
)
|
|
68
79
|
db.session.add(step)
|
|
69
|
-
db.session.
|
|
70
|
-
global_config.runner_status = {"id":step.id, "type": "task"}
|
|
80
|
+
db.session.flush()
|
|
81
|
+
global_config.runner_status = {"id": step.id, "type": "task"}
|
|
82
|
+
|
|
71
83
|
try:
|
|
72
|
-
|
|
84
|
+
kwargs = self._convert_kwargs_type(kwargs, function_executable)
|
|
85
|
+
|
|
86
|
+
if inspect.iscoroutinefunction(function_executable):
|
|
87
|
+
output = await function_executable(**kwargs)
|
|
88
|
+
else:
|
|
89
|
+
output = function_executable(**kwargs)
|
|
90
|
+
|
|
73
91
|
step.output = output
|
|
74
92
|
step.end_time = datetime.now()
|
|
75
93
|
success = True
|
|
76
94
|
except Exception as e:
|
|
77
|
-
step.run_error = e
|
|
95
|
+
step.run_error = str(e)
|
|
78
96
|
step.end_time = datetime.now()
|
|
79
97
|
success = False
|
|
80
|
-
output = e
|
|
98
|
+
output = str(e)
|
|
81
99
|
finally:
|
|
82
100
|
db.session.commit()
|
|
83
101
|
self.lock.release()
|
|
84
|
-
|
|
102
|
+
|
|
103
|
+
return dict(success=success, output=output)
|
|
104
|
+
|
|
105
|
+
@staticmethod
|
|
106
|
+
def _convert_kwargs_type(kwargs, function_executable):
|
|
107
|
+
def convert_guess(str_value):
|
|
108
|
+
str_value = str_value.strip()
|
|
109
|
+
if str_value.isdigit() or (str_value.startswith('-') and str_value[1:].isdigit()):
|
|
110
|
+
return int(str_value)
|
|
111
|
+
try:
|
|
112
|
+
return float(str_value)
|
|
113
|
+
except ValueError:
|
|
114
|
+
return str_value
|
|
115
|
+
|
|
116
|
+
sig = inspect.signature(function_executable)
|
|
117
|
+
converted = {}
|
|
118
|
+
|
|
119
|
+
for name, value in kwargs.items():
|
|
120
|
+
if name in sig.parameters:
|
|
121
|
+
param = sig.parameters[name]
|
|
122
|
+
if param.annotation != inspect.Parameter.empty:
|
|
123
|
+
# convert using type hint
|
|
124
|
+
try:
|
|
125
|
+
converted[name] = param.annotation(value)
|
|
126
|
+
except Exception:
|
|
127
|
+
converted[name] = value
|
|
128
|
+
else:
|
|
129
|
+
# no type hint → guess
|
|
130
|
+
converted[name] = convert_guess(value)
|
|
131
|
+
return converted
|
ivoryos/utils/utils.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import ast
|
|
2
2
|
import importlib
|
|
3
3
|
import inspect
|
|
4
|
+
import json
|
|
4
5
|
import logging
|
|
5
6
|
import os
|
|
6
7
|
import pickle
|
|
@@ -15,7 +16,7 @@ from flask_login import current_user
|
|
|
15
16
|
from flask_socketio import SocketIO
|
|
16
17
|
|
|
17
18
|
from ivoryos.utils.db_models import Script
|
|
18
|
-
|
|
19
|
+
from ivoryos.utils.decorators import BUILDING_BLOCKS
|
|
19
20
|
|
|
20
21
|
def get_script_file():
|
|
21
22
|
"""Get script from Flask session and returns the script"""
|
|
@@ -105,7 +106,8 @@ def _inspect_class(class_object=None, debug=False):
|
|
|
105
106
|
try:
|
|
106
107
|
annotation = inspect.signature(method)
|
|
107
108
|
docstring = inspect.getdoc(method)
|
|
108
|
-
|
|
109
|
+
coroutine = inspect.iscoroutinefunction(method)
|
|
110
|
+
functions[function] = dict(signature=annotation, docstring=docstring, coroutine=coroutine,)
|
|
109
111
|
|
|
110
112
|
except Exception:
|
|
111
113
|
pass
|
|
@@ -141,6 +143,7 @@ def _get_type_from_parameters(arg, parameters):
|
|
|
141
143
|
def _convert_by_str(args, arg_types):
|
|
142
144
|
"""
|
|
143
145
|
Converts a value to type through eval(f'{type}("{args}")')
|
|
146
|
+
v1.3.4 TODO try str lastly, otherwise it's always converted to str
|
|
144
147
|
"""
|
|
145
148
|
if type(arg_types) is not list:
|
|
146
149
|
arg_types = [arg_types]
|
|
@@ -151,6 +154,7 @@ def _convert_by_str(args, arg_types):
|
|
|
151
154
|
return args
|
|
152
155
|
except Exception:
|
|
153
156
|
raise TypeError(f"Input type error: cannot convert '{args}' to {arg_type}.")
|
|
157
|
+
return args
|
|
154
158
|
|
|
155
159
|
|
|
156
160
|
def _convert_by_class(args, arg_types):
|
|
@@ -348,8 +352,8 @@ def create_deck_snapshot(deck, save: bool = False, output_path: str = '', exclud
|
|
|
348
352
|
for name, class_type in items.items():
|
|
349
353
|
print(f" {name}: {class_type}")
|
|
350
354
|
|
|
351
|
-
print_section("✅ INCLUDED", deck_summary["included"])
|
|
352
|
-
print_section("❌ FAILED", deck_summary["failed"])
|
|
355
|
+
print_section("✅ INCLUDED MODULES", deck_summary["included"])
|
|
356
|
+
print_section("❌ FAILED MODULES", deck_summary["failed"])
|
|
353
357
|
print("\n")
|
|
354
358
|
|
|
355
359
|
print_deck_snapshot(deck_summary)
|
|
@@ -364,6 +368,28 @@ def create_deck_snapshot(deck, save: bool = False, output_path: str = '', exclud
|
|
|
364
368
|
return deck_snapshot
|
|
365
369
|
|
|
366
370
|
|
|
371
|
+
def create_block_snapshot(save: bool = False, output_path: str = ''):
|
|
372
|
+
block_snapshot = {}
|
|
373
|
+
included = {}
|
|
374
|
+
failed = {}
|
|
375
|
+
for category, data in BUILDING_BLOCKS.items():
|
|
376
|
+
key = f"blocks.{category}"
|
|
377
|
+
block_snapshot[key] = {}
|
|
378
|
+
|
|
379
|
+
for func_name, meta in data.items():
|
|
380
|
+
func = meta["func"]
|
|
381
|
+
block_snapshot[key][func_name] = {
|
|
382
|
+
"signature": meta["signature"],
|
|
383
|
+
"docstring": meta["docstring"],
|
|
384
|
+
"coroutine": meta["coroutine"],
|
|
385
|
+
"path": f"{func.__module__}.{func.__qualname__}"
|
|
386
|
+
}
|
|
387
|
+
if block_snapshot:
|
|
388
|
+
print(f"\n=== ✅ BUILDING_BLOCKS ({len(block_snapshot)}) ===")
|
|
389
|
+
for category, blocks in block_snapshot.items():
|
|
390
|
+
print(f" {category}: ", ",".join(blocks.keys()))
|
|
391
|
+
return block_snapshot
|
|
392
|
+
|
|
367
393
|
def load_deck(pkl_name: str):
|
|
368
394
|
"""
|
|
369
395
|
Loads a pickled deck snapshot from disk on offline mode
|
|
@@ -420,4 +446,21 @@ def get_local_ip():
|
|
|
420
446
|
ip = '127.0.0.1'
|
|
421
447
|
finally:
|
|
422
448
|
s.close()
|
|
423
|
-
return ip
|
|
449
|
+
return ip
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
def safe_dump(obj):
|
|
453
|
+
try:
|
|
454
|
+
json.dumps(obj)
|
|
455
|
+
return obj
|
|
456
|
+
except (TypeError, OverflowError):
|
|
457
|
+
return repr(obj) # store readable representation
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
def create_module_snapshot(module):
|
|
461
|
+
classes = inspect.getmembers(module, inspect.isclass)
|
|
462
|
+
api_variables = {}
|
|
463
|
+
for i in classes:
|
|
464
|
+
# globals()[i[0]] = i[1]
|
|
465
|
+
api_variables[i[0]] = i[1]
|
|
466
|
+
return api_variables
|
ivoryos/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "1.
|
|
1
|
+
__version__ = "1.4.4"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ivoryos
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.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
|
|
@@ -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
|
|
@@ -16,10 +17,24 @@ Requires-Dist: Flask-SQLAlchemy
|
|
|
16
17
|
Requires-Dist: Flask-WTF
|
|
17
18
|
Requires-Dist: SQLAlchemy-Utils
|
|
18
19
|
Requires-Dist: python-dotenv
|
|
20
|
+
Requires-Dist: pandas
|
|
19
21
|
Requires-Dist: astor; python_version < "3.9"
|
|
20
|
-
Provides-Extra: optimizer
|
|
21
|
-
Requires-Dist: ax-platform; extra == "optimizer"
|
|
22
|
-
|
|
22
|
+
Provides-Extra: optimizer-ax
|
|
23
|
+
Requires-Dist: ax-platform; extra == "optimizer-ax"
|
|
24
|
+
Provides-Extra: optimizer-baybe
|
|
25
|
+
Requires-Dist: baybe; extra == "optimizer-baybe"
|
|
26
|
+
Provides-Extra: optimizer-nimo
|
|
27
|
+
Requires-Dist: nimo; extra == "optimizer-nimo"
|
|
28
|
+
Provides-Extra: optimizers
|
|
29
|
+
Requires-Dist: ax-platform>=1.1.2; extra == "optimizers"
|
|
30
|
+
Requires-Dist: baybe>=0.14.0; extra == "optimizers"
|
|
31
|
+
Requires-Dist: nimo; extra == "optimizers"
|
|
32
|
+
Provides-Extra: doc
|
|
33
|
+
Requires-Dist: sphinx; extra == "doc"
|
|
34
|
+
Requires-Dist: sphinx-rtd-theme; extra == "doc"
|
|
35
|
+
Requires-Dist: sphinxcontrib-httpdomain; extra == "doc"
|
|
36
|
+
Provides-Extra: dev
|
|
37
|
+
Requires-Dist: pytest; extra == "dev"
|
|
23
38
|
Dynamic: license-file
|
|
24
39
|
|
|
25
40
|
[](https://ivoryos.readthedocs.io/en/latest/?badge=latest)
|
|
@@ -28,33 +43,36 @@ Dynamic: license-file
|
|
|
28
43
|
[](https://youtu.be/dFfJv9I2-1g)
|
|
29
44
|
[](https://youtu.be/flr5ydiE96s)
|
|
30
45
|
[](https://www.nature.com/articles/s41467-025-60514-w)
|
|
31
|
-
[](https://discord.gg/AX5P9EdGVX)
|
|
32
46
|
|
|
33
|
-
![]
|
|
34
|
-
|
|
35
|
-
|
|
47
|
+
[//]: # ([](https://discord.gg/AX5P9EdGVX))
|
|
48
|
+
|
|
49
|
+

|
|
50
|
+
|
|
51
|
+
# [IvoryOS](https://ivoryos.ai): interoperable orchestrator for self-driving laboratories (SDLs)
|
|
52
|
+
|
|
53
|
+
A **plug-and-play** web interface for flexible, modular SDLs —
|
|
54
|
+
you focus on developing protocols, IvoryOS handles the rest.
|
|
55
|
+
|
|
56
|
+

|
|
36
57
|
|
|
37
58
|
---
|
|
38
59
|
|
|
39
60
|
## Table of Contents
|
|
40
|
-
- [
|
|
61
|
+
- [What IvoryOS does](#what-ivoryos-does)
|
|
41
62
|
- [System requirements](#system-requirements)
|
|
42
63
|
- [Installation](#installation)
|
|
43
|
-
- [Quick Start](#quick-start)
|
|
44
64
|
- [Features](#features)
|
|
45
65
|
- [Demo](#demo)
|
|
46
66
|
- [Roadmap](#roadmap)
|
|
67
|
+
- [Contributing](#contributing)
|
|
47
68
|
- [Acknowledgements](#acknowledgements)
|
|
48
69
|
|
|
49
70
|
---
|
|
50
|
-
##
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
-
|
|
55
|
-
- Automatically displaying functions and parameters in a web UI
|
|
56
|
-
- Allowing users to **design**, **manage**, and **execute** experimental workflows with minimal changes to existing scripts
|
|
57
|
-
- Providing natural language support for workflow design and execution, check [IvoryOS MCP](https://gitlab.com/heingroup/ivoryos-suite/ivoryos-mcp) for more details.
|
|
71
|
+
## What IvoryOS Does
|
|
72
|
+
- Turns Python modules into UIs by dynamically inspecting your hardware APIs, functions, and workflows.
|
|
73
|
+
- Standardizes optimization inputs/outputs, making any optimizer plug-and-play.
|
|
74
|
+
- Provides a visual workflow builder for designing and running experiments.
|
|
75
|
+
- Adds natural-language control for creating and executing workflows, see [IvoryOS MCP](https://gitlab.com/heingroup/ivoryos-suite/ivoryos-mcp) for more details.
|
|
58
76
|
|
|
59
77
|
----
|
|
60
78
|
## System Requirements
|
|
@@ -76,10 +94,13 @@ Building UIs for SDLs is challenging because flexibility and modularity make the
|
|
|
76
94
|
- SQLAlchemy-Utils~=0.41
|
|
77
95
|
- Flask-WTF~=1.2
|
|
78
96
|
- python-dotenv==1.0.1
|
|
97
|
+
- pandas
|
|
79
98
|
|
|
80
99
|
**Optional:**
|
|
81
|
-
- ax-platform
|
|
82
|
-
- baybe
|
|
100
|
+
- ax-platform==1.1.2
|
|
101
|
+
- baybe==0.14.0
|
|
102
|
+
- nimo
|
|
103
|
+
- slack-sdk
|
|
83
104
|
</details>
|
|
84
105
|
|
|
85
106
|
---
|
|
@@ -90,22 +111,31 @@ From PyPI:
|
|
|
90
111
|
```bash
|
|
91
112
|
pip install ivoryos
|
|
92
113
|
```
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
114
|
+
|
|
115
|
+
[//]: # (From source:)
|
|
116
|
+
|
|
117
|
+
[//]: # (```bash)
|
|
118
|
+
|
|
119
|
+
[//]: # (git clone https://gitlab.com/heingroup/ivoryos.git)
|
|
120
|
+
|
|
121
|
+
[//]: # (cd ivoryos)
|
|
122
|
+
|
|
123
|
+
[//]: # (pip install -e .)
|
|
124
|
+
|
|
125
|
+
[//]: # (```)
|
|
99
126
|
|
|
100
127
|
|
|
101
128
|
## Quick start
|
|
102
|
-
In your
|
|
129
|
+
In your script, where you initialize or import your robot:
|
|
103
130
|
```python
|
|
131
|
+
my_robot = Robot()
|
|
132
|
+
|
|
104
133
|
import ivoryos
|
|
105
134
|
|
|
106
135
|
ivoryos.run(__name__)
|
|
107
136
|
```
|
|
108
|
-
|
|
137
|
+
Then run the script and visit `http://localhost:8000` in your browser.
|
|
138
|
+
Use `admin` for both username and password, and start building workflows!
|
|
109
139
|
|
|
110
140
|
----
|
|
111
141
|
## Features
|
|
@@ -127,42 +157,72 @@ Add single or multiple loggers:
|
|
|
127
157
|
ivoryos.run(__name__, logger="logger name")
|
|
128
158
|
ivoryos.run(__name__, logger=["logger 1", "logger 2"])
|
|
129
159
|
```
|
|
160
|
+
### Human-in-the-loop
|
|
161
|
+
Use `pause` in flow control to pause the workflow and send a notification with custom message handler(s).
|
|
162
|
+
When run into `pause`, it will pause, send a message, and wait for human's response. Example of a Slack bot:
|
|
163
|
+
```python
|
|
164
|
+
|
|
165
|
+
def slack_bot(msg: str = "Hi"):
|
|
166
|
+
"""
|
|
167
|
+
a function that can be used as a notification handler function("msg")
|
|
168
|
+
:param msg: message to send
|
|
169
|
+
"""
|
|
170
|
+
from slack_sdk import WebClient
|
|
171
|
+
|
|
172
|
+
slack_token = "your slack token"
|
|
173
|
+
client = WebClient(token=slack_token)
|
|
174
|
+
|
|
175
|
+
my_user_id = "your user id" # replace with your actual Slack user ID
|
|
176
|
+
|
|
177
|
+
client.chat_postMessage(channel=my_user_id, text=msg)
|
|
178
|
+
|
|
179
|
+
import ivoryos
|
|
180
|
+
ivoryos.run(__name__, notification_handler=slack_bot)
|
|
181
|
+
```
|
|
182
|
+
|
|
130
183
|
### Directory Structure
|
|
131
184
|
|
|
132
|
-
Created automatically on first run:
|
|
185
|
+
Created automatically in the same working directory on the first run:
|
|
186
|
+
<details>
|
|
187
|
+
<summary>click to see the data folder structure</summary>
|
|
188
|
+
|
|
133
189
|
- **`ivoryos_data/`**:
|
|
134
|
-
- **`
|
|
135
|
-
- **`
|
|
136
|
-
- **`
|
|
137
|
-
- **`
|
|
138
|
-
- **`default.log`**: Application logs
|
|
139
|
-
- **`ivoryos.db`**: Local database
|
|
190
|
+
- **`config_csv/`**: Batch configuration `csv`
|
|
191
|
+
- **`pseudo_deck/`**: Offline deck `.pkl`
|
|
192
|
+
- **`results/`**: Execution results
|
|
193
|
+
- **`scripts/`**: Compiled workflows Python scripts
|
|
194
|
+
- **`default.log`**: Application logs
|
|
195
|
+
- **`ivoryos.db`**: Local database
|
|
196
|
+
</details>
|
|
197
|
+
|
|
140
198
|
---
|
|
141
|
-
## Demo
|
|
142
|
-
In the [abstract_sdl.py](https://gitlab.com/heingroup/ivoryos/-/blob/main/example/abstract_sdl_example/abstract_sdl.py)
|
|
143
|
-
```Python
|
|
144
|
-
ivoryos.run(__name__)
|
|
145
|
-
```
|
|
146
199
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
200
|
+
## Demo
|
|
201
|
+
Online demo at [demo.ivoryos.ai](https://demo.ivoryos.ai).
|
|
202
|
+
Local version in [abstract_sdl.py](https://gitlab.com/heingroup/ivoryos/-/blob/main/community/examples/abstract_sdl_example/abstract_sdl.py)
|
|
150
203
|
|
|
151
204
|
---
|
|
152
205
|
|
|
153
206
|
## Roadmap
|
|
154
207
|
|
|
155
|
-
- [x] Allow plugin pages ✅
|
|
156
|
-
- [x] pause, resume, abort current and pending workflows ✅
|
|
157
208
|
- [ ] dropdown input
|
|
158
209
|
- [ ] snapshot version control
|
|
159
|
-
- [ ] optimizer-agnostic
|
|
160
210
|
- [ ] check batch-config file compatibility
|
|
161
211
|
|
|
162
212
|
---
|
|
163
213
|
|
|
214
|
+
## Contributing
|
|
215
|
+
|
|
216
|
+
We welcome all contributions — from core improvements to new drivers, plugins, and real-world use cases.
|
|
217
|
+
See `CONTRIBUTING.md` for details and let us know you're interested: https://forms.gle/fPSvw5LEGrweUQUH8
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
164
221
|
## Citing
|
|
165
222
|
|
|
223
|
+
<details>
|
|
224
|
+
<summary>Click to see citations</summary>
|
|
225
|
+
|
|
166
226
|
If you find this project useful, please consider citing the following manuscript:
|
|
167
227
|
|
|
168
228
|
> Zhang, W., Hao, L., Lai, V. et al. [IvoryOS: an interoperable web interface for orchestrating Python-based self-driving laboratories.](https://www.nature.com/articles/s41467-025-60514-w) Nat Commun 16, 5182 (2025).
|
|
@@ -196,6 +256,8 @@ For an additional perspective related to the development of the tool, please see
|
|
|
196
256
|
url = {https://communities.springernature.com/posts/behind-ivoryos-empowering-scientists-to-harness-self-driving-labs-for-accelerated-discovery}
|
|
197
257
|
}
|
|
198
258
|
```
|
|
259
|
+
</details>
|
|
260
|
+
|
|
199
261
|
---
|
|
200
262
|
## Acknowledgements
|
|
201
|
-
Authors acknowledge Telescope Innovations Corp., Hein Lab members for their valuable suggestions and contributions.
|
|
263
|
+
Authors acknowledge Telescope Innovations Corp., UBC Hein Lab, and Acceleration Consortium members for their valuable suggestions and contributions.
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
docs/source/conf.py,sha256=hETfkDMTdj-NYgSI_671gVOVd7iwqU3tEeXnQe2xEWs,2004
|
|
2
|
+
ivoryos/__init__.py,sha256=gEvBO2y5TRq06Itjjej3iAcq73UsihqKPWcb2HykPwM,463
|
|
3
|
+
ivoryos/app.py,sha256=TG-RctBKj8VVOeXciYZ2s_ehP01PACwvLRfl2f3vF6w,5561
|
|
4
|
+
ivoryos/config.py,sha256=y3RxNjiIola9tK7jg-mHM8EzLMwiLwOzoisXkDvj0gA,2174
|
|
5
|
+
ivoryos/server.py,sha256=9zw3c4IGj3DCtjRzEdEG3N4uxAtRZA1fowQGW1d_y94,7208
|
|
6
|
+
ivoryos/socket_handlers.py,sha256=KSh8TxbLFTcmaa-lEb5rKJvOYjKgnGTtaKZST8Wh3b8,1326
|
|
7
|
+
ivoryos/version.py,sha256=6stz_YFBRP_lguydVylsTgSmj3VTb6WFq8Sp45WYQyY,22
|
|
8
|
+
ivoryos/optimizer/ax_optimizer.py,sha256=43w4ollMfWn1DPB4jJ15pc13XxIktWjlxHn9NX339go,8566
|
|
9
|
+
ivoryos/optimizer/base_optimizer.py,sha256=m_wXan_xPueUWNQi3-561Pe_060tsKuUcCTOOu0qu8s,2652
|
|
10
|
+
ivoryos/optimizer/baybe_optimizer.py,sha256=grXmePNuTQsCgRDhJGsAWQuSvvt0hqjhvzpyrZKYjD0,8502
|
|
11
|
+
ivoryos/optimizer/nimo_optimizer.py,sha256=ZevLJUhJKpwatyiOSmPEz3JAUZTdZNKEc_J7z1fAvVQ,7303
|
|
12
|
+
ivoryos/optimizer/registry.py,sha256=dLMo5cszcwa06hfBxdINQIGpkHtRe5-J3J7t76Jq6X0,306
|
|
13
|
+
ivoryos/routes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
|
+
ivoryos/routes/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
|
+
ivoryos/routes/auth/auth.py,sha256=AkYnyI4u5ySshpwPK8Z0KVEbR9X1BGdrqDu_IDea-AM,4348
|
|
16
|
+
ivoryos/routes/auth/templates/change_password.html,sha256=GfnytIPyhiLgnc2FzX-aXad1IV2I0nE3A-BQ4KVdaA0,1517
|
|
17
|
+
ivoryos/routes/auth/templates/login.html,sha256=WSRrKbdM_oobqSXFRTo-j9UlOgp6sYzS9tm7TqqPULI,1207
|
|
18
|
+
ivoryos/routes/auth/templates/signup.html,sha256=b5LTXtpfTSkSS7X8u1ldwQbbgEFTk6UNMAediA5BwBY,1465
|
|
19
|
+
ivoryos/routes/control/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
|
+
ivoryos/routes/control/control.py,sha256=0g23PoPPoTeMD7Szm3UAW10sEMLpSD-iTfASAZE4iIE,6442
|
|
21
|
+
ivoryos/routes/control/control_file.py,sha256=3fQ9R8EcdqKs_hABn2EqRAB1xC2DHAT_q_pwsMIDDQI,864
|
|
22
|
+
ivoryos/routes/control/control_new_device.py,sha256=oHbNUjTyv9yh-4FNTxkJqunaZbyH0RiiLoqIjLYUTEQ,5725
|
|
23
|
+
ivoryos/routes/control/utils.py,sha256=XlhhqAtOj7n3XfHPDxJ8TvCV2K2I2IixB0CBkl1QeQc,1242
|
|
24
|
+
ivoryos/routes/control/templates/controllers.html,sha256=5hF3zcx5Rpy0Zaoq-5YGrR_TvPD9MGIa30fI4smEii0,9702
|
|
25
|
+
ivoryos/routes/control/templates/controllers_new.html,sha256=eVeLABT39DWOIYrwWClw7sAD3lCoAGCznygPgFbQoRc,5945
|
|
26
|
+
ivoryos/routes/data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
27
|
+
ivoryos/routes/data/data.py,sha256=MR7Vu7HB9dWjVr3cPwJbfIvTWs1Rqh2uXOPWIEFivzs,6399
|
|
28
|
+
ivoryos/routes/data/templates/workflow_database.html,sha256=ofvHcovpwmJXo1SFiSrL8I9kLU_3U1UxsJUUrQ2CJUU,4878
|
|
29
|
+
ivoryos/routes/data/templates/workflow_view.html,sha256=NljmhBVBUYfoOkuUWpVYUYsQkBFPbKlmzYFmFY7KHvY,13011
|
|
30
|
+
ivoryos/routes/data/templates/components/step_card.html,sha256=fxTg-1_Vw8b8QzAUXsPMfFgTmcHo8esAdLTUvK1cKVI,3726
|
|
31
|
+
ivoryos/routes/design/__init__.py,sha256=zS3HXKaw0ALL5n6t_W1rUz5Uj5_tTQ-Y1VMXyzewvR0,113
|
|
32
|
+
ivoryos/routes/design/design.py,sha256=n7768F60Nx04ST7yZSoKiNsoRFCwKjFsZvdzveoFLq0,20042
|
|
33
|
+
ivoryos/routes/design/design_file.py,sha256=MVIc5uGSaGxZhs86hfPjX2n0iy1OcXeLq7b9Ucdg4VQ,2115
|
|
34
|
+
ivoryos/routes/design/design_step.py,sha256=W2mFKMOkbSDBk4Gx9wRYcwjeru2mj-ub1l_K4hPRuZY,6031
|
|
35
|
+
ivoryos/routes/design/templates/experiment_builder.html,sha256=B5NHnOfTbC8xIfmhIQZlQn1_eTd1Ld6c_q5xoMed_Hs,1747
|
|
36
|
+
ivoryos/routes/design/templates/components/action_form.html,sha256=kXJOrJLbFsMHHWVSuMQHpt1xFrUMnwgzTG8e6Qfn0Cg,3042
|
|
37
|
+
ivoryos/routes/design/templates/components/actions_panel.html,sha256=jHTR58saTUIZInBdC-vLc1ZTbStLiULeWbupjB4hQzo,977
|
|
38
|
+
ivoryos/routes/design/templates/components/autofill_toggle.html,sha256=CRVQUHoQT7sOSO5-Vax54ImHdT4G_mEgqR5OQkeUwK8,617
|
|
39
|
+
ivoryos/routes/design/templates/components/canvas.html,sha256=bKLCJaG1B36Yy9Vsnz4P5qiX4BPdfaGe9JeQQzu9rsI,268
|
|
40
|
+
ivoryos/routes/design/templates/components/canvas_footer.html,sha256=5VRRacMZbzx0hUej0NPP-PmXM_AtUqduHzDS7a60cQY,435
|
|
41
|
+
ivoryos/routes/design/templates/components/canvas_header.html,sha256=7iIzLDGHX7MnmBbf98nWtLDprbeIgoNV4dJUO1zE4Tc,3598
|
|
42
|
+
ivoryos/routes/design/templates/components/canvas_main.html,sha256=nLEtp3U2YtfJwob1kR8ua8-UVdu9hwc6z1L5UMNVz8c,1524
|
|
43
|
+
ivoryos/routes/design/templates/components/deck_selector.html,sha256=ryTRpljYezo0AzGLCJu_qOMokjjnft3GIxddmNGtBA0,657
|
|
44
|
+
ivoryos/routes/design/templates/components/edit_action_form.html,sha256=1xL0wueewp_Hdua9DzYJBry09Qcchh4XgGmt4qPCwnc,2782
|
|
45
|
+
ivoryos/routes/design/templates/components/info_modal.html,sha256=ifetOzL124a7NZFsJhjmm04fTaKyibHJ32I0ek0zPoA,19156
|
|
46
|
+
ivoryos/routes/design/templates/components/instruments_panel.html,sha256=tRKd-wOqKjaMJCLuGgRmHtxIgSjklhBkuX8arm5aTCU,4268
|
|
47
|
+
ivoryos/routes/design/templates/components/modals.html,sha256=6Dl8I8oD4ln7kK8C5e92pFVVH5KDte-vVTL0U_6NSTg,306
|
|
48
|
+
ivoryos/routes/design/templates/components/python_code_overlay.html,sha256=Iyk-wOzk1x2997kqGt3uJONh_rpi5sCUs7VCRDg8Q9U,2150
|
|
49
|
+
ivoryos/routes/design/templates/components/sidebar.html,sha256=A6dRo53zIB6QJVrRLJcBZHUNJ3qpYPnR3kWxM8gTkjw,501
|
|
50
|
+
ivoryos/routes/design/templates/components/text_to_code_panel.html,sha256=d-omdXk-PXAR5AyWPr4Rc4pqsebZOiTiMrnz3pPCnUY,1197
|
|
51
|
+
ivoryos/routes/design/templates/components/modals/drop_modal.html,sha256=LPxcycSiBjdQbajYOegjMQEi7ValcaczGoWmW8Sz3Ms,779
|
|
52
|
+
ivoryos/routes/design/templates/components/modals/json_modal.html,sha256=R-SeEdhtuDVbwOWYYH_hCdpul7y4ybCWoNwVIO5j49s,1122
|
|
53
|
+
ivoryos/routes/design/templates/components/modals/new_script_modal.html,sha256=pxZdWWDgI52VsTFzz6pIM9m_dTwR6jARcvCYQ6fV3Lc,937
|
|
54
|
+
ivoryos/routes/design/templates/components/modals/rename_modal.html,sha256=40rLNF9JprdXekB3mv_S3OdqVuQYOe-BZSCgOnIkxJQ,1202
|
|
55
|
+
ivoryos/routes/design/templates/components/modals/saveas_modal.html,sha256=N5PEqUuK3qxDFbtDKFnzHwhLarQLPHiX-XQAdQPL1AU,1555
|
|
56
|
+
ivoryos/routes/execute/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
57
|
+
ivoryos/routes/execute/execute.py,sha256=hf64lnhiFxjAXy7b5XsFbORKAlkFTQEtqbfrhdYplXk,14603
|
|
58
|
+
ivoryos/routes/execute/execute_file.py,sha256=TelWYV295p4ZPhkUDJSVxfYROfVaKodEmDPTS2plQHI,2816
|
|
59
|
+
ivoryos/routes/execute/templates/experiment_run.html,sha256=fvHRrG5ii1v5zPHL9N_ljDGHUo3t3amiproVPtnOFVE,944
|
|
60
|
+
ivoryos/routes/execute/templates/components/error_modal.html,sha256=5Dmp9V0Ys6781Y-pKn_mc4N9J46c8EwIkjkHX22xCsw,1025
|
|
61
|
+
ivoryos/routes/execute/templates/components/logging_panel.html,sha256=-elcawnE4CaumyivzxaHW3S5xSv5CgZtXt0OHljlits,2554
|
|
62
|
+
ivoryos/routes/execute/templates/components/progress_panel.html,sha256=-nX76aFLxSOiYgI1xMjznC9rDYF-Vb92TmfjXYpBtps,1323
|
|
63
|
+
ivoryos/routes/execute/templates/components/run_panel.html,sha256=CmK-LYJ4K6RonHn6l9eJkqRw0XQizThOugxiXZonSDs,373
|
|
64
|
+
ivoryos/routes/execute/templates/components/run_tabs.html,sha256=HZUBQxJIigp9DgMvcxZOoR_pqz4ZW9kpxhbCjW6_dRg,2724
|
|
65
|
+
ivoryos/routes/execute/templates/components/tab_bayesian.html,sha256=oxtXWb5rUo88yTyyB9dZNxfzi0YanKy8NS9SBuiZKbI,24864
|
|
66
|
+
ivoryos/routes/execute/templates/components/tab_configuration.html,sha256=uZQfi8QLIGbCvgQR2M-fuFAjW4id9ANlYgBUbTJfaZw,14878
|
|
67
|
+
ivoryos/routes/execute/templates/components/tab_repeat.html,sha256=s8Q9Vuztf_h0vy97CBjHwgMdbaLQwall6StVdbL6FY8,989
|
|
68
|
+
ivoryos/routes/library/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
69
|
+
ivoryos/routes/library/library.py,sha256=QKeP34COssXDipn3d9yEn7pVPYbPb-eFg_X0C8JTvVQ,5387
|
|
70
|
+
ivoryos/routes/library/templates/library.html,sha256=kMaQphkoGQdxIRcQmVcEIn8eghuv2AAClHpo7jGDVv8,4021
|
|
71
|
+
ivoryos/routes/main/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
72
|
+
ivoryos/routes/main/main.py,sha256=3hM85DB0edtjTh0YYVqwfWe0-gL2dX7F6HooTrCRvvs,1856
|
|
73
|
+
ivoryos/routes/main/templates/help.html,sha256=IOktMEsOPk0SCiMBXZ4mpffClERAyX8W82fel71M3M0,9370
|
|
74
|
+
ivoryos/routes/main/templates/home.html,sha256=BDvwkVthxniQ157H6E2hgYHT1Vv1GVBwu6dQejtzwoo,4633
|
|
75
|
+
ivoryos/static/favicon.ico,sha256=RhlrPtfITOkzC9BjP1UB1V5L9Oyp6NwNtWeMcGOnpyc,15406
|
|
76
|
+
ivoryos/static/ivoryos_logo.png,sha256=I-1POqhLdPveruxsFbKhKUKAXspHfyxvowpCRFxEzvc,11656
|
|
77
|
+
ivoryos/static/logo.webp,sha256=lXgfQR-4mHTH83k7VV9iB54-oC2ipe6uZvbwdOnLETc,14974
|
|
78
|
+
ivoryos/static/style.css,sha256=zQVx35A5g6JMJ-K84-6fSKtzXGjp_p5ZVG6KLHPM2IE,4021
|
|
79
|
+
ivoryos/static/gui_annotation/Slide1.png,sha256=Lm4gdOkUF5HIUFaB94tl6koQVkzpitKj43GXV_XYMMc,121727
|
|
80
|
+
ivoryos/static/gui_annotation/Slide2.PNG,sha256=z3wQ9oVgg4JTWVLQGKK_KhtepRHUYP1e05XUWGT2A0I,118761
|
|
81
|
+
ivoryos/static/js/action_handlers.js,sha256=VC_kHFk0KKE7Q20PNRSIyxUDgrl_-54Tr2w55JvlhCU,11512
|
|
82
|
+
ivoryos/static/js/db_delete.js,sha256=l67fqUaN_FVDaL7v91Hd7LyRbxnqXx9nyjF34-7aewY,561
|
|
83
|
+
ivoryos/static/js/overlay.js,sha256=dPxop19es0E0ZUSY3d_4exIk7CJuQEnlW5uTt5fZfzI,483
|
|
84
|
+
ivoryos/static/js/script_metadata.js,sha256=m8VYZ8OGT2oTx1kXMXq60bKQI9WCbJNkzcFDzLvRuGc,1188
|
|
85
|
+
ivoryos/static/js/socket_handler.js,sha256=5MGLj-nNA3053eBcko4MZAB5zq2mkgr1g__c_rr3cE8,6849
|
|
86
|
+
ivoryos/static/js/sortable_card.js,sha256=ifmlGe3yy0U_KzMphV4ClRhK2DLOvkELYMlq1vECuac,807
|
|
87
|
+
ivoryos/static/js/sortable_design.js,sha256=ZezDxKtFxqmqSLfFy-HJ61LvQSXL36imYud6TIREs3U,5503
|
|
88
|
+
ivoryos/static/js/ui_state.js,sha256=XYsOcfGlduqLlqHySvPrRrR50CiAsml51duqneigsRY,3368
|
|
89
|
+
ivoryos/templates/base.html,sha256=uJ5lcYUCImiOasyP3g1-dQ-9Fdkff0Re6uUEGud2ySo,12011
|
|
90
|
+
ivoryos/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
91
|
+
ivoryos/utils/bo_campaign.py,sha256=Z4WXJwbf1Hhg1dtlvhWHncBiq9Cuaf0BiqR60bWy36U,10038
|
|
92
|
+
ivoryos/utils/client_proxy.py,sha256=74G3HAuq50iEHkSvlMZFmQaukm613FbRgOdzO_T3dMg,10191
|
|
93
|
+
ivoryos/utils/db_models.py,sha256=3cO5EcekYMDQQl3LujXR_8i0Lrgvs34BX34KUtafqwY,37994
|
|
94
|
+
ivoryos/utils/decorators.py,sha256=pcD8WijFjNfkNvr-wmJSWTHn_OqCOCpAO-7w_qg9_xM,1051
|
|
95
|
+
ivoryos/utils/form.py,sha256=MNIvmx4RhDukFACwyrXS-88tBeMKeNdxVba_KzOab_Y,23469
|
|
96
|
+
ivoryos/utils/global_config.py,sha256=leYoEXvAS0AH4xQpYsqu4HI9CJ9-wiLM-pIh_bEG4Ak,3087
|
|
97
|
+
ivoryos/utils/llm_agent.py,sha256=-lVCkjPlpLues9sNTmaT7bT4sdhWvV2DiojNwzB2Lcw,6422
|
|
98
|
+
ivoryos/utils/nest_script.py,sha256=kc4V0cBca5TviqQuquMFBF8Q5IjqKqj2R6g-_N6gMak,10645
|
|
99
|
+
ivoryos/utils/py_to_json.py,sha256=ZtejHgwdEAUCVVMYeVNR8G7ceLINue294q6WpiJ6jn0,9734
|
|
100
|
+
ivoryos/utils/script_runner.py,sha256=jjgloXAbcZprZmjeS-IXnw-KCIFQWKjPYqsv_PrN9Qs,32249
|
|
101
|
+
ivoryos/utils/serilize.py,sha256=lkBhkz8r2bLmz2_xOb0c4ptSSOqjIu6krj5YYK4Nvj8,6784
|
|
102
|
+
ivoryos/utils/task_runner.py,sha256=AnY0DwS5ytU5f7OfCFlNJ72yAonA3tla4UhC7MOM3mw,4818
|
|
103
|
+
ivoryos/utils/utils.py,sha256=n0tQKIsEIVqrWEjN8vJHmobDcZOPmvyrHxoBAdaQr4g,15414
|
|
104
|
+
ivoryos-1.4.4.dist-info/licenses/LICENSE,sha256=p2c8S8i-8YqMpZCJnadLz1-ofxnRMILzz6NCMIypRag,1084
|
|
105
|
+
tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
106
|
+
tests/conftest.py,sha256=u2sQ6U-Lghyl7et1Oz6J2E5VZ47VINKcjRM_2leAE2s,3627
|
|
107
|
+
tests/integration/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
108
|
+
tests/integration/test_route_auth.py,sha256=l3ZDqr0oiCWS3yYSXGK5yMP6qI2t7Sv5I9zoYTkiyQU,2754
|
|
109
|
+
tests/integration/test_route_control.py,sha256=YYIll84bTUEKiAxFiFSz6LF3fTldPNfCtHs0IR3mSdM,3935
|
|
110
|
+
tests/integration/test_route_database.py,sha256=mS026W_hEuCTMpSkdRWvM-f4MYykK_6nRDJ4K5a7QA0,2342
|
|
111
|
+
tests/integration/test_route_design.py,sha256=PJAvGRiCY6B53Pu1v5vPAVHHsuaqRmRKk2eesSNshLU,1157
|
|
112
|
+
tests/integration/test_route_main.py,sha256=bmuf8Y_9CRWhiLLf4up11ltEd5YCdsLx6I-o26VGDEw,1228
|
|
113
|
+
tests/integration/test_sockets.py,sha256=4ZyFyExm7a-DYzVqpzEONpWeb1a0IT68wyFaQu0rY_Y,925
|
|
114
|
+
tests/unit/test_type_conversion.py,sha256=zJjlBZPF4U0TJDECdFgYHPf-7lIEoQ3uy015RIRgTTA,2346
|
|
115
|
+
tests/unit/test_util.py,sha256=XSrZ3V2gKYhr1qEniWU9RY2Rm7PnO06ZCM05GaElncI,76
|
|
116
|
+
ivoryos-1.4.4.dist-info/METADATA,sha256=NEmN9NwX6lx6QAbX4JQciWSJfRNrmoFfP5qdjA6QAPY,9135
|
|
117
|
+
ivoryos-1.4.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
118
|
+
ivoryos-1.4.4.dist-info/top_level.txt,sha256=ZxZvj1N-GvvGOSN8pBy9SU9Ohf3ehzJfmGDh-M-0YuI,19
|
|
119
|
+
ivoryos-1.4.4.dist-info/RECORD,,
|
tests/__init__.py
ADDED
|
File without changes
|