ivoryos 1.0.8__py3-none-any.whl → 1.1.0__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 (78) hide show
  1. ivoryos/__init__.py +19 -7
  2. ivoryos/routes/api/api.py +109 -0
  3. ivoryos/routes/auth/auth.py +5 -5
  4. ivoryos/routes/control/control.py +55 -353
  5. ivoryos/routes/control/control_file.py +36 -0
  6. ivoryos/routes/control/control_new_device.py +142 -0
  7. ivoryos/routes/control/templates/controllers.html +137 -0
  8. ivoryos/routes/control/templates/controllers_new.html +112 -0
  9. ivoryos/routes/control/utils.py +38 -0
  10. ivoryos/routes/data/data.py +108 -0
  11. ivoryos/routes/{database/templates/database → data/templates}/workflow_database.html +7 -7
  12. ivoryos/routes/{database/templates/database → data/templates}/workflow_view.html +3 -3
  13. ivoryos/routes/design/__init__.py +4 -0
  14. ivoryos/routes/design/design.py +96 -517
  15. ivoryos/routes/design/design_file.py +57 -0
  16. ivoryos/routes/design/design_step.py +43 -0
  17. ivoryos/routes/design/templates/components/action_form.html +52 -0
  18. ivoryos/routes/design/templates/components/action_list.html +15 -0
  19. ivoryos/routes/design/templates/components/autofill_toggle.html +14 -0
  20. ivoryos/routes/design/templates/components/canvas.html +14 -0
  21. ivoryos/routes/design/templates/components/canvas_footer.html +5 -0
  22. ivoryos/routes/design/templates/components/canvas_header.html +54 -0
  23. ivoryos/routes/design/templates/components/deck_selector.html +12 -0
  24. ivoryos/routes/design/templates/components/edit_action_form.html +29 -0
  25. ivoryos/routes/design/templates/components/instrument_panel.html +23 -0
  26. ivoryos/routes/design/templates/components/modals/drop_modal.html +19 -0
  27. ivoryos/routes/design/templates/components/modals/json_modal.html +22 -0
  28. ivoryos/routes/design/templates/components/modals/new_script_modal.html +18 -0
  29. ivoryos/routes/design/templates/components/modals/rename_modal.html +23 -0
  30. ivoryos/routes/design/templates/components/modals/saveas_modal.html +27 -0
  31. ivoryos/routes/design/templates/components/modals.html +6 -0
  32. ivoryos/routes/design/templates/components/operations_panel.html +43 -0
  33. ivoryos/routes/design/templates/components/python_code_overlay.html +17 -0
  34. ivoryos/routes/design/templates/components/script_info.html +31 -0
  35. ivoryos/routes/design/templates/components/scripts.html +50 -0
  36. ivoryos/routes/design/templates/components/sidebar.html +16 -0
  37. ivoryos/routes/design/templates/components/text_to_code_panel.html +20 -0
  38. ivoryos/routes/design/templates/experiment_builder.html +41 -0
  39. ivoryos/routes/execute/__init__.py +0 -0
  40. ivoryos/routes/execute/execute.py +173 -0
  41. ivoryos/routes/execute/execute_file.py +44 -0
  42. ivoryos/routes/execute/templates/components/error_modal.html +20 -0
  43. ivoryos/routes/execute/templates/components/logging_panel.html +31 -0
  44. ivoryos/routes/execute/templates/components/progress_panel.html +27 -0
  45. ivoryos/routes/execute/templates/components/run_panel.html +9 -0
  46. ivoryos/routes/execute/templates/components/run_tabs.html +17 -0
  47. ivoryos/routes/execute/templates/components/tab_bayesian.html +147 -0
  48. ivoryos/routes/execute/templates/components/tab_configuration.html +98 -0
  49. ivoryos/routes/execute/templates/components/tab_repeat.html +14 -0
  50. ivoryos/routes/execute/templates/experiment_run.html +294 -0
  51. ivoryos/routes/library/__init__.py +0 -0
  52. ivoryos/routes/{database/database.py → library/library.py} +10 -112
  53. ivoryos/routes/{database/templates/database/scripts_database.html → library/templates/library.html} +8 -8
  54. ivoryos/routes/main/main.py +1 -1
  55. ivoryos/routes/main/templates/{main/home.html → home.html} +4 -4
  56. ivoryos/socket_handlers.py +52 -0
  57. ivoryos/templates/base.html +4 -4
  58. ivoryos/utils/bo_campaign.py +43 -3
  59. ivoryos/utils/form.py +1 -0
  60. ivoryos/utils/py_to_json.py +225 -0
  61. ivoryos/utils/script_runner.py +30 -7
  62. ivoryos/version.py +1 -1
  63. {ivoryos-1.0.8.dist-info → ivoryos-1.1.0.dist-info}/METADATA +5 -8
  64. ivoryos-1.1.0.dist-info/RECORD +102 -0
  65. ivoryos/routes/control/templates/control/controllers.html +0 -78
  66. ivoryos/routes/control/templates/control/controllers_home.html +0 -55
  67. ivoryos/routes/control/templates/control/controllers_new.html +0 -89
  68. ivoryos/routes/design/templates/design/experiment_builder.html +0 -521
  69. ivoryos/routes/design/templates/design/experiment_run.html +0 -558
  70. ivoryos-1.0.8.dist-info/RECORD +0 -61
  71. /ivoryos/routes/auth/templates/{auth/login.html → login.html} +0 -0
  72. /ivoryos/routes/auth/templates/{auth/signup.html → signup.html} +0 -0
  73. /ivoryos/routes/{database → data}/__init__.py +0 -0
  74. /ivoryos/routes/{database/templates/database → data/templates/components}/step_card.html +0 -0
  75. /ivoryos/routes/main/templates/{main/help.html → help.html} +0 -0
  76. {ivoryos-1.0.8.dist-info → ivoryos-1.1.0.dist-info}/LICENSE +0 -0
  77. {ivoryos-1.0.8.dist-info → ivoryos-1.1.0.dist-info}/WHEEL +0 -0
  78. {ivoryos-1.0.8.dist-info → ivoryos-1.1.0.dist-info}/top_level.txt +0 -0
