ivoryos 1.0.9__py3-none-any.whl → 1.4.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. docs/source/conf.py +84 -0
  2. ivoryos/__init__.py +17 -207
  3. ivoryos/app.py +154 -0
  4. ivoryos/config.py +1 -0
  5. ivoryos/optimizer/ax_optimizer.py +191 -0
  6. ivoryos/optimizer/base_optimizer.py +84 -0
  7. ivoryos/optimizer/baybe_optimizer.py +193 -0
  8. ivoryos/optimizer/nimo_optimizer.py +173 -0
  9. ivoryos/optimizer/registry.py +11 -0
  10. ivoryos/routes/auth/auth.py +43 -14
  11. ivoryos/routes/auth/templates/change_password.html +32 -0
  12. ivoryos/routes/control/control.py +101 -366
  13. ivoryos/routes/control/control_file.py +33 -0
  14. ivoryos/routes/control/control_new_device.py +152 -0
  15. ivoryos/routes/control/templates/controllers.html +193 -0
  16. ivoryos/routes/control/templates/controllers_new.html +112 -0
  17. ivoryos/routes/control/utils.py +40 -0
  18. ivoryos/routes/data/data.py +197 -0
  19. ivoryos/routes/data/templates/components/step_card.html +78 -0
  20. ivoryos/routes/{database/templates/database → data/templates}/workflow_database.html +14 -8
  21. ivoryos/routes/data/templates/workflow_view.html +360 -0
  22. ivoryos/routes/design/__init__.py +4 -0
  23. ivoryos/routes/design/design.py +348 -657
  24. ivoryos/routes/design/design_file.py +68 -0
  25. ivoryos/routes/design/design_step.py +171 -0
  26. ivoryos/routes/design/templates/components/action_form.html +53 -0
  27. ivoryos/routes/design/templates/components/actions_panel.html +25 -0
  28. ivoryos/routes/design/templates/components/autofill_toggle.html +10 -0
  29. ivoryos/routes/design/templates/components/canvas.html +5 -0
  30. ivoryos/routes/design/templates/components/canvas_footer.html +9 -0
  31. ivoryos/routes/design/templates/components/canvas_header.html +75 -0
  32. ivoryos/routes/design/templates/components/canvas_main.html +39 -0
  33. ivoryos/routes/design/templates/components/deck_selector.html +10 -0
  34. ivoryos/routes/design/templates/components/edit_action_form.html +53 -0
  35. ivoryos/routes/design/templates/components/info_modal.html +318 -0
  36. ivoryos/routes/design/templates/components/instruments_panel.html +88 -0
  37. ivoryos/routes/design/templates/components/modals/drop_modal.html +17 -0
  38. ivoryos/routes/design/templates/components/modals/json_modal.html +22 -0
  39. ivoryos/routes/design/templates/components/modals/new_script_modal.html +17 -0
  40. ivoryos/routes/design/templates/components/modals/rename_modal.html +23 -0
  41. ivoryos/routes/design/templates/components/modals/saveas_modal.html +27 -0
  42. ivoryos/routes/design/templates/components/modals.html +6 -0
  43. ivoryos/routes/design/templates/components/python_code_overlay.html +56 -0
  44. ivoryos/routes/design/templates/components/sidebar.html +15 -0
  45. ivoryos/routes/design/templates/components/text_to_code_panel.html +20 -0
  46. ivoryos/routes/design/templates/experiment_builder.html +44 -0
  47. ivoryos/routes/execute/__init__.py +0 -0
  48. ivoryos/routes/execute/execute.py +377 -0
  49. ivoryos/routes/execute/execute_file.py +78 -0
  50. ivoryos/routes/execute/templates/components/error_modal.html +20 -0
  51. ivoryos/routes/execute/templates/components/logging_panel.html +56 -0
  52. ivoryos/routes/execute/templates/components/progress_panel.html +27 -0
  53. ivoryos/routes/execute/templates/components/run_panel.html +9 -0
  54. ivoryos/routes/execute/templates/components/run_tabs.html +60 -0
  55. ivoryos/routes/execute/templates/components/tab_bayesian.html +520 -0
  56. ivoryos/routes/execute/templates/components/tab_configuration.html +383 -0
  57. ivoryos/routes/execute/templates/components/tab_repeat.html +18 -0
  58. ivoryos/routes/execute/templates/experiment_run.html +30 -0
  59. ivoryos/routes/library/__init__.py +0 -0
  60. ivoryos/routes/library/library.py +157 -0
  61. ivoryos/routes/{database/templates/database/scripts_database.html → library/templates/library.html} +32 -23
  62. ivoryos/routes/main/main.py +31 -3
  63. ivoryos/routes/main/templates/{main/home.html → home.html} +4 -4
  64. ivoryos/server.py +180 -0
  65. ivoryos/socket_handlers.py +52 -0
  66. ivoryos/static/ivoryos_logo.png +0 -0
  67. ivoryos/static/js/action_handlers.js +384 -0
  68. ivoryos/static/js/db_delete.js +23 -0
  69. ivoryos/static/js/script_metadata.js +39 -0
  70. ivoryos/static/js/socket_handler.js +40 -5
  71. ivoryos/static/js/sortable_design.js +107 -56
  72. ivoryos/static/js/ui_state.js +114 -0
  73. ivoryos/templates/base.html +67 -8
  74. ivoryos/utils/bo_campaign.py +180 -3
  75. ivoryos/utils/client_proxy.py +267 -36
  76. ivoryos/utils/db_models.py +300 -65
  77. ivoryos/utils/decorators.py +34 -0
  78. ivoryos/utils/form.py +63 -29
  79. ivoryos/utils/global_config.py +34 -1
  80. ivoryos/utils/nest_script.py +314 -0
  81. ivoryos/utils/py_to_json.py +295 -0
  82. ivoryos/utils/script_runner.py +599 -165
  83. ivoryos/utils/serilize.py +201 -0
  84. ivoryos/utils/task_runner.py +71 -21
  85. ivoryos/utils/utils.py +50 -6
  86. ivoryos/version.py +1 -1
  87. ivoryos-1.4.4.dist-info/METADATA +263 -0
  88. ivoryos-1.4.4.dist-info/RECORD +119 -0
  89. {ivoryos-1.0.9.dist-info → ivoryos-1.4.4.dist-info}/WHEEL +1 -1
  90. {ivoryos-1.0.9.dist-info → ivoryos-1.4.4.dist-info}/top_level.txt +1 -0
  91. tests/unit/test_type_conversion.py +42 -0
  92. tests/unit/test_util.py +3 -0
  93. ivoryos/routes/control/templates/control/controllers.html +0 -78
  94. ivoryos/routes/control/templates/control/controllers_home.html +0 -55
  95. ivoryos/routes/control/templates/control/controllers_new.html +0 -89
  96. ivoryos/routes/database/database.py +0 -306
  97. ivoryos/routes/database/templates/database/step_card.html +0 -7
  98. ivoryos/routes/database/templates/database/workflow_view.html +0 -130
  99. ivoryos/routes/design/templates/design/experiment_builder.html +0 -521
  100. ivoryos/routes/design/templates/design/experiment_run.html +0 -558
  101. ivoryos-1.0.9.dist-info/METADATA +0 -218
  102. ivoryos-1.0.9.dist-info/RECORD +0 -61
  103. /ivoryos/routes/auth/templates/{auth/login.html → login.html} +0 -0
  104. /ivoryos/routes/auth/templates/{auth/signup.html → signup.html} +0 -0
  105. /ivoryos/routes/{database → data}/__init__.py +0 -0
  106. /ivoryos/routes/main/templates/{main/help.html → help.html} +0 -0
  107. {ivoryos-1.0.9.dist-info → ivoryos-1.4.4.dist-info/licenses}/LICENSE +0 -0
