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
@@ -1,57 +1,288 @@
1
- # import argparse
2
1
  import os
2
+ import re
3
+ from typing import Dict, Set, Any, Optional
3
4
 
4
- # import requests
5
5
 
6
- # session = requests.Session()
6
+ class ProxyGenerator:
7
+ """
8
+ A class to generate Python proxy interfaces for API clients.
7
9
 
10
+ This generator creates client classes that wrap API endpoints,
11
+ automatically handling request/response cycles and error handling.
12
+ """
8
13
 
9
- # Function to create class and methods dynamically
10
- def create_function(url, class_name, functions):
11
- class_template = f'class {class_name.capitalize()}:\n url = "{url}ivoryos/api/control/deck.{class_name}"\n'
14
+ # Common typing symbols to scan for in function signatures
15
+ TYPING_SYMBOLS = {
16
+ "Optional", "Union", "List", "Dict", "Tuple",
17
+ "Any", "Callable", "Iterable", "Sequence", "Set"
18
+ }
12
19
 
13
- for function_name, details in functions.items():
14
- signature = details['signature']
15
- docstring = details.get('docstring', '')
20
+ def __init__(self, base_url: str, api_path_template: str = "ivoryos/instruments/deck.{class_name}"):
21
+ """
22
+ Initialize the ProxyGenerator.
23
+
24
+ Args:
25
+ base_url: The base URL for the API
26
+ api_path_template: Template for API paths, with {class_name} placeholder
27
+ """
28
+ self.base_url = base_url.rstrip('/')
29
+ self.api_path_template = api_path_template
30
+ self.used_typing_symbols: Set[str] = set()
31
+
32
+ def extract_typing_from_signatures(self, functions: Dict[str, Dict[str, Any]]) -> Set[str]:
33
+ """
34
+ Scan function signatures for typing symbols and track usage.
35
+
36
+ Args:
37
+ functions: Dictionary of function definitions with signatures
38
+
39
+ Returns:
40
+ Set of typing symbols found in the signatures
41
+ """
42
+ for function_data in functions.values():
43
+ signature = function_data.get("signature", "")
44
+ for symbol in self.TYPING_SYMBOLS:
45
+ if re.search(rf"\b{symbol}\b", signature):
46
+ self.used_typing_symbols.add(symbol)
47
+ return self.used_typing_symbols
48
+
49
+ def create_class_definition(self, class_name: str, functions: Dict[str, Dict[str, Any]]) -> str:
50
+ """
51
+ Generate a class definition string for one API client class.
52
+
53
+ Args:
54
+ class_name: Name of the class to generate
55
+ functions: Dictionary of function definitions
56
+
57
+ Returns:
58
+ String containing the complete class definition
59
+ """
60
+ capitalized_name = class_name.capitalize()
61
+ api_url = f"{self.base_url}/{self.api_path_template.format(class_name=class_name)}"
62
+
63
+ class_template = f"class {capitalized_name}:\n"
64
+ class_template += f' """Auto-generated API client for {class_name} operations."""\n'
65
+ class_template += f' url = "{api_url}"\n\n'
66
+
67
+ # Add the __init__ with auth
68
+ class_template += self._generate_init()
69
+
70
+ # Add the _auth
71
+ class_template += self._generate_auth()
72
+
73
+ # Add the base _call method
74
+ class_template += self._generate_call_method()
75
+
76
+ # Add individual methods for each function
77
+ for function_name, details in functions.items():
78
+ method_def = self._generate_method(function_name, details)
79
+ class_template += method_def + "\n"
80
+
81
+ return class_template
82
+
83
+ def _generate_call_method(self) -> str:
84
+ """Generate the base _call method for API communication."""
85
+ return ''' def _call(self, payload):
86
+ """Make API call with error handling."""
87
+ res = session.post(self.url, json=payload, allow_redirects=False)
88
+ # Handle 302 redirect (likely auth issue)
89
+ if res.status_code == 302:
90
+ try:
91
+ self._auth()
92
+ res = session.post(self.url, json=payload, allow_redirects=False)
93
+ except Exception as e:
94
+ raise AuthenticationError(
95
+ "Authentication failed during re-attempt. "
96
+ "Please check your credentials or connection."
97
+ ) from e
98
+ res.raise_for_status()
99
+ data = res.json()
100
+ if not data.get('success'):
101
+ raise Exception(data.get('output', "Unknown API error."))
102
+ return data.get('output')
103
+
104
+ '''
105
+
106
+ def _generate_method(self, function_name: str, details: Dict[str, Any]) -> str:
107
+ """
108
+ Generate a single method definition.
109
+
110
+ Args:
111
+ function_name: Name of the method
112
+ details: Function details including signature and docstring
113
+
114
+ Returns:
115
+ String containing the method definition
116
+ """
117
+ signature = details.get("signature", "(self)")
118
+ docstring = details.get("docstring", "")
119
+
120
+ # Build method header
121
+ method = f" def {function_name}{signature}:\n"
16
122
 
