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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. docs/source/conf.py +84 -0
  2. ivoryos/__init__.py +17 -207
  3. ivoryos/app.py +154 -0
  4. ivoryos/config.py +1 -0
  5. ivoryos/optimizer/ax_optimizer.py +191 -0
  6. ivoryos/optimizer/base_optimizer.py +84 -0
  7. ivoryos/optimizer/baybe_optimizer.py +193 -0
  8. ivoryos/optimizer/nimo_optimizer.py +173 -0
  9. ivoryos/optimizer/registry.py +11 -0
  10. ivoryos/routes/auth/auth.py +43 -14
  11. ivoryos/routes/auth/templates/change_password.html +32 -0
  12. ivoryos/routes/control/control.py +101 -366
  13. ivoryos/routes/control/control_file.py +33 -0
  14. ivoryos/routes/control/control_new_device.py +152 -0
  15. ivoryos/routes/control/templates/controllers.html +193 -0
  16. ivoryos/routes/control/templates/controllers_new.html +112 -0
  17. ivoryos/routes/control/utils.py +40 -0
  18. ivoryos/routes/data/data.py +197 -0
  19. ivoryos/routes/data/templates/components/step_card.html +78 -0
  20. ivoryos/routes/{database/templates/database → data/templates}/workflow_database.html +14 -8
  21. ivoryos/routes/data/templates/workflow_view.html +360 -0
  22. ivoryos/routes/design/__init__.py +4 -0
  23. ivoryos/routes/design/design.py +348 -657
  24. ivoryos/routes/design/design_file.py +68 -0
  25. ivoryos/routes/design/design_step.py +171 -0
  26. ivoryos/routes/design/templates/components/action_form.html +53 -0
  27. ivoryos/routes/design/templates/components/actions_panel.html +25 -0
  28. ivoryos/routes/design/templates/components/autofill_toggle.html +10 -0
  29. ivoryos/routes/design/templates/components/canvas.html +5 -0
  30. ivoryos/routes/design/templates/components/canvas_footer.html +9 -0
  31. ivoryos/routes/design/templates/components/canvas_header.html +75 -0
  32. ivoryos/routes/design/templates/components/canvas_main.html +39 -0
  33. ivoryos/routes/design/templates/components/deck_selector.html +10 -0
  34. ivoryos/routes/design/templates/components/edit_action_form.html +53 -0
  35. ivoryos/routes/design/templates/components/info_modal.html +318 -0
  36. ivoryos/routes/design/templates/components/instruments_panel.html +88 -0
  37. ivoryos/routes/design/templates/components/modals/drop_modal.html +17 -0
  38. ivoryos/routes/design/templates/components/modals/json_modal.html +22 -0
  39. ivoryos/routes/design/templates/components/modals/new_script_modal.html +17 -0
  40. ivoryos/routes/design/templates/components/modals/rename_modal.html +23 -0
  41. ivoryos/routes/design/templates/components/modals/saveas_modal.html +27 -0
  42. ivoryos/routes/design/templates/components/modals.html +6 -0
  43. ivoryos/routes/design/templates/components/python_code_overlay.html +56 -0
  44. ivoryos/routes/design/templates/components/sidebar.html +15 -0
  45. ivoryos/routes/design/templates/components/text_to_code_panel.html +20 -0
  46. ivoryos/routes/design/templates/experiment_builder.html +44 -0
  47. ivoryos/routes/execute/__init__.py +0 -0
  48. ivoryos/routes/execute/execute.py +377 -0
  49. ivoryos/routes/execute/execute_file.py +78 -0
  50. ivoryos/routes/execute/templates/components/error_modal.html +20 -0
  51. ivoryos/routes/execute/templates/components/logging_panel.html +56 -0
  52. ivoryos/routes/execute/templates/components/progress_panel.html +27 -0
  53. ivoryos/routes/execute/templates/components/run_panel.html +9 -0
  54. ivoryos/routes/execute/templates/components/run_tabs.html +60 -0
  55. ivoryos/routes/execute/templates/components/tab_bayesian.html +520 -0
  56. ivoryos/routes/execute/templates/components/tab_configuration.html +383 -0
  57. ivoryos/routes/execute/templates/components/tab_repeat.html +18 -0
  58. ivoryos/routes/execute/templates/experiment_run.html +30 -0
  59. ivoryos/routes/library/__init__.py +0 -0
  60. ivoryos/routes/library/library.py +157 -0
  61. ivoryos/routes/{database/templates/database/scripts_database.html → library/templates/library.html} +32 -23
  62. ivoryos/routes/main/main.py +31 -3
  63. ivoryos/routes/main/templates/{main/home.html → home.html} +4 -4
  64. ivoryos/server.py +180 -0
  65. ivoryos/socket_handlers.py +52 -0
  66. ivoryos/static/ivoryos_logo.png +0 -0
  67. ivoryos/static/js/action_handlers.js +384 -0
  68. ivoryos/static/js/db_delete.js +23 -0
  69. ivoryos/static/js/script_metadata.js +39 -0
  70. ivoryos/static/js/socket_handler.js +40 -5
  71. ivoryos/static/js/sortable_design.js +107 -56
  72. ivoryos/static/js/ui_state.js +114 -0
  73. ivoryos/templates/base.html +67 -8
  74. ivoryos/utils/bo_campaign.py +180 -3
  75. ivoryos/utils/client_proxy.py +267 -36
  76. ivoryos/utils/db_models.py +300 -65
  77. ivoryos/utils/decorators.py +34 -0
  78. ivoryos/utils/form.py +63 -29
  79. ivoryos/utils/global_config.py +34 -1
  80. ivoryos/utils/nest_script.py +314 -0
  81. ivoryos/utils/py_to_json.py +295 -0
  82. ivoryos/utils/script_runner.py +599 -165
  83. ivoryos/utils/serilize.py +201 -0
  84. ivoryos/utils/task_runner.py +71 -21
  85. ivoryos/utils/utils.py +50 -6
  86. ivoryos/version.py +1 -1
  87. ivoryos-1.4.4.dist-info/METADATA +263 -0
  88. ivoryos-1.4.4.dist-info/RECORD +119 -0
  89. {ivoryos-1.0.9.dist-info → ivoryos-1.4.4.dist-info}/WHEEL +1 -1
  90. {ivoryos-1.0.9.dist-info → ivoryos-1.4.4.dist-info}/top_level.txt +1 -0
  91. tests/unit/test_type_conversion.py +42 -0
  92. tests/unit/test_util.py +3 -0
  93. ivoryos/routes/control/templates/control/controllers.html +0 -78
  94. ivoryos/routes/control/templates/control/controllers_home.html +0 -55
  95. ivoryos/routes/control/templates/control/controllers_new.html +0 -89
  96. ivoryos/routes/database/database.py +0 -306
  97. ivoryos/routes/database/templates/database/step_card.html +0 -7
  98. ivoryos/routes/database/templates/database/workflow_view.html +0 -130
  99. ivoryos/routes/design/templates/design/experiment_builder.html +0 -521
  100. ivoryos/routes/design/templates/design/experiment_run.html +0 -558
  101. ivoryos-1.0.9.dist-info/METADATA +0 -218
  102. ivoryos-1.0.9.dist-info/RECORD +0 -61
  103. /ivoryos/routes/auth/templates/{auth/login.html → login.html} +0 -0
  104. /ivoryos/routes/auth/templates/{auth/signup.html → signup.html} +0 -0
  105. /ivoryos/routes/{database → data}/__init__.py +0 -0
  106. /ivoryos/routes/main/templates/{main/help.html → help.html} +0 -0
  107. {ivoryos-1.0.9.dist-info → ivoryos-1.4.4.dist-info/licenses}/LICENSE +0 -0
