ivoryos 1.0.8__tar.gz → 1.1.0__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.

Files changed (117) hide show
  1. {ivoryos-1.0.8/ivoryos.egg-info → ivoryos-1.1.0}/PKG-INFO +5 -8
  2. {ivoryos-1.0.8 → ivoryos-1.1.0}/README.md +4 -7
  3. {ivoryos-1.0.8 → ivoryos-1.1.0}/ivoryos/__init__.py +19 -7
  4. ivoryos-1.1.0/ivoryos/routes/api/api.py +109 -0
  5. {ivoryos-1.0.8 → ivoryos-1.1.0}/ivoryos/routes/auth/auth.py +5 -5
  6. ivoryos-1.1.0/ivoryos/routes/control/control.py +131 -0
  7. ivoryos-1.1.0/ivoryos/routes/control/control_file.py +36 -0
  8. ivoryos-1.1.0/ivoryos/routes/control/control_new_device.py +142 -0
  9. ivoryos-1.1.0/ivoryos/routes/control/templates/controllers.html +137 -0
  10. ivoryos-1.1.0/ivoryos/routes/control/templates/controllers_new.html +112 -0
  11. ivoryos-1.1.0/ivoryos/routes/control/utils.py +38 -0
  12. ivoryos-1.1.0/ivoryos/routes/data/data.py +108 -0
  13. {ivoryos-1.0.8/ivoryos/routes/database/templates/database → ivoryos-1.1.0/ivoryos/routes/data/templates}/workflow_database.html +7 -7
  14. {ivoryos-1.0.8/ivoryos/routes/database/templates/database → ivoryos-1.1.0/ivoryos/routes/data/templates}/workflow_view.html +3 -3
  15. ivoryos-1.1.0/ivoryos/routes/design/__init__.py +4 -0
  16. ivoryos-1.1.0/ivoryos/routes/design/design.py +365 -0
  17. ivoryos-1.1.0/ivoryos/routes/design/design_file.py +57 -0
  18. ivoryos-1.1.0/ivoryos/routes/design/design_step.py +43 -0
  19. ivoryos-1.1.0/ivoryos/routes/design/templates/components/action_form.html +52 -0
  20. ivoryos-1.1.0/ivoryos/routes/design/templates/components/action_list.html +15 -0
  21. ivoryos-1.1.0/ivoryos/routes/design/templates/components/autofill_toggle.html +14 -0
  22. ivoryos-1.1.0/ivoryos/routes/design/templates/components/canvas.html +14 -0
  23. ivoryos-1.1.0/ivoryos/routes/design/templates/components/canvas_footer.html +5 -0
  24. ivoryos-1.1.0/ivoryos/routes/design/templates/components/canvas_header.html +54 -0
  25. ivoryos-1.1.0/ivoryos/routes/design/templates/components/deck_selector.html +12 -0
  26. ivoryos-1.1.0/ivoryos/routes/design/templates/components/edit_action_form.html +29 -0
  27. ivoryos-1.1.0/ivoryos/routes/design/templates/components/instrument_panel.html +23 -0
  28. ivoryos-1.1.0/ivoryos/routes/design/templates/components/modals/drop_modal.html +19 -0
  29. ivoryos-1.1.0/ivoryos/routes/design/templates/components/modals/json_modal.html +22 -0
  30. ivoryos-1.1.0/ivoryos/routes/design/templates/components/modals/new_script_modal.html +18 -0
  31. ivoryos-1.1.0/ivoryos/routes/design/templates/components/modals/rename_modal.html +23 -0
  32. ivoryos-1.1.0/ivoryos/routes/design/templates/components/modals/saveas_modal.html +27 -0
  33. ivoryos-1.1.0/ivoryos/routes/design/templates/components/modals.html +6 -0
  34. ivoryos-1.1.0/ivoryos/routes/design/templates/components/operations_panel.html +43 -0
  35. ivoryos-1.1.0/ivoryos/routes/design/templates/components/python_code_overlay.html +17 -0
  36. ivoryos-1.1.0/ivoryos/routes/design/templates/components/script_info.html +31 -0
  37. ivoryos-1.1.0/ivoryos/routes/design/templates/components/scripts.html +50 -0
  38. ivoryos-1.1.0/ivoryos/routes/design/templates/components/sidebar.html +16 -0
  39. ivoryos-1.1.0/ivoryos/routes/design/templates/components/text_to_code_panel.html +20 -0
  40. ivoryos-1.1.0/ivoryos/routes/design/templates/experiment_builder.html +41 -0
  41. ivoryos-1.1.0/ivoryos/routes/execute/execute.py +173 -0
  42. ivoryos-1.1.0/ivoryos/routes/execute/execute_file.py +44 -0
  43. ivoryos-1.1.0/ivoryos/routes/execute/templates/components/error_modal.html +20 -0
  44. ivoryos-1.1.0/ivoryos/routes/execute/templates/components/logging_panel.html +31 -0
  45. ivoryos-1.1.0/ivoryos/routes/execute/templates/components/progress_panel.html +27 -0
  46. ivoryos-1.1.0/ivoryos/routes/execute/templates/components/run_panel.html +9 -0
  47. ivoryos-1.1.0/ivoryos/routes/execute/templates/components/run_tabs.html +17 -0
  48. ivoryos-1.1.0/ivoryos/routes/execute/templates/components/tab_bayesian.html +147 -0
  49. ivoryos-1.1.0/ivoryos/routes/execute/templates/components/tab_configuration.html +98 -0
  50. ivoryos-1.1.0/ivoryos/routes/execute/templates/components/tab_repeat.html +14 -0
  51. ivoryos-1.1.0/ivoryos/routes/execute/templates/experiment_run.html +294 -0
  52. ivoryos-1.0.8/ivoryos/routes/database/database.py → ivoryos-1.1.0/ivoryos/routes/library/library.py +10 -112
  53. ivoryos-1.0.8/ivoryos/routes/database/templates/database/scripts_database.html → ivoryos-1.1.0/ivoryos/routes/library/templates/library.html +8 -8
  54. {ivoryos-1.0.8 → ivoryos-1.1.0}/ivoryos/routes/main/main.py +1 -1
  55. {ivoryos-1.0.8/ivoryos/routes/main/templates/main → ivoryos-1.1.0/ivoryos/routes/main/templates}/home.html +4 -4
  56. ivoryos-1.1.0/ivoryos/socket_handlers.py +52 -0
  57. {ivoryos-1.0.8 → ivoryos-1.1.0}/ivoryos/templates/base.html +4 -4
  58. {ivoryos-1.0.8 → ivoryos-1.1.0}/ivoryos/utils/bo_campaign.py +43 -3
  59. {ivoryos-1.0.8 → ivoryos-1.1.0}/ivoryos/utils/form.py +1 -0
  60. ivoryos-1.1.0/ivoryos/utils/py_to_json.py +225 -0
  61. {ivoryos-1.0.8 → ivoryos-1.1.0}/ivoryos/utils/script_runner.py +30 -7
  62. ivoryos-1.1.0/ivoryos/version.py +1 -0
  63. {ivoryos-1.0.8 → ivoryos-1.1.0/ivoryos.egg-info}/PKG-INFO +5 -8
  64. ivoryos-1.1.0/ivoryos.egg-info/SOURCES.txt +106 -0
  65. ivoryos-1.1.0/tests/integration/__init__.py +0 -0
  66. ivoryos-1.0.8/ivoryos/routes/control/control.py +0 -429
  67. ivoryos-1.0.8/ivoryos/routes/control/templates/control/controllers.html +0 -78
  68. ivoryos-1.0.8/ivoryos/routes/control/templates/control/controllers_home.html +0 -55
  69. ivoryos-1.0.8/ivoryos/routes/control/templates/control/controllers_new.html +0 -89
  70. ivoryos-1.0.8/ivoryos/routes/design/design.py +0 -786
  71. ivoryos-1.0.8/ivoryos/routes/design/templates/design/experiment_builder.html +0 -521
  72. ivoryos-1.0.8/ivoryos/routes/design/templates/design/experiment_run.html +0 -558
  73. ivoryos-1.0.8/ivoryos/version.py +0 -1
  74. ivoryos-1.0.8/ivoryos.egg-info/SOURCES.txt +0 -65
  75. {ivoryos-1.0.8 → ivoryos-1.1.0}/LICENSE +0 -0
  76. {ivoryos-1.0.8 → ivoryos-1.1.0}/MANIFEST.in +0 -0
  77. {ivoryos-1.0.8 → ivoryos-1.1.0}/ivoryos/config.py +0 -0
  78. {ivoryos-1.0.8 → ivoryos-1.1.0}/ivoryos/routes/__init__.py +0 -0
  79. {ivoryos-1.0.8 → ivoryos-1.1.0}/ivoryos/routes/auth/__init__.py +0 -0
  80. {ivoryos-1.0.8/ivoryos/routes/auth/templates/auth → ivoryos-1.1.0/ivoryos/routes/auth/templates}/login.html +0 -0
  81. {ivoryos-1.0.8/ivoryos/routes/auth/templates/auth → ivoryos-1.1.0/ivoryos/routes/auth/templates}/signup.html +0 -0
  82. {ivoryos-1.0.8 → ivoryos-1.1.0}/ivoryos/routes/control/__init__.py +0 -0
  83. {ivoryos-1.0.8/ivoryos/routes/database → ivoryos-1.1.0/ivoryos/routes/data}/__init__.py +0 -0
  84. {ivoryos-1.0.8/ivoryos/routes/database/templates/database → ivoryos-1.1.0/ivoryos/routes/data/templates/components}/step_card.html +0 -0
  85. {ivoryos-1.0.8/ivoryos/routes/design → ivoryos-1.1.0/ivoryos/routes/execute}/__init__.py +0 -0
  86. {ivoryos-1.0.8/ivoryos/routes/main → ivoryos-1.1.0/ivoryos/routes/library}/__init__.py +0 -0
  87. {ivoryos-1.0.8/ivoryos/utils → ivoryos-1.1.0/ivoryos/routes/main}/__init__.py +0 -0
  88. {ivoryos-1.0.8/ivoryos/routes/main/templates/main → ivoryos-1.1.0/ivoryos/routes/main/templates}/help.html +0 -0
  89. {ivoryos-1.0.8 → ivoryos-1.1.0}/ivoryos/static/favicon.ico +0 -0
  90. {ivoryos-1.0.8 → ivoryos-1.1.0}/ivoryos/static/gui_annotation/Slide1.png +0 -0
  91. {ivoryos-1.0.8 → ivoryos-1.1.0}/ivoryos/static/gui_annotation/Slide2.PNG +0 -0
  92. {ivoryos-1.0.8 → ivoryos-1.1.0}/ivoryos/static/js/overlay.js +0 -0
  93. {ivoryos-1.0.8 → ivoryos-1.1.0}/ivoryos/static/js/socket_handler.js +0 -0
  94. {ivoryos-1.0.8 → ivoryos-1.1.0}/ivoryos/static/js/sortable_card.js +0 -0
  95. {ivoryos-1.0.8 → ivoryos-1.1.0}/ivoryos/static/js/sortable_design.js +0 -0
  96. {ivoryos-1.0.8 → ivoryos-1.1.0}/ivoryos/static/logo.webp +0 -0
  97. {ivoryos-1.0.8 → ivoryos-1.1.0}/ivoryos/static/style.css +0 -0
  98. {ivoryos-1.0.8/tests → ivoryos-1.1.0/ivoryos/utils}/__init__.py +0 -0
  99. {ivoryos-1.0.8 → ivoryos-1.1.0}/ivoryos/utils/client_proxy.py +0 -0
  100. {ivoryos-1.0.8 → ivoryos-1.1.0}/ivoryos/utils/db_models.py +0 -0
  101. {ivoryos-1.0.8 → ivoryos-1.1.0}/ivoryos/utils/global_config.py +0 -0
  102. {ivoryos-1.0.8 → ivoryos-1.1.0}/ivoryos/utils/llm_agent.py +0 -0
  103. {ivoryos-1.0.8 → ivoryos-1.1.0}/ivoryos/utils/task_runner.py +0 -0
  104. {ivoryos-1.0.8 → ivoryos-1.1.0}/ivoryos/utils/utils.py +0 -0
  105. {ivoryos-1.0.8 → ivoryos-1.1.0}/ivoryos.egg-info/dependency_links.txt +0 -0
  106. {ivoryos-1.0.8 → ivoryos-1.1.0}/ivoryos.egg-info/requires.txt +0 -0
  107. {ivoryos-1.0.8 → ivoryos-1.1.0}/ivoryos.egg-info/top_level.txt +0 -0
  108. {ivoryos-1.0.8 → ivoryos-1.1.0}/setup.cfg +0 -0
  109. {ivoryos-1.0.8 → ivoryos-1.1.0}/setup.py +0 -0
  110. {ivoryos-1.0.8/tests/integration → ivoryos-1.1.0/tests}/__init__.py +0 -0
  111. {ivoryos-1.0.8 → ivoryos-1.1.0}/tests/conftest.py +0 -0
  112. {ivoryos-1.0.8 → ivoryos-1.1.0}/tests/integration/test_route_auth.py +0 -0
  113. {ivoryos-1.0.8 → ivoryos-1.1.0}/tests/integration/test_route_control.py +0 -0
  114. {ivoryos-1.0.8 → ivoryos-1.1.0}/tests/integration/test_route_database.py +0 -0
  115. {ivoryos-1.0.8 → ivoryos-1.1.0}/tests/integration/test_route_design.py +0 -0
  116. {ivoryos-1.0.8 → ivoryos-1.1.0}/tests/integration/test_route_main.py +0 -0
  117. {ivoryos-1.0.8 → ivoryos-1.1.0}/tests/integration/test_sockets.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ivoryos