17
- # Creating the function definition
18
- method = f' def {function_name}{signature}:\n'
19
123
  if docstring:
20
124
  method += f' """{docstring}"""\n'
21
125
 
22
- # Generating the session.post code for sending data
23
- method += ' return session.post(self.url, data={'
24
- method += f'"hidden_name": "{function_name}"'
126
+ # Build payload
127
+ method += f' payload = {{"hidden_name": "{function_name}"}}\n'
128
+
129
+ # Extract parameters from signature (excluding 'self')
130
+ params = self._extract_parameters(signature)
131
+
132
+ for param_name in params:
133
+ method += f' payload["{param_name}"] = {param_name}\n'
134
+
135
+ method += " return self._call(payload)\n"
25
136
 
26
- # Extracting the parameters from the signature string for the data payload
27
- param_str = signature[6:-1] # Remove the "(self" and final ")"
28
- params = [param.strip() for param in param_str.split(',')] if param_str else []
137
+ return method
138
+
139
+ def _extract_parameters(self, signature: str) -> list:
140
+ """
141
+ Extract parameter names from a function signature.
142
+
143
+ Args:
144
+ signature: Function signature string like "(self, param1, param2: int = 5)"
145
+
146
+ Returns:
147
+ List of parameter names (excluding 'self')
148
+ """
149
+ # Remove parentheses and split by comma
150
+ param_str = signature.strip("()").strip()
151
+ if not param_str or param_str == "self":
152
+ return []
153
+
154
+ params = [param.strip() for param in param_str.split(",")]
155
+ result = []
29
156
 
30
157
  for param in params:
31
- param_name = param.split(':')[0].strip() # Split on ':' and get parameter name
32
- method += f', "{param_name}": {param_name}'
158
+ if param and param != "self":
159
+ # Extract parameter name (before : or = if present)
160
+ param_name = param.split(":")[0].split("=")[0].strip()
161
+ if param_name:
162
+ result.append(param_name)
163
+
164
+ return result
165
+
166
+ def generate_proxy_file(self,
167
+ snapshot: Dict[str, Dict[str, Any]],
168
+ output_path: str,
169
+ filename: str = "generated_proxy.py") -> str:
170
+ """
171
+ Generate the complete proxy file from a snapshot of instruments.
172
+
173
+ Args:
174
+ snapshot: Dictionary containing instrument data with functions
175
+ output_path: Directory to write the output file
176
+ filename: Name of the output file
177
+
178
+ Returns:
179
+ Path to the generated file
180
+ """
181
+ class_definitions = {}
182
+ self.used_typing_symbols.clear()
183
+
184
+ # Process each instrument in the snapshot
185
+ for instrument_key, instrument_data in snapshot.items():
186
+ # Convert function signatures to strings if needed
187
+ for function_key, function_data in instrument_data.items():
188
+ if 'signature' in function_data:
189
+ function_data['signature'] = str(function_data['signature'])
190
+
191
+ # Extract class name from instrument path
192
+ class_name = instrument_key.split('.')[-1]
193
+
194
+ # Generate class definition
195
+ class_definitions[class_name] = self.create_class_definition(
196
+ class_name, instrument_data
197
+ )
198
+
199
+ # Track typing symbols used
200
+ self.extract_typing_from_signatures(instrument_data)
201
+
202
+ # Write the complete file
203
+ filepath = self._write_proxy_file(class_definitions, output_path, filename)
204
+ return filepath
205
+
206
+ def _write_proxy_file(self,
207
+ class_definitions: Dict[str, str],
208
+ output_path: str,
209
+ filename: str) -> str:
210
+ """
211
+ Write the generated classes to a Python file.
212
+
213
+ Args:
214
+ class_definitions: Dictionary of class names to class definition strings
215
+ output_path: Directory to write the file
216
+ filename: Name of the file
217
+
218
+ Returns:
219
+ Full path to the written file
220
+ """
221
+ filepath = os.path.join(output_path, filename)
222
+
223
+ with open(filepath, "w") as f:
224
+ # Write imports
225
+ f.write("import requests\n")
226
+ if self.used_typing_symbols:
227
+ f.write(f"from typing import {', '.join(sorted(self.used_typing_symbols))}\n")
228
+ f.write("\n")
229
+
230
+ # Write session setup
231
+ f.write("session = requests.Session()\n\n")
232
+
233
+ # Write class definitions
234
+ for class_name, class_def in class_definitions.items():
235
+ f.write(class_def)
236
+ f.write("\n")
237
+
238
+ # Create default instances
239
+ f.write("# Default instances for convenience\n")
240
+ for class_name in class_definitions.keys():
241
+ instance_name = class_name.lower()
242
+ f.write(f"{instance_name} = {class_name.capitalize()}()\n")
243
+
244
+ return filepath
33
245
 
34
- method += '}).json()\n'
35
- class_template += method + '\n'
246
+ def generate_from_flask_route(self,
247
+ snapshot: Dict[str, Dict[str, Any]],
248
+ request_url_root: str,
249
+ output_folder: str) -> str:
250
+ """
251
+ Convenience method that matches the original Flask route behavior.
36
252
 
37
- return class_template
253
+ Args:
254
+ snapshot: The deck snapshot from global_config
255
+ request_url_root: The URL root from Flask request
256
+ output_folder: Output folder path from app config
38
257
 
39
- # Function to export the generated classes to a Python script
40
- def export_to_python(class_definitions, path):
41
- with open(os.path.join(path, "generated_proxy.py"), 'w') as f:
42
- # Writing the imports at the top of the script
43
- f.write('import requests\n\n')
44
- f.write('session = requests.Session()\n\n')
258
+ Returns:
259
+ Path to the generated file
260
+ """
261
+ # Set the base URL from the request
262
+ self.base_url = request_url_root.rstrip('/')
45
263
 
46
- # Writing each class definition to the file
47
- for class_name, class_def in class_definitions.items():
48
- f.write(class_def)
49
- f.write('\n')
264
+ # Generate the proxy file
265
+ return self.generate_proxy_file(snapshot, output_folder)
50
266
 
51
- # Creating instances of the dynamically generated classes
52
- for class_name in class_definitions.keys():
53
- instance_name = class_name.lower() # Using lowercase for instance names
54
- f.write(f'{instance_name} = {class_name.capitalize()}()\n')
267
+ def _generate_init(self):
268
+ return ''' def __init__(self, username=None, password=None):
269
+ """Initialize the client with authentication."""
270
+ self.username = username
271
+ self.password = password
272
+ self._auth()
55
273
 
274
+ '''
56
275
 
57
276
 
277
+ def _generate_auth(self):
278
+ return f""" def _auth(self):
279
+ username = self.username or 'admin'
280
+ password = self.password or 'admin'
281
+ res = session.post(
282
+ '{self.base_url}/ivoryos/auth/login',
283
+ data={{"username": username, "password": password}}
284
+ )
285
+ if res.status_code != 200:
286
+ raise Exception("Authentication failed")
287
+
288
+ """