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.
- ivoryos/__init__.py +19 -7
- ivoryos/routes/api/api.py +109 -0
- ivoryos/routes/auth/auth.py +5 -5
- ivoryos/routes/control/control.py +55 -353
- ivoryos/routes/control/control_file.py +36 -0
- ivoryos/routes/control/control_new_device.py +142 -0
- ivoryos/routes/control/templates/controllers.html +137 -0
- ivoryos/routes/control/templates/controllers_new.html +112 -0
- ivoryos/routes/control/utils.py +38 -0
- ivoryos/routes/data/data.py +108 -0
- ivoryos/routes/{database/templates/database → data/templates}/workflow_database.html +7 -7
- ivoryos/routes/{database/templates/database → data/templates}/workflow_view.html +3 -3
- ivoryos/routes/design/__init__.py +4 -0
- ivoryos/routes/design/design.py +96 -517
- ivoryos/routes/design/design_file.py +57 -0
- ivoryos/routes/design/design_step.py +43 -0
- ivoryos/routes/design/templates/components/action_form.html +52 -0
- ivoryos/routes/design/templates/components/action_list.html +15 -0
- ivoryos/routes/design/templates/components/autofill_toggle.html +14 -0
- ivoryos/routes/design/templates/components/canvas.html +14 -0
- ivoryos/routes/design/templates/components/canvas_footer.html +5 -0
- ivoryos/routes/design/templates/components/canvas_header.html +54 -0
- ivoryos/routes/design/templates/components/deck_selector.html +12 -0
- ivoryos/routes/design/templates/components/edit_action_form.html +29 -0
- ivoryos/routes/design/templates/components/instrument_panel.html +23 -0
- ivoryos/routes/design/templates/components/modals/drop_modal.html +19 -0
- ivoryos/routes/design/templates/components/modals/json_modal.html +22 -0
- ivoryos/routes/design/templates/components/modals/new_script_modal.html +18 -0
- ivoryos/routes/design/templates/components/modals/rename_modal.html +23 -0
- ivoryos/routes/design/templates/components/modals/saveas_modal.html +27 -0
- ivoryos/routes/design/templates/components/modals.html +6 -0
- ivoryos/routes/design/templates/components/operations_panel.html +43 -0
- ivoryos/routes/design/templates/components/python_code_overlay.html +17 -0
- ivoryos/routes/design/templates/components/script_info.html +31 -0
- ivoryos/routes/design/templates/components/scripts.html +50 -0
- ivoryos/routes/design/templates/components/sidebar.html +16 -0
- ivoryos/routes/design/templates/components/text_to_code_panel.html +20 -0
- ivoryos/routes/design/templates/experiment_builder.html +41 -0
- ivoryos/routes/execute/__init__.py +0 -0
- ivoryos/routes/execute/execute.py +173 -0
- ivoryos/routes/execute/execute_file.py +44 -0
- ivoryos/routes/execute/templates/components/error_modal.html +20 -0
- ivoryos/routes/execute/templates/components/logging_panel.html +31 -0
- ivoryos/routes/execute/templates/components/progress_panel.html +27 -0
- ivoryos/routes/execute/templates/components/run_panel.html +9 -0
- ivoryos/routes/execute/templates/components/run_tabs.html +17 -0
- ivoryos/routes/execute/templates/components/tab_bayesian.html +147 -0
- ivoryos/routes/execute/templates/components/tab_configuration.html +98 -0
- ivoryos/routes/execute/templates/components/tab_repeat.html +14 -0
- ivoryos/routes/execute/templates/experiment_run.html +294 -0
- ivoryos/routes/library/__init__.py +0 -0
- ivoryos/routes/{database/database.py → library/library.py} +10 -112
- ivoryos/routes/{database/templates/database/scripts_database.html → library/templates/library.html} +8 -8
- ivoryos/routes/main/main.py +1 -1
- ivoryos/routes/main/templates/{main/home.html → home.html} +4 -4
- ivoryos/socket_handlers.py +52 -0
- ivoryos/templates/base.html +4 -4
- ivoryos/utils/bo_campaign.py +43 -3
- ivoryos/utils/form.py +1 -0
- ivoryos/utils/py_to_json.py +225 -0
- ivoryos/utils/script_runner.py +30 -7
- ivoryos/version.py +1 -1
- {ivoryos-1.0.8.dist-info → ivoryos-1.1.0.dist-info}/METADATA +5 -8
- ivoryos-1.1.0.dist-info/RECORD +102 -0
- ivoryos/routes/control/templates/control/controllers.html +0 -78
- ivoryos/routes/control/templates/control/controllers_home.html +0 -55
- ivoryos/routes/control/templates/control/controllers_new.html +0 -89
- ivoryos/routes/design/templates/design/experiment_builder.html +0 -521
- ivoryos/routes/design/templates/design/experiment_run.html +0 -558
- ivoryos-1.0.8.dist-info/RECORD +0 -61
- /ivoryos/routes/auth/templates/{auth/login.html → login.html} +0 -0
- /ivoryos/routes/auth/templates/{auth/signup.html → signup.html} +0 -0
- /ivoryos/routes/{database → data}/__init__.py +0 -0
- /ivoryos/routes/{database/templates/database → data/templates/components}/step_card.html +0 -0
- /ivoryos/routes/main/templates/{main/help.html → help.html} +0 -0
- {ivoryos-1.0.8.dist-info → ivoryos-1.1.0.dist-info}/LICENSE +0 -0
- {ivoryos-1.0.8.dist-info → ivoryos-1.1.0.dist-info}/WHEEL +0 -0
- {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.
|
|
11
|
-
from ivoryos.routes.
|
|
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(
|
|
40
|
-
app.register_blueprint(
|
|
41
|
-
app.register_blueprint(
|
|
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.
|
|
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
|
ivoryos/routes/auth/auth.py
CHANGED
|
@@ -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
|
|
9
|
+
auth = Blueprint('auth', __name__, template_folder='templates')
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
@auth.route('/
|
|
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'))
|
|
49
|
+
return redirect(url_for('auth.login'))
|
|
50
50
|
return render_template('login.html')
|
|
51
51
|
|
|
52
52
|
|
|
53
|
-
@auth.route('/
|
|
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("/
|
|
87
|
+
@auth.route("/logout")
|
|
88
88
|
@login_required
|
|
89
89
|
def logout():
|
|
90
90
|
"""
|
|
@@ -1,345 +1,74 @@
|
|
|
1
|
-
import
|
|
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.
|
|
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
|
|
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("/
|
|
21
|
+
@control.route("/home", strict_slashes=False, methods=["GET", "POST"])
|
|
94
22
|
@login_required
|
|
95
|
-
def
|
|
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
|
-
|
|
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
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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('
|
|
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('
|
|
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]
|