ivoryos 1.2.5__tar.gz → 1.2.6__tar.gz
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-1.2.5 → ivoryos-1.2.6}/PKG-INFO +1 -1
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/api/api.py +1 -1
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/control/control.py +46 -31
- ivoryos-1.2.6/ivoryos/routes/control/control_file.py +33 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/static/js/action_handlers.js +1 -1
- ivoryos-1.2.6/ivoryos/utils/client_proxy.py +288 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/utils/form.py +5 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/utils/task_runner.py +3 -3
- ivoryos-1.2.6/ivoryos/version.py +1 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos.egg-info/PKG-INFO +1 -1
- ivoryos-1.2.5/ivoryos/routes/control/control_file.py +0 -36
- ivoryos-1.2.5/ivoryos/utils/client_proxy.py +0 -57
- ivoryos-1.2.5/ivoryos/version.py +0 -1
- {ivoryos-1.2.5 → ivoryos-1.2.6}/LICENSE +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/README.md +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/__init__.py +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/config.py +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/optimizer/ax_optimizer.py +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/optimizer/base_optimizer.py +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/optimizer/baybe_optimizer.py +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/optimizer/registry.py +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/__init__.py +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/auth/__init__.py +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/auth/auth.py +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/auth/templates/login.html +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/auth/templates/signup.html +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/control/__init__.py +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/control/control_new_device.py +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/control/templates/controllers.html +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/control/templates/controllers_new.html +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/control/utils.py +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/data/__init__.py +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/data/data.py +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/data/templates/components/step_card.html +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/data/templates/workflow_database.html +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/data/templates/workflow_view.html +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/design/__init__.py +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/design/design.py +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/design/design_file.py +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/design/design_step.py +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/design/templates/components/action_form.html +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/design/templates/components/actions_panel.html +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/design/templates/components/autofill_toggle.html +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/design/templates/components/canvas.html +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/design/templates/components/canvas_footer.html +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/design/templates/components/canvas_header.html +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/design/templates/components/canvas_main.html +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/design/templates/components/deck_selector.html +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/design/templates/components/edit_action_form.html +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/design/templates/components/instruments_panel.html +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/design/templates/components/modals/drop_modal.html +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/design/templates/components/modals/json_modal.html +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/design/templates/components/modals/new_script_modal.html +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/design/templates/components/modals/rename_modal.html +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/design/templates/components/modals/saveas_modal.html +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/design/templates/components/modals.html +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/design/templates/components/python_code_overlay.html +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/design/templates/components/sidebar.html +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/design/templates/components/text_to_code_panel.html +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/design/templates/experiment_builder.html +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/execute/__init__.py +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/execute/execute.py +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/execute/execute_file.py +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/execute/templates/components/error_modal.html +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/execute/templates/components/logging_panel.html +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/execute/templates/components/progress_panel.html +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/execute/templates/components/run_panel.html +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/execute/templates/components/run_tabs.html +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/execute/templates/components/tab_bayesian.html +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/execute/templates/components/tab_configuration.html +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/execute/templates/components/tab_repeat.html +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/execute/templates/experiment_run.html +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/library/__init__.py +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/library/library.py +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/library/templates/library.html +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/main/__init__.py +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/main/main.py +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/main/templates/help.html +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/main/templates/home.html +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/socket_handlers.py +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/static/favicon.ico +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/static/gui_annotation/Slide1.png +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/static/gui_annotation/Slide2.PNG +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/static/js/db_delete.js +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/static/js/overlay.js +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/static/js/script_metadata.js +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/static/js/socket_handler.js +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/static/js/sortable_card.js +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/static/js/sortable_design.js +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/static/js/ui_state.js +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/static/logo.webp +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/static/style.css +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/templates/base.html +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/utils/__init__.py +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/utils/bo_campaign.py +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/utils/db_models.py +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/utils/global_config.py +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/utils/llm_agent.py +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/utils/py_to_json.py +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/utils/script_runner.py +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/utils/serilize.py +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/utils/utils.py +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos.egg-info/SOURCES.txt +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos.egg-info/dependency_links.txt +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos.egg-info/requires.txt +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos.egg-info/top_level.txt +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/pyproject.toml +0 -0
- {ivoryos-1.2.5 → ivoryos-1.2.6}/setup.cfg +0 -0
|
@@ -37,7 +37,7 @@ def backend_control(instrument: str=None):
|
|
|
37
37
|
forms = create_form_from_module(sdl_module=inst_object, autofill=False, design=False)
|
|
38
38
|
|
|
39
39
|
if request.method == 'POST':
|
|
40
|
-
method_name = request.
|
|
40
|
+
method_name = request.json.get("hidden_name", None)
|
|
41
41
|
form = forms.get(method_name, None)
|
|
42
42
|
if form:
|
|
43
43
|
kwargs = {field.name: field.data for field in form if field.name not in ['csrf_token', 'hidden_name']}
|
|
@@ -21,7 +21,7 @@ control.register_blueprint(control_temp)
|
|
|
21
21
|
@control.route("/", strict_slashes=False, methods=["GET", "POST"])
|
|
22
22
|
@control.route("/<string:instrument>", strict_slashes=False, methods=["GET", "POST"])
|
|
23
23
|
@login_required
|
|
24
|
-
def deck_controllers():
|
|
24
|
+
def deck_controllers(instrument: str = None):
|
|
25
25
|
"""
|
|
26
26
|
.. :quickref: Direct Control; device (instruments) and methods
|
|
27
27
|
|
|
@@ -44,41 +44,56 @@ def deck_controllers():
|
|
|
44
44
|
:status 200: render template with instruments and methods
|
|
45
45
|
|
|
46
46
|
"""
|
|
47
|
-
|
|
48
|
-
temp_variables = global_config.defined_variables.keys()
|
|
49
|
-
instrument = request.args.get('instrument')
|
|
47
|
+
instrument = instrument or request.args.get("instrument")
|
|
50
48
|
forms = None
|
|
51
49
|
if instrument:
|
|
52
50
|
inst_object = find_instrument_by_name(instrument)
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
51
|
+
forms = create_form_from_module(sdl_module=inst_object, autofill=False, design=False)
|
|
52
|
+
|
|
53
|
+
if request.method == "POST":
|
|
54
|
+
if not forms:
|
|
55
|
+
return jsonify({"success": False, "error": "Instrument not found"}), 404
|
|
56
|
+
|
|
57
|
+
payload = request.get_json() if request.is_json else request.form.to_dict()
|
|
58
|
+
method_name = payload.pop("hidden_name", None)
|
|
59
|
+
form = forms.get(method_name)
|
|
60
|
+
|
|
61
|
+
if not form:
|
|
62
|
+
return jsonify({"success": False, "error": f"Method {method_name} not found"}), 404
|
|
63
|
+
|
|
64
|
+
# Extract kwargs
|
|
65
|
+
if request.is_json:
|
|
66
|
+
kwargs = {k: v for k, v in payload.items() if k not in ["csrf_token", "hidden_wait"]}
|
|
67
|
+
else:
|
|
68
|
+
kwargs = {field.name: field.data for field in form if field.name not in ["csrf_token", "hidden_name"]}
|
|
69
|
+
|
|
70
|
+
wait = str(payload.get("hidden_wait", "true")).lower() == "true"
|
|
71
|
+
|
|
72
|
+
output = runner.run_single_step(
|
|
73
|
+
component=instrument, method=method_name, kwargs=kwargs, wait=wait,
|
|
74
|
+
current_app=current_app._get_current_object()
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
if request.is_json:
|
|
78
|
+
return jsonify(output)
|
|
79
|
+
else:
|
|
80
|
+
if output.get("success"):
|
|
81
|
+
flash(f"Run Success! Output: {output.get('output', 'None')}")
|
|
75
82
|
else:
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
83
|
+
flash(f"Run Error! {output.get('output', 'Unknown error occurred.')}", "error")
|
|
84
|
+
|
|
85
|
+
# GET request → render web form or return snapshot for API
|
|
86
|
+
if request.is_json or request.accept_mimetypes["application/json"]:
|
|
87
|
+
snapshot = global_config.deck_snapshot.copy()
|
|
88
|
+
for instrument_key, instrument_data in snapshot.items():
|
|
89
|
+
for function_key, function_data in instrument_data.items():
|
|
90
|
+
function_data["signature"] = str(function_data["signature"])
|
|
91
|
+
return jsonify(snapshot)
|
|
92
|
+
|
|
93
|
+
deck_variables = global_config.deck_snapshot.keys()
|
|
94
|
+
temp_variables = global_config.defined_variables.keys()
|
|
80
95
|
return render_template(
|
|
81
|
-
|
|
96
|
+
"controllers.html",
|
|
82
97
|
defined_variables=deck_variables,
|
|
83
98
|
temp_variables=temp_variables,
|
|
84
99
|
instrument=instrument,
|
|
@@ -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)
|
|
@@ -15,7 +15,7 @@ function saveWorkflow(link) {
|
|
|
15
15
|
.then(data => {
|
|
16
16
|
if (data.success) {
|
|
17
17
|
// flash a success message
|
|
18
|
-
flash("Workflow saved successfully", "success");
|
|
18
|
+
// flash("Workflow saved successfully", "success");
|
|
19
19
|
window.location.reload(); // or update the UI dynamically
|
|
20
20
|
} else {
|
|
21
21
|
alert("Failed to save workflow: " + data.error);
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
from typing import Dict, Set, Any, Optional
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ProxyGenerator:
|
|
7
|
+
"""
|
|
8
|
+
A class to generate Python proxy interfaces for API clients.
|
|
9
|
+
|
|
10
|
+
This generator creates client classes that wrap API endpoints,
|
|
11
|
+
automatically handling request/response cycles and error handling.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
# Common typing symbols to scan for in function signatures
|
|
15
|
+
TYPING_SYMBOLS = {
|
|
16
|
+
"Optional", "Union", "List", "Dict", "Tuple",
|
|
17
|
+
"Any", "Callable", "Iterable", "Sequence", "Set"
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
def __init__(self, base_url: str, api_path_template: str = "ivoryos/instruments/deck.{class_name}"):
|
|
21
|
+
"""
|
|
22
|
+
Initialize the ProxyGenerator.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
base_url: The base URL for the API
|
|
26
|
+
api_path_template: Template for API paths, with {class_name} placeholder
|
|
27
|
+
"""
|
|
28
|
+
self.base_url = base_url.rstrip('/')
|
|
29
|
+
self.api_path_template = api_path_template
|
|
30
|
+
self.used_typing_symbols: Set[str] = set()
|
|
31
|
+
|
|
32
|
+
def extract_typing_from_signatures(self, functions: Dict[str, Dict[str, Any]]) -> Set[str]:
|
|
33
|
+
"""
|
|
34
|
+
Scan function signatures for typing symbols and track usage.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
functions: Dictionary of function definitions with signatures
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
Set of typing symbols found in the signatures
|
|
41
|
+
"""
|
|
42
|
+
for function_data in functions.values():
|
|
43
|
+
signature = function_data.get("signature", "")
|
|
44
|
+
for symbol in self.TYPING_SYMBOLS:
|
|
45
|
+
if re.search(rf"\b{symbol}\b", signature):
|
|
46
|
+
self.used_typing_symbols.add(symbol)
|
|
47
|
+
return self.used_typing_symbols
|
|
48
|
+
|
|
49
|
+
def create_class_definition(self, class_name: str, functions: Dict[str, Dict[str, Any]]) -> str:
|
|
50
|
+
"""
|
|
51
|
+
Generate a class definition string for one API client class.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
class_name: Name of the class to generate
|
|
55
|
+
functions: Dictionary of function definitions
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
String containing the complete class definition
|
|
59
|
+
"""
|
|
60
|
+
capitalized_name = class_name.capitalize()
|
|
61
|
+
api_url = f"{self.base_url}/{self.api_path_template.format(class_name=class_name)}"
|
|
62
|
+
|
|
63
|
+
class_template = f"class {capitalized_name}:\n"
|
|
64
|
+
class_template += f' """Auto-generated API client for {class_name} operations."""\n'
|
|
65
|
+
class_template += f' url = "{api_url}"\n\n'
|
|
66
|
+
|
|
67
|
+
# Add the __init__ with auth
|
|
68
|
+
class_template += self._generate_init()
|
|
69
|
+
|
|
70
|
+
# Add the _auth
|
|
71
|
+
class_template += self._generate_auth()
|
|
72
|
+
|
|
73
|
+
# Add the base _call method
|
|
74
|
+
class_template += self._generate_call_method()
|
|
75
|
+
|
|
76
|
+
# Add individual methods for each function
|
|
77
|
+
for function_name, details in functions.items():
|
|
78
|
+
method_def = self._generate_method(function_name, details)
|
|
79
|
+
class_template += method_def + "\n"
|
|
80
|
+
|
|
81
|
+
return class_template
|
|
82
|
+
|
|
83
|
+
def _generate_call_method(self) -> str:
|
|
84
|
+
"""Generate the base _call method for API communication."""
|
|
85
|
+
return ''' def _call(self, payload):
|
|
86
|
+
"""Make API call with error handling."""
|
|
87
|
+
res = session.post(self.url, json=payload, allow_redirects=False)
|
|
88
|
+
# Handle 302 redirect (likely auth issue)
|
|
89
|
+
if res.status_code == 302:
|
|
90
|
+
try:
|
|
91
|
+
self._auth()
|
|
92
|
+
res = session.post(self.url, json=payload, allow_redirects=False)
|
|
93
|
+
except Exception as e:
|
|
94
|
+
raise AuthenticationError(
|
|
95
|
+
"Authentication failed during re-attempt. "
|
|
96
|
+
"Please check your credentials or connection."
|
|
97
|
+
) from e
|
|
98
|
+
res.raise_for_status()
|
|
99
|
+
data = res.json()
|
|
100
|
+
if not data.get('success'):
|
|
101
|
+
raise Exception(data.get('output', "Unknown API error."))
|
|
102
|
+
return data.get('output')
|
|
103
|
+
|
|
104
|
+
'''
|
|
105
|
+
|
|
106
|
+
def _generate_method(self, function_name: str, details: Dict[str, Any]) -> str:
|
|
107
|
+
"""
|
|
108
|
+
Generate a single method definition.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
function_name: Name of the method
|
|
112
|
+
details: Function details including signature and docstring
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
String containing the method definition
|
|
116
|
+
"""
|
|
117
|
+
signature = details.get("signature", "(self)")
|
|
118
|
+
docstring = details.get("docstring", "")
|
|
119
|
+
|
|
120
|
+
# Build method header
|
|
121
|
+
method = f" def {function_name}{signature}:\n"
|
|
122
|
+
|
|
123
|
+
if docstring:
|
|
124
|
+
method += f' """{docstring}"""\n'
|
|
125
|
+
|
|
126
|
+
# Build payload
|
|
127
|
+
method += f' payload = {{"hidden_name": "{function_name}"}}\n'
|
|
128
|
+
|
|
129
|
+
# Extract parameters from signature (excluding 'self')
|
|
130
|
+
params = self._extract_parameters(signature)
|
|
131
|
+
|
|
132
|
+
for param_name in params:
|
|
133
|
+
method += f' payload["{param_name}"] = {param_name}\n'
|
|
134
|
+
|
|
135
|
+
method += " return self._call(payload)\n"
|
|
136
|
+
|
|
137
|
+
return method
|
|
138
|
+
|
|
139
|
+
def _extract_parameters(self, signature: str) -> list:
|
|
140
|
+
"""
|
|
141
|
+
Extract parameter names from a function signature.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
signature: Function signature string like "(self, param1, param2: int = 5)"
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
List of parameter names (excluding 'self')
|
|
148
|
+
"""
|
|
149
|
+
# Remove parentheses and split by comma
|
|
150
|
+
param_str = signature.strip("()").strip()
|
|
151
|
+
if not param_str or param_str == "self":
|
|
152
|
+
return []
|
|
153
|
+
|
|
154
|
+
params = [param.strip() for param in param_str.split(",")]
|
|
155
|
+
result = []
|
|
156
|
+
|
|
157
|
+
for param in params:
|
|
158
|
+
if param and param != "self":
|
|
159
|
+
# Extract parameter name (before : or = if present)
|
|
160
|
+
param_name = param.split(":")[0].split("=")[0].strip()
|
|
161
|
+
if param_name:
|
|
162
|
+
result.append(param_name)
|
|
163
|
+
|
|
164
|
+
return result
|
|
165
|
+
|
|
166
|
+
def generate_proxy_file(self,
|
|
167
|
+
snapshot: Dict[str, Dict[str, Any]],
|
|
168
|
+
output_path: str,
|
|
169
|
+
filename: str = "generated_proxy.py") -> str:
|
|
170
|
+
"""
|
|
171
|
+
Generate the complete proxy file from a snapshot of instruments.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
snapshot: Dictionary containing instrument data with functions
|
|
175
|
+
output_path: Directory to write the output file
|
|
176
|
+
filename: Name of the output file
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
Path to the generated file
|
|
180
|
+
"""
|
|
181
|
+
class_definitions = {}
|
|
182
|
+
self.used_typing_symbols.clear()
|
|
183
|
+
|
|
184
|
+
# Process each instrument in the snapshot
|
|
185
|
+
for instrument_key, instrument_data in snapshot.items():
|
|
186
|
+
# Convert function signatures to strings if needed
|
|
187
|
+
for function_key, function_data in instrument_data.items():
|
|
188
|
+
if 'signature' in function_data:
|
|
189
|
+
function_data['signature'] = str(function_data['signature'])
|
|
190
|
+
|
|
191
|
+
# Extract class name from instrument path
|
|
192
|
+
class_name = instrument_key.split('.')[-1]
|
|
193
|
+
|
|
194
|
+
# Generate class definition
|
|
195
|
+
class_definitions[class_name] = self.create_class_definition(
|
|
196
|
+
class_name, instrument_data
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
# Track typing symbols used
|
|
200
|
+
self.extract_typing_from_signatures(instrument_data)
|
|
201
|
+
|
|
202
|
+
# Write the complete file
|
|
203
|
+
filepath = self._write_proxy_file(class_definitions, output_path, filename)
|
|
204
|
+
return filepath
|
|
205
|
+
|
|
206
|
+
def _write_proxy_file(self,
|
|
207
|
+
class_definitions: Dict[str, str],
|
|
208
|
+
output_path: str,
|
|
209
|
+
filename: str) -> str:
|
|
210
|
+
"""
|
|
211
|
+
Write the generated classes to a Python file.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
class_definitions: Dictionary of class names to class definition strings
|
|
215
|
+
output_path: Directory to write the file
|
|
216
|
+
filename: Name of the file
|
|
217
|
+
|
|
218
|
+
Returns:
|
|
219
|
+
Full path to the written file
|
|
220
|
+
"""
|
|
221
|
+
filepath = os.path.join(output_path, filename)
|
|
222
|
+
|
|
223
|
+
with open(filepath, "w") as f:
|
|
224
|
+
# Write imports
|
|
225
|
+
f.write("import requests\n")
|
|
226
|
+
if self.used_typing_symbols:
|
|
227
|
+
f.write(f"from typing import {', '.join(sorted(self.used_typing_symbols))}\n")
|
|
228
|
+
f.write("\n")
|
|
229
|
+
|
|
230
|
+
# Write session setup
|
|
231
|
+
f.write("session = requests.Session()\n\n")
|
|
232
|
+
|
|
233
|
+
# Write class definitions
|
|
234
|
+
for class_name, class_def in class_definitions.items():
|
|
235
|
+
f.write(class_def)
|
|
236
|
+
f.write("\n")
|
|
237
|
+
|
|
238
|
+
# Create default instances
|
|
239
|
+
f.write("# Default instances for convenience\n")
|
|
240
|
+
for class_name in class_definitions.keys():
|
|
241
|
+
instance_name = class_name.lower()
|
|
242
|
+
f.write(f"{instance_name} = {class_name.capitalize()}()\n")
|
|
243
|
+
|
|
244
|
+
return filepath
|
|
245
|
+
|
|
246
|
+
def generate_from_flask_route(self,
|
|
247
|
+
snapshot: Dict[str, Dict[str, Any]],
|
|
248
|
+
request_url_root: str,
|
|
249
|
+
output_folder: str) -> str:
|
|
250
|
+
"""
|
|
251
|
+
Convenience method that matches the original Flask route behavior.
|
|
252
|
+
|
|
253
|
+
Args:
|
|
254
|
+
snapshot: The deck snapshot from global_config
|
|
255
|
+
request_url_root: The URL root from Flask request
|
|
256
|
+
output_folder: Output folder path from app config
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
Path to the generated file
|
|
260
|
+
"""
|
|
261
|
+
# Set the base URL from the request
|
|
262
|
+
self.base_url = request_url_root.rstrip('/')
|
|
263
|
+
|
|
264
|
+
# Generate the proxy file
|
|
265
|
+
return self.generate_proxy_file(snapshot, output_folder)
|
|
266
|
+
|
|
267
|
+
def _generate_init(self):
|
|
268
|
+
return ''' def __init__(self, username=None, password=None):
|
|
269
|
+
"""Initialize the client with authentication."""
|
|
270
|
+
self.username = username
|
|
271
|
+
self.password = password
|
|
272
|
+
self._auth()
|
|
273
|
+
|
|
274
|
+
'''
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def _generate_auth(self):
|
|
278
|
+
return f""" def _auth(self):
|
|
279
|
+
username = self.username or 'admin'
|
|
280
|
+
password = self.password or 'admin'
|
|
281
|
+
res = session.post(
|
|
282
|
+
'{self.base_url}/ivoryos/auth/login',
|
|
283
|
+
data={{"username": username, "password": password}}
|
|
284
|
+
)
|
|
285
|
+
if res.status_code != 200:
|
|
286
|
+
raise Exception("Authentication failed")
|
|
287
|
+
|
|
288
|
+
"""
|
|
@@ -299,6 +299,11 @@ def create_form_for_method(method, autofill, script=None, design=True):
|
|
|
299
299
|
if optional:
|
|
300
300
|
field_kwargs["filters"] = [lambda x: x if x != '' else None]
|
|
301
301
|
|
|
302
|
+
if annotation is bool:
|
|
303
|
+
# Boolean fields should not use InputRequired
|
|
304
|
+
field_kwargs["validators"] = [] # or [Optional()]
|
|
305
|
+
else:
|
|
306
|
+
field_kwargs["validators"] = [InputRequired()] if param.default is param.empty else [Optional()]
|
|
302
307
|
|
|
303
308
|
render_kwargs = {"placeholder": placeholder_text}
|
|
304
309
|
|
|
@@ -64,7 +64,7 @@ class TaskRunner:
|
|
|
64
64
|
|
|
65
65
|
# with self.lock:
|
|
66
66
|
with current_app.app_context():
|
|
67
|
-
step = SingleStep(method_name=method_name, kwargs=kwargs, run_error=
|
|
67
|
+
step = SingleStep(method_name=method_name, kwargs=kwargs, run_error=None, start_time=datetime.now())
|
|
68
68
|
db.session.add(step)
|
|
69
69
|
db.session.commit()
|
|
70
70
|
global_config.runner_status = {"id":step.id, "type": "task"}
|
|
@@ -74,10 +74,10 @@ class TaskRunner:
|
|
|
74
74
|
step.end_time = datetime.now()
|
|
75
75
|
success = True
|
|
76
76
|
except Exception as e:
|
|
77
|
-
step.run_error = e
|
|
77
|
+
step.run_error = str(e)
|
|
78
78
|
step.end_time = datetime.now()
|
|
79
79
|
success = False
|
|
80
|
-
output = e
|
|
80
|
+
output = str(e)
|
|
81
81
|
finally:
|
|
82
82
|
db.session.commit()
|
|
83
83
|
self.lock.release()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.2.6"
|
|
@@ -1,36 +0,0 @@
|
|
|
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 export_to_python, create_function
|
|
6
|
-
from ivoryos.utils.global_config import GlobalConfig
|
|
7
|
-
|
|
8
|
-
global_config = GlobalConfig()
|
|
9
|
-
|
|
10
|
-
control_file = Blueprint('file', __name__)
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
@control_file.route("/files/proxy", strict_slashes=False)
|
|
14
|
-
@login_required
|
|
15
|
-
def download_proxy():
|
|
16
|
-
"""
|
|
17
|
-
.. :quickref: Direct Control Files; download proxy interface
|
|
18
|
-
|
|
19
|
-
download proxy Python interface
|
|
20
|
-
|
|
21
|
-
.. http:get:: /files/proxy
|
|
22
|
-
"""
|
|
23
|
-
snapshot = global_config.deck_snapshot.copy()
|
|
24
|
-
class_definitions = {}
|
|
25
|
-
# Iterate through each instrument in the snapshot
|
|
26
|
-
for instrument_key, instrument_data in snapshot.items():
|
|
27
|
-
# Iterate through each function associated with the current instrument
|
|
28
|
-
for function_key, function_data in instrument_data.items():
|
|
29
|
-
# Convert the function signature to a string representation
|
|
30
|
-
function_data['signature'] = str(function_data['signature'])
|
|
31
|
-
class_name = instrument_key.split('.')[-1] # Extracting the class name from the path
|
|
32
|
-
class_definitions[class_name.capitalize()] = create_function(request.url_root, class_name, instrument_data)
|
|
33
|
-
# Export the generated class definitions to a .py script
|
|
34
|
-
export_to_python(class_definitions, current_app.config["OUTPUT_FOLDER"])
|
|
35
|
-
filepath = os.path.join(current_app.config["OUTPUT_FOLDER"], "generated_proxy.py")
|
|
36
|
-
return send_file(os.path.abspath(filepath), as_attachment=True)
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
# import argparse
|
|
2
|
-
import os
|
|
3
|
-
|
|
4
|
-
# import requests
|
|
5
|
-
|
|
6
|
-
# session = requests.Session()
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
# Function to create class and methods dynamically
|
|
10
|
-
def create_function(url, class_name, functions):
|
|
11
|
-
class_template = f'class {class_name.capitalize()}:\n url = "{url}ivoryos/api/control/deck.{class_name}"\n'
|
|
12
|
-
|
|
13
|
-
for function_name, details in functions.items():
|
|
14
|
-
signature = details['signature']
|
|
15
|
-
docstring = details.get('docstring', '')
|
|
16
|
-
|
|
17
|
-
# Creating the function definition
|
|
18
|
-
method = f' def {function_name}{signature}:\n'
|
|
19
|
-
if docstring:
|
|
20
|
-
method += f' """{docstring}"""\n'
|
|
21
|
-
|
|
22
|
-
# Generating the session.post code for sending data
|
|
23
|
-
method += ' return session.post(self.url, data={'
|
|
24
|
-
method += f'"hidden_name": "{function_name}"'
|
|
25
|
-
|
|
26
|
-
# Extracting the parameters from the signature string for the data payload
|
|
27
|
-
param_str = signature[6:-1] # Remove the "(self" and final ")"
|
|
28
|
-
params = [param.strip() for param in param_str.split(',')] if param_str else []
|
|
29
|
-
|
|
30
|
-
for param in params:
|
|
31
|
-
param_name = param.split(':')[0].strip() # Split on ':' and get parameter name
|
|
32
|
-
method += f', "{param_name}": {param_name}'
|
|
33
|
-
|
|
34
|
-
method += '}).json()\n'
|
|
35
|
-
class_template += method + '\n'
|
|
36
|
-
|
|
37
|
-
return class_template
|
|
38
|
-
|
|
39
|
-
# Function to export the generated classes to a Python script
|
|
40
|
-
def export_to_python(class_definitions, path):
|
|
41
|
-
with open(os.path.join(path, "generated_proxy.py"), 'w') as f:
|
|
42
|
-
# Writing the imports at the top of the script
|
|
43
|
-
f.write('import requests\n\n')
|
|
44
|
-
f.write('session = requests.Session()\n\n')
|
|
45
|
-
|
|
46
|
-
# Writing each class definition to the file
|
|
47
|
-
for class_name, class_def in class_definitions.items():
|
|
48
|
-
f.write(class_def)
|
|
49
|
-
f.write('\n')
|
|
50
|
-
|
|
51
|
-
# Creating instances of the dynamically generated classes
|
|
52
|
-
for class_name in class_definitions.keys():
|
|
53
|
-
instance_name = class_name.lower() # Using lowercase for instance names
|
|
54
|
-
f.write(f'{instance_name} = {class_name.capitalize()}()\n')
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
ivoryos-1.2.5/ivoryos/version.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "1.2.5"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/design/templates/components/actions_panel.html
RENAMED
|
File without changes
|
{ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/design/templates/components/autofill_toggle.html
RENAMED
|
File without changes
|
|
File without changes
|
{ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/design/templates/components/canvas_footer.html
RENAMED
|
File without changes
|
{ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/design/templates/components/canvas_header.html
RENAMED
|
File without changes
|
|
File without changes
|
{ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/design/templates/components/deck_selector.html
RENAMED
|
File without changes
|
{ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/design/templates/components/edit_action_form.html
RENAMED
|
File without changes
|
{ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/design/templates/components/instruments_panel.html
RENAMED
|
File without changes
|
{ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/design/templates/components/modals/drop_modal.html
RENAMED
|
File without changes
|
{ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/design/templates/components/modals/json_modal.html
RENAMED
|
File without changes
|
|
File without changes
|
{ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/design/templates/components/modals/rename_modal.html
RENAMED
|
File without changes
|
{ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/design/templates/components/modals/saveas_modal.html
RENAMED
|
File without changes
|
|
File without changes
|
{ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/design/templates/components/python_code_overlay.html
RENAMED
|
File without changes
|
|
File without changes
|
{ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/design/templates/components/text_to_code_panel.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/execute/templates/components/error_modal.html
RENAMED
|
File without changes
|
{ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/execute/templates/components/logging_panel.html
RENAMED
|
File without changes
|
{ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/execute/templates/components/progress_panel.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/execute/templates/components/tab_bayesian.html
RENAMED
|
File without changes
|
{ivoryos-1.2.5 → ivoryos-1.2.6}/ivoryos/routes/execute/templates/components/tab_configuration.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|