ivoryos 0.1.8__py3-none-any.whl → 0.1.10__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.

Potentially problematic release.


This version of ivoryos might be problematic. Click here for more details.

Files changed (37) hide show
  1. ivoryos/__init__.py +118 -99
  2. ivoryos/config.py +47 -47
  3. ivoryos/routes/auth/auth.py +100 -65
  4. ivoryos/routes/auth/templates/auth/login.html +25 -25
  5. ivoryos/routes/auth/templates/auth/signup.html +32 -32
  6. ivoryos/routes/control/control.py +400 -272
  7. ivoryos/routes/control/templates/control/controllers.html +75 -75
  8. ivoryos/routes/control/templates/control/controllers_home.html +50 -50
  9. ivoryos/routes/control/templates/control/controllers_new.html +89 -89
  10. ivoryos/routes/database/database.py +188 -114
  11. ivoryos/routes/database/templates/database/experiment_database.html +72 -72
  12. ivoryos/routes/design/design.py +542 -406
  13. ivoryos/routes/design/templates/design/experiment_builder.html +415 -412
  14. ivoryos/routes/design/templates/design/experiment_run.html +325 -325
  15. ivoryos/routes/main/main.py +42 -25
  16. ivoryos/routes/main/templates/main/help.html +141 -141
  17. ivoryos/routes/main/templates/main/home.html +68 -68
  18. ivoryos/static/.DS_Store +0 -0
  19. ivoryos/static/js/overlay.js +12 -12
  20. ivoryos/static/js/socket_handler.js +34 -34
  21. ivoryos/static/js/sortable_card.js +24 -24
  22. ivoryos/static/js/sortable_design.js +36 -36
  23. ivoryos/static/style.css +201 -201
  24. ivoryos/templates/base.html +143 -143
  25. ivoryos/utils/db_models.py +518 -500
  26. ivoryos/utils/form.py +316 -316
  27. ivoryos/utils/global_config.py +67 -67
  28. ivoryos/utils/llm_agent.py +183 -183
  29. ivoryos/utils/script_runner.py +165 -164
  30. ivoryos/utils/utils.py +425 -422
  31. ivoryos/version.py +1 -0
  32. {ivoryos-0.1.8.dist-info → ivoryos-0.1.10.dist-info}/LICENSE +21 -21
  33. {ivoryos-0.1.8.dist-info → ivoryos-0.1.10.dist-info}/METADATA +170 -166
  34. ivoryos-0.1.10.dist-info/RECORD +47 -0
  35. {ivoryos-0.1.8.dist-info → ivoryos-0.1.10.dist-info}/WHEEL +1 -1
  36. ivoryos-0.1.8.dist-info/RECORD +0 -45
  37. {ivoryos-0.1.8.dist-info → ivoryos-0.1.10.dist-info}/top_level.txt +0 -0