@@ -0,0 +1,152 @@
1
+ import importlib
2
+ import os
3
+ from flask import Blueprint, request, current_app, send_file, flash, redirect, url_for, session, render_template
4
+ from flask_login import login_required
5
+
6
+ from ivoryos.utils import utils
7
+ # from ivoryos.routes.control.utils import find_instrument_by_name
8
+ from ivoryos.utils.global_config import GlobalConfig
9
+
10
+ global_config = GlobalConfig()
11
+
12
+ control_temp = Blueprint('temp', __name__)
13
+
14
+ @control_temp.route("/new/module", methods=['POST'])
15
+ def import_api():
16
+ """
17
+ .. :quickref: Advanced Features; Manually import API module(s)
18
+
19
+ importing other Python modules
20
+
21
+ .. http:post:: /instruments/new/module
22
+
23
+ :form filepath: API (Python class) module filepath
24
+
25
+ import the module and redirect to :http:get:`/ivoryos/instruments/new/`
26
+
27
+ """
28
+ filepath = request.form.get('filepath')
29
+ # filepath.replace('\\', '/')
30
+ name = os.path.split(filepath)[-1].split('.')[0]
31
+ try:
32
+ spec = importlib.util.spec_from_file_location(name, filepath)
33
+ module = importlib.util.module_from_spec(spec)
34
+ spec.loader.exec_module(module)
35
+ cls_dict = utils.create_module_snapshot(module=module)
36
+
37
+ def merge_to_global(old: dict, new: dict):
38
+ overwritten = []
39
+
40
+ for key, value in new.items():
41
+ if key in old:
42
+ overwritten.append(key) # record duplicates
43
+ old[key] = value # overwrite or insert
44
+
45
+ return overwritten
46
+
47
+ duplicates = merge_to_global(global_config.api_variables, cls_dict)
48
+ if duplicates:
49
+ # optionally, you can log duplicates
50
+ flash(f"Overwritten classes: {', '.join(duplicates)}")
51
+ # should handle path error and file type error
52
+ except Exception as e:
53
+ flash(e.__str__())
54
+ return redirect(url_for("control.temp.new_controller"))
55
+
56
+
57
+
58
+ @control_temp.route("/new/deck-python", methods=['POST'])
59
+ def import_deck():
60
+ """
61
+ .. :quickref: Advanced Features; Manually import a deck
62
+
63
+ .. http:post:: /instruments/new/deck-python
64
+
65
+ :form filepath: deck module filepath
66
+
67
+ import the module and redirect to the previous page
68
+
69
+ """
70
+ script = utils.get_script_file()
71
+ filepath = request.form.get('filepath')
72
+ session['dismiss'] = request.form.get('dismiss')
73
+ update = request.form.get('update')
74
+ back = request.referrer
75
+ if session['dismiss']:
76
+ return redirect(back)
77
+ name = os.path.split(filepath)[-1].split('.')[0]
78
+ try:
79
+ module = utils.import_module_by_filepath(filepath=filepath, name=name)
80
+ utils.save_to_history(filepath, current_app.config["DECK_HISTORY"])
81
+ module_sigs = utils.create_deck_snapshot(module, save=update, output_path=current_app.config["DUMMY_DECK"])
82
+ if not len(module_sigs) > 0:
83
+ flash("Invalid hardware deck, connect instruments in deck script", "error")
84
+ return redirect(url_for("control.deck_controllers"))
85
+ global_config.deck = module
86
+ global_config.deck_snapshot = module_sigs
87
+
88
+ if script.deck is None:
89
+ script.deck = module.__name__
90
+ # file path error exception
91
+ except Exception as e:
92
+ flash(e.__str__())
93
+ return redirect(back)
94
+
95
+
96
+ @control_temp.route("/new/", strict_slashes=False)
97
+ @control_temp.route("/new/<string:instrument>", methods=['GET', 'POST'])
98
+ @login_required
99
+ def new_controller(instrument:str=None):
100
+ """
101
+ .. :quickref: Advanced Features; connect to a new device
102
+
103
+ interface for connecting a new <instrument>
104
+
105
+ .. http:get:: /instruments/new/
106
+
107
+ :param instrument: instrument name
108
+ :type instrument: str
109
+
110
+ .. http:post:: /instruments/new/
111
+
112
+ :form device_name: module instance name (e.g. my_instance = MyClass())
113
+ :form kwargs: dynamic module initialization kwargs fields
114
+
115
+ """
116
+ device = None
117
+ args = None
118
+ if instrument:
119
+
120
+ device = global_config.api_variables[instrument]
121
+ args = utils.inspect.signature(device.__init__)
122
+
123
+ if request.method == 'POST':
124
+ device_name = request.form.get("device_name", "")
125
+ if device_name and device_name in globals():
126
+ flash("Device name is defined. Try another name, or leave it as blank to auto-configure")
127
+ # return render_template('controllers_new.html', instrument=instrument,
128
+ # api_variables=global_config.api_variables,
129
+ # device=device, args=args, defined_variables=global_config.defined_variables)
130
+ if device_name == "":
131
+ device_name = device.__name__.lower() + "_"
132
+ num = 1
133
+ while device_name + str(num) in global_config.defined_variables:
134
+ num += 1
135
+ device_name = device_name + str(num)
136
+ kwargs = request.form.to_dict()
137
+ kwargs.pop("device_name")
138
+ for i in kwargs:
139
+ if kwargs[i] in global_config.defined_variables:
140
+ kwargs[i] = global_config.defined_variables[kwargs[i]]
141
+ try:
142
+ utils.convert_config_type(kwargs, device.__init__.__annotations__, is_class=True)
143
+ except Exception as e:
144
+ flash(e)
145
+ try:
146
+ global_config.defined_variables[device_name] = device(**kwargs)
147
+ # global_config.defined_variables.add(device_name)
148
+ return redirect(url_for('control.deck_controllers'))
149
+ except Exception as e:
150
+ flash(e)
151
+ return render_template('controllers_new.html', instrument=instrument, api_variables=global_config.api_variables,
152
+ device=device, args=args, defined_variables=global_config.defined_variables)
@@ -0,0 +1,193 @@
1
+ {% extends 'base.html' %}
2
+ {% block title %}IvoryOS | Controllers {% endblock %}
3
+
4
+ {% block body %}
5
+ <div id="overlay" class="overlay">
6
+ <div>
7
+ <h3 id="overlay-text"></h3>
8
+ <div class="spinner-border" role="status"></div>
9
+ </div>
10
+ </div>
11
+ <div class="container-fluid">
12
+ <div class="row">
13
+ <!-- Sidebar: Instruments -->
14
+ <nav class="col-md-3 col-lg-2 d-md-block bg-light sidebar py-3" style="height: 100vh; overflow-y: auto; position: sticky; top: 0;">
15
+ <div class="sidebar-sticky">
16
+ <!-- Deck Instruments -->
17
+ <div class="mb-4">
18
+ <div class="list-group">
19
+ {% for inst in defined_variables %}
20
+ <a class="list-group-item list-group-item-action d-flex align-items-center {% if instrument == inst %}active bg-primary text-white border-0{% else %}bg-light{% endif %}"
21
+ href="{{ url_for('control.deck_controllers') }}?instrument={{ inst }}"
22
+ style="border-radius: 0.5rem; margin-bottom: 0.5rem; transition: background 0.2s;">
23
+ <span class="flex-grow-1">{{ inst | format_name }}</span>
24
+ {% if instrument == inst %}
25
+ <span class="ms-auto">&gt;</span>
26
+ {% endif %}
27
+ </a>
28
+ {% endfor %}
29
+ </div>
30
+ </div>
31
+
32
+
33
+
34
+ <!-- Temp Instruments -->
35
+ {% if temp_variables %}
36
+ <div class="mb-4">
37
+ <h6 class="fw-bold text-secondary mb-2" style="letter-spacing: 1px;">Temp Instruments</h6>
38
+ <div class="list-group">
39
+ {% for inst in temp_variables %}
40
+ <a class="list-group-item list-group-item-action d-flex align-items-center {% if instrument == inst %}active bg-warning text-dark border-0{% else %}bg-light{% endif %}"
41
+ href="{{ url_for('control.deck_controllers') }}?instrument={{ inst }}"
42
+ style="border-radius: 0.5rem; margin-bottom: 0.5rem; transition: background 0.2s;">
43
+ <span class="flex-grow-1">{{ inst | format_name }}</span>
44
+ {% if instrument == inst %}
45
+ <span class="ms-auto">&gt;</span>
46
+ {% endif %}
47
+ </a>
48
+ {% endfor %}
49
+ </div>
50
+ </div>
51
+ {% endif %}
52
+
53
+ {% if block_variables %}
54
+ <div class="mb-4">
55
+ <h6 class="fw-bold text-secondary mb-2" style="letter-spacing: 1px;">Methods</h6>
56
+ <div class="list-group">
57
+ {% for inst in block_variables %}
58
+ <a class="list-group-item list-group-item-action d-flex align-items-center {% if instrument == inst %}active bg-warning text-dark border-0{% else %}bg-light{% endif %}"
59
+ href="{{ url_for('control.deck_controllers') }}?instrument={{ inst }}"
60
+ style="border-radius: 0.5rem; margin-bottom: 0.5rem; transition: background 0.2s;">
61
+ <span class="flex-grow-1">{{ inst | format_name }}</span>
62
+ {% if instrument == inst %}
63
+ <span class="ms-auto">&gt;</span>
64
+ {% endif %}
65
+ </a>
66
+ {% endfor %}
67
+ </div>
68
+ </div>
69
+ {% endif %}
70
+ <!-- Action Buttons -->
71
+ <div class="mb-4">
72
+ <a href="{{ url_for('control.file.download_proxy', filetype='proxy') }}" class="btn btn-outline-primary w-100 mb-2">
73
+ Download proxy
74
+ </a>
75
+ <a href="{{ url_for('control.temp.new_controller') }}" class="btn btn-outline-success w-100">
76
+ New connection
77
+ </a>
78
+ </div>
79
+ </div>
80
+ </nav>
81
+
82
+ <!-- Main: Method Cards -->
83
+ <main class="col-md-9 ms-sm-auto col-lg-10 px-md-4" style="height: 100vh; overflow-y: auto;">
84
+ {% if instrument%}
85
+ {# <h2 class="text-secondary">{{ instrument }} controller</h2>#}
86
+ <div class="grid-container" id="sortable-grid" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); gap: 1rem; width: 100%;">
87
+ {% set hidden = session.get('hidden_functions', {}) %}
88
+ {% set hidden_instrument = hidden.get(instrument, []) %}
89
+ {% for function, form in forms.items() %}
90
+ {% if function not in hidden_instrument %}
91
+ <div class="card" id="{{function}}" style="margin: 0;">
92
+ <div class="bg-white rounded shadow-sm h-100">
93
+ <i class="bi bi-info-circle ms-2" data-bs-toggle="tooltip" data-bs-placement="top" title='{{ form.hidden_name.description or "Docstring is not available" }}' ></i>
94
+ <a href="{{ url_for('control.hide_function', instrument=instrument, function=function) }}"
95
+ data-method="patch" data-payload='{"hidden":true}' class="toggle-visibility">
96
+ <i style="float: right;" class="bi bi-eye-slash-fill text-muted" title="Hide function"></i>
97
+ </a>
98
+ <div class="form-control" style="border: none">
99
+ <form role="form" method='POST' name="{{function}}" id="{{function}}" action="{{ url_for('control.deck_controllers') }}?instrument={{ instrument }}">
100
+ <div class="form-group">
101
+ {{ form.hidden_tag() }}
102
+ {% for field in form %}
103
+ {% if field.type not in ['CSRFTokenField', 'HiddenField'] %}
104
+ <div class="input-group mb-3">
105
+ <label class="input-group-text">{{ field.label.text | format_name }}</label>
106
+ {% if field.type == "SubmitField" %}
107
+ {{ field(class="btn btn-dark") }}
108
+ {% elif field.type == "BooleanField" %}
109
+ {{ field(class="form-check-input") }}
110
+ {% elif field.type == "FlexibleEnumField" %}
111
+ <input type="text" id="{{ field.id }}" name="{{ field.name }}" value="{{ field.data }}"
112
+ list="{{ field.id }}_options" placeholder="{{ field.render_kw.placeholder if field.render_kw and field.render_kw.placeholder }}"
113
+ class="form-control">
114
+ <datalist id="{{ field.id }}_options">
115
+ {% for key in field.choices %}
116
+ <option value="{{ key }}">{{ key }}</option>
117
+ {% endfor %}
118
+ </datalist>
119
+ {% else %}
120
+ {{ field(class="form-control") }}
121
+ {% endif %}
122
+ </div>
123
+ {% endif %}
124
+ {% endfor %}
125
+ </div>
126
+ <div class="input-group mb-3">
127
+ <button type="submit" name="{{ function }}" id="{{ function }}" class="form-control" style="background-color: #a5cece;">
128
+ {{ function | format_name }}
129
+ </button>
130
+ </div>
131
+ </form>
132
+ </div>
133
+ </div>
134
+ </div>
135
+ {% endif %}
136
+ {% endfor %}
137
+ </div>
138
+ <!-- Hidden functions accordion -->
139
+ <div class="accordion accordion-flush" id="accordionActions" >
140
+ <div class="accordion-item">
141
+ <h4 class="accordion-header">
142
+ <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#hidden">
143
+ Hidden functions
144
+ </button>
145
+ </h4>
146
+ </div>
147
+ <div id="hidden" class="accordion-collapse collapse" data-bs-parent="#accordionActions">
148
+ <div class="accordion-body">
149
+ {% for function in hidden_instrument %}
150
+ <div>
151
+ {{ function }}
152
+ <a href="{{ url_for('control.hide_function', instrument=instrument, function=function) }}"
153
+ data-method="patch" data-payload='{"hidden":false}' class="toggle-visibility">
154
+ <i class="bi bi-eye-fill text-success" title="Show function"></i>
155
+ </a>
156
+ </div>
157
+ {% endfor %}
158
+ </div>
159
+ </div>
160
+ </div>
161
+ <script>
162
+
163
+ document.querySelectorAll('.toggle-visibility').forEach(el => {
164
+ el.addEventListener('click', e => {
165
+ e.preventDefault();
166
+ const url = el.getAttribute('href');
167
+ const payload = JSON.parse(el.getAttribute('data-payload') || '{}');
168
+ fetch(url, {
169
+ method: 'PATCH',
170
+ headers: {
171
+ 'Content-Type': 'application/json'
172
+ },
173
+ body: JSON.stringify(payload)
174
+ }).then(response => {
175
+ if (response.ok) {
176
+ location.reload(); // or update the DOM directly
177
+ }
178
+ });
179
+ });
180
+ });
181
+
182
+ const saveOrderUrl = `{{ url_for('control.save_order', instrument=instrument) }}`;
183
+ const buttonIds = {{ session['card_order'][instrument] | tojson }};
184
+ </script>
185
+ <script src="{{ url_for('static', filename='js/sortable_card.js') }}"></script>
186
+ <script src="{{ url_for('static', filename='js/overlay.js') }}"></script>
187
+ {% else %}
188
+ <div class="alert alert-info mt-4">Select an instrument to view its methods.</div>
189
+ {% endif %}
190
+ </main>
191
+ </div>
192
+ </div>
193
+ {% endblock %}
@@ -0,0 +1,112 @@
1
+ {% extends 'base.html' %}
2
+ {% block title %}IvoryOS | New devices{% endblock %}
3
+
4
+ {% block body %}
5
+ <div class="row">
6
+ <!-- Available Python API -->
7
+ <div class="col-xl-4 col-lg-4 col-md-6 mb-4">
8
+ <div class="card shadow-sm mb-4">
9
+ <div class="card-header">
10
+ <h5 class="mb-0">Available Python API</h5>
11
+ </div>
12
+ <div class="card-body">
13
+ {% for instrument in api_variables %}
14
+ <div class="card mb-2">
15
+ <div class="card-body p-2">
16
+ <a href="{{ url_for('control.temp.new_controller', instrument=instrument) }}" class="text-dark stretched-link">{{ instrument }}</a>
17
+ </div>
18
+ </div>
19
+ {% endfor %}
20
+ <div class="card mt-3">
21
+ <div class="card-body p-2">
22
+ <a data-bs-toggle="modal" href="#importAPI" class="stretched-link">
23
+ <i class="bi bi-folder-plus"></i> Import API
24
+ </a>
25
+ </div>
26
+ </div>
27
+ </div>
28
+ </div>
29
+ </div>
30
+
31
+ <!-- Connecting Device -->
32
+ <div class="col-xl-5 col-lg-5 col-md-6 mb-4">
33
+ {% if device %}
34
+ {# {{ device }}#}
35
+ <div class="card shadow-sm mb-4">
36
+ <div class="card-header">
37
+ <h5 class="mb-0">Connecting</h5>
38
+ </div>
39
+ <div class="card-body">
40
+ <form role="form" method="POST" name="init" action="{{ url_for('control.temp.new_controller', instrument=instrument) }}">
41
+ <div class="mb-3">
42
+ <label class="form-label" for="device_name">Name this device</label>
43
+ <input class="form-control" type="text" id="device_name" name="device_name" aria-describedby="nameHelpBlock" placeholder="e.g. {{device.__name__}}_1">
44
+ <div id="nameHelpBlock" class="form-text">
45
+ Name your instrument, avoid names that are defined on the right
46
+ </div>
47
+ </div>
48
+ {% for arg in device.__init__.__annotations__ %}
49
+ {% if not arg == "return" %}
50
+ <div class="mb-3">
51
+ <label class="form-label" for="{{arg}}">{{arg}}</label>
52
+ <input class="form-control" type="text" id="{{arg}}" name="{{arg}}"
53
+ placeholder="{{device.__init__.__annotations__[arg].__name__}}"
54
+ value="{{args.parameters[arg].default if not args.parameters[arg].default.__name__ == '_empty' else ''}}">
55
+ {% if device.__init__.__annotations__[arg].__module__ is not in ["builtins", "typing"] %}
56
+ <a role="button" href="{{ url_for('control.temp.new_controller', instrument=device.__init__.__annotations__[arg].__name__) }}" class="btn btn-secondary btn-sm mt-2">Initialize {{device.__init__.__annotations__[arg].__name__}} first</a>
57
+ {% endif %}
58
+ </div>
59
+ {% endif %}
60
+ {% endfor %}
61
+ <button type="submit" class="btn btn-dark">Connect</button>
62
+ </form>
63
+ </div>
64
+ </div>
65
+ {% endif %}
66
+ </div>
67
+
68
+ <!-- Defined Instruments -->
69
+ <div class="col-xl-3 col-lg-3 col-md-6 mb-4">
70
+ <div class="card shadow-sm mb-4">
71
+ <div class="card-header">
72
+ <h5 class="mb-0">Defined Instruments</h5>
73
+ </div>
74
+ <div class="card-body">
75
+ {% if defined_variables %}
76
+ <ul class="list-group">
77
+ {% for instrument in defined_variables %}
78
+ <li class="list-group-item">{{ instrument }}</li>
79
+ {% endfor %}
80
+ </ul>
81
+ {% else %}
82
+ <span class="text-muted">No instruments defined.</span>
83
+ {% endif %}
84
+ </div>
85
+ </div>
86
+ </div>
87
+ </div>
88
+
89
+ <!-- Import API Modal -->
90
+ <div class="modal fade" id="importAPI" tabindex="-1" aria-labelledby="importModal" aria-hidden="true">
91
+ <div class="modal-dialog">
92
+ <div class="modal-content">
93
+ <div class="modal-header">
94
+ <h1 class="modal-title fs-5" id="importModal">Import API by file path</h1>
95
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
96
+ </div>
97
+ <form method="POST" action="{{ url_for('control.temp.import_api') }}" enctype="multipart/form-data">
98
+ <div class="modal-body">
99
+ <div class="mb-3">
100
+ <label class="form-label" for="filepath">File Path:</label>
101
+ <input type="text" class="form-control" name="filepath" id="filepath">
102
+ </div>
103
+ </div>
104
+ <div class="modal-footer">
105
+ <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
106
+ <button type="submit" class="btn btn-primary">Save</button>
107
+ </div>
108
+ </form>
109
+ </div>
110
+ </div>
111
+ </div>
112
+ {% endblock %}
@@ -0,0 +1,40 @@
1
+ from flask import session
2
+
3
+ from ivoryos.utils.global_config import GlobalConfig
4
+
5
+
6
+ global_config = GlobalConfig()
7
+
8
+
9
+ def find_instrument_by_name(name: str):
10
+ """
11
+ find instrument class object by instance name
12
+ """
13
+ if name.startswith("deck"):
14
+ name = name.replace("deck.", "")
15
+ return getattr(global_config.deck, name)
16
+ elif name.startswith("blocks"):
17
+ return global_config.building_blocks[name]
18
+ elif name in global_config.defined_variables:
19
+ return global_config.defined_variables[name]
20
+ elif name in globals():
21
+ return globals()[name]
22
+
23
+
24
+ def get_session_by_instrument(session_name, instrument):
25
+ """get data from session by instrument"""
26
+ session_object = session.get(session_name, {})
27
+ functions = session_object.get(instrument, [])
28
+ return functions
29
+
30
+
31
+ def post_session_by_instrument(session_name, instrument, data):
32
+ """
33
+ save new data to session by instrument
34
+ :param session_name: "card_order" or "hidden_functions"
35
+ :param instrument: function name of class object
36
+ :param data: order list or hidden function list
37
+ """
38
+ session_object = session.get(session_name, {})
39
+ session_object[instrument] = data
40
+ session[session_name] = session_object