ivoryos 0.1.9__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.
- ivoryos/__init__.py +118 -99
- ivoryos/config.py +47 -47
- ivoryos/routes/auth/auth.py +100 -65
- ivoryos/routes/auth/templates/auth/login.html +25 -25
- ivoryos/routes/auth/templates/auth/signup.html +32 -32
- ivoryos/routes/control/control.py +400 -272
- ivoryos/routes/control/templates/control/controllers.html +75 -75
- ivoryos/routes/control/templates/control/controllers_home.html +50 -50
- ivoryos/routes/control/templates/control/controllers_new.html +89 -89
- ivoryos/routes/database/database.py +188 -114
- ivoryos/routes/database/templates/database/experiment_database.html +72 -72
- ivoryos/routes/design/design.py +541 -416
- ivoryos/routes/design/templates/design/experiment_builder.html +415 -415
- ivoryos/routes/design/templates/design/experiment_run.html +325 -325
- ivoryos/routes/main/main.py +42 -25
- ivoryos/routes/main/templates/main/help.html +141 -141
- ivoryos/routes/main/templates/main/home.html +68 -68
- ivoryos/static/.DS_Store +0 -0
- ivoryos/static/js/overlay.js +12 -12
- ivoryos/static/js/socket_handler.js +34 -34
- ivoryos/static/js/sortable_card.js +24 -24
- ivoryos/static/js/sortable_design.js +36 -36
- ivoryos/static/style.css +201 -201
- ivoryos/templates/base.html +143 -143
- ivoryos/utils/db_models.py +518 -518
- ivoryos/utils/form.py +316 -316
- ivoryos/utils/global_config.py +67 -67
- ivoryos/utils/llm_agent.py +183 -183
- ivoryos/utils/script_runner.py +165 -164
- ivoryos/utils/utils.py +425 -422
- ivoryos/version.py +1 -0
- {ivoryos-0.1.9.dist-info → ivoryos-0.1.10.dist-info}/LICENSE +21 -21
- {ivoryos-0.1.9.dist-info → ivoryos-0.1.10.dist-info}/METADATA +170 -169
- ivoryos-0.1.10.dist-info/RECORD +47 -0
- {ivoryos-0.1.9.dist-info → ivoryos-0.1.10.dist-info}/WHEEL +1 -1
- ivoryos-0.1.9.dist-info/RECORD +0 -45
- {ivoryos-0.1.9.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
|
-
|
|
307
|
-
if param_type == "choice":
|
|
308
|
-
|
|
309
|
-
if param_type == "fixed":
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
"""
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
# print(f"{package} is
|
|
357
|
-
|
|
358
|
-
#
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
"""
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
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
|