ivoryos/__init__.py CHANGED
@@ -3,12 +3,17 @@ import sys
3
3
  from typing import Union
4
4
 
5
5
  from flask import Flask, redirect, url_for, g, Blueprint
6
+ sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
6
7
 
7
8
  from ivoryos.config import Config, get_config
8
9
  from ivoryos.routes.auth.auth import auth, login_manager
9
10
  from ivoryos.routes.control.control import control
10
- from ivoryos.routes.database.database import database
11
- from ivoryos.routes.design.design import design, socketio
11
+ from ivoryos.routes.data.data import data
12
+ from ivoryos.routes.library.library import library
13
+ from ivoryos.routes.design.design import design
14
+ from ivoryos.routes.execute.execute import execute
15
+ from ivoryos.routes.api.api import api
16
+ from ivoryos.socket_handlers import socketio
12
17
  from ivoryos.routes.main.main import main
13
18
  # from ivoryos.routes.monitor.monitor import monitor
14
19
  from ivoryos.utils import utils
@@ -35,10 +40,13 @@ def enforce_sqlite_foreign_keys(dbapi_connection, connection_record):
35
40
  url_prefix = os.getenv('URL_PREFIX', "/ivoryos")
36
41
  app = Flask(__name__, static_url_path=f'{url_prefix}/static', static_folder='static')
37
42
  app.register_blueprint(main, url_prefix=url_prefix)
38
- app.register_blueprint(auth, url_prefix=url_prefix)
39
- app.register_blueprint(control, url_prefix=url_prefix)
40
- app.register_blueprint(design, url_prefix=url_prefix)
41
- app.register_blueprint(database, url_prefix=url_prefix)
43
+ app.register_blueprint(auth, url_prefix=f'{url_prefix}/{auth.name}')
44
+ app.register_blueprint(library, url_prefix=f'{url_prefix}/{library.name}')
45
+ app.register_blueprint(control, url_prefix=f'{url_prefix}/{control.name}')
46
+ app.register_blueprint(design, url_prefix=f'{url_prefix}/{design.name}')
47
+ app.register_blueprint(execute, url_prefix=f'{url_prefix}/{execute.name}')
48
+ app.register_blueprint(data, url_prefix=f'{url_prefix}/{data.name}')
49
+ app.register_blueprint(api, url_prefix=f'{url_prefix}/{api.name}')
42
50
 
43
51
  @login_manager.user_loader
44
52
  def load_user(user_id):
