ivoryos 1.0.9__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 +17 -207
- ivoryos/app.py +154 -0
- ivoryos/config.py +1 -0
- ivoryos/optimizer/ax_optimizer.py +191 -0
- ivoryos/optimizer/base_optimizer.py +84 -0
- ivoryos/optimizer/baybe_optimizer.py +193 -0
- ivoryos/optimizer/nimo_optimizer.py +173 -0
- ivoryos/optimizer/registry.py +11 -0
- ivoryos/routes/auth/auth.py +43 -14
- ivoryos/routes/auth/templates/change_password.html +32 -0
- ivoryos/routes/control/control.py +101 -366
- ivoryos/routes/control/control_file.py +33 -0
- ivoryos/routes/control/control_new_device.py +152 -0
- ivoryos/routes/control/templates/controllers.html +193 -0
- ivoryos/routes/control/templates/controllers_new.html +112 -0
- ivoryos/routes/control/utils.py +40 -0
- ivoryos/routes/data/data.py +197 -0
- ivoryos/routes/data/templates/components/step_card.html +78 -0
- ivoryos/routes/{database/templates/database → data/templates}/workflow_database.html +14 -8
- ivoryos/routes/data/templates/workflow_view.html +360 -0
- ivoryos/routes/design/__init__.py +4 -0
- ivoryos/routes/design/design.py +348 -657
- ivoryos/routes/design/design_file.py +68 -0
- ivoryos/routes/design/design_step.py +171 -0
- ivoryos/routes/design/templates/components/action_form.html +53 -0
- ivoryos/routes/design/templates/components/actions_panel.html +25 -0
- ivoryos/routes/design/templates/components/autofill_toggle.html +10 -0
- ivoryos/routes/design/templates/components/canvas.html +5 -0
- ivoryos/routes/design/templates/components/canvas_footer.html +9 -0
- ivoryos/routes/design/templates/components/canvas_header.html +75 -0
- ivoryos/routes/design/templates/components/canvas_main.html +39 -0
- ivoryos/routes/design/templates/components/deck_selector.html +10 -0
- ivoryos/routes/design/templates/components/edit_action_form.html +53 -0
- ivoryos/routes/design/templates/components/info_modal.html +318 -0
- ivoryos/routes/design/templates/components/instruments_panel.html +88 -0
- ivoryos/routes/design/templates/components/modals/drop_modal.html +17 -0
- ivoryos/routes/design/templates/components/modals/json_modal.html +22 -0
- ivoryos/routes/design/templates/components/modals/new_script_modal.html +17 -0
- ivoryos/routes/design/templates/components/modals/rename_modal.html +23 -0
- ivoryos/routes/design/templates/components/modals/saveas_modal.html +27 -0
- ivoryos/routes/design/templates/components/modals.html +6 -0
- ivoryos/routes/design/templates/components/python_code_overlay.html +56 -0
- ivoryos/routes/design/templates/components/sidebar.html +15 -0
- ivoryos/routes/design/templates/components/text_to_code_panel.html +20 -0
- ivoryos/routes/design/templates/experiment_builder.html +44 -0
- ivoryos/routes/execute/__init__.py +0 -0
- ivoryos/routes/execute/execute.py +377 -0
- ivoryos/routes/execute/execute_file.py +78 -0
- ivoryos/routes/execute/templates/components/error_modal.html +20 -0
- ivoryos/routes/execute/templates/components/logging_panel.html +56 -0
- ivoryos/routes/execute/templates/components/progress_panel.html +27 -0
- ivoryos/routes/execute/templates/components/run_panel.html +9 -0
- ivoryos/routes/execute/templates/components/run_tabs.html +60 -0
- ivoryos/routes/execute/templates/components/tab_bayesian.html +520 -0
- ivoryos/routes/execute/templates/components/tab_configuration.html +383 -0
- ivoryos/routes/execute/templates/components/tab_repeat.html +18 -0
- ivoryos/routes/execute/templates/experiment_run.html +30 -0
- ivoryos/routes/library/__init__.py +0 -0
- ivoryos/routes/library/library.py +157 -0
- ivoryos/routes/{database/templates/database/scripts_database.html → library/templates/library.html} +32 -23
- ivoryos/routes/main/main.py +31 -3
- ivoryos/routes/main/templates/{main/home.html → home.html} +4 -4
- ivoryos/server.py +180 -0
- ivoryos/socket_handlers.py +52 -0
- ivoryos/static/ivoryos_logo.png +0 -0
- ivoryos/static/js/action_handlers.js +384 -0
- ivoryos/static/js/db_delete.js +23 -0
- ivoryos/static/js/script_metadata.js +39 -0
- ivoryos/static/js/socket_handler.js +40 -5
- ivoryos/static/js/sortable_design.js +107 -56
- ivoryos/static/js/ui_state.js +114 -0
- ivoryos/templates/base.html +67 -8
- ivoryos/utils/bo_campaign.py +180 -3
- ivoryos/utils/client_proxy.py +267 -36
- ivoryos/utils/db_models.py +300 -65
- ivoryos/utils/decorators.py +34 -0
- ivoryos/utils/form.py +63 -29
- ivoryos/utils/global_config.py +34 -1
- ivoryos/utils/nest_script.py +314 -0
- ivoryos/utils/py_to_json.py +295 -0
- ivoryos/utils/script_runner.py +599 -165
- ivoryos/utils/serilize.py +201 -0
- ivoryos/utils/task_runner.py +71 -21
- ivoryos/utils/utils.py +50 -6
- ivoryos/version.py +1 -1
- ivoryos-1.4.4.dist-info/METADATA +263 -0
- ivoryos-1.4.4.dist-info/RECORD +119 -0
- {ivoryos-1.0.9.dist-info → ivoryos-1.4.4.dist-info}/WHEEL +1 -1
- {ivoryos-1.0.9.dist-info → ivoryos-1.4.4.dist-info}/top_level.txt +1 -0
- tests/unit/test_type_conversion.py +42 -0
- tests/unit/test_util.py +3 -0
- ivoryos/routes/control/templates/control/controllers.html +0 -78
- ivoryos/routes/control/templates/control/controllers_home.html +0 -55
- ivoryos/routes/control/templates/control/controllers_new.html +0 -89
- ivoryos/routes/database/database.py +0 -306
- ivoryos/routes/database/templates/database/step_card.html +0 -7
- ivoryos/routes/database/templates/database/workflow_view.html +0 -130
- ivoryos/routes/design/templates/design/experiment_builder.html +0 -521
- ivoryos/routes/design/templates/design/experiment_run.html +0 -558
- ivoryos-1.0.9.dist-info/METADATA +0 -218
- ivoryos-1.0.9.dist-info/RECORD +0 -61
- /ivoryos/routes/auth/templates/{auth/login.html → login.html} +0 -0
- /ivoryos/routes/auth/templates/{auth/signup.html → signup.html} +0 -0
- /ivoryos/routes/{database → data}/__init__.py +0 -0
- /ivoryos/routes/main/templates/{main/help.html → help.html} +0 -0
- {ivoryos-1.0.9.dist-info → ivoryos-1.4.4.dist-info/licenses}/LICENSE +0 -0
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import ast
|
|
2
|
+
import json
|
|
3
|
+
import uuid
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
from ivoryos.utils.global_config import GlobalConfig
|
|
7
|
+
|
|
8
|
+
global_config = GlobalConfig()
|
|
9
|
+
|
|
10
|
+
if global_config.building_blocks:
|
|
11
|
+
building_blocks = {
|
|
12
|
+
inner_key: f"{block_key}.{inner_key}"
|
|
13
|
+
for block_key, block_value in global_config.building_blocks.items()
|
|
14
|
+
for inner_key in block_value.keys()
|
|
15
|
+
}
|
|
16
|
+
else:
|
|
17
|
+
building_blocks = {}
|
|
18
|
+
|
|
19
|
+
def generate_uuid():
|
|
20
|
+
return int(str(uuid.uuid4().int)[:15])
|
|
21
|
+
|
|
22
|
+
def infer_type(value):
|
|
23
|
+
if isinstance(value, bool):
|
|
24
|
+
return "bool"
|
|
25
|
+
elif isinstance(value, int):
|
|
26
|
+
return "int"
|
|
27
|
+
elif isinstance(value, float):
|
|
28
|
+
return "float"
|
|
29
|
+
elif isinstance(value, str):
|
|
30
|
+
return "str"
|
|
31
|
+
elif isinstance(value, (ast.Name, str)) and str(value).startswith("#"):
|
|
32
|
+
return "float" # default fallback for variables
|
|
33
|
+
else:
|
|
34
|
+
return "unknown"
|
|
35
|
+
|
|
36
|
+
def convert_to_cards(source_code: str):
|
|
37
|
+
tree = ast.parse(source_code)
|
|
38
|
+
cards = []
|
|
39
|
+
card_id = 1
|
|
40
|
+
block_stack = [] # to track control flow UUIDs
|
|
41
|
+
|
|
42
|
+
def new_id():
|
|
43
|
+
nonlocal card_id
|
|
44
|
+
val = card_id
|
|
45
|
+
card_id += 1
|
|
46
|
+
return val
|
|
47
|
+
|
|
48
|
+
def add_card(card):
|
|
49
|
+
cards.append(card)
|
|
50
|
+
|
|
51
|
+
def is_supported_assignment(node):
|
|
52
|
+
return (
|
|
53
|
+
isinstance(node.targets[0], ast.Name) and
|
|
54
|
+
isinstance(node.value, ast.Constant)
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
class CardVisitor(ast.NodeVisitor):
|
|
58
|
+
def __init__(self):
|
|
59
|
+
self.defined_types = {} # <-- always exists
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def visit_FunctionDef(self, node):
|
|
63
|
+
self.defined_types = {
|
|
64
|
+
arg.arg: ast.unparse(arg.annotation) if arg.annotation else "float"
|
|
65
|
+
for arg in node.args.args
|
|
66
|
+
}
|
|
67
|
+
for stmt in node.body:
|
|
68
|
+
self.visit(stmt)
|
|
69
|
+
|
|
70
|
+
def visit_If(self, node):
|
|
71
|
+
uuid_ = generate_uuid()
|
|
72
|
+
block_stack.append(("if", uuid_))
|
|
73
|
+
|
|
74
|
+
add_card({
|
|
75
|
+
"action": "if",
|
|
76
|
+
"arg_types": {"statement": ""},
|
|
77
|
+
"args": {"statement": ast.unparse(node.test)},
|
|
78
|
+
"id": new_id(),
|
|
79
|
+
"instrument": "if",
|
|
80
|
+
"return": "",
|
|
81
|
+
"uuid": uuid_
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
for stmt in node.body:
|
|
85
|
+
self.visit(stmt)
|
|
86
|
+
|
|
87
|
+
if node.orelse:
|
|
88
|
+
add_card({
|
|
89
|
+
"action": "else",
|
|
90
|
+
"args": {},
|
|
91
|
+
"id": new_id(),
|
|
92
|
+
"instrument": "if",
|
|
93
|
+
"return": "",
|
|
94
|
+
"uuid": uuid_
|
|
95
|
+
})
|
|
96
|
+
for stmt in node.orelse:
|
|
97
|
+
self.visit(stmt)
|
|
98
|
+
|
|
99
|
+
_, block_uuid = block_stack.pop()
|
|
100
|
+
add_card({
|
|
101
|
+
"action": "endif",
|
|
102
|
+
"args": {},
|
|
103
|
+
"id": new_id(),
|
|
104
|
+
"instrument": "if",
|
|
105
|
+
"return": "",
|
|
106
|
+
"uuid": block_uuid
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
def visit_While(self, node):
|
|
110
|
+
uuid_ = generate_uuid()
|
|
111
|
+
block_stack.append(("while", uuid_))
|
|
112
|
+
|
|
113
|
+
add_card({
|
|
114
|
+
"action": "while",
|
|
115
|
+
"arg_types": {"statement": ""},
|
|
116
|
+
"args": {"statement": ast.unparse(node.test)},
|
|
117
|
+
"id": new_id(),
|
|
118
|
+
"instrument": "while",
|
|
119
|
+
"return": "",
|
|
120
|
+
"uuid": uuid_
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
for stmt in node.body:
|
|
124
|
+
self.visit(stmt)
|
|
125
|
+
|
|
126
|
+
_, block_uuid = block_stack.pop()
|
|
127
|
+
add_card({
|
|
128
|
+
"action": "endwhile",
|
|
129
|
+
"args": {},
|
|
130
|
+
"id": new_id(),
|
|
131
|
+
"instrument": "while",
|
|
132
|
+
"return": "",
|
|
133
|
+
"uuid": block_uuid
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
def visit_Assign(self, node):
|
|
137
|
+
if is_supported_assignment(node):
|
|
138
|
+
var_name = node.targets[0].id
|
|
139
|
+
value = node.value.value
|
|
140
|
+
add_card({
|
|
141
|
+
"action": var_name,
|
|
142
|
+
"arg_types": {"statement": infer_type(value)},
|
|
143
|
+
"args": {"statement": value},
|
|
144
|
+
"id": new_id(),
|
|
145
|
+
"instrument": "variable",
|
|
146
|
+
"return": "",
|
|
147
|
+
"uuid": generate_uuid()
|
|
148
|
+
})
|
|
149
|
+
elif isinstance(node.value, ast.Await):
|
|
150
|
+
self.handle_call(node.value.value, ret_var=node.targets[0].id, awaited=True)
|
|
151
|
+
|
|
152
|
+
elif isinstance(node.value, ast.Call):
|
|
153
|
+
self.handle_call(node.value, ret_var=node.targets[0].id)
|
|
154
|
+
|
|
155
|
+
def visit_Expr(self, node):
|
|
156
|
+
if isinstance(node.value, ast.Await):
|
|
157
|
+
# node.value is ast.Await
|
|
158
|
+
self.handle_call(node.value.value, awaited=True)
|
|
159
|
+
elif isinstance(node.value, ast.Call):
|
|
160
|
+
self.handle_call(node.value)
|
|
161
|
+
|
|
162
|
+
def handle_call(self, node, ret_var="", awaited=False):
|
|
163
|
+
func_parts = []
|
|
164
|
+
f = node.func
|
|
165
|
+
while isinstance(f, ast.Attribute):
|
|
166
|
+
func_parts.insert(0, f.attr)
|
|
167
|
+
f = f.value
|
|
168
|
+
if isinstance(f, ast.Name):
|
|
169
|
+
func_parts.insert(0, f.id)
|
|
170
|
+
|
|
171
|
+
full_func_name = ".".join(func_parts)
|
|
172
|
+
|
|
173
|
+
# Check if this is a deck call or a building block
|
|
174
|
+
if full_func_name.startswith("deck.") or full_func_name.startswith("blocks."):
|
|
175
|
+
instrument = ".".join(func_parts[:-1])
|
|
176
|
+
action = func_parts[-1]
|
|
177
|
+
# not starting with deck or block, check if it's a decorated function
|
|
178
|
+
# ["general", "action"] or ["action"]
|
|
179
|
+
elif func_parts[-1] in building_blocks.keys():
|
|
180
|
+
instrument = building_blocks.get(func_parts[-1])
|
|
181
|
+
action = func_parts[-1]
|
|
182
|
+
else:
|
|
183
|
+
# ignore other calls
|
|
184
|
+
return
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
# --- special case for time.sleep ---
|
|
189
|
+
if instrument == "time" and action == "sleep":
|
|
190
|
+
wait_value = None
|
|
191
|
+
if node.args:
|
|
192
|
+
arg_node = node.args[0]
|
|
193
|
+
if isinstance(arg_node, ast.Constant):
|
|
194
|
+
wait_value = arg_node.value
|
|
195
|
+
elif isinstance(arg_node, ast.Name):
|
|
196
|
+
wait_value = f"#{arg_node.id}"
|
|
197
|
+
else:
|
|
198
|
+
wait_value = ast.unparse(arg_node)
|
|
199
|
+
|
|
200
|
+
add_card({
|
|
201
|
+
"action": "wait",
|
|
202
|
+
"arg_types": {"statement": infer_type(wait_value)},
|
|
203
|
+
"args": {"statement": wait_value},
|
|
204
|
+
"id": new_id(),
|
|
205
|
+
"instrument": "wait",
|
|
206
|
+
"return": ret_var,
|
|
207
|
+
"uuid": generate_uuid()
|
|
208
|
+
})
|
|
209
|
+
return
|
|
210
|
+
# -----------------------------------
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
args = {}
|
|
214
|
+
arg_types = {}
|
|
215
|
+
|
|
216
|
+
for kw in node.keywords:
|
|
217
|
+
if kw.arg is None and isinstance(kw.value, ast.Dict):
|
|
218
|
+
for k_node, v_node in zip(kw.value.keys, kw.value.values):
|
|
219
|
+
key = k_node.value if isinstance(k_node, ast.Constant) else ast.unparse(k_node)
|
|
220
|
+
if isinstance(v_node, ast.Constant):
|
|
221
|
+
value = v_node.value
|
|
222
|
+
elif isinstance(v_node, ast.Name):
|
|
223
|
+
value = f"#{v_node.id}"
|
|
224
|
+
else:
|
|
225
|
+
value = ast.unparse(v_node)
|
|
226
|
+
args[key] = value
|
|
227
|
+
arg_types[key] = infer_type(value)
|
|
228
|
+
else:
|
|
229
|
+
if isinstance(kw.value, ast.Constant):
|
|
230
|
+
value = kw.value.value
|
|
231
|
+
elif isinstance(kw.value, ast.Name):
|
|
232
|
+
value = f"#{kw.value.id}"
|
|
233
|
+
else:
|
|
234
|
+
value = ast.unparse(kw.value)
|
|
235
|
+
args[kw.arg] = value
|
|
236
|
+
arg_types[kw.arg] = (
|
|
237
|
+
self.defined_types.get(kw.value.id, "float")
|
|
238
|
+
if isinstance(kw.value, ast.Name)
|
|
239
|
+
else infer_type(value)
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
card = {
|
|
243
|
+
"action": action,
|
|
244
|
+
"arg_types": arg_types,
|
|
245
|
+
"args": args,
|
|
246
|
+
"id": new_id(),
|
|
247
|
+
"instrument": instrument,
|
|
248
|
+
"return": ret_var,
|
|
249
|
+
"uuid": generate_uuid()
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if awaited:
|
|
253
|
+
card["coroutine"] = True # mark as coroutine if awaited
|
|
254
|
+
|
|
255
|
+
add_card(card)
|
|
256
|
+
|
|
257
|
+
CardVisitor().visit(tree)
|
|
258
|
+
return cards
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
if __name__ == "__main__":
|
|
262
|
+
test = '''def workflow_dynamic(solid_amount_mg, methanol_amount_ml):
|
|
263
|
+
"""
|
|
264
|
+
SDL workflow: dose solid, add methanol, equilibrate, and analyze
|
|
265
|
+
|
|
266
|
+
Args:
|
|
267
|
+
solid_amount_mg (float): Amount of solid to dose in mg
|
|
268
|
+
methanol_amount_ml (float): Amount of methanol to dose in ml
|
|
269
|
+
|
|
270
|
+
Returns:
|
|
271
|
+
dict: Results containing analysis data
|
|
272
|
+
"""
|
|
273
|
+
# Step 1: Dose solid material
|
|
274
|
+
deck.sdl.dose_solid(amount_in_mg=solid_amount_mg)
|
|
275
|
+
|
|
276
|
+
# Step 2: Add methanol solvent
|
|
277
|
+
deck.sdl.dose_solvent(solvent_name='Methanol', amount_in_ml=methanol_amount_ml)
|
|
278
|
+
|
|
279
|
+
# Step 3: Equilibrate at room temperature (assuming ~23°C) for 20 seconds
|
|
280
|
+
deck.sdl.equilibrate(temp=23.0, duration=20.0)
|
|
281
|
+
|
|
282
|
+
# Step 4: Analyze the sample
|
|
283
|
+
analysis_results = deck.sdl.analyze(param_1=1, param_2=2)
|
|
284
|
+
|
|
285
|
+
# test block
|
|
286
|
+
result = blocks.general.test(**{'a': 1, 'b': 2})
|
|
287
|
+
|
|
288
|
+
# Brief pause for system stability
|
|
289
|
+
time.sleep(1.0)
|
|
290
|
+
|
|
291
|
+
# Return only analysis results
|
|
292
|
+
return {'analysis_results': analysis_results}
|
|
293
|
+
'''
|
|
294
|
+
from pprint import pprint
|
|
295
|
+
pprint(json.dumps(convert_to_cards(test)))
|