ivoryos/utils/utils.py CHANGED
@@ -1,422 +1,425 @@
1
- import ast
2
- import importlib
3
- import inspect
4
- import logging
5
- import os
6
- import pickle
7
- import subprocess
8
- import sys
9
- from typing import Optional, Dict, Tuple
10
-
11
- from flask import session
12
- from flask_socketio import SocketIO
13
-
14
- from ivoryos.utils.db_models import Script
15
-
16
-
17
- def get_script_file():
18
- """Get script from Flask session and returns the script"""
19
- session_script = session.get("scripts")
20
- if session_script:
21
- s = Script()
22
- s.__dict__.update(**session_script)
23
- return s
24
- else:
25
- return Script(author=session.get('user'))
26
-
27
-
28
- def post_script_file(script, is_dict=False):
29
- """
30
- Post script to Flask. Script will be converted to a dict if it is a Script object
31
- :param script: Script to post
32
- :param is_dict: if the script is a dictionary,
33
- """
34
- if is_dict:
35
- session['scripts'] = script
36
- else:
37
- session['scripts'] = script.as_dict()
38
-
39
-
40
- def create_gui_dir(parent_path):
41
- """
42
- Creates folders for ivoryos data
43
- """
44
- os.makedirs(parent_path, exist_ok=True)
45
- for path in ["config_csv", "scripts", "results", "pseudo_deck"]:
46
- os.makedirs(os.path.join(parent_path, path), exist_ok=True)
47
-
48
-
49
- def save_to_history(filepath, history_path):
50
- """
51
- For manual deck connection only
52
- save deck file path that successfully connected to ivoryos to a history file
53
- """
54
- connections = []
55
- try:
56
- with open(history_path, 'r') as file:
57
- lines = file.read()
58
- connections = lines.split('\n')
59
- except FileNotFoundError:
60
- pass
61
- if filepath not in connections:
62
- with open(history_path, 'a') as file:
63
- file.writelines(f"{filepath}\n")
64
-
65
-
66
- def import_history(history_path):
67
- """
68
- For manual deck connection only
69
- load deck connection history from history file
70
- """
71
- connections = []
72
- try:
73
- with open(history_path, 'r') as file:
74
- lines = file.read()
75
- connections = lines.split('\n')
76
- except FileNotFoundError:
77
- pass
78
- connections = [i for i in connections if not i == '']
79
- return connections
80
-
81
-
82
- def available_pseudo_deck(path):
83
- """
84
- load pseudo deck (snapshot) from connection history
85
- """
86
- import os
87
- return os.listdir(path)
88
-
89
-
90
- def _inspect_class(class_object=None, debug=False):
91
- """
92
- inspect class object: inspect function signature if not name.startswith("_")
93
- :param class_object: class object
94
- :param debug: debug mode will inspect function.startswith("_")
95
- :return: function: Dict[str, Dict[str, Union[Signature, str, None]]]
96
- """
97
- functions = {}
98
- under_score = "_"
99
- if debug:
100
- under_score = "__"
101
- for function, method in inspect.getmembers(type(class_object), predicate=inspect.isfunction):
102
- if not function.startswith(under_score) and not function.isupper():
103
- try:
104
- annotation = inspect.signature(method)
105
- # if doc_string:
106
- docstring = inspect.getdoc(method)
107
- functions[function] = dict(signature=annotation, docstring=docstring)
108
-
109
- # handle getter setters todo
110
- # if callable(att):
111
- # functions[function] = inspect.signature(att)
112
- # else:
113
- # att = getattr(class_object.__class__, function)
114
- # if isinstance(att, property) and att.fset is not None:
115
- # setter = att.fset.__annotations__
116
- # setter.pop('return', None)
117
- # if setter:
118
- # functions[function] = setter
119
- except Exception:
120
- pass
121
- return functions
122
-
123
-
124
- def _get_type_from_parameters(arg, parameters):
125
- """get argument types from inspection"""
126
- arg_type = ''
127
- if type(parameters) is inspect.Signature:
128
- annotation = parameters.parameters[arg].annotation
129
- elif type(parameters) is dict:
130
- annotation = parameters[arg]
131
- if annotation is not inspect._empty:
132
- # print(p[arg].annotation)
133
- if annotation.__module__ == 'typing':
134
- if hasattr(annotation, '_name') and annotation._name in ["Optional", "Union"]:
135
- # print(p[arg].annotation.__args__)
136
- arg_type = [i.__name__ for i in annotation.__args__]
137
- elif hasattr(annotation, '__origin__'):
138
- arg_type = annotation.__origin__.__name__
139
- else:
140
- # TODO
141
- pass
142
- else:
143
- arg_type = annotation.__name__
144
- return arg_type
145
-
146
-
147
- def find_variable_in_script(script: Script, args: Dict[str, str]) -> Optional[Tuple[Dict[str, str], Dict[str, str]]]:
148
- # TODO: need to search for if the variable exists
149
- added_variables: list[Dict[str, str]] = [action for action in script.currently_editing_script if
150
- action["instrument"] == "variable"]
151
-
152
- possible_variable_arguments = {}
153
- possible_variable_types = {}
154
-
155
- for arg_name, arg_val in args.items():
156
- for added_variable in added_variables:
157
- if added_variable["action"] == arg_val:
158
- possible_variable_arguments[arg_name] = added_variable["action"]
159
- possible_variable_types[arg_name] = "variable"
160
-
161
- return possible_variable_arguments, possible_variable_types
162
-
163
-
164
- def _convert_by_str(args, arg_types):
165
- """
166
- Converts a value to type through eval(f'{type}("{args}")')
167
- """
168
- if type(arg_types) is not list:
169
- arg_types = [arg_types]
170
- for arg_type in arg_types:
171
- if not arg_type == "any":
172
- try:
173
- args = eval(f'{arg_type}("{args}")') if type(args) is str else eval(f'{arg_type}({args})')
174
- return args
175
- except Exception:
176
- raise TypeError(f"Input type error: cannot convert '{args}' to {arg_type}.")
177
-
178
-
179
- def _convert_by_class(args, arg_types):
180
- """
181
- Converts a value to type through type(arg)
182
- """
183
- if arg_types.__module__ == 'builtins':
184
- args = arg_types(args)
185
- return args
186
- elif arg_types.__module__ == "typing":
187
- for i in arg_types.__args__: # for typing.Union
188
- try:
189
- args = i(args)
190
- return args
191
- except Exception:
192
- pass
193
- raise TypeError("Input type error.")
194
- # else:
195
- # args = globals()[args]
196
- return args
197
-
198
-
199
- def convert_config_type(args, arg_types, is_class: bool = False):
200
- """
201
- Converts an argument from str to an arg type
202
- """
203
- bool_dict = {"True": True, "False": False}
204
- # print(args, arg_types)
205
- # print(globals())
206
- if args:
207
- for arg in args:
208
- if arg not in arg_types.keys():
209
- raise ValueError("config file format not supported.")
210
- if args[arg] == '' or args[arg] == "None":
211
- args[arg] = None
212
- # elif args[arg] == "True" or args[arg] == "False":
213
- # args[arg] = bool_dict[args[arg]]
214
- else:
215
- arg_type = arg_types[arg]
216
- try:
217
- args[arg] = ast.literal_eval(args[arg])
218
- except ValueError:
219
- pass
220
- if type(args[arg]) is not arg_type and not type(args[arg]).__name__ == arg_type:
221
- if is_class:
222
- # if arg_type.__module__ == 'builtins':
223
- args[arg] = _convert_by_class(args[arg], arg_type)
224
- else:
225
- args[arg] = _convert_by_str(args[arg], arg_type)
226
- return args
227
-
228
-
229
- def import_module_by_filepath(filepath: str, name: str):
230
- """
231
- Import module by file path
232
- :param filepath: full path of module
233
- :param name: module's name
234
- """
235
- spec = importlib.util.spec_from_file_location(name, filepath)
236
- module = importlib.util.module_from_spec(spec)
237
- spec.loader.exec_module(module)
238
- return module
239
-
240
-
241
- class SocketIOHandler(logging.Handler):
242
- def __init__(self, socketio: SocketIO):
243
- super().__init__()
244
- self.formatter = logging.Formatter('%(asctime)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
245
- self.socketio = socketio
246
-
247
- def emit(self, record):
248
- message = self.format(record)
249
- # session["last_log"] = message
250
- self.socketio.emit('log', {'message': message})
251
-
252
-
253
- def start_logger(socketio: SocketIO, logger_name: str, log_filename: str = None):
254
- """
255
- stream logger to web through web socketIO
256
- """
257
- # logging.basicConfig( format='%(asctime)s - %(message)s')
258
- formatter = logging.Formatter(fmt='%(asctime)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
259
- logger = logging.getLogger(logger_name)
260
- logger.setLevel(logging.INFO)
261
- file_handler = logging.FileHandler(filename=log_filename, )
262
- file_handler.setFormatter(formatter)
263
- logger.addHandler(file_handler)
264
- # console_logger = logging.StreamHandler() # stream to console
265
- # logger.addHandler(console_logger)
266
- socketio_handler = SocketIOHandler(socketio)
267
- logger.addHandler(socketio_handler)
268
- return logger
269
-
270
-
271
- def ax_wrapper(data: dict):
272
- """
273
- Ax platform wrapper function for creating optimization campaign parameters and objective from the web form input
274
- :param data: e.g.,
275
- {
276
- "param_1_type": "range", "param_1_value": [1,2],
277
- "param_2_type": "range", "param_2_value": [1,2],
278
- "obj_1_min": True,
279
- "obj_2_min": True
280
- }
281
- :return: the optimization campaign parameters
282
- parameter=[
283
- {"name": "param_1", "type": "range", "bounds": [1,2]},
284
- {"name": "param_1", "type": "range", "bounds": [1,2]}
285
- ]
286
- objectives=[
287
- {"name": "obj_1", "min": True, "threshold": None},
288
- {"name": "obj_2", "min": True, "threshold": None},
289
- ]
290
- """
291
- from ax.service.utils.instantiation import ObjectiveProperties
292
- parameter = []
293
- objectives = {}
294
- # Iterate through the webui_data dictionary
295
- for key, value in data.items():
296
- # Check if the key corresponds to a parameter type
297
- if "_type" in key:
298
- param_name = key.split("_type")[0]
299
- param_type = value
300
- param_value = data[f"{param_name}_value"].split(",")
301
- try:
302
- values = [float(v) for v in param_value]
303
- except Exception:
304
- values = param_value
305
- if param_type == "range":
306
- parameter.append({"name": param_name, "type": param_type, "bounds": values})
307
- if param_type == "choice":
308
- parameter.append({"name": param_name, "type": param_type, "values": values})
309
- if param_type == "fixed":
310
- parameter.append({"name": param_name, "type": param_type, "value": values[0]})
311
- elif key.endswith("_min"):
312
- if not value == 'none':
313
- obj_name = key.split("_min")[0]
314
- is_min = True if value == "minimize" else False
315
-
316
- threshold = None if f"{obj_name}_threshold" not in data else data[f"{obj_name}_threshold"]
317
- properties = ObjectiveProperties(minimize=is_min, threshold=threshold)
318
- objectives[obj_name] = properties
319
- return parameter, objectives
320
-
321
-
322
- def ax_initiation(data):
323
- """
324
- create Ax campaign from the web form input
325
- :param data:
326
- """
327
- install_and_import("ax", "ax-platform")
328
- parameter, objectives = ax_wrapper(data)
329
- from ax.service.ax_client import AxClient
330
- ax_client = AxClient()
331
- ax_client.create_experiment(parameter, objectives)
332
- return ax_client
333
-
334
-
335
- def get_arg_type(args, parameters):
336
- arg_types = {}
337
- # print(args, parameters)
338
- if args:
339
- for arg in args:
340
- arg_types[arg] = _get_type_from_parameters(arg, parameters)
341
- return arg_types
342
-
343
-
344
- def install_and_import(package, package_name=None):
345
- """
346
- Install the package and import it
347
- :param package: package to import and install
348
- :param package_name: pip install package name if different from package
349
- """
350
- try:
351
- # Check if the package is already installed
352
- importlib.import_module(package)
353
- # print(f"{package} is already installed.")
354
- except ImportError:
355
- # If not installed, install it
356
- # print(f"{package} is not installed. Installing now...")
357
- subprocess.check_call([sys.executable, "-m", "pip", "install", package_name or package])
358
- # print(f"{package} has been installed successfully.")
359
-
360
-
361
- def web_config_entry_wrapper(data: dict, config_type: list):
362
- """
363
- Wrap the data dictionary from web config entries during execution configuration
364
- :param data: data dictionary
365
- :param config_type: data entry types ["str", "int", "float", "bool"]
366
- """
367
- rows = {} # Dictionary to hold webui_data organized by rows
368
-
369
- # Organize webui_data by rows
370
- for key, value in data.items():
371
- if value: # Only process non-empty values
372
- # Extract the field name and row index
373
- field_name, row_index = key.split('[')
374
- row_index = int(row_index.rstrip(']'))
375
-
376
- # If row not in rows, create a new dictionary for that row
377
- if row_index not in rows:
378
- rows[row_index] = {}
379
-
380
- # Add or update the field value in the specific row's dictionary
381
- rows[row_index][field_name] = value
382
-
383
- # Filter out any empty rows and create a list of dictionaries
384
- filtered_rows = [row for row in rows.values() if len(row) == len(config_type)]
385
-
386
- return filtered_rows
387
-
388
-
389
- def create_deck_snapshot(deck, save: bool = False, output_path: str = ''):
390
- """
391
- Create a deck snapshot of the given script
392
- :param deck: python module name to create the deck snapshot from e.g. __main__
393
- :param save: save the deck snapshot into pickle file
394
- :param output_path: path to save the pickle file
395
- """
396
- deck_snapshot = {f"deck.{name}": _inspect_class(val) for name, val in vars(deck).items()
397
- if not type(val).__module__ == 'builtins'
398
- and not name[0].isupper()
399
- and not name.startswith("_")}
400
- if deck_snapshot and save:
401
- # pseudo_deck = parse_dict
402
- parse_dict = deck_snapshot.copy()
403
- parse_dict["deck_name"] = os.path.splitext(os.path.basename(deck.__file__))[
404
- 0] if deck.__name__ == "__main__" else deck.__name__
405
- with open(os.path.join(output_path, f"{parse_dict['deck_name']}.pkl"), 'wb') as file:
406
- pickle.dump(parse_dict, file)
407
- return deck_snapshot
408
-
409
-
410
- def load_deck(pkl_name: str):
411
- """
412
- Loads a pickled deck snapshot from disk on offline mode
413
- :param pkl_name: name of the pickle file
414
- """
415
- if not pkl_name:
416
- return None
417
- try:
418
- with open(pkl_name, 'rb') as f:
419
- pseudo_deck = pickle.load(f)
420
- return pseudo_deck
421
- except FileNotFoundError:
422
- return None
1
+ import ast
2
+ import importlib
3
+ import inspect
4
+ import logging
5
+ import os
6
+ import pickle
7
+ import subprocess
8
+ import sys
9
+ from typing import Optional, Dict, Tuple
10
+
11
+ from flask import session
12
+ from flask_socketio import SocketIO
13
+
14
+ from ivoryos.utils.db_models import Script
15
+
16
+
17
+ def get_script_file():
18
+ """Get script from Flask session and returns the script"""
19
+ session_script = session.get("scripts")
20
+ if session_script:
21
+ s = Script()
22
+ s.__dict__.update(**session_script)
23
+ return s
24
+ else:
25
+ return Script(author=session.get('user'))
26
+
27
+
28
+ def post_script_file(script, is_dict=False):
29
+ """
30
+ Post script to Flask. Script will be converted to a dict if it is a Script object
31
+ :param script: Script to post
32
+ :param is_dict: if the script is a dictionary,
33
+ """
34
+ if is_dict:
35
+ session['scripts'] = script
36
+ else:
37
+ session['scripts'] = script.as_dict()
38
+
39
+
40
+ def create_gui_dir(parent_path):
41
+ """
42
+ Creates folders for ivoryos data
43
+ """
44
+ os.makedirs(parent_path, exist_ok=True)
45
+ for path in ["config_csv", "scripts", "results", "pseudo_deck"]:
46
+ os.makedirs(os.path.join(parent_path, path), exist_ok=True)
47
+
48
+
49
+ def save_to_history(filepath, history_path):
50
+ """
51
+ For manual deck connection only
52
+ save deck file path that successfully connected to ivoryos to a history file
53
+ """
54
+ connections = []
55
+ try:
56
+ with open(history_path, 'r') as file:
57
+ lines = file.read()
58
+ connections = lines.split('\n')
59
+ except FileNotFoundError:
60
+ pass
61
+ if filepath not in connections:
62
+ with open(history_path, 'a') as file:
63
+ file.writelines(f"{filepath}\n")
64
+
65
+
66
+ def import_history(history_path):
67
+ """
68
+ For manual deck connection only
69
+ load deck connection history from history file
70
+ """
71
+ connections = []
72
+ try:
73
+ with open(history_path, 'r') as file:
74
+ lines = file.read()
75
+ connections = lines.split('\n')
76
+ except FileNotFoundError:
77
+ pass
78
+ connections = [i for i in connections if not i == '']
79
+ return connections
80
+
81
+
82
+ def available_pseudo_deck(path):
83
+ """
84
+ load pseudo deck (snapshot) from connection history
85
+ """
86
+ import os
87
+ return os.listdir(path)
88
+
89
+
90
+ def _inspect_class(class_object=None, debug=False):
91
+ """
92
+ inspect class object: inspect function signature if not name.startswith("_")
93
+ :param class_object: class object
94
+ :param debug: debug mode will inspect function.startswith("_")
95
+ :return: function: Dict[str, Dict[str, Union[Signature, str, None]]]
96
+ """
97
+ functions = {}
98
+ under_score = "_"
99
+ if debug:
100
+ under_score = "__"
101
+ for function, method in inspect.getmembers(type(class_object), predicate=inspect.isfunction):
102
+ if not function.startswith(under_score) and not function.isupper():
103
+ try:
104
+ annotation = inspect.signature(method)
105
+ # if doc_string:
106
+ docstring = inspect.getdoc(method)
107
+ functions[function] = dict(signature=annotation, docstring=docstring)
108
+
109
+ # handle getter setters todo
110
+ # if callable(att):
111
+ # functions[function] = inspect.signature(att)
112
+ # else:
113
+ # att = getattr(class_object.__class__, function)
114
+ # if isinstance(att, property) and att.fset is not None:
115
+ # setter = att.fset.__annotations__
116
+ # setter.pop('return', None)
117
+ # if setter:
118
+ # functions[function] = setter
119
+ except Exception:
120
+ pass
121
+ return functions
122
+
123
+
124
+ def _get_type_from_parameters(arg, parameters):
125
+ """get argument types from inspection"""
126
+ arg_type = ''
127
+ if type(parameters) is inspect.Signature:
128
+ annotation = parameters.parameters[arg].annotation
129
+ elif type(parameters) is dict:
130
+ annotation = parameters[arg]
131
+ if annotation is not inspect._empty:
132
+ # print(p[arg].annotation)
133
+ if annotation.__module__ == 'typing':
134
+ if hasattr(annotation, '_name') and annotation._name in ["Optional", "Union"]:
135
+ # print(p[arg].annotation.__args__)
136
+ arg_type = [i.__name__ for i in annotation.__args__]
137
+ elif hasattr(annotation, '__origin__'):
138
+ arg_type = annotation.__origin__.__name__
139
+ else:
140
+ # TODO
141
+ pass
142
+ else:
143
+ arg_type = annotation.__name__
144
+ return arg_type
145
+
146
+
147
+ def find_variable_in_script(script: Script, args: Dict[str, str]) -> Optional[Tuple[Dict[str, str], Dict[str, str]]]:
148
+ # TODO: need to search for if the variable exists
149
+ added_variables: list[Dict[str, str]] = [action for action in script.currently_editing_script if
150
+ action["instrument"] == "variable"]
151
+
152
+ possible_variable_arguments = {}
153
+ possible_variable_types = {}
154
+
155
+ for arg_name, arg_val in args.items():
156
+ for added_variable in added_variables:
157
+ if added_variable["action"] == arg_val:
158
+ possible_variable_arguments[arg_name] = added_variable["action"]
159
+ possible_variable_types[arg_name] = "variable"
160
+
161
+ return possible_variable_arguments, possible_variable_types
162
+
163
+
164
+ def _convert_by_str(args, arg_types):
165
+ """
166
+ Converts a value to type through eval(f'{type}("{args}")')
167
+ """
168
+ if type(arg_types) is not list:
169
+ arg_types = [arg_types]
170
+ for arg_type in arg_types:
171
+ if not arg_type == "any":
172
+ try:
173
+ args = eval(f'{arg_type}("{args}")') if type(args) is str else eval(f'{arg_type}({args})')
174
+ return args
175
+ except Exception:
176
+ raise TypeError(f"Input type error: cannot convert '{args}' to {arg_type}.")
177
+
178
+
179
+ def _convert_by_class(args, arg_types):
180
+ """
181
+ Converts a value to type through type(arg)
182
+ """
183
+ if arg_types.__module__ == 'builtins':
184
+ args = arg_types(args)
185
+ return args
186
+ elif arg_types.__module__ == "typing":
187
+ for i in arg_types.__args__: # for typing.Union
188
+ try:
189
+ args = i(args)
190
+ return args
191
+ except Exception:
192
+ pass
193
+ raise TypeError("Input type error.")
194
+ # else:
195
+ # args = globals()[args]
196
+ return args
197
+
198
+
199
+ def convert_config_type(args, arg_types, is_class: bool = False):
200
+ """
201
+ Converts an argument from str to an arg type
202
+ """
203
+ bool_dict = {"True": True, "False": False}
204
+ # print(args, arg_types)
205
+ # print(globals())
206
+ if args:
207
+ for arg in args:
208
+ if arg not in arg_types.keys():
209
+ raise ValueError("config file format not supported.")
210
+ if args[arg] == '' or args[arg] == "None":
211
+ args[arg] = None
212
+ # elif args[arg] == "True" or args[arg] == "False":
213
+ # args[arg] = bool_dict[args[arg]]
214
+ else:
215
+ arg_type = arg_types[arg]
216
+ try:
217
+ args[arg] = ast.literal_eval(args[arg])
218
+ except ValueError:
219
+ pass
220
+ if type(args[arg]) is not arg_type and not type(args[arg]).__name__ == arg_type:
221
+ if is_class:
222
+ # if arg_type.__module__ == 'builtins':
223
+ args[arg] = _convert_by_class(args[arg], arg_type)
224
+ else:
225
+ args[arg] = _convert_by_str(args[arg], arg_type)
226
+ return args
227
+
228
+
229
+ def import_module_by_filepath(filepath: str, name: str):
230
+ """
231
+ Import module by file path
232
+ :param filepath: full path of module
233
+ :param name: module's name
234
+ """
235
+ spec = importlib.util.spec_from_file_location(name, filepath)
236
+ module = importlib.util.module_from_spec(spec)
237
+ spec.loader.exec_module(module)
238
+ return module
239
+
240
+
241
+ class SocketIOHandler(logging.Handler):
242
+ def __init__(self, socketio: SocketIO):
243
+ super().__init__()
244
+ self.formatter = logging.Formatter('%(asctime)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
245
+ self.socketio = socketio
246
+
247
+ def emit(self, record):
248
+ message = self.format(record)
249
+ # session["last_log"] = message
250
+ self.socketio.emit('log', {'message': message})
251
+
252
+
253
+ def start_logger(socketio: SocketIO, logger_name: str, log_filename: str = None):
254
+ """
255
+ stream logger to web through web socketIO
256
+ """
257
+ # logging.basicConfig( format='%(asctime)s - %(message)s')
258
+ formatter = logging.Formatter(fmt='%(asctime)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
259
+ logger = logging.getLogger(logger_name)
260
+ logger.setLevel(logging.INFO)
261
+ file_handler = logging.FileHandler(filename=log_filename, )
262
+ file_handler.setFormatter(formatter)
263
+ logger.addHandler(file_handler)
264
+ # console_logger = logging.StreamHandler() # stream to console
265
+ # logger.addHandler(console_logger)
266
+ socketio_handler = SocketIOHandler(socketio)
267
+ logger.addHandler(socketio_handler)
268
+ return logger
269
+
270
+
271
+ def ax_wrapper(data: dict, arg_types:list):
272
+ """
273
+ Ax platform wrapper function for creating optimization campaign parameters and objective from the web form input
274
+ :param data: e.g.,
275
+ {
276
+ "param_1_type": "range", "param_1_value": [1,2],
277
+ "param_2_type": "range", "param_2_value": [1,2],
278
+ "obj_1_min": True,
279
+ "obj_2_min": True
280
+ }
281
+ :return: the optimization campaign parameters
282
+ parameter=[
283
+ {"name": "param_1", "type": "range", "bounds": [1,2]},
284
+ {"name": "param_1", "type": "range", "bounds": [1,2]}
285
+ ]
286
+ objectives=[
287
+ {"name": "obj_1", "min": True, "threshold": None},
288
+ {"name": "obj_2", "min": True, "threshold": None},
289
+ ]
290
+ """
291
+ from ax.service.utils.instantiation import ObjectiveProperties
292
+ parameter = []
293
+ objectives = {}
294
+ # Iterate through the webui_data dictionary
295
+ for key, value in data.items():
296
+ # Check if the key corresponds to a parameter type
297
+ if "_type" in key:
298
+ param_name = key.split("_type")[0]
299
+ param_type = value
300
+ param_value = data[f"{param_name}_value"].split(",")
301
+ try:
302
+ values = [float(v) for v in param_value]
303
+ except Exception:
304
+ values = param_value
305
+ if param_type == "range":
306
+ param = {"name": param_name, "type": param_type, "bounds": values}
307
+ if param_type == "choice":
308
+ param = {"name": param_name, "type": param_type, "values": values}
309
+ if param_type == "fixed":
310
+ param = {"name": param_name, "type": param_type, "value": values[0]}
311
+ _type = arg_types[param_name] if arg_types[param_name] in ["str", "bool", "int"] else "float"
312
+ param.update({"value_type": _type})
313
+ parameter.append(param)
314
+ elif key.endswith("_min"):
315
+ if not value == 'none':
316
+ obj_name = key.split("_min")[0]
317
+ is_min = True if value == "minimize" else False
318
+
319
+ threshold = None if f"{obj_name}_threshold" not in data else data[f"{obj_name}_threshold"]
320
+ properties = ObjectiveProperties(minimize=is_min, threshold=threshold)
321
+ objectives[obj_name] = properties
322
+ return parameter, objectives
323
+
324
+
325
+ def ax_initiation(data, arg_types):
326
+ """
327
+ create Ax campaign from the web form input
328
+ :param data:
329
+ """
330
+ install_and_import("ax", "ax-platform")
331
+ parameter, objectives = ax_wrapper(data, arg_types)
332
+ from ax.service.ax_client import AxClient
333
+ ax_client = AxClient()
334
+ ax_client.create_experiment(parameter, objectives)
335
+ return ax_client
336
+
337
+
338
+ def get_arg_type(args, parameters):
339
+ arg_types = {}
340
+ # print(args, parameters)
341
+ if args:
342
+ for arg in args:
343
+ arg_types[arg] = _get_type_from_parameters(arg, parameters)
344
+ return arg_types
345
+
346
+
347
+ def install_and_import(package, package_name=None):
348
+ """
349
+ Install the package and import it
350
+ :param package: package to import and install
351
+ :param package_name: pip install package name if different from package
352
+ """
353
+ try:
354
+ # Check if the package is already installed
355
+ importlib.import_module(package)
356
+ # print(f"{package} is already installed.")
357
+ except ImportError:
358
+ # If not installed, install it
359
+ # print(f"{package} is not installed. Installing now...")
360
+ subprocess.check_call([sys.executable, "-m", "pip", "install", package_name or package])
361
+ # print(f"{package} has been installed successfully.")
362
+
363
+
364
+ def web_config_entry_wrapper(data: dict, config_type: list):
365
+ """
366
+ Wrap the data dictionary from web config entries during execution configuration
367
+ :param data: data dictionary
368
+ :param config_type: data entry types ["str", "int", "float", "bool"]
369
+ """
370
+ rows = {} # Dictionary to hold webui_data organized by rows
371
+
372
+ # Organize webui_data by rows
373
+ for key, value in data.items():
374
+ if value: # Only process non-empty values
375
+ # Extract the field name and row index
376
+ field_name, row_index = key.split('[')
377
+ row_index = int(row_index.rstrip(']'))
378
+
379
+ # If row not in rows, create a new dictionary for that row
380
+ if row_index not in rows:
381
+ rows[row_index] = {}
382
+
383
+ # Add or update the field value in the specific row's dictionary
384
+ rows[row_index][field_name] = value
385
+
386
+ # Filter out any empty rows and create a list of dictionaries
387
+ filtered_rows = [row for row in rows.values() if len(row) == len(config_type)]
388
+
389
+ return filtered_rows
390
+
391
+
392
+ def create_deck_snapshot(deck, save: bool = False, output_path: str = ''):
393
+ """
394
+ Create a deck snapshot of the given script
395
+ :param deck: python module name to create the deck snapshot from e.g. __main__
396
+ :param save: save the deck snapshot into pickle file
397
+ :param output_path: path to save the pickle file
398
+ """
399
+ deck_snapshot = {f"deck.{name}": _inspect_class(val) for name, val in vars(deck).items()
400
+ if not type(val).__module__ == 'builtins'
401
+ and not name[0].isupper()
402
+ and not name.startswith("_")}
403
+ if deck_snapshot and save:
404
+ # pseudo_deck = parse_dict
405
+ parse_dict = deck_snapshot.copy()
406
+ parse_dict["deck_name"] = os.path.splitext(os.path.basename(deck.__file__))[
407
+ 0] if deck.__name__ == "__main__" else deck.__name__
408
+ with open(os.path.join(output_path, f"{parse_dict['deck_name']}.pkl"), 'wb') as file:
409
+ pickle.dump(parse_dict, file)
410
+ return deck_snapshot
411
+
412
+
413
+ def load_deck(pkl_name: str):
414
+ """
415
+ Loads a pickled deck snapshot from disk on offline mode
416
+ :param pkl_name: name of the pickle file
417
+ """
418
+ if not pkl_name:
419
+ return None
420
+ try:
421
+ with open(pkl_name, 'rb') as f:
422
+ pseudo_deck = pickle.load(f)
423
+ return pseudo_deck
424
+ except FileNotFoundError:
425
+ return None