@@ -166,7 +174,7 @@ def run(module=None, host="0.0.0.0", port=None, debug=None, llm_server=None, mod
166
174
 
167
175
  # in case Python 3.12 or higher doesn't log URL
168
176
  if sys.version_info >= (3, 12):
169
- ip = utils.get_ip_address()
177
+ ip = utils.get_local_ip()
170
178
  print(f"Server running at http://localhost:{port}")
171
179
  if not ip == "127.0.0.1":
172
180
  print(f"Server running at http://{ip}:{port}")
@@ -195,6 +203,10 @@ def load_installed_plugins(app, socketio):
195
203
  def load_plugins(blueprints: Union[list, Blueprint], app, socketio):
196
204
  """
197
205
  Dynamically load installed plugins and attach Flask-SocketIO.
206
+ :param blueprints: Union[list, Blueprint] list of Blueprint objects or a single Blueprint object
207
+ :param app: Flask application instance
208
+ :param socketio: Flask-SocketIO instance
209
+ :return: list of plugin names
198
210
  """
199
211
  plugin_names = []
200
212
  if not isinstance(blueprints, list):
@@ -0,0 +1,109 @@
1
+ import os
2
+ from flask import Blueprint, jsonify, request, current_app
3
+
4
+ from ivoryos.routes.control.control import find_instrument_by_name
5
+ from ivoryos.utils.form import create_form_from_module
6
+ from ivoryos.utils.global_config import GlobalConfig
7
+ from ivoryos.utils.db_models import Script, WorkflowRun, SingleStep, WorkflowStep
8
+
9
+ from ivoryos.socket_handlers import abort_pending, abort_current, pause, retry, runner
10
+
11
+ api = Blueprint('api', __name__)
12
+ global_config = GlobalConfig()
13
+
14
+
15
+
16
+ @api.route("/runner/status", methods=["GET"])
17
+ def runner_status():
18
+ """Get the execution status"""
19
+ # runner = global_config.runner
20
+ runner_busy = global_config.runner_lock.locked()
21
+ status = {"busy": runner_busy}
22
+ task_status = global_config.runner_status
23
+ current_step = {}
24
+
25
+ if task_status is not None:
26
+ task_type = task_status["type"]
27
+ task_id = task_status["id"]
28
+ if task_type == "task":
29
+ step = SingleStep.query.get(task_id)
30
+ current_step = step.as_dict()
31
+ if task_type == "workflow":
32
+ workflow = WorkflowRun.query.get(task_id)
33
+ if workflow is not None:
34
+ latest_step = WorkflowStep.query.filter_by(workflow_id=workflow.id).order_by(
35
+ WorkflowStep.start_time.desc()).first()
36
+ if latest_step is not None:
37
+ current_step = latest_step.as_dict()
38
+ status["workflow_status"] = {"workflow_info": workflow.as_dict(), "runner_status": runner.get_status()}
39
+ status["current_task"] = current_step
40
+ return jsonify(status), 200
41
+
42
+
43
+ @api.route("/runner/abort_pending", methods=["POST"])
44
+ def api_abort_pending():
45
+ """Abort pending action(s) during execution"""
46
+ abort_pending()
47
+ return jsonify({"status": "ok"}), 200
48
+
49
+
50
+ @api.route("/runner/abort_current", methods=["POST"])
51
+ def api_abort_current():
52
+ """Abort right after current action during execution"""
53
+ abort_current()
54
+ return jsonify({"status": "ok"}), 200
55
+
56
+
57
+ @api.route("/runner/pause", methods=["POST"])
58
+ def api_pause():
59
+ """Pause during execution"""
60
+ msg = pause()
61
+ return jsonify({"status": "ok", "pause_status": msg}), 200
62
+
63
+
64
+ @api.route("/runner/retry", methods=["POST"])
65
+ def api_retry():
66
+ """Retry when error occur during execution"""
67
+ retry()
68
+ return jsonify({"status": "ok, retrying failed step"}), 200
69
+
70
+
71
+
72
+ @api.route("/control/", strict_slashes=False, methods=['GET'])
73
+ @api.route("/control/<instrument>", methods=['POST'])
74
+ def backend_control(instrument: str=None):
75
+ """
76
+ .. :quickref: Backend Control; backend control
77
+
78
+ backend control through http requests
79
+
80
+ .. http:get:: /api/control/
81
+
82
+ :param instrument: instrument name
83
+ :type instrument: str
84
+
85
+ .. http:post:: /api/control/
86
+
87
+ """
88
+ if instrument:
89
+ inst_object = find_instrument_by_name(instrument)
90
+ forms = create_form_from_module(sdl_module=inst_object, autofill=False, design=False)
91
+
92
+ if request.method == 'POST':
93
+ method_name = request.form.get("hidden_name", None)
94
+ form = forms.get(method_name, None)
95
+ if form:
96
+ kwargs = {field.name: field.data for field in form if field.name not in ['csrf_token', 'hidden_name']}
97
+ wait = request.form.get("hidden_wait", "true") == "true"
98
+ output = runner.run_single_step(component=instrument, method=method_name, kwargs=kwargs, wait=wait,
99
+ current_app=current_app._get_current_object())
100
+ return jsonify(output), 200
101
+
102
+ snapshot = global_config.deck_snapshot.copy()
103
+ # Iterate through each instrument in the snapshot
104
+ for instrument_key, instrument_data in snapshot.items():
105
+ # Iterate through each function associated with the current instrument
106
+ for function_key, function_data in instrument_data.items():
107
+ # Convert the function signature to a string representation
108
+ function_data['signature'] = str(function_data['signature'])
109
+ return jsonify(snapshot), 200
@@ -6,10 +6,10 @@ from ivoryos.utils.db_models import Script, User, db
6
6
  from ivoryos.utils.utils import post_script_file
7
7
  login_manager = LoginManager()
8
8
 
9
- auth = Blueprint('auth', __name__, template_folder='templates/auth')
9
+ auth = Blueprint('auth', __name__, template_folder='templates')
10
10
 
11
11
 
12
- @auth.route('/auth/login', methods=['GET', 'POST'])
12
+ @auth.route('/login', methods=['GET', 'POST'])
13
13
  def login():
14
14
  """
15
15
  .. :quickref: User; login user
@@ -46,11 +46,11 @@ def login():
46
46
  return redirect(url_for('main.index'))
47
47
  else:
48
48
  flash("Incorrect username or password")
49
- return redirect(url_for('auth.login')), 401
49
+ return redirect(url_for('auth.login'))
50
50
  return render_template('login.html')
51
51
 
52
52
 
53
- @auth.route('/auth/signup', methods=['GET', 'POST'])
53
+ @auth.route('/signup', methods=['GET', 'POST'])
54
54
  def signup():
55
55
  """
56
56
  .. :quickref: User; signup for a new account
@@ -84,7 +84,7 @@ def signup():
84
84
  return render_template('signup.html')
85
85
 
86
86
 
87
- @auth.route("/auth/logout")
87
+ @auth.route("/logout")
88
88
  @login_required
89
89
  def logout():
90
90
  """
@@ -1,345 +1,74 @@
1
- import os
2
-
3
- from flask import Blueprint, redirect, url_for, flash, request, render_template, session, current_app, jsonify, \
4
- send_file
1
+ from flask import Blueprint, redirect, flash, request, render_template, session, current_app
5
2
  from flask_login import login_required
6
3
 
7
- from ivoryos.utils.client_proxy import export_to_python, create_function
4
+ from ivoryos.routes.control.control_file import control_file
5
+ from ivoryos.routes.control.control_new_device import control_temp
6
+ from ivoryos.routes.control.utils import post_session_by_instrument, get_session_by_instrument, find_instrument_by_name
8
7
  from ivoryos.utils.global_config import GlobalConfig
9
- from ivoryos.utils import utils
10
8
  from ivoryos.utils.form import create_form_from_module, format_name
11
9
  from ivoryos.utils.task_runner import TaskRunner
12
10
 
13
11
  global_config = GlobalConfig()
14
12
  runner = TaskRunner()
15
13
 
16
- control = Blueprint('control', __name__, template_folder='templates/control')
17
-
18
-
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)
14
+ control = Blueprint('control', __name__, template_folder='templates')
32
15
 
16
+ control.register_blueprint(control_file)
17
+ control.register_blueprint(control_temp)
33
18
 
34
- @control.route("/control/new/", strict_slashes=False)
35
- @control.route("/control/new/<instrument>", methods=['GET', 'POST'])
36
- @login_required
37
- def new_controller(instrument=None):
38
- """
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/
49
-
50
- :form device_name: module instance name (e.g. my_instance = MyClass())
51
- :form kwargs: dynamic module initialization kwargs fields
52
-
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
19
 
92
20
 
93
- @control.route("/control/home/temp", strict_slashes=False)
21
+ @control.route("/home", strict_slashes=False, methods=["GET", "POST"])
94
22
  @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)