@@ -1,350 +1,127 @@
1
- import os
1
+ import copy
2
2
 
3
- from flask import Blueprint, redirect, url_for, flash, request, render_template, session, current_app, jsonify, \
4
- send_file
3
+ from flask import Blueprint, redirect, flash, request, render_template, session, current_app, jsonify
5
4
  from flask_login import login_required
6
5
 
7
- from ivoryos.utils.client_proxy import export_to_python, create_function
6
+ from ivoryos.routes.control.control_file import control_file
7
+ from ivoryos.routes.control.control_new_device import control_temp
8
+ from ivoryos.routes.control.utils import post_session_by_instrument, get_session_by_instrument, find_instrument_by_name
8
9
  from ivoryos.utils.global_config import GlobalConfig
9
- from ivoryos.utils import utils
10
- from ivoryos.utils.form import create_form_from_module, format_name
10
+ from ivoryos.utils.form import create_form_from_module, create_form_from_pseudo
11
11
  from ivoryos.utils.task_runner import TaskRunner
12
12
 
13
13
  global_config = GlobalConfig()
14
14
  runner = TaskRunner()
15
15
 
16
- control = Blueprint('control', __name__, template_folder='templates/control')
16
+ control = Blueprint('control', __name__, template_folder='templates')
17
17
 
