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.
Files changed (107) hide show
  1. docs/source/conf.py +84 -0
  2. ivoryos/__init__.py +17 -207
  3. ivoryos/app.py +154 -0
  4. ivoryos/config.py +1 -0
  5. ivoryos/optimizer/ax_optimizer.py +191 -0
  6. ivoryos/optimizer/base_optimizer.py +84 -0
  7. ivoryos/optimizer/baybe_optimizer.py +193 -0
  8. ivoryos/optimizer/nimo_optimizer.py +173 -0
  9. ivoryos/optimizer/registry.py +11 -0
  10. ivoryos/routes/auth/auth.py +43 -14
  11. ivoryos/routes/auth/templates/change_password.html +32 -0
  12. ivoryos/routes/control/control.py +101 -366
  13. ivoryos/routes/control/control_file.py +33 -0
  14. ivoryos/routes/control/control_new_device.py +152 -0
  15. ivoryos/routes/control/templates/controllers.html +193 -0
  16. ivoryos/routes/control/templates/controllers_new.html +112 -0
  17. ivoryos/routes/control/utils.py +40 -0
  18. ivoryos/routes/data/data.py +197 -0
  19. ivoryos/routes/data/templates/components/step_card.html +78 -0
  20. ivoryos/routes/{database/templates/database → data/templates}/workflow_database.html +14 -8
  21. ivoryos/routes/data/templates/workflow_view.html +360 -0
  22. ivoryos/routes/design/__init__.py +4 -0
  23. ivoryos/routes/design/design.py +348 -657
  24. ivoryos/routes/design/design_file.py +68 -0
  25. ivoryos/routes/design/design_step.py +171 -0
  26. ivoryos/routes/design/templates/components/action_form.html +53 -0
  27. ivoryos/routes/design/templates/components/actions_panel.html +25 -0
  28. ivoryos/routes/design/templates/components/autofill_toggle.html +10 -0
  29. ivoryos/routes/design/templates/components/canvas.html +5 -0
  30. ivoryos/routes/design/templates/components/canvas_footer.html +9 -0
  31. ivoryos/routes/design/templates/components/canvas_header.html +75 -0
  32. ivoryos/routes/design/templates/components/canvas_main.html +39 -0
  33. ivoryos/routes/design/templates/components/deck_selector.html +10 -0
  34. ivoryos/routes/design/templates/components/edit_action_form.html +53 -0
  35. ivoryos/routes/design/templates/components/info_modal.html +318 -0
  36. ivoryos/routes/design/templates/components/instruments_panel.html +88 -0
  37. ivoryos/routes/design/templates/components/modals/drop_modal.html +17 -0
  38. ivoryos/routes/design/templates/components/modals/json_modal.html +22 -0
  39. ivoryos/routes/design/templates/components/modals/new_script_modal.html +17 -0
  40. ivoryos/routes/design/templates/components/modals/rename_modal.html +23 -0
  41. ivoryos/routes/design/templates/components/modals/saveas_modal.html +27 -0
  42. ivoryos/routes/design/templates/components/modals.html +6 -0
  43. ivoryos/routes/design/templates/components/python_code_overlay.html +56 -0
  44. ivoryos/routes/design/templates/components/sidebar.html +15 -0
  45. ivoryos/routes/design/templates/components/text_to_code_panel.html +20 -0
  46. ivoryos/routes/design/templates/experiment_builder.html +44 -0
  47. ivoryos/routes/execute/__init__.py +0 -0
  48. ivoryos/routes/execute/execute.py +377 -0
  49. ivoryos/routes/execute/execute_file.py +78 -0
  50. ivoryos/routes/execute/templates/components/error_modal.html +20 -0
  51. ivoryos/routes/execute/templates/components/logging_panel.html +56 -0
  52. ivoryos/routes/execute/templates/components/progress_panel.html +27 -0
  53. ivoryos/routes/execute/templates/components/run_panel.html +9 -0
  54. ivoryos/routes/execute/templates/components/run_tabs.html +60 -0
  55. ivoryos/routes/execute/templates/components/tab_bayesian.html +520 -0
  56. ivoryos/routes/execute/templates/components/tab_configuration.html +383 -0
  57. ivoryos/routes/execute/templates/components/tab_repeat.html +18 -0
  58. ivoryos/routes/execute/templates/experiment_run.html +30 -0
  59. ivoryos/routes/library/__init__.py +0 -0
  60. ivoryos/routes/library/library.py +157 -0
  61. ivoryos/routes/{database/templates/database/scripts_database.html → library/templates/library.html} +32 -23
  62. ivoryos/routes/main/main.py +31 -3
  63. ivoryos/routes/main/templates/{main/home.html → home.html} +4 -4
  64. ivoryos/server.py +180 -0
  65. ivoryos/socket_handlers.py +52 -0
  66. ivoryos/static/ivoryos_logo.png +0 -0
  67. ivoryos/static/js/action_handlers.js +384 -0
  68. ivoryos/static/js/db_delete.js +23 -0
  69. ivoryos/static/js/script_metadata.js +39 -0
  70. ivoryos/static/js/socket_handler.js +40 -5
  71. ivoryos/static/js/sortable_design.js +107 -56
  72. ivoryos/static/js/ui_state.js +114 -0
  73. ivoryos/templates/base.html +67 -8
  74. ivoryos/utils/bo_campaign.py +180 -3
  75. ivoryos/utils/client_proxy.py +267 -36
  76. ivoryos/utils/db_models.py +300 -65
  77. ivoryos/utils/decorators.py +34 -0
  78. ivoryos/utils/form.py +63 -29
  79. ivoryos/utils/global_config.py +34 -1
  80. ivoryos/utils/nest_script.py +314 -0
  81. ivoryos/utils/py_to_json.py +295 -0
  82. ivoryos/utils/script_runner.py +599 -165
  83. ivoryos/utils/serilize.py +201 -0
  84. ivoryos/utils/task_runner.py +71 -21
  85. ivoryos/utils/utils.py +50 -6
  86. ivoryos/version.py +1 -1
  87. ivoryos-1.4.4.dist-info/METADATA +263 -0
  88. ivoryos-1.4.4.dist-info/RECORD +119 -0
  89. {ivoryos-1.0.9.dist-info → ivoryos-1.4.4.dist-info}/WHEEL +1 -1
  90. {ivoryos-1.0.9.dist-info → ivoryos-1.4.4.dist-info}/top_level.txt +1 -0
  91. tests/unit/test_type_conversion.py +42 -0
  92. tests/unit/test_util.py +3 -0
  93. ivoryos/routes/control/templates/control/controllers.html +0 -78
  94. ivoryos/routes/control/templates/control/controllers_home.html +0 -55
  95. ivoryos/routes/control/templates/control/controllers_new.html +0 -89
  96. ivoryos/routes/database/database.py +0 -306
  97. ivoryos/routes/database/templates/database/step_card.html +0 -7
  98. ivoryos/routes/database/templates/database/workflow_view.html +0 -130
  99. ivoryos/routes/design/templates/design/experiment_builder.html +0 -521
  100. ivoryos/routes/design/templates/design/experiment_run.html +0 -558
  101. ivoryos-1.0.9.dist-info/METADATA +0 -218
  102. ivoryos-1.0.9.dist-info/RECORD +0 -61
  103. /ivoryos/routes/auth/templates/{auth/login.html → login.html} +0 -0
  104. /ivoryos/routes/auth/templates/{auth/signup.html → signup.html} +0 -0
  105. /ivoryos/routes/{database → data}/__init__.py +0 -0
  106. /ivoryos/routes/main/templates/{main/help.html → help.html} +0 -0
  107. {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)))