3
- Version: 1.0.8
3
+ Version: 1.1.0
4
4
  Summary: an open-source Python package enabling Self-Driving Labs (SDLs) interoperability
5
5
  Home-page: https://gitlab.com/heingroup/ivoryos
6
6
  Author: Ivory Zhang
@@ -39,7 +39,7 @@ With the least modification of the current workflow, user can design, manage and
39
39
  This software is developed and tested using Windows. This software and its dependencies are compatible across major platforms: Linux, macOS, and Windows. Some dependencies (Flask-SQLAlchemy) may require additional setup.
40
40
 
41
41
  ### Python Version
42
- Python >=3.7 for best compatibility.
42
+ Python >=3.10 for best compatibility. Python >=3.7 without Ax.
43
43
  ### Python dependencies
44
44
  This software is compatible with the latest versions of all dependencies.
45
45
  - bcrypt~=4.0
@@ -50,8 +50,7 @@ This software is compatible with the latest versions of all dependencies.
50
50
  - SQLAlchemy-Utils~=0.41
51
51
  - Flask-WTF~=1.2
52
52
  - python-dotenv==1.0.1
53
- - openai (optional ~=1.53)
54
- - ax-platform (optional ~=0.3 or ~=0.4 for Python>=3.9)
53
+ - ax-platform (optional ~=0.4 for Python>=3.9)
55
54
 