18
+ control.register_blueprint(control_file)
19
+ control.register_blueprint(control_temp)
18
20
 
19
- @control.route("/control/home/deck", strict_slashes=False)
20
- @login_required
21
- def deck_controllers():
22
- """
23
- .. :quickref: Direct Control; controls home interface
24
-
25
- deck control home interface for listing all deck instruments
26
-
27
- .. http:get:: /control/home/deck
28
- """
29
- deck_variables = global_config.deck_snapshot.keys()
30
- deck_list = utils.import_history(os.path.join(current_app.config["OUTPUT_FOLDER"], 'deck_history.txt'))
31
- return render_template('controllers_home.html', defined_variables=deck_variables, deck=True, history=deck_list)
32
21
 
33
22
 
34
- @control.route("/control/new/", strict_slashes=False)
35
- @control.route("/control/new/<instrument>", methods=['GET', 'POST'])
23
+ @control.route("/", strict_slashes=False, methods=["GET", "POST"])
24
+ @control.route("/<string:instrument>", strict_slashes=False, methods=["GET", "POST"])
36
25
  @login_required
37
- def new_controller(instrument=None):
26
+ async def deck_controllers(instrument: str = None):
38
27
  """
39
- .. :quickref: Direct Control; connect to a new device
40
-
41
- interface for connecting a new <instrument>
42
-
43
- .. http:get:: /control/new/
44
-
45
- :param instrument: instrument name
46
- :type instrument: str
47
-
48
- .. http:post:: /control/new/
28
+ .. :quickref: Direct Control; device (instruments) and methods
49
29
 
50
- :form device_name: module instance name (e.g. my_instance = MyClass())
51
- :form kwargs: dynamic module initialization kwargs fields
30
+ device home interface for listing all instruments and methods, selecting an instrument to run its methods
52
31
 
53
- """
54
- device = None
55
- args = None
56
- if instrument:
57
-
58
- device = find_instrument_by_name(instrument)
59
- args = utils.inspect.signature(device.__init__)
60
-
61
- if request.method == 'POST':
62
- device_name = request.form.get("device_name", "")
63
- if device_name and device_name in globals():
64
- flash("Device name is defined. Try another name, or leave it as blank to auto-configure")
65
- return render_template('controllers_new.html', instrument=instrument,
66
- api_variables=global_config.api_variables,
67
- device=device, args=args, defined_variables=global_config.defined_variables)
68
- if device_name == "":
69
- device_name = device.__name__.lower() + "_"
70
- num = 1
71
- while device_name + str(num) in global_config.defined_variables:
72
- num += 1
73
- device_name = device_name + str(num)
74
- kwargs = request.form.to_dict()
75
- kwargs.pop("device_name")
76
- for i in kwargs:
77
- if kwargs[i] in global_config.defined_variables:
78
- kwargs[i] = global_config.defined_variables[kwargs[i]]
79
- try:
80
- utils.convert_config_type(kwargs, device.__init__.__annotations__, is_class=True)
81
- except Exception as e:
82
- flash(e)
83
- try:
84
- global_config.defined_variables[device_name] = device(**kwargs)
85
- # global_config.defined_variables.add(device_name)
86
- return redirect(url_for('control.controllers_home'))
87
- except Exception as e:
88
- flash(e)
89
- return render_template('controllers_new.html', instrument=instrument, api_variables=global_config.api_variables,
90
- device=device, args=args, defined_variables=global_config.defined_variables)
91
-
92
-
93
- @control.route("/control/home/temp", strict_slashes=False)
94
- @login_required
95
- def controllers_home():
96
- """
97
- .. :quickref: Direct Control; temp control home interface
98
-
99
- temporarily connected devices home interface for listing all instruments
100
-
101
- .. http:get:: /control/home/temp
102
-
103
- """
104
- # defined_variables = parse_deck(deck)
105
- defined_variables = global_config.defined_variables.keys()
106
- return render_template('controllers_home.html', defined_variables=defined_variables)
107
-
108
-
109
- @control.route("/control/<instrument>/methods", methods=['GET', 'POST'])
110
- @login_required
111
- def controllers(instrument: str):
112
- """
113
- .. :quickref: Direct Control; control interface
114
-
115
- control interface for selected <instrument>
116
-
117
- .. http:get:: /control/<instrument>/methods
118
-
119
- :param instrument: instrument name
120
- :type instrument: str
121
-
122
- .. http:post:: /control/<instrument>/methods
123
-
124
- :form hidden_name: function name (hidden field)
125
- :form kwargs: dynamic kwargs field
126
-
127
- """
128
- inst_object = find_instrument_by_name(instrument)
129
- _forms = create_form_from_module(sdl_module=inst_object, autofill=False, design=False)
130
- functions = list(_forms.keys())
131
-
132
- order = get_session_by_instrument('card_order', instrument)
133
- hidden_functions = get_session_by_instrument('hide_function', instrument)
134
-
135
- for function in functions:
136
- if function not in hidden_functions and function not in order:
137
- order.append(function)
138
- post_session_by_instrument('card_order', instrument, order)
139
- forms = {name: _forms[name] for name in order if name in _forms}
140
- if request.method == 'POST':
141
- all_kwargs = request.form.copy()
142
- method_name = all_kwargs.pop("hidden_name", None)
143
- # if method_name is not None:
144
- form = forms.get(method_name)
145
- kwargs = {field.name: field.data for field in form if field.name != 'csrf_token'}
146
- function_executable = getattr(inst_object, method_name)
147
- if form and form.validate_on_submit():
148
- try:
149
- kwargs.pop("hidden_name")
150
- output = runner.run_single_step(instrument, method_name, kwargs, wait=True,
151
- current_app=current_app._get_current_object())
152
- # output = function_executable(**kwargs)
153
- flash(f"\nRun Success! Output value: {output}.")
154
- except Exception as e:
155
- flash(e.__str__())
156
- else:
157
- flash(form.errors)
158
- return render_template('controllers.html', instrument=instrument, forms=forms, format_name=format_name)
32
+ .. http:get:: /instruments
159
33
 