159
-
160
- @control.route("/control/download", strict_slashes=False)
161
- @login_required
162
- def download_proxy():
163
- """
164
- .. :quickref: Direct Control; download proxy interface
165
-
166
- download proxy interface
167
-
168
- .. http:get:: /control/download
23
+ def deck_controllers():
169
24
  """
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
190
-
191
- backend control through http requests
192
-
193
- .. http:get:: /api/control/
194
-
195
- :param instrument: instrument name
196
- :type instrument: str
197
-
198
- .. http:post:: /api/control/
199
-
25
+ Combined controllers page: sidebar with all instruments, main area with method cards for selected instrument.
200
26
  """
27
+ deck_variables = global_config.deck_snapshot.keys()
28
+ temp_variables = global_config.defined_variables.keys()
29
+ instrument = request.args.get('instrument')
30
+ forms = None
31
+ # format_name_fn = format_name
201
32
  if instrument:
202
33
  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/`
257
-
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
308
-
309
- .. http:post:: /control/import_deck
310
-
311
- :form filepath: deck module filepath
312
-
313
- import the module and redirect to the previous page
314
-
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'])
34
+ _forms = create_form_from_module(sdl_module=inst_object, autofill=False, design=False)
35
+ order = get_session_by_instrument('card_order', instrument)
36
+ hidden_functions = get_session_by_instrument('hidden_functions', instrument)
37
+ functions = list(_forms.keys())
38
+ for function in functions:
39
+ if function not in hidden_functions and function not in order:
40
+ order.append(function)
41
+ post_session_by_instrument('card_order', instrument, order)
42
+ forms = {name: _forms[name] for name in order if name in _forms}
43
+ # Handle POST for method execution
44
+ if request.method == 'POST':
45
+ all_kwargs = request.form.copy()
46
+ method_name = all_kwargs.pop("hidden_name", None)
47
+ form = forms.get(method_name)
48
+ kwargs = {field.name: field.data for field in form if field.name != 'csrf_token'} if form else {}
49
+ if form and form.validate_on_submit():
50
+ try:
51
+ kwargs.pop("hidden_name", None)
52
+ output = runner.run_single_step(instrument, method_name, kwargs, wait=True, current_app=current_app._get_current_object())
53
+ flash(f"\nRun Success! Output value: {output}.")
54
+ except Exception as e:
55
+ flash(str(e))
56
+ else:
57
+ if form:
58
+ flash(form.errors)
59
+ else:
60
+ flash("Invalid method selected.")
61
+ return render_template(
62
+ 'controllers.html',
63
+ defined_variables=deck_variables,
64
+ temp_variables=temp_variables,
65
+ instrument=instrument,
66
+ forms=forms,
67
+ format_name=format_name,
68
+ session=session
69
+ )
70
+
71
+ @control.route('/<instrument>/save-order', methods=['POST'])
343
72
  def save_order(instrument: str):
344
73
  """
345
74
  .. :quickref: Control Customization; Save functions' order
@@ -355,7 +84,7 @@ def save_order(instrument: str):
355
84
  return '', 204
356
85
 
357
86
 
358
- @control.route('/control/<instrument>/<function>/hide')
87
+ @control.route('/<instrument>/<function>/hide')
359
88
  def hide_function(instrument, function):
360
89
  """
361
90
  .. :quickref: Control Customization; Hide function
@@ -376,7 +105,7 @@ def hide_function(instrument, function):
376
105
  return redirect(back)
377
106
 
378
107
 
379
- @control.route('/control/<instrument>/<function>/unhide')
108
+ @control.route('/<instrument>/<function>/unhide')
380
109
  def remove_hidden(instrument: str, function: str):
381
110
  """
382
111
  .. :quickref: Control Customization; Remove a hidden function
@@ -397,33 +126,6 @@ def remove_hidden(instrument: str, function: str):
397
126
  return redirect(back)
398
127
 
399
128
 
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
129
 
406
130
 
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
131
 
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]