56
55
  ## Installation
57
56
  ```bash
@@ -61,7 +60,7 @@ or
61
60
  ```bash
62
61
  git clone https://gitlab.com/heingroup/ivoryos.git
63
62
  cd ivoryos
64
- pip install -e .
63
+ pip install .
65
64
  ```
66
65
 
67
66
  The installation may take 10 to 30 seconds to install. The installation time may vary and take up to several minutes, depending on the network speed, computer performance, and virtual environment settings.
@@ -89,7 +88,7 @@ Create an account and login (local database)
89
88
  [//]: # (![PyPI - Downloads](https://img.shields.io/pypi/dm/ivoryos))
90
89
 
91
90
 
92
- ### Additional settings
91
+ ### Additional settings (not actively maintained)
93
92
  #### AI assistant
94
93
  To streamline the experimental design on SDLs, we also integrate Large Language Models (LLMs) to interpret the inspected functions and generate code according to task descriptions.
95
94
 
@@ -141,8 +140,6 @@ ivoryos.run(__name__)
141
140
  ### Deck function and web form
142
141
  ![](https://gitlab.com/heingroup/ivoryos/raw/main/docs/source/_static/demo.gif)
143
142
 
144
- ### Text-to-code demo
145
- ![](https://gitlab.com/heingroup/ivoryos/raw/main/docs/source/_static/text-to-code.gif)
146
143
 
147
144
  ### Directory structure
148
145
 
@@ -28,7 +28,7 @@ With the least modification of the current workflow, user can design, manage and
28
28
  This software is developed and tested using Windows. This software and its dependencies are compatible across major platforms: Linux, macOS, and Windows. Some dependencies (Flask-SQLAlchemy) may require additional setup.
29
29
 
30
30
  ### Python Version
31
- Python >=3.7 for best compatibility.
31
+ Python >=3.10 for best compatibility. Python >=3.7 without Ax.
32
32
  ### Python dependencies
33
33
  This software is compatible with the latest versions of all dependencies.
34
34
  - bcrypt~=4.0
@@ -39,8 +39,7 @@ This software is compatible with the latest versions of all dependencies.
39
39
  - SQLAlchemy-Utils~=0.41
40
40
  - Flask-WTF~=1.2
41
41
  - python-dotenv==1.0.1
42
- - openai (optional ~=1.53)
43
- - ax-platform (optional ~=0.3 or ~=0.4 for Python>=3.9)
42
+ - ax-platform (optional ~=0.4 for Python>=3.9)
44
43
 
45
44
  ## Installation
46
45
  ```bash