160
- @control.route("/control/download", strict_slashes=False)
161
- @login_required
162
- def download_proxy():
163
- """
164
- .. :quickref: Direct Control; download proxy interface
34
+ get all instruments for home page
165
35
 
166
- download proxy interface
36
+ .. http:get:: /instruments/<string:instrument>
167
37
 
168
- .. http:get:: /control/download
169
- """
170
- snapshot = global_config.deck_snapshot.copy()
171
- class_definitions = {}
172
- # Iterate through each instrument in the snapshot
173
- for instrument_key, instrument_data in snapshot.items():
174
- # Iterate through each function associated with the current instrument
175
- for function_key, function_data in instrument_data.items():
176
- # Convert the function signature to a string representation
177
- function_data['signature'] = str(function_data['signature'])
178
- class_name = instrument_key.split('.')[-1] # Extracting the class name from the path
179
- class_definitions[class_name.capitalize()] = create_function(request.url_root, class_name, instrument_data)
180
- # Export the generated class definitions to a .py script
181
- export_to_python(class_definitions, current_app.config["OUTPUT_FOLDER"])
182
- filepath = os.path.join(current_app.config["OUTPUT_FOLDER"], "generated_proxy.py")
183
- return send_file(os.path.abspath(filepath), as_attachment=True)
184
-
185
- @control.route("/api/control/", strict_slashes=False, methods=['GET'])
186
- @control.route("/api/control/<instrument>", methods=['POST'])
187
- def backend_control(instrument: str=None):
188
- """
189
- .. :quickref: Backend Control; backend control
38
+ get all methods of the given <instrument>
190
39
 
191
- backend control through http requests
40
+ .. http:post:: /instruments/<string:instrument>
192
41
 
193
- .. http:get:: /api/control/
42
+ send POST request to run a method of the given <instrument>
194
43
 
195
- :param instrument: instrument name
44
+ :param instrument: instrument name, if not provided, list all instruments
196
45
  :type instrument: str
197
-
198
- .. http:post:: /api/control/
46
+ :status 200: render template with instruments and methods
199
47
 
200
48
  """
