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.

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 +541 -416
  13. ivoryos/routes/design/templates/design/experiment_builder.html +415 -415
  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 -518
  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.9.dist-info → ivoryos-0.1.10.dist-info}/LICENSE +21 -21
  33. {ivoryos-0.1.9.dist-info → ivoryos-0.1.10.dist-info}/METADATA +170 -169
  34. ivoryos-0.1.10.dist-info/RECORD +47 -0
  35. {ivoryos-0.1.9.dist-info → ivoryos-0.1.10.dist-info}/WHEEL +1 -1
  36. ivoryos-0.1.9.dist-info/RECORD +0 -45
  37. {ivoryos-0.1.9.dist-info → ivoryos-0.1.10.dist-info}/top_level.txt +0 -0
@@ -1,272 +1,400 @@
1
- import os
2
-
3
- from flask import Blueprint, redirect, url_for, flash, request, render_template, session, current_app, jsonify
4
- from flask_login import login_required
5
-
6
- from ivoryos.utils.global_config import GlobalConfig
7
- from ivoryos.utils import utils
8
- from ivoryos.utils.form import create_form_from_module, format_name
9
-
10
- global_config = GlobalConfig()
11
-
12
- control = Blueprint('control', __name__, template_folder='templates/control')
13
-
14
-
15
- @control.route("/my_deck")
16
- @login_required
17
- def deck_controllers():
18
- deck_variables = global_config.deck_snapshot.keys()
19
- deck_list = utils.import_history(os.path.join(current_app.config["OUTPUT_FOLDER"], 'deck_history.txt'))
20
- return render_template('controllers_home.html', defined_variables=deck_variables, deck=True, history=deck_list)
21
-
22
-
23
- @control.route("/new_controller/")
24
- @control.route("/new_controller/<instrument>", methods=['GET', 'POST'])
25
- @login_required
26
- def new_controller(instrument=None):
27
- device = None
28
- args = None
29
- if instrument:
30
-
31
- device = find_instrument_by_name(instrument)
32
- args = utils.inspect.signature(device.__init__)
33
-
34
- if request.method == 'POST':
35
- device_name = request.form.get("device_name", "")
36
- if device_name and device_name in globals():
37
- flash("Device name is defined. Try another name, or leave it as blank to auto-configure")
38
- return render_template('controllers_new.html', instrument=instrument,
39
- api_variables=global_config.api_variables,
40
- device=device, args=args, defined_variables=global_config.defined_variables)
41
- if device_name == "":
42
- device_name = device.__name__.lower() + "_"
43
- num = 1
44
- while device_name + str(num) in global_config.defined_variables:
45
- num += 1
46
- device_name = device_name + str(num)
47
- kwargs = request.form.to_dict()
48
- kwargs.pop("device_name")
49
- for i in kwargs:
50
- if kwargs[i] in global_config.defined_variables:
51
- kwargs[i] = global_config.defined_variables[kwargs[i]]
52
- try:
53
- utils.convert_config_type(kwargs, device.__init__.__annotations__, is_class=True)
54
- except Exception as e:
55
- flash(e)
56
- try:
57
- global_config.defined_variables[device_name] = device(**kwargs)
58
- # global_config.defined_variables.add(device_name)
59
- return redirect(url_for('control.controllers_home'))
60
- except Exception as e:
61
- flash(e)
62
- return render_template('controllers_new.html', instrument=instrument, api_variables=global_config.api_variables,
63
- device=device, args=args, defined_variables=global_config.defined_variables)
64
-
65
-
66
- @control.route("/controllers")
67
- @login_required
68
- def controllers_home():
69
- # defined_variables = parse_deck(deck)
70
- return render_template('controllers_home.html', defined_variables=global_config.defined_variables)
71
-
72
-
73
- @control.route("/controllers/<instrument>", methods=['GET', 'POST'])
74
- @login_required
75
- def controllers(instrument):
76
- inst_object = find_instrument_by_name(instrument)
77
- _forms = create_form_from_module(sdl_module=inst_object, autofill=False, design=False)
78
- functions = list(_forms.keys())
79
-
80
- order = get_session_by_instrument('card_order', instrument)
81
- hidden_functions = get_session_by_instrument('hide_function', instrument)
82
-
83
- for function in functions:
84
- if function not in hidden_functions and function not in order:
85
- order.append(function)
86
- post_session_by_instrument('card_order', instrument, order)
87
- forms = {name: _forms[name] for name in order if name in _forms}
88
- if request.method == 'POST':
89
- all_kwargs = request.form.copy()
90
- method_name = all_kwargs.pop("hidden_name", None)
91
- # if method_name is not None:
92
- form = forms.get(method_name)
93
- kwargs = {field.name: field.data for field in form if field.name != 'csrf_token'}
94
- function_executable = getattr(inst_object, method_name)
95
- if form and form.validate_on_submit():
96
- try:
97
- kwargs.pop("hidden_name")
98
- output = function_executable(**kwargs)
99
- flash(f"\nRun Success! Output value: {output}.")
100
- except Exception as e:
101
- flash(e.__str__())
102
- else:
103
- flash(form.errors)
104
- return render_template('controllers.html', instrument=instrument, forms=forms, format_name=format_name)
105
-
106
-
107
- @control.route("/backend_control/<instrument>", methods=['GET', 'POST'])
108
- @login_required
109
- def backend_control(instrument):
110
- inst_object = find_instrument_by_name(instrument)
111
- forms = create_form_from_module(sdl_module=inst_object, autofill=False, design=False)
112
- if request.method == 'POST':
113
- all_kwargs = request.form.copy()
114
- method_name = all_kwargs.pop("hidden_name", None)
115
- # if method_name is not None:
116
- form = forms.get(method_name, None)
117
- kwargs = {field.name: field.data for field in form if field.name != 'csrf_token'}
118
- function_executable = getattr(inst_object, method_name)
119
- if form:
120
- # print(kwargs)
121
- try:
122
- kwargs.pop("hidden_name")
123
- output = function_executable(**kwargs)
124
- json_output = jsonify(output)
125
- except Exception as e:
126
- json_output = jsonify(e.__str__())
127
- return json_output, 400
128
- else:
129
- return "instrument not exist", 400
130
- return json_output, 200
131
-
132
-
133
- @control.route("/import_api", methods=['GET', 'POST'])
134
- def import_api():
135
- filepath = request.form.get('filepath')
136
- # filepath.replace('\\', '/')
137
- name = os.path.split(filepath)[-1].split('.')[0]
138
- try:
139
- spec = utils.importlib.util.spec_from_file_location(name, filepath)
140
- module = utils.importlib.util.module_from_spec(spec)
141
- spec.loader.exec_module(module)
142
- classes = utils.inspect.getmembers(module, utils.inspect.isclass)
143
- if len(classes) == 0:
144
- flash("Invalid import: no class found in the path")
145
- return redirect(url_for("control.controllers_home"))
146
- for i in classes:
147
- globals()[i[0]] = i[1]
148
- global_config.api_variables.add(i[0])
149
- # should handle path error and file type error
150
- except Exception as e:
151
- flash(e.__str__())
152
- return redirect(url_for("control.new_controller"))
153
-
154
-
155
- # @control.route("/disconnect", methods=["GET"])
156
- # @control.route("/disconnect/<device_name>", methods=["GET"])
157
- # def disconnect(device_name=None):
158
- # """TODO handle disconnect device"""
159
- # if device_name:
160
- # try:
161
- # exec(device_name + ".disconnect()")
162
- # except Exception:
163
- # pass
164
- # global_config.defined_variables.remove(device_name)
165
- # globals().pop(device_name)
166
- # return redirect(url_for('control.controllers_home'))
167
- #
168
- # deck_variables = ["deck." + var for var in set(dir(deck))
169
- # if not (var.startswith("_") or var[0].isupper() or var.startswith("repackage"))
170
- # and not type(eval("deck." + var)).__module__ == 'builtins']
171
- # for i in deck_variables:
172
- # try:
173
- # exec(i + ".disconnect()")
174
- # except Exception:
175
- # pass
176
- # globals()["deck"] = None
177
- # return redirect(url_for('control.deck_controllers'))
178
-
179
-
180
- @control.route("/import_deck", methods=['POST'])
181
- def import_deck():
182
- # global deck
183
- script = utils.get_script_file()
184
- filepath = request.form.get('filepath')
185
- session['dismiss'] = request.form.get('dismiss')
186
- update = request.form.get('update')
187
- back = request.referrer
188
- if session['dismiss']:
189
- return redirect(back)
190
- name = os.path.split(filepath)[-1].split('.')[0]
191
- try:
192
- module = utils.import_module_by_filepath(filepath=filepath, name=name)
193
- utils.save_to_history(filepath, current_app.config["DECK_HISTORY"])
194
- module_sigs = utils.create_deck_snapshot(module, save=update, output_path=current_app.config["DUMMY_DECK"])
195
- if not len(module_sigs) > 0:
196
- flash("Invalid hardware deck, connect instruments in deck script", "error")
197
- return redirect(url_for("control.deck_controllers"))
198
- global_config.deck = module
199
- global_config.deck_snapshot = module_sigs
200
-
201
- if script.deck is None:
202
- script.deck = module.__name__
203
- # file path error exception
204
- except Exception as e:
205
- flash(e.__str__())
206
- return redirect(back)
207
-
208
-
209
- @control.route('/save-order/<instrument>', methods=['POST'])
210
- def save_order(instrument):
211
- # Save the new order for the specified group to session
212
- data = request.json
213
- post_session_by_instrument('card_order', instrument, data['order'])
214
- return '', 204
215
-
216
-
217
- @control.route('/hide_function/<instrument>/<function>')
218
- def hide_function(instrument, function):
219
- back = request.referrer
220
- functions = get_session_by_instrument("hidden_functions", instrument)
221
- order = get_session_by_instrument("card_order", instrument)
222
- if function not in functions:
223
- functions.append(function)
224
- order.remove(function)
225
- post_session_by_instrument('hidden_functions', instrument, functions)
226
- post_session_by_instrument('card_order', instrument, order)
227
- return redirect(back)
228
-
229
-
230
- @control.route('/remove_hidden/<instrument>/<function>')
231
- def remove_hidden(instrument, function):
232
- back = request.referrer
233
- functions = get_session_by_instrument("hidden_functions", instrument)
234
- order = get_session_by_instrument("card_order", instrument)
235
- if function in functions:
236
- functions.remove(function)
237
- order.append(function)
238
- post_session_by_instrument('hidden_functions', instrument, functions)
239
- post_session_by_instrument('card_order', instrument, order)
240
- return redirect(back)
241
-
242
-
243
- def get_session_by_instrument(session_name, instrument):
244
- """get data from session by instrument"""
245
- session_object = session.get(session_name, {})
246
- functions = session_object.get(instrument, [])
247
- return functions
248
-
249
-
250
- def post_session_by_instrument(session_name, instrument, data):
251
- """
252
- save new data to session by instrument
253
- :param session_name: "card_order" or "hidden_functions"
254
- :param instrument: function name of class object
255
- :param data: order list or hidden function list
256
- """
257
- session_object = session.get(session_name, {})
258
- session_object[instrument] = data
259
- session[session_name] = session_object
260
-
261
-
262
- def find_instrument_by_name(name: str):
263
- """
264
- find instrument class object by instance name
265
- """
266
- if name.startswith("deck"):
267
- name = name.replace("deck.", "")
268
- return getattr(global_config.deck, name)
269
- elif name in global_config.defined_variables:
270
- return global_config.defined_variables[name]
271
- elif name in globals():
272
- return globals()[name]
1
+ import os
2
+
3
+ from flask import Blueprint, redirect, url_for, flash, request, render_template, session, current_app, jsonify
4
+ from flask_login import login_required
5
+
6
+ from ivoryos.utils.global_config import GlobalConfig
7
+ from ivoryos.utils import utils
8
+ from ivoryos.utils.form import create_form_from_module, format_name
9
+
10
+ global_config = GlobalConfig()
11
+
12
+ control = Blueprint('control', __name__, template_folder='templates/control')
13
+
14
+
15
+ @control.route("/my_deck")
16
+ @login_required
17
+ def deck_controllers():
18
+ """
19
+ .. :quickref: Direct Control; controls home interface
20
+
21
+ deck control home interface for listing all deck instruments
22
+
23
+ .. http:get:: /my_deck
24
+ """
25
+ deck_variables = global_config.deck_snapshot.keys()
26
+ deck_list = utils.import_history(os.path.join(current_app.config["OUTPUT_FOLDER"], 'deck_history.txt'))
27
+ return render_template('controllers_home.html', defined_variables=deck_variables, deck=True, history=deck_list)
28
+
29
+
30
+ @control.route("/new_controller/")
31
+ @control.route("/new_controller/<instrument>", methods=['GET', 'POST'])
32
+ @login_required
33
+ def new_controller(instrument=None):
34
+ """
35
+ .. :quickref: Direct Control; connect to a new device
36
+
37
+ interface for connecting a new <instrument>
38
+
39
+ .. http:get:: /new_controller
40
+
41
+ :param instrument: instrument name
42
+ :type instrument: str
43
+
44
+ .. http:post:: /new_controller
45
+
46
+ :form device_name: module instance name (e.g. my_instance = MyClass())
47
+ :form kwargs: dynamic module initialization kwargs fields
48
+
49
+ """
50
+ device = None
51
+ args = None
52
+ if instrument:
53
+
54
+ device = find_instrument_by_name(instrument)
55
+ args = utils.inspect.signature(device.__init__)
56
+
57
+ if request.method == 'POST':
58
+ device_name = request.form.get("device_name", "")
59
+ if device_name and device_name in globals():
60
+ flash("Device name is defined. Try another name, or leave it as blank to auto-configure")
61
+ return render_template('controllers_new.html', instrument=instrument,
62
+ api_variables=global_config.api_variables,
63
+ device=device, args=args, defined_variables=global_config.defined_variables)
64
+ if device_name == "":
65
+ device_name = device.__name__.lower() + "_"
66
+ num = 1
67
+ while device_name + str(num) in global_config.defined_variables:
68
+ num += 1
69
+ device_name = device_name + str(num)
70
+ kwargs = request.form.to_dict()
71
+ kwargs.pop("device_name")
72
+ for i in kwargs:
73
+ if kwargs[i] in global_config.defined_variables:
74
+ kwargs[i] = global_config.defined_variables[kwargs[i]]
75
+ try:
76
+ utils.convert_config_type(kwargs, device.__init__.__annotations__, is_class=True)
77
+ except Exception as e:
78
+ flash(e)
79
+ try:
80
+ global_config.defined_variables[device_name] = device(**kwargs)
81
+ # global_config.defined_variables.add(device_name)
82
+ return redirect(url_for('control.controllers_home'))
83
+ except Exception as e:
84
+ flash(e)
85
+ return render_template('controllers_new.html', instrument=instrument, api_variables=global_config.api_variables,
86
+ device=device, args=args, defined_variables=global_config.defined_variables)
87
+
88
+
89
+ @control.route("/controllers")
90
+ @login_required
91
+ def controllers_home():
92
+ """
93
+ .. :quickref: Direct Control; temp control home interface
94
+
95
+ temporarily connected devices home interface for listing all instruments
96
+
97
+ .. http:get:: /controllers
98
+
99
+ """
100
+ # defined_variables = parse_deck(deck)
101
+ return render_template('controllers_home.html', defined_variables=global_config.defined_variables)
102
+
103
+
104
+ @control.route("/controllers/<instrument>", methods=['GET', 'POST'])
105
+ @login_required
106
+ def controllers(instrument: str):
107
+ """
108
+ .. :quickref: Direct Control; control interface
109
+
110
+ control interface for selected <instrument>
111
+
112
+ .. http:get:: /controllers
113
+
114
+ :param instrument: instrument name
115
+ :type instrument: str
116
+
117
+ .. http:post:: /controllers
118
+
119
+ :form hidden_name: function name (hidden field)
120
+ :form kwargs: dynamic kwargs field
121
+
122
+ """
123
+ inst_object = find_instrument_by_name(instrument)
124
+ _forms = create_form_from_module(sdl_module=inst_object, autofill=False, design=False)
125
+ functions = list(_forms.keys())
126
+
127
+ order = get_session_by_instrument('card_order', instrument)
128
+ hidden_functions = get_session_by_instrument('hide_function', instrument)
129
+
130
+ for function in functions:
131
+ if function not in hidden_functions and function not in order:
132
+ order.append(function)
133
+ post_session_by_instrument('card_order', instrument, order)
134
+ forms = {name: _forms[name] for name in order if name in _forms}
135
+ if request.method == 'POST':
136
+ all_kwargs = request.form.copy()
137
+ method_name = all_kwargs.pop("hidden_name", None)
138
+ # if method_name is not None:
139
+ form = forms.get(method_name)
140
+ kwargs = {field.name: field.data for field in form if field.name != 'csrf_token'}
141
+ function_executable = getattr(inst_object, method_name)
142
+ if form and form.validate_on_submit():
143
+ try:
144
+ kwargs.pop("hidden_name")
145
+ output = function_executable(**kwargs)
146
+ flash(f"\nRun Success! Output value: {output}.")
147
+ except Exception as e:
148
+ flash(e.__str__())
149
+ else:
150
+ flash(form.errors)
151
+ return render_template('controllers.html', instrument=instrument, forms=forms, format_name=format_name)
152
+
153
+
154
+ @control.route("/backend_control/<instrument>", methods=['GET', 'POST'])
155
+ def backend_control(instrument: str=None):
156
+ """
157
+ .. :quickref: Backend Control; backend control
158
+
159
+ backend control through http requests
160
+
161
+ .. http:get:: /backend_control
162
+
163
+ :param instrument: instrument name
164
+ :type instrument: str
165
+
166
+ .. http:post:: /backend_control
167
+
168
+ """
169
+ inst_object = find_instrument_by_name(instrument)
170
+ forms = create_form_from_module(sdl_module=inst_object, autofill=False, design=False)
171
+
172
+ if request.method == 'POST':
173
+ all_kwargs = request.form.copy()
174
+ method_name = all_kwargs.pop("hidden_name", None)
175
+ # if method_name is not None:
176
+ form = forms.get(method_name, None)
177
+ kwargs = {field.name: field.data for field in form if field.name != 'csrf_token'}
178
+ function_executable = getattr(inst_object, method_name)
179
+ if form:
180
+ # print(kwargs)
181
+ try:
182
+ kwargs.pop("hidden_name")
183
+ output = function_executable(**kwargs)
184
+ json_output = jsonify(output)
185
+ except Exception as e:
186
+ json_output = jsonify(e.__str__())
187
+ return json_output, 400
188
+ else:
189
+ return "instrument not exist", 400
190
+ return '', 200
191
+
192
+
193
+ @control.route("/backend_control", methods=['GET'])
194
+ def backend_client():
195
+ """
196
+ .. :quickref: Backend Control; get snapshot
197
+
198
+ backend control through http requests
199
+
200
+ .. http:get:: /backend_control
201
+ """
202
+ # Create a snapshot of the current deck configuration
203
+ snapshot = global_config.deck_snapshot.copy()
204
+
205
+ # Iterate through each instrument in the snapshot
206
+ for instrument_key, instrument_data in snapshot.items():
207
+ # Iterate through each function associated with the current instrument
208
+ for function_key, function_data in instrument_data.items():
209
+ # Convert the function signature to a string representation
210
+ function_data['signature'] = str(function_data['signature'])
211
+
212
+ json_output = jsonify(snapshot)
213
+ return json_output, 200
214
+
215
+
216
+ @control.route("/import_api", methods=['POST'])
217
+ def import_api():
218
+ """
219
+ .. :quickref: Advanced Features; Manually import API module(s)
220
+
221
+ importing other Python modules
222
+
223
+ .. http:post:: /import_api
224
+
225
+ :form filepath: API (Python class) module filepath
226
+
227
+ import the module and redirect to :http:get:`/ivoryos/new_controller/`
228
+
229
+ """
230
+ filepath = request.form.get('filepath')
231
+ # filepath.replace('\\', '/')
232
+ name = os.path.split(filepath)[-1].split('.')[0]
233
+ try:
234
+ spec = utils.importlib.util.spec_from_file_location(name, filepath)
235
+ module = utils.importlib.util.module_from_spec(spec)
236
+ spec.loader.exec_module(module)
237
+ classes = utils.inspect.getmembers(module, utils.inspect.isclass)
238
+ if len(classes) == 0:
239
+ flash("Invalid import: no class found in the path")
240
+ return redirect(url_for("control.controllers_home"))
241
+ for i in classes:
242
+ globals()[i[0]] = i[1]
243
+ global_config.api_variables.add(i[0])
244
+ # should handle path error and file type error
245
+ except Exception as e:
246
+ flash(e.__str__())
247
+ return redirect(url_for("control.new_controller"))
248
+
249
+
250
+ # @control.route("/disconnect", methods=["GET"])
251
+ # @control.route("/disconnect/<device_name>", methods=["GET"])
252
+ # def disconnect(device_name=None):
253
+ # """TODO handle disconnect device"""
254
+ # if device_name:
255
+ # try:
256
+ # exec(device_name + ".disconnect()")
257
+ # except Exception:
258
+ # pass
259
+ # global_config.defined_variables.remove(device_name)
260
+ # globals().pop(device_name)
261
+ # return redirect(url_for('control.controllers_home'))
262
+ #
263
+ # deck_variables = ["deck." + var for var in set(dir(deck))
264
+ # if not (var.startswith("_") or var[0].isupper() or var.startswith("repackage"))
265
+ # and not type(eval("deck." + var)).__module__ == 'builtins']
266
+ # for i in deck_variables:
267
+ # try:
268
+ # exec(i + ".disconnect()")
269
+ # except Exception:
270
+ # pass
271
+ # globals()["deck"] = None
272
+ # return redirect(url_for('control.deck_controllers'))
273
+
274
+
275
+ @control.route("/import_deck", methods=['POST'])
276
+ def import_deck():
277
+ """
278
+ .. :quickref: Advanced Features; Manually import a deck
279
+
280
+ .. http:post:: /import_deck
281
+
282
+ :form filepath: deck module filepath
283
+
284
+ import the module and redirect to the previous page
285
+
286
+ """
287
+ script = utils.get_script_file()
288
+ filepath = request.form.get('filepath')
289
+ session['dismiss'] = request.form.get('dismiss')
290
+ update = request.form.get('update')
291
+ back = request.referrer
292
+ if session['dismiss']:
293
+ return redirect(back)
294
+ name = os.path.split(filepath)[-1].split('.')[0]
295
+ try:
296
+ module = utils.import_module_by_filepath(filepath=filepath, name=name)
297
+ utils.save_to_history(filepath, current_app.config["DECK_HISTORY"])
298
+ module_sigs = utils.create_deck_snapshot(module, save=update, output_path=current_app.config["DUMMY_DECK"])
299
+ if not len(module_sigs) > 0:
300
+ flash("Invalid hardware deck, connect instruments in deck script", "error")
301
+ return redirect(url_for("control.deck_controllers"))
302
+ global_config.deck = module
303
+ global_config.deck_snapshot = module_sigs
304
+
305
+ if script.deck is None:
306
+ script.deck = module.__name__
307
+ # file path error exception
308
+ except Exception as e:
309
+ flash(e.__str__())
310
+ return redirect(back)
311
+
312
+
313
+ @control.route('/save-order/<instrument>', methods=['POST'])
314
+ def save_order(instrument: str):
315
+ """
316
+ .. :quickref: Control Customization; Save functions' order
317
+
318
+ .. http:post:: /save-order
319
+
320
+ save function drag and drop order for the given <instrument>
321
+
322
+ """
323
+ # Save the new order for the specified group to session
324
+ data = request.json
325
+ post_session_by_instrument('card_order', instrument, data['order'])
326
+ return '', 204
327
+
328
+
329
+ @control.route('/hide_function/<instrument>/<function>')
330
+ def hide_function(instrument, function):
331
+ """
332
+ .. :quickref: Control Customization; Hide function
333
+
334
+ .. http:get:: /hide_function
335
+
336
+ Hide the given <instrument> and <function>
337
+
338
+ """
339
+ back = request.referrer
340
+ functions = get_session_by_instrument("hidden_functions", instrument)
341
+ order = get_session_by_instrument("card_order", instrument)
342
+ if function not in functions:
343
+ functions.append(function)
344
+ order.remove(function)
345
+ post_session_by_instrument('hidden_functions', instrument, functions)
346
+ post_session_by_instrument('card_order', instrument, order)
347
+ return redirect(back)
348
+
349
+
350
+ @control.route('/remove_hidden/<instrument>/<function>')
351
+ def remove_hidden(instrument: str, function: str):
352
+ """
353
+ .. :quickref: Control Customization; Remove a hidden function
354
+
355
+ .. http:get:: /remove_hidden
356
+
357
+ Un-hide the given <instrument> and <function>
358
+
359
+ """
360
+ back = request.referrer
361
+ functions = get_session_by_instrument("hidden_functions", instrument)
362
+ order = get_session_by_instrument("card_order", instrument)
363
+ if function in functions:
364
+ functions.remove(function)
365
+ order.append(function)
366
+ post_session_by_instrument('hidden_functions', instrument, functions)
367
+ post_session_by_instrument('card_order', instrument, order)
368
+ return redirect(back)
369
+
370
+
371
+ def get_session_by_instrument(session_name, instrument):
372
+ """get data from session by instrument"""
373
+ session_object = session.get(session_name, {})
374
+ functions = session_object.get(instrument, [])
375
+ return functions
376
+
377
+
378
+ def post_session_by_instrument(session_name, instrument, data):
379
+ """
380
+ save new data to session by instrument
381
+ :param session_name: "card_order" or "hidden_functions"
382
+ :param instrument: function name of class object
383
+ :param data: order list or hidden function list
384
+ """
385
+ session_object = session.get(session_name, {})
386
+ session_object[instrument] = data
387
+ session[session_name] = session_object
388
+
389
+
390
+ def find_instrument_by_name(name: str):
391
+ """
392
+ find instrument class object by instance name
393
+ """
394
+ if name.startswith("deck"):
395
+ name = name.replace("deck.", "")
396
+ return getattr(global_config.deck, name)
397
+ elif name in global_config.defined_variables:
398
+ return global_config.defined_variables[name]
399
+ elif name in globals():
400
+ return globals()[name]