@@ -50,7 +49,7 @@ or
50
49
  ```bash
51
50
  git clone https://gitlab.com/heingroup/ivoryos.git
52
51
  cd ivoryos
53
- pip install -e .
52
+ pip install .
54
53
  ```
55
54
 
56
55
  The installation may take 10 to 30 seconds to install. The installation time may vary and take up to several minutes, depending on the network speed, computer performance, and virtual environment settings.
@@ -78,7 +77,7 @@ Create an account and login (local database)
78
77
  [//]: # (![PyPI - Downloads](https://img.shields.io/pypi/dm/ivoryos))
79
78
 
80
79
 
81
- ### Additional settings
80
+ ### Additional settings (not actively maintained)
82
81
  #### AI assistant
83
82
  To streamline the experimental design on SDLs, we also integrate Large Language Models (LLMs) to interpret the inspected functions and generate code according to task descriptions.
84
83
 
@@ -130,8 +129,6 @@ ivoryos.run(__name__)
130
129
  ### Deck function and web form
131
130
  ![](https://gitlab.com/heingroup/ivoryos/raw/main/docs/source/_static/demo.gif)
132
131
 
133
- ### Text-to-code demo
134
- ![](https://gitlab.com/heingroup/ivoryos/raw/main/docs/source/_static/text-to-code.gif)
135
132
 
136
133
  ### Directory structure
137
134
 
@@ -3,12 +3,17 @@ import sys
3
3
  from typing import Union
4
4
 
5
5
  from flask import Flask, redirect, url_for, g, Blueprint
6
+ sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
6
7
 
7
8
  from ivoryos.config import Config, get_config
8
9
  from ivoryos.routes.auth.auth import auth, login_manager
9
10
  from ivoryos.routes.control.control import control
10
- from ivoryos.routes.database.database import database
11
- from ivoryos.routes.design.design import design, socketio
11
+ from ivoryos.routes.data.data import data
12
+ from ivoryos.routes.library.library import library
13
+ from ivoryos.routes.design.design import design
14
+ from ivoryos.routes.execute.execute import execute
15
+ from ivoryos.routes.api.api import api
16
+ from ivoryos.socket_handlers import socketio
12
17
  from ivoryos.routes.main.main import main
13
18
  # from ivoryos.routes.monitor.monitor import monitor
14
19
  from ivoryos.utils import utils
@@ -35,10 +40,13 @@ def enforce_sqlite_foreign_keys(dbapi_connection, connection_record):
35
40
  url_prefix = os.getenv('URL_PREFIX', "/ivoryos")
36
41
  app = Flask(__name__, static_url_path=f'{url_prefix}/static', static_folder='static')
37
42
  app.register_blueprint(main, url_prefix=url_prefix)
38
- app.register_blueprint(auth, url_prefix=url_prefix)
39
- app.register_blueprint(control, url_prefix=url_prefix)
40
- app.register_blueprint(design, url_prefix=url_prefix)
41
- app.register_blueprint(database, url_prefix=url_prefix)
43
+ app.register_blueprint(auth, url_prefix=f'{url_prefix}/{auth.name}')
44
+ app.register_blueprint(library, url_prefix=f'{url_prefix}/{library.name}')
45
+ app.register_blueprint(control, url_prefix=f'{url_prefix}/{control.name}')
46
+ app.register_blueprint(design, url_prefix=f'{url_prefix}/{design.name}')
47
+ app.register_blueprint(execute, url_prefix=f'{url_prefix}/{execute.name}')
48
+ app.register_blueprint(data, url_prefix=f'{url_prefix}/{data.name}')
49
+ app.register_blueprint(api, url_prefix=f'{url_prefix}/{api.name}')
42
50
 
43
51
  @login_manager.user_loader
44
52
  def load_user(user_id):
@@ -166,7 +174,7 @@ def run(module=None, host="0.0.0.0", port=None, debug=None, llm_server=None, mod
166
174
 
167
175
  # in case Python 3.12 or higher doesn't log URL
168
176
  if sys.version_info >= (3, 12):
169
- ip = utils.get_ip_address()
177
+ ip = utils.get_local_ip()
170
178
  print(f"Server running at http://localhost:{port}")
171
179
  if not ip == "127.0.0.1":
172
180
  print(f"Server running at http://{ip}:{port}")
@@ -195,6 +203,10 @@ def load_installed_plugins(app, socketio):
195
203
  def load_plugins(blueprints: Union[list, Blueprint], app, socketio):
196
204
  """
197
205
  Dynamically load installed plugins and attach Flask-SocketIO.
206
+ :param blueprints: Union[list, Blueprint] list of Blueprint objects or a single Blueprint object
207
+ :param app: Flask application instance
208
+ :param socketio: Flask-SocketIO instance
209
+ :return: list of plugin names
198
210
  """
199
211
  plugin_names = []
200
212
  if not isinstance(blueprints, list):
@@ -0,0 +1,109 @@
1
+ import os
2
+ from flask import Blueprint, jsonify, request, current_app
3
+
4
+ from ivoryos.routes.control.control import find_instrument_by_name
5
+ from ivoryos.utils.form import create_form_from_module
6
+ from ivoryos.utils.global_config import GlobalConfig
7
+ from ivoryos.utils.db_models import Script, WorkflowRun, SingleStep, WorkflowStep
8
+
9
+ from ivoryos.socket_handlers import abort_pending, abort_current, pause, retry, runner
10
+
11
+ api = Blueprint('api', __name__)
12
+ global_config = GlobalConfig()
13
+
14
+
15
+
16
+ @api.route("/runner/status", methods=["GET"])
17
+ def runner_status():
18
+ """Get the execution status"""
19
+ # runner = global_config.runner
20
+ runner_busy = global_config.runner_lock.locked()
21
+ status = {"busy": runner_busy}
22
+ task_status = global_config.runner_status
23
+ current_step = {}
24
+
25
+ if task_status is not None:
26
+ task_type = task_status["type"]
27
+ task_id = task_status["id"]
28
+ if task_type == "task":
29
+ step = SingleStep.query.get(task_id)
30
+ current_step = step.as_dict()
31
+ if task_type == "workflow":
32
+ workflow = WorkflowRun.query.get(task_id)
33
+ if workflow is not None:
34
+ latest_step = WorkflowStep.query.filter_by(workflow_id=workflow.id).order_by(
35
+ WorkflowStep.start_time.desc()).first()
36
+ if latest_step is not None:
37
+ current_step = latest_step.as_dict()
38
+ status["workflow_status"] = {"workflow_info": workflow.as_dict(), "runner_status": runner.get_status()}
39
+ status["current_task"] = current_step
40
+ return jsonify(status), 200
41
+
42
+
43
+ @api.route("/runner/abort_pending", methods=["POST"])
44
+ def api_abort_pending():
45
+ """Abort pending action(s) during execution"""
46
+ abort_pending()
47
+ return jsonify({"status": "ok"}), 200
48
+
49
+
50
+ @api.route("/runner/abort_current", methods=["POST"])
51
+ def api_abort_current():
52
+ """Abort right after current action during execution"""
53
+ abort_current()
54
+ return jsonify({"status": "ok"}), 200
55
+
56
+
57
+ @api.route("/runner/pause", methods=["POST"])
58
+ def api_pause():
59
+ """Pause during execution"""
60
+ msg = pause()
61
+ return jsonify({"status": "ok", "pause_status": msg}), 200
62
+
63
+
64
+ @api.route("/runner/retry", methods=["POST"])
65
+ def api_retry():
66
+ """Retry when error occur during execution"""
67
+ retry()
68
+ return jsonify({"status": "ok, retrying failed step"}), 200
69
+
70
+
71
+
72
+ @api.route("/control/", strict_slashes=False, methods=['GET'])
73
+ @api.route("/control/<instrument>", methods=['POST'])
74
+ def backend_control(instrument: str=None):
75
+ """
76
+ .. :quickref: Backend Control; backend control
77
+
78
+ backend control through http requests
79
+
80
+ .. http:get:: /api/control/
81
+
82
+ :param instrument: instrument name
83
+ :type instrument: str
84
+
85
+ .. http:post:: /api/control/
86
+
87
+ """
88
+ if instrument:
89
+ inst_object = find_instrument_by_name(instrument)
90
+ forms = create_form_from_module(sdl_module=inst_object, autofill=False, design=False)
91
+
92
+ if request.method == 'POST':
93
+ method_name = request.form.get("hidden_name", None)
94
+ form = forms.get(method_name, None)
95
+ if form:
96
+ kwargs = {field.name: field.data for field in form if field.name not in ['csrf_token', 'hidden_name']}
97
+ wait = request.form.get("hidden_wait", "true") == "true"
98
+ output = runner.run_single_step(component=instrument, method=method_name, kwargs=kwargs, wait=wait,
99
+ current_app=current_app._get_current_object())
100
+ return jsonify(output), 200
101
+
102
+ snapshot = global_config.deck_snapshot.copy()
103
+ # Iterate through each instrument in the snapshot
104
+ for instrument_key, instrument_data in snapshot.items():
105
+ # Iterate through each function associated with the current instrument
106
+ for function_key, function_data in instrument_data.items():
107
+ # Convert the function signature to a string representation
108
+ function_data['signature'] = str(function_data['signature'])
109
+ return jsonify(snapshot), 200
@@ -6,10 +6,10 @@ from ivoryos.utils.db_models import Script, User, db
6
6
  from ivoryos.utils.utils import post_script_file
7
7
  login_manager = LoginManager()
8
8
 
9
- auth = Blueprint('auth', __name__, template_folder='templates/auth')
9
+ auth = Blueprint('auth', __name__, template_folder='templates')
10
10
 
11
11
 
12
- @auth.route('/auth/login', methods=['GET', 'POST'])
12
+ @auth.route('/login', methods=['GET', 'POST'])
13
13
  def login():
14
14
  """
15
15
  .. :quickref: User; login user
@@ -46,11 +46,11 @@ def login():
46
46
  return redirect(url_for('main.index'))
47
47
  else:
48
48
  flash("Incorrect username or password")
49
- return redirect(url_for('auth.login')), 401
49
+ return redirect(url_for('auth.login'))
50
50
  return render_template('login.html')
51
51
 
52
52
 
53
- @auth.route('/auth/signup', methods=['GET', 'POST'])
53
+ @auth.route('/signup', methods=['GET', 'POST'])
54
54
  def signup():
55
55
  """
56
56
  .. :quickref: User; signup for a new account
@@ -84,7 +84,7 @@ def signup():
84
84
  return render_template('signup.html')
85
85
 
86
86
 
87
- @auth.route("/auth/logout")
87
+ @auth.route("/logout")
88
88
  @login_required
89
89
  def logout():
90
90
  """
@@ -0,0 +1,131 @@
1
+ from flask import Blueprint, redirect, flash, request, render_template, session, current_app
2
+ from flask_login import login_required
3
+
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
7
+ from ivoryos.utils.global_config import GlobalConfig
8
+ from ivoryos.utils.form import create_form_from_module, format_name
9
+ from ivoryos.utils.task_runner import TaskRunner
10
+
11
+ global_config = GlobalConfig()
12
+ runner = TaskRunner()
13
+
14
+ control = Blueprint('control', __name__, template_folder='templates')
15
+
16
+ control.register_blueprint(control_file)
17
+ control.register_blueprint(control_temp)
18
+
19
+
20
+
21
+ @control.route("/home", strict_slashes=False, methods=["GET", "POST"])
22
+ @login_required
23
+ def deck_controllers():
24
+ """
25
+ Combined controllers page: sidebar with all instruments, main area with method cards for selected instrument.
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
32
+ if instrument:
33
+ inst_object = find_instrument_by_name(instrument)
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'])
72
+ def save_order(instrument: str):
73
+ """
74
+ .. :quickref: Control Customization; Save functions' order
75
+
76
+ .. http:post:: /control/save-order
77
+
78
+ save function drag and drop order for the given <instrument>
79
+
80
+ """
81
+ # Save the new order for the specified group to session
82
+ data = request.json
83
+ post_session_by_instrument('card_order', instrument, data['order'])
84
+ return '', 204
85
+
86
+
87
+ @control.route('/<instrument>/<function>/hide')
88
+ def hide_function(instrument, function):
89
+ """
90
+ .. :quickref: Control Customization; Hide function
91
+
92
+ .. http:get:: //control/<instrument>/<function>/hide
93
+
94
+ Hide the given <instrument> and <function>
95
+
96
+ """
97
+ back = request.referrer
98
+ functions = get_session_by_instrument("hidden_functions", instrument)
99
+ order = get_session_by_instrument("card_order", instrument)
100
+ if function not in functions:
101
+ functions.append(function)
102
+ order.remove(function)
103
+ post_session_by_instrument('hidden_functions', instrument, functions)
104
+ post_session_by_instrument('card_order', instrument, order)
105
+ return redirect(back)
106
+
107
+
108
+ @control.route('/<instrument>/<function>/unhide')
109
+ def remove_hidden(instrument: str, function: str):
110
+ """
111
+ .. :quickref: Control Customization; Remove a hidden function
112
+
113
+ .. http:get:: /control/<instrument>/<function>/unhide
114
+
115
+ Un-hide the given <instrument> and <function>
116
+
117
+ """
118
+ back = request.referrer
119
+ functions = get_session_by_instrument("hidden_functions", instrument)
120
+ order = get_session_by_instrument("card_order", instrument)
121
+ if function in functions:
122
+ functions.remove(function)
123
+ order.append(function)
124
+ post_session_by_instrument('hidden_functions', instrument, functions)
125
+ post_session_by_instrument('card_order', instrument, order)
126
+ return redirect(back)
127
+
128
+
129
+
130
+
131
+
@@ -0,0 +1,36 @@
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("/download/proxy", strict_slashes=False)
14
+ @login_required
15
+ def download_proxy():
16
+ """
17
+ .. :quickref: Direct Control; download proxy interface
18
+
19
+ download proxy interface
20
+
21
+ .. http:get:: /control/download
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)
@@ -0,0 +1,142 @@
1
+ import os
2
+ from flask import Blueprint, request, current_app, send_file, flash, redirect, url_for, session, render_template
3
+ from flask_login import login_required
4
+
5
+ from ivoryos.utils import utils
6
+ from ivoryos.routes.control.utils import find_instrument_by_name
7
+ from ivoryos.utils.global_config import GlobalConfig
8
+
9
+ global_config = GlobalConfig()
10
+
11
+ control_temp = Blueprint('temp', __name__)
12
+
13
+ @control_temp.route("/import/module", methods=['POST'])
14
+ def import_api():
15
+ """
16
+ .. :quickref: Advanced Features; Manually import API module(s)
17
+
18
+ importing other Python modules
19
+
20
+ .. http:post:: /control/import/module
21
+
22
+ :form filepath: API (Python class) module filepath
23
+
24
+ import the module and redirect to :http:get:`/ivoryos/control/new/`
25
+
26
+ """
27
+ filepath = request.form.get('filepath')
28
+ # filepath.replace('\\', '/')
29
+ name = os.path.split(filepath)[-1].split('.')[0]
30
+ try:
31
+ spec = utils.importlib.util.spec_from_file_location(name, filepath)
32
+ module = utils.importlib.util.module_from_spec(spec)
33
+ spec.loader.exec_module(module)
34
+ classes = utils.inspect.getmembers(module, utils.inspect.isclass)
35
+ if len(classes) == 0:
36
+ flash("Invalid import: no class found in the path")
37
+ return redirect(url_for("control.controllers_home"))
38
+ for i in classes:
39
+ globals()[i[0]] = i[1]
40
+ global_config.api_variables.add(i[0])
41
+ # should handle path error and file type error
42
+ except Exception as e:
43
+ flash(e.__str__())
44
+ return redirect(url_for("control.temp.new_controller"))
45
+
46
+
47
+
48
+ @control_temp.route("/import/deck", methods=['POST'])
49
+ def import_deck():
50
+ """
51
+ .. :quickref: Advanced Features; Manually import a deck
52
+
53
+ .. http:post:: /control/import_deck
54
+
55
+ :form filepath: deck module filepath
56
+
57
+ import the module and redirect to the previous page
58
+
59
+ """
60
+ script = utils.get_script_file()
61
+ filepath = request.form.get('filepath')
62
+ session['dismiss'] = request.form.get('dismiss')
63
+ update = request.form.get('update')
64
+ back = request.referrer
65
+ if session['dismiss']:
66
+ return redirect(back)
67
+ name = os.path.split(filepath)[-1].split('.')[0]
68
+ try:
69
+ module = utils.import_module_by_filepath(filepath=filepath, name=name)
70
+ utils.save_to_history(filepath, current_app.config["DECK_HISTORY"])
71
+ module_sigs = utils.create_deck_snapshot(module, save=update, output_path=current_app.config["DUMMY_DECK"])
72
+ if not len(module_sigs) > 0:
73
+ flash("Invalid hardware deck, connect instruments in deck script", "error")
74
+ return redirect(url_for("control.deck_controllers"))
75
+ global_config.deck = module
76
+ global_config.deck_snapshot = module_sigs
77
+
78
+ if script.deck is None:
79
+ script.deck = module.__name__
80
+ # file path error exception
81
+ except Exception as e:
82
+ flash(e.__str__())
83
+ return redirect(back)
84
+
85
+
86
+ @control_temp.route("/new/", strict_slashes=False)
87
+ @control_temp.route("/new/<instrument>", methods=['GET', 'POST'])
88
+ @login_required
89
+ def new_controller(instrument=None):
90
+ """
91
+ .. :quickref: Direct Control; connect to a new device
92
+
93
+ interface for connecting a new <instrument>
94
+
95
+ .. http:get:: /control/new/
96
+
97
+ :param instrument: instrument name
98
+ :type instrument: str
99
+
100
+ .. http:post:: /control/new/
101
+
102
+ :form device_name: module instance name (e.g. my_instance = MyClass())
103
+ :form kwargs: dynamic module initialization kwargs fields
104
+
105
+ """
106
+ device = None
107
+ args = None
108
+ if instrument:
109
+
110
+ device = globals()[instrument]
111
+ args = utils.inspect.signature(device.__init__)
112
+
113
+ if request.method == 'POST':
114
+ device_name = request.form.get("device_name", "")
115
+ if device_name and device_name in globals():
116
+ flash("Device name is defined. Try another name, or leave it as blank to auto-configure")
117
+ # return render_template('controllers_new.html', instrument=instrument,
118
+ # api_variables=global_config.api_variables,
119
+ # device=device, args=args, defined_variables=global_config.defined_variables)
120
+ if device_name == "":
121
+ device_name = device.__name__.lower() + "_"
122
+ num = 1
123
+ while device_name + str(num) in global_config.defined_variables:
124
+ num += 1
125
+ device_name = device_name + str(num)
126
+ kwargs = request.form.to_dict()
127
+ kwargs.pop("device_name")
128
+ for i in kwargs:
129
+ if kwargs[i] in global_config.defined_variables:
130
+ kwargs[i] = global_config.defined_variables[kwargs[i]]
131
+ try:
132
+ utils.convert_config_type(kwargs, device.__init__.__annotations__, is_class=True)
133
+ except Exception as e:
134
+ flash(e)
135
+ try:
136
+ global_config.defined_variables[device_name] = device(**kwargs)
137
+ # global_config.defined_variables.add(device_name)
138
+ return redirect(url_for('control.deck_controllers'))
139
+ except Exception as e:
140
+ flash(e)
141
+ return render_template('controllers_new.html', instrument=instrument, api_variables=global_config.api_variables,
142
+ device=device, args=args, defined_variables=global_config.defined_variables)