49
+ instrument = instrument or request.args.get("instrument")
50
+ forms = None
201
51
  if instrument:
202
52
  inst_object = find_instrument_by_name(instrument)
203
- forms = create_form_from_module(sdl_module=inst_object, autofill=False, design=False)
204
-
205
- if request.method == 'POST':
206
- method_name = request.form.get("hidden_name", None)
207
- form = forms.get(method_name, None)
208
- if form:
209
- kwargs = {field.name: field.data for field in form if field.name not in ['csrf_token', 'hidden_name']}
210
- wait = request.form.get("hidden_wait", "true") == "true"
211
- output = runner.run_single_step(component=instrument, method=method_name, kwargs=kwargs, wait=wait,
212
- current_app=current_app._get_current_object())
213
- return jsonify(output), 200
214
-
215
- snapshot = global_config.deck_snapshot.copy()
216
- # Iterate through each instrument in the snapshot
217
- for instrument_key, instrument_data in snapshot.items():
218
- # Iterate through each function associated with the current instrument
219
- for function_key, function_data in instrument_data.items():
220
- # Convert the function signature to a string representation
221
- function_data['signature'] = str(function_data['signature'])
222
- return jsonify(snapshot), 200
223
-
224
- # @control.route("/api/control", strict_slashes=False, methods=['GET'])
225
- # def backend_client():
226
- # """
227
- # .. :quickref: Backend Control; get snapshot
228
- #
229
- # backend control through http requests
230
- #
231
- # .. http:get:: /api/control/summary
232
- # """
233
- # # Create a snapshot of the current deck configuration
234
- # snapshot = global_config.deck_snapshot.copy()
235
- #
236
- # # Iterate through each instrument in the snapshot
237
- # for instrument_key, instrument_data in snapshot.items():
238
- # # Iterate through each function associated with the current instrument
239
- # for function_key, function_data in instrument_data.items():
240
- # # Convert the function signature to a string representation
241
- # function_data['signature'] = str(function_data['signature'])
242
- # return jsonify(snapshot), 200
243
-
244
-
245
- @control.route("/control/import/module", methods=['POST'])
246
- def import_api():
247
- """
248
- .. :quickref: Advanced Features; Manually import API module(s)
249
-
250
- importing other Python modules
251
-
252
- .. http:post:: /control/import/module
253
-
254
- :form filepath: API (Python class) module filepath
255
-
256
- import the module and redirect to :http:get:`/ivoryos/control/new/`
53
+ if instrument.startswith("blocks"):
54
+ forms = create_form_from_pseudo(pseudo=inst_object, autofill=False, design=False)
55
+ else:
56
+ forms = create_form_from_module(sdl_module=inst_object, autofill=False, design=False)
57
+ order = get_session_by_instrument('card_order', instrument)
58
+ hidden_functions = get_session_by_instrument('hidden_functions', instrument)
59
+ functions = list(forms.keys())
60
+ for function in functions:
61
+ if function not in hidden_functions and function not in order:
62
+ order.append(function)
63
+ post_session_by_instrument('card_order', instrument, order)
64
+ forms = {name: forms[name] for name in order if name in forms}
65
+
66
+ if request.method == "POST":
67
+ if not forms:
68
+ return jsonify({"success": False, "error": "Instrument not found"}), 404
69
+
70
+ payload = request.get_json() if request.is_json else request.form.to_dict()
71
+ method_name = payload.pop("hidden_name", None)
72
+ form = forms.get(method_name)
257
73
 
258
- """
259
- filepath = request.form.get('filepath')
260
- # filepath.replace('\\', '/')
261
- name = os.path.split(filepath)[-1].split('.')[0]
262
- try:
263
- spec = utils.importlib.util.spec_from_file_location(name, filepath)
264
- module = utils.importlib.util.module_from_spec(spec)
265
- spec.loader.exec_module(module)
266
- classes = utils.inspect.getmembers(module, utils.inspect.isclass)
267
- if len(classes) == 0:
268
- flash("Invalid import: no class found in the path")
269
- return redirect(url_for("control.controllers_home"))
270
- for i in classes:
271
- globals()[i[0]] = i[1]
272
- global_config.api_variables.add(i[0])
273
- # should handle path error and file type error
274
- except Exception as e:
275
- flash(e.__str__())
276
- return redirect(url_for("control.new_controller"))
277
-
278
-
279
- # @control.route("/disconnect", methods=["GET"])
280
- # @control.route("/disconnect/<device_name>", methods=["GET"])
281
- # def disconnect(device_name=None):
282
- # """TODO handle disconnect device"""
283
- # if device_name:
284
- # try:
285
- # exec(device_name + ".disconnect()")
286
- # except Exception:
287
- # pass
288
- # global_config.defined_variables.remove(device_name)
289
- # globals().pop(device_name)
290
- # return redirect(url_for('control.controllers_home'))
291
- #
292
- # deck_variables = ["deck." + var for var in set(dir(deck))
293
- # if not (var.startswith("_") or var[0].isupper() or var.startswith("repackage"))
294
- # and not type(eval("deck." + var)).__module__ == 'builtins']
295
- # for i in deck_variables:
296
- # try:
297
- # exec(i + ".disconnect()")
298
- # except Exception:
299
- # pass
300
- # globals()["deck"] = None
301
- # return redirect(url_for('control.deck_controllers'))
302
-
303
-
304
- @control.route("/control/import/deck", methods=['POST'])
305
- def import_deck():
306
- """
307
- .. :quickref: Advanced Features; Manually import a deck
74
+ if not form:
75
+ return jsonify({"success": False, "error": f"Method {method_name} not found"}), 404
308
76
 
309
- .. http:post:: /control/import_deck
77
+ # Extract kwargs
78
+ if request.is_json:
79
+ kwargs = {k: v for k, v in payload.items() if k not in ["csrf_token", "hidden_wait"]}
80
+ else:
81
+ kwargs = {field.name: field.data for field in form if field.name not in ["csrf_token", "hidden_name"]}
310
82
 
311
- :form filepath: deck module filepath
83
+ wait = str(payload.get("hidden_wait", "true")).lower() == "true"
312
84
 
313
- import the module and redirect to the previous page
85
+ output = await runner.run_single_step(
86
+ component=instrument, method=method_name, kwargs=kwargs, wait=wait,
87
+ current_app=current_app._get_current_object()
88
+ )
314
89
 
315
- """
316
- script = utils.get_script_file()
317
- filepath = request.form.get('filepath')
318
- session['dismiss'] = request.form.get('dismiss')
319
- update = request.form.get('update')
320
- back = request.referrer
321
- if session['dismiss']:
322
- return redirect(back)
323
- name = os.path.split(filepath)[-1].split('.')[0]
324
- try:
325
- module = utils.import_module_by_filepath(filepath=filepath, name=name)
326
- utils.save_to_history(filepath, current_app.config["DECK_HISTORY"])
327
- module_sigs = utils.create_deck_snapshot(module, save=update, output_path=current_app.config["DUMMY_DECK"])
328
- if not len(module_sigs) > 0:
329
- flash("Invalid hardware deck, connect instruments in deck script", "error")
330
- return redirect(url_for("control.deck_controllers"))
331
- global_config.deck = module
332
- global_config.deck_snapshot = module_sigs
333
-
334
- if script.deck is None:
335
- script.deck = module.__name__
336
- # file path error exception
337
- except Exception as e:
338
- flash(e.__str__())
339
- return redirect(back)
340
-
341
-
342
- @control.route('/control/<instrument>/save-order', methods=['POST'])
90
+ if request.is_json:
91
+ return jsonify(output)
92
+ else:
93
+ if output.get("success"):
94
+ flash(f"Run Success! Output: {output.get('output', 'None')}")
95
+ else:
96
+ flash(f"Run Error! {output.get('output', 'Unknown error occurred.')}", "error")
97
+
98
+ # GET request → render web form or return snapshot for API
99
+ if request.is_json or request.accept_mimetypes.best_match(['application/json', 'text/html']) == 'application/json':
100
+ # 1.3.2 fix snapshot copy, add building blocks to snapshots
101
+ snapshot = copy.deepcopy(global_config.deck_snapshot)
102
+ building_blocks = copy.deepcopy(global_config.building_blocks)
103
+ snapshot.update(building_blocks)
104
+ for instrument_key, instrument_data in snapshot.items():
105
+ for function_key, function_data in instrument_data.items():
106
+ function_data["signature"] = str(function_data["signature"])
107
+ return jsonify(snapshot)
108
+
109
+ return render_template(
110
+ "controllers.html",
111
+ defined_variables=global_config.deck_snapshot.keys(),
112
+ block_variables=global_config.building_blocks.keys(),
113
+ temp_variables=global_config.defined_variables.keys(),
114
+ instrument=instrument,
115
+ forms=forms,
116
+ session=session
117
+ )
118
+
119
+ @control.route('/<string:instrument>/actions/order', methods=['POST'])
343
120
  def save_order(instrument: str):
344
121
  """
345
122
  .. :quickref: Control Customization; Save functions' order
346
123
 
347
- .. http:post:: /control/save-order
124
+ .. http:post:: instruments/<string:instrument>/actions/order
348
125
 
349
126
  save function drag and drop order for the given <instrument>
350
127
 
@@ -354,76 +131,34 @@ def save_order(instrument: str):
354
131
  post_session_by_instrument('card_order', instrument, data['order'])
355
132
  return '', 204
356
133
 
357
-
358
- @control.route('/control/<instrument>/<function>/hide')
359
- def hide_function(instrument, function):
134
+ @control.route('/<string:instrument>/actions/<string:function>', methods=["PATCH"])
135
+ def hide_function(instrument: str, function: str):
360
136
  """
361
- .. :quickref: Control Customization; Hide function
137
+ .. :quickref: Control Customization; Toggle function visibility
362
138
 
363
- .. http:get:: //control/<instrument>/<function>/hide
139
+ .. http:patch:: /instruments/<instrument>/actions/<function>
364
140
 
365
- Hide the given <instrument> and <function>
141
+ Toggle visibility for the given <instrument> and <function>
366
142
 
367
143
  """
368
144
  back = request.referrer
145
+ data = request.get_json()
146
+ hidden = data.get('hidden', True)
369
147
  functions = get_session_by_instrument("hidden_functions", instrument)
370
148
  order = get_session_by_instrument("card_order", instrument)
371
- if function not in functions:
149
+ if hidden and function not in functions:
372
150
  functions.append(function)
373
- order.remove(function)
374
- post_session_by_instrument('hidden_functions', instrument, functions)
375
- post_session_by_instrument('card_order', instrument, order)
376
- return redirect(back)
377
-
378
-
379
- @control.route('/control/<instrument>/<function>/unhide')
380
- def remove_hidden(instrument: str, function: str):
381
- """
382
- .. :quickref: Control Customization; Remove a hidden function
383
-
384
- .. http:get:: /control/<instrument>/<function>/unhide
385
-
386
- Un-hide the given <instrument> and <function>
387
-
388
- """
389
- back = request.referrer
390
- functions = get_session_by_instrument("hidden_functions", instrument)
391
- order = get_session_by_instrument("card_order", instrument)
392
- if function in functions:
151
+ if function in order:
152
+ order.remove(function)
153
+ elif not hidden and function in functions:
393
154
  functions.remove(function)
394
- order.append(function)
155
+ if function not in order:
156
+ order.append(function)
395
157
  post_session_by_instrument('hidden_functions', instrument, functions)
396
158
  post_session_by_instrument('card_order', instrument, order)
397
- return redirect(back)
159
+ return jsonify(success=True, message="Visibility updated")
398
160
 
399
161
 
400
- def get_session_by_instrument(session_name, instrument):
401
- """get data from session by instrument"""
402
- session_object = session.get(session_name, {})
403
- functions = session_object.get(instrument, [])
404
- return functions
405
162
 
406
163
 
407
- def post_session_by_instrument(session_name, instrument, data):
408
- """
409
- save new data to session by instrument
410
- :param session_name: "card_order" or "hidden_functions"
411
- :param instrument: function name of class object
412
- :param data: order list or hidden function list
413
- """
414
- session_object = session.get(session_name, {})
415
- session_object[instrument] = data
416
- session[session_name] = session_object
417
-
418
164
 
419
- def find_instrument_by_name(name: str):
420
- """
421
- find instrument class object by instance name
422
- """
423
- if name.startswith("deck"):
424
- name = name.replace("deck.", "")
425
- return getattr(global_config.deck, name)
426
- elif name in global_config.defined_variables:
427
- return global_config.defined_variables[name]
428
- elif name in globals():
429
- return globals()[name]
@@ -0,0 +1,33 @@
1
+ import os
2
+ from flask import Blueprint, request,current_app, send_file
3
+ from flask_login import login_required
4
+
5
+ from ivoryos.utils.client_proxy import ProxyGenerator
6
+ from ivoryos.utils.global_config import GlobalConfig
7
+
8
+ global_config = GlobalConfig()
9
+
10
+ control_file = Blueprint('file', __name__)
11
+
12
+
13
+
14
+ @control_file.route("/files/proxy", strict_slashes=False)
15
+ @login_required
16
+ def download_proxy():
17
+ """
18
+ .. :quickref: Direct Control Files; Download proxy Python interface
19
+
20
+ download proxy Python interface
21
+
22
+ .. http:get:: /files/proxy
23
+ """
24
+ generator = ProxyGenerator(request.url_root)
25
+ snapshot = global_config.deck_snapshot.copy()
26
+
27
+ filepath = generator.generate_from_flask_route(
28
+ snapshot,
29
+ request.url_root,
30
+ current_app.config["OUTPUT_FOLDER"]
31
+ )
32
+
33
+ return send_file(os.path.abspath(filepath), as_attachment=True)