ivoryos 0.1.13__tar.gz → 0.1.15__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 (54) hide show
  1. {ivoryos-0.1.13/ivoryos.egg-info → ivoryos-0.1.15}/PKG-INFO +1 -1
  2. {ivoryos-0.1.13 → ivoryos-0.1.15}/ivoryos/__init__.py +24 -7
  3. {ivoryos-0.1.13 → ivoryos-0.1.15}/ivoryos/routes/control/templates/control/controllers.html +3 -0
  4. {ivoryos-0.1.13 → ivoryos-0.1.15}/ivoryos/routes/design/design.py +17 -22
  5. {ivoryos-0.1.13 → ivoryos-0.1.15}/ivoryos/routes/design/templates/design/experiment_builder.html +3 -1
  6. {ivoryos-0.1.13 → ivoryos-0.1.15}/ivoryos/routes/main/main.py +1 -1
  7. {ivoryos-0.1.13 → ivoryos-0.1.15}/ivoryos/routes/main/templates/main/home.html +19 -17
  8. ivoryos-0.1.15/ivoryos/routes/monitor/monitor.py +21 -0
  9. ivoryos-0.1.15/ivoryos/routes/monitor/templates/monitor/monitor.html +24 -0
  10. {ivoryos-0.1.13 → ivoryos-0.1.15}/ivoryos/templates/base.html +17 -10
  11. {ivoryos-0.1.13 → ivoryos-0.1.15}/ivoryos/utils/db_models.py +22 -39
  12. {ivoryos-0.1.13 → ivoryos-0.1.15}/ivoryos/utils/form.py +81 -41
  13. {ivoryos-0.1.13 → ivoryos-0.1.15}/ivoryos/utils/utils.py +2 -32
  14. {ivoryos-0.1.13 → ivoryos-0.1.15/ivoryos.egg-info}/PKG-INFO +1 -1
  15. {ivoryos-0.1.13 → ivoryos-0.1.15}/ivoryos.egg-info/SOURCES.txt +2 -1
  16. {ivoryos-0.1.13 → ivoryos-0.1.15}/setup.py +1 -1
  17. ivoryos-0.1.13/ivoryos/version.py +0 -1
  18. {ivoryos-0.1.13 → ivoryos-0.1.15}/LICENSE +0 -0
  19. {ivoryos-0.1.13 → ivoryos-0.1.15}/MANIFEST.in +0 -0
  20. {ivoryos-0.1.13 → ivoryos-0.1.15}/README.md +0 -0
  21. {ivoryos-0.1.13 → ivoryos-0.1.15}/ivoryos/config.py +0 -0
  22. {ivoryos-0.1.13 → ivoryos-0.1.15}/ivoryos/routes/__init__.py +0 -0
  23. {ivoryos-0.1.13 → ivoryos-0.1.15}/ivoryos/routes/auth/__init__.py +0 -0
  24. {ivoryos-0.1.13 → ivoryos-0.1.15}/ivoryos/routes/auth/auth.py +0 -0
  25. {ivoryos-0.1.13 → ivoryos-0.1.15}/ivoryos/routes/auth/templates/auth/login.html +0 -0
  26. {ivoryos-0.1.13 → ivoryos-0.1.15}/ivoryos/routes/auth/templates/auth/signup.html +0 -0
  27. {ivoryos-0.1.13 → ivoryos-0.1.15}/ivoryos/routes/control/__init__.py +0 -0
  28. {ivoryos-0.1.13 → ivoryos-0.1.15}/ivoryos/routes/control/control.py +0 -0
  29. {ivoryos-0.1.13 → ivoryos-0.1.15}/ivoryos/routes/control/templates/control/controllers_home.html +0 -0
  30. {ivoryos-0.1.13 → ivoryos-0.1.15}/ivoryos/routes/control/templates/control/controllers_new.html +0 -0
  31. {ivoryos-0.1.13 → ivoryos-0.1.15}/ivoryos/routes/database/__init__.py +0 -0
  32. {ivoryos-0.1.13 → ivoryos-0.1.15}/ivoryos/routes/database/database.py +0 -0
  33. {ivoryos-0.1.13 → ivoryos-0.1.15}/ivoryos/routes/database/templates/database/experiment_database.html +0 -0
  34. {ivoryos-0.1.13 → ivoryos-0.1.15}/ivoryos/routes/design/__init__.py +0 -0
  35. {ivoryos-0.1.13 → ivoryos-0.1.15}/ivoryos/routes/design/templates/design/experiment_run.html +0 -0
  36. {ivoryos-0.1.13 → ivoryos-0.1.15}/ivoryos/routes/main/__init__.py +0 -0
  37. {ivoryos-0.1.13 → ivoryos-0.1.15}/ivoryos/routes/main/templates/main/help.html +0 -0
  38. {ivoryos-0.1.13 → ivoryos-0.1.15}/ivoryos/static/favicon.ico +0 -0
  39. {ivoryos-0.1.13 → ivoryos-0.1.15}/ivoryos/static/gui_annotation/Slide1.png +0 -0
  40. {ivoryos-0.1.13 → ivoryos-0.1.15}/ivoryos/static/gui_annotation/Slide2.PNG +0 -0
  41. {ivoryos-0.1.13 → ivoryos-0.1.15}/ivoryos/static/js/overlay.js +0 -0
  42. {ivoryos-0.1.13 → ivoryos-0.1.15}/ivoryos/static/js/socket_handler.js +0 -0
  43. {ivoryos-0.1.13 → ivoryos-0.1.15}/ivoryos/static/js/sortable_card.js +0 -0
  44. {ivoryos-0.1.13 → ivoryos-0.1.15}/ivoryos/static/js/sortable_design.js +0 -0
  45. {ivoryos-0.1.13 → ivoryos-0.1.15}/ivoryos/static/logo.webp +0 -0
  46. {ivoryos-0.1.13 → ivoryos-0.1.15}/ivoryos/static/style.css +0 -0
  47. {ivoryos-0.1.13 → ivoryos-0.1.15}/ivoryos/utils/__init__.py +0 -0
  48. {ivoryos-0.1.13 → ivoryos-0.1.15}/ivoryos/utils/global_config.py +0 -0
  49. {ivoryos-0.1.13 → ivoryos-0.1.15}/ivoryos/utils/llm_agent.py +0 -0
  50. {ivoryos-0.1.13 → ivoryos-0.1.15}/ivoryos/utils/script_runner.py +0 -0
  51. {ivoryos-0.1.13 → ivoryos-0.1.15}/ivoryos.egg-info/dependency_links.txt +0 -0
  52. {ivoryos-0.1.13 → ivoryos-0.1.15}/ivoryos.egg-info/requires.txt +0 -0
  53. {ivoryos-0.1.13 → ivoryos-0.1.15}/ivoryos.egg-info/top_level.txt +0 -0
  54. {ivoryos-0.1.13 → ivoryos-0.1.15}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ivoryos
3
- Version: 0.1.13
3
+ Version: 0.1.15
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
@@ -10,21 +10,17 @@ from ivoryos.routes.control.control import control
10
10
  from ivoryos.routes.database.database import database
11
11
  from ivoryos.routes.design.design import design, socketio
12
12
  from ivoryos.routes.main.main import main
13
+ from ivoryos.routes.monitor.monitor import monitor
13
14
  from ivoryos.utils import utils
14
15
  from ivoryos.utils.db_models import db
15
16
  from ivoryos.utils.global_config import GlobalConfig
16
17
  from ivoryos.utils.script_runner import ScriptRunner
17
- from ivoryos.version import __version__ as ivoryos_version
18
+ from version import __version__ as ivoryos_version
18
19
 
19
20
  global_config = GlobalConfig()
20
21
 
21
22
  url_prefix = os.getenv('URL_PREFIX', "/ivoryos")
22
23
  app = Flask(__name__, static_url_path=f'{url_prefix}/static', static_folder='static')
23
- app.register_blueprint(main, url_prefix=url_prefix)
24
- app.register_blueprint(auth, url_prefix=url_prefix)
25
- app.register_blueprint(design, url_prefix=url_prefix)
26
- app.register_blueprint(database, url_prefix=url_prefix)
27
- app.register_blueprint(control, url_prefix=url_prefix)
28
24
 
29
25
 
30
26
  def create_app(config_class=None):
@@ -70,6 +66,7 @@ def run(module=None, host="0.0.0.0", port=None, debug=None, llm_server=None, mod
70
66
  config: Config = None,
71
67
  logger: Union[str, list] = None,
72
68
  logger_output_name: str = None,
69
+ enable_design=True, stream_address=None
73
70
  ):
74
71
  """
75
72
  Start ivoryOS app server.
@@ -83,9 +80,29 @@ def run(module=None, host="0.0.0.0", port=None, debug=None, llm_server=None, mod
83
80
  :param config: config class, defaults to None
84
81
  :param logger: logger name of list of logger names, defaults to None
85
82
  :param logger_output_name: log file save name of logger, defaults to None, and will use "default.log"
86
-
83
+ :param enable_design:
84
+ :param stream_address:
87
85
  """
88
86
  app = create_app(config_class=config or get_config()) # Create app instance using factory function
87
+ enable_monitor = stream_address is not None
88
+
89
+ def inject_nav_config():
90
+ """Make NAV_CONFIG available globally to all templates."""
91
+ return dict(
92
+ enable_design=enable_design,
93
+ enable_monitor=enable_monitor,
94
+ )
95
+
96
+ # todo modular page
97
+ app.context_processor(inject_nav_config)
98
+ app.register_blueprint(main, url_prefix=url_prefix)
99
+ app.register_blueprint(auth, url_prefix=url_prefix)
100
+ if enable_design:
101
+ app.register_blueprint(design, url_prefix=url_prefix)
102
+ app.register_blueprint(database, url_prefix=url_prefix)
103
+ if enable_monitor:
104
+ app.register_blueprint(monitor, url_prefix=url_prefix)
105
+ app.register_blueprint(control, url_prefix=url_prefix)
89
106
 
90
107
  port = port or int(os.environ.get("PORT", 8000))
91
108
  debug = debug if debug is not None else app.config.get('DEBUG', True)
@@ -16,6 +16,7 @@
16
16
  {% if function not in hidden_instrument %}
17
17
  <div class="card" id="{{function}}">
18
18
  <div class="bg-white rounded shadow-sm flex-fill">
19
+ <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>
19
20
  <a style="float: right" aria-label="Close" href="{{ url_for('control.hide_function', instrument=instrument, function=function) }}"><i class="bi bi-eye-slash-fill"></i></a>
20
21
  <div class="form-control" style="border: none">
21
22
  <form role="form" method='POST' name="{{function}}" id="{{function}}">
@@ -38,7 +39,9 @@
38
39
  </div>
39
40
  <div class="input-group mb-3">
40
41
  <button type="submit" name="{{ function }}" id="{{ function }}" class="form-control" style="background-color: #a5cece;">{{format_name(function)}} </button>
42
+
41
43
  </div>
44
+
42
45
  </form>
43
46
  </div>
44
47
  </div>
@@ -100,22 +100,25 @@ def experiment_builder(instrument=None):
100
100
 
101
101
  deck_list = utils.available_pseudo_deck(current_app.config["DUMMY_DECK"])
102
102
 
103
- functions = []
103
+ functions = {}
104
104
  if deck:
105
105
  deck_variables = global_config.deck_snapshot.keys()
106
106
  else:
107
107
  deck_variables = list(pseudo_deck.keys()) if pseudo_deck else []
108
108
  deck_variables.remove("deck_name") if len(deck_variables) > 0 else deck_variables
109
- edit_action = session.get("edit_action")
110
- if instrument:
109
+ edit_action_info = session.get("edit_action")
110
+ if edit_action_info:
111
+ forms = create_form_from_action(edit_action_info, script=script)
112
+ elif instrument:
111
113
  if instrument in ['if', 'while', 'variable', 'wait', 'repeat']:
112
- forms = create_builtin_form(instrument, autofill=autofill, script=script)
114
+ forms = create_builtin_form(instrument, script=script)
113
115
  else:
114
116
  if deck:
115
- function_metadata = global_config.deck_snapshot.get(instrument, {})
117
+ functions = global_config.deck_snapshot.get(instrument, {})
116
118
  elif pseudo_deck:
117
- function_metadata = pseudo_deck.get(instrument, {})
118
- functions = {key: data.get('signature', {}) for key, data in function_metadata.items()}
119
+ functions = pseudo_deck.get(instrument, {})
120
+ # print(function_metadata)
121
+ # functions = {key: data.get('signature', {}) for key, data in function_metadata.items()}
119
122
  forms = create_form_from_pseudo(pseudo=functions, autofill=autofill, script=script)
120
123
  if request.method == 'POST' and "hidden_name" in request.form:
121
124
  # all_kwargs = request.form.copy()
@@ -128,21 +131,14 @@ def experiment_builder(instrument=None):
128
131
  function_name = kwargs.pop("hidden_name")
129
132
  save_data = kwargs.pop('return', '')
130
133
 
131
-
132
-
133
134
  primitive_arg_types = utils.get_arg_type(kwargs, functions[function_name])
134
135
 
135
- # print(script.get_added_variables(), script.get_output_variables())
136
- arg_types = {}
137
- # arg_types.update(variable_kwargs_types)
138
- arg_types.update(primitive_arg_types)
139
- # all_kwargs.update(variable_kwargs)
140
- script.eval_list(kwargs, arg_types)
141
- kwargs = script.eval_variables(kwargs)
136
+ script.eval_list(kwargs, primitive_arg_types)
137
+ kwargs = script.validate_variables(kwargs)
142
138
  action = {"instrument": instrument, "action": function_name,
143
- "args": {name: arg for (name, arg) in kwargs.items()},
139
+ "args": kwargs,
144
140
  "return": save_data,
145
- 'arg_types': arg_types}
141
+ 'arg_types': primitive_arg_types}
146
142
  script.add_action(action=action)
147
143
  else:
148
144
  flash(form.errors)
@@ -167,14 +163,13 @@ def experiment_builder(instrument=None):
167
163
  autofill = not autofill
168
164
  forms = create_form_from_pseudo(functions, autofill=autofill, script=script)
169
165
  session['autofill'] = autofill
170
- elif edit_action:
171
- forms = create_form_from_action(edit_action, script=script)
166
+
172
167
  utils.post_script_file(script)
173
168
  design_buttons = create_action_button(script)
174
169
  return render_template('experiment_builder.html', off_line=off_line, instrument=instrument, history=deck_list,
175
170
  script=script, defined_variables=deck_variables,
176
171
  local_variables=global_config.defined_variables,
177
- functions=functions, forms=forms, buttons=design_buttons, format_name=format_name,
172
+ forms=forms, buttons=design_buttons, format_name=format_name,
178
173
  use_llm=enable_llm)
179
174
 
180
175
 
@@ -505,7 +500,7 @@ def edit_action(uuid: str):
505
500
  # print(kwargs)
506
501
  if forms and forms.validate_on_submit():
507
502
  save_as = kwargs.pop('return', '')
508
- kwargs = script.eval_variables(kwargs)
503
+ kwargs = script.validate_variables(kwargs)
509
504
  # try:
510
505
  script.update_by_uuid(uuid=uuid, args=kwargs, output=save_as)
511
506
  # except Exception as e:
@@ -150,12 +150,12 @@
150
150
  {{ format_name(name) }}
151
151
  </button>
152
152
  </h2>
153
+
153
154
  <div id="{{name}}" class="accordion-collapse collapse" data-bs-parent="#accordionActions">
154
155
  <div class="accordion-body">
155
156
  <form role="form" method='POST' name="add" id="add">
156
157
  <div class="form-group">
157
158
  {{ form.hidden_tag() }}
158
- {# {{ form.hidden_name() }}#}
159
159
  {% for field in form %}
160
160
  {% if field.type not in ['CSRFTokenField', 'HiddenField'] %}
161
161
  <div class="input-group mb-3">
@@ -172,6 +172,8 @@
172
172
  {% endfor %}
173
173
  </div>
174
174
  <button type="submit" class="btn btn-dark">Add </button>
175
+ <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>
176
+
175
177
  </form>
176
178
 
177
179
 
@@ -1,6 +1,6 @@
1
1
  from flask import Blueprint, render_template, current_app
2
2
  from flask_login import login_required
3
- from ivoryos.version import __version__ as ivoryos_version
3
+ from version import __version__ as ivoryos_version
4
4
 
5
5
  main = Blueprint('main', __name__, template_folder='templates/main')
6
6
 
@@ -9,32 +9,32 @@
9
9
  </h1>
10
10
  <p>Version: {{ version }}</p>
11
11
  <div class="row">
12
-
13
- <div class="col-lg-6 mb-4 d-flex align-items-stretch">
14
- <div class="card rounded shadow-sm flex-fill">
15
- <div class="card-body">
16
- <h5 class="card-title">Browse designs</h5>
17
- <p class="card-text">Browse all workflows saved in the database.</p>
18
- <a href="{{ url_for('database.load_from_database') }}" class="stretched-link"></a>
12
+ {% if enable_design %}
13
+ <div class="col-lg-6 mb-4 d-flex align-items-stretch">
14
+ <div class="card rounded shadow-sm flex-fill">
15
+ <div class="card-body">
16
+ <h5 class="card-title">Browse designs</h5>
17
+ <p class="card-text">Browse all workflows saved in the database.</p>
18
+ <a href="{{ url_for('database.load_from_database') }}" class="stretched-link"></a>
19
+ </div>
19
20
  </div>
20
21
  </div>
21
- </div>
22
- <div class="col-lg-6 mb-4 d-flex align-items-stretch">
23
- <div class="card rounded shadow-sm flex-fill">
24
- <div class="card-body">
25
- <h5 class="card-title">Edit designs</h5>
26
- <p class="card-text">Build your workflow from current deck functions.</p>
27
- <a href="{{ url_for('design.experiment_builder') }}" class="stretched-link"></a>
22
+ <div class="col-lg-6 mb-4 d-flex align-items-stretch">
23
+ <div class="card rounded shadow-sm flex-fill">
24
+ <div class="card-body">
25
+ <h5 class="card-title">Edit designs</h5>
26
+ <p class="card-text">Build your workflow from current deck functions.</p>
27
+ <a href="{{ url_for('design.experiment_builder') }}" class="stretched-link"></a>
28
+ </div>
28
29
  </div>
29
30
  </div>
30
- </div>
31
+ {% endif %}
31
32
  </div>
32
33
 
33
34
  <br><br><br>
34
35
  {% if not off_line %}
35
- {# <h5>Only available in online mode</h5>#}
36
- {# <hr>#}
37
36
  <div class="row">
37
+ {% if enable_design %}
38
38
  <div class="col-lg-6 mb-4 d-flex align-items-stretch">
39
39
  <div class="card rounded shadow-sm flex-fill">
40
40
  <div class="card-body">
@@ -44,6 +44,8 @@
44
44
  </div>
45
45
  </div>
46
46
  </div>
47
+ {% endif %}
48
+
47
49
  <div class="col-lg-6 mb-4 d-flex align-items-stretch">
48
50
  <div class="card rounded shadow-sm flex-fill">
49
51
  <div class="card-body">
@@ -0,0 +1,21 @@
1
+ from flask import Blueprint, flash, render_template
2
+ from flask_login import login_required
3
+
4
+ from ivoryos.utils.global_config import GlobalConfig
5
+
6
+ global_config = GlobalConfig()
7
+
8
+ monitor = Blueprint('monitor', __name__, template_folder='templates/monitor')
9
+
10
+
11
+ @monitor.route("/stream_feed")
12
+ @login_required
13
+ def stream_feed():
14
+ """
15
+ .. :quickref: Monitor; stream feed
16
+
17
+ display feed by ip address
18
+ e.g. HeinSight output feed
19
+ .. http:get:: /my_deck
20
+ """
21
+ return render_template('monitor.html', stream_address="http://127.0.0.1:8001/frame")
@@ -0,0 +1,24 @@
1
+ {% extends 'base.html' %}
2
+ {% block title %}IvoryOS | Monitor {% endblock %}
3
+
4
+ {% block body %}
5
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.4.1/socket.io.js"></script>
6
+
7
+ <h1>Live Stream</h1>
8
+ <img src="{{ stream_address }}" width="800" height="600">
9
+
10
+ {% if no_deck_warning and not dismiss %}
11
+ {# auto pop import when there is no deck#}
12
+ <script type="text/javascript">
13
+ function OpenBootstrapPopup() {
14
+ $("#importModal").modal('show');
15
+ }
16
+ window.onload = function () {
17
+ OpenBootstrapPopup();
18
+ };
19
+ </script>
20
+ {% endif %}
21
+
22
+ <script src="{{ url_for('static', filename='js/socket_handler.js') }}"></script>
23
+
24
+ {% endblock %}
@@ -23,7 +23,7 @@
23
23
  <body>
24
24
  <nav class="navbar navbar-expand-lg navbar-light bg-light fixed-top">
25
25
  <div class= "container">
26
-
26
+ {# {{ module_config }}#}
27
27
  <a class="navbar-brand" href="{{ url_for('main.index') }}">
28
28
  <img src="{{url_for('static', filename='logo.webp')}}" alt="Logo" height="60" class="d-inline-block align-text-bottom">
29
29
  </a>
@@ -36,15 +36,22 @@
36
36
  <li class="nav-item">
37
37
  <a class="nav-link" href="{{ url_for('main.index') }}" aria-current="page">Home</a>
38
38
  </li>
39
- <li class="nav-item">
40
- <a class="nav-link" href="{{ url_for('database.load_from_database') }}" aria-current="page">Library</a>
41
- </li>
42
- <li class="nav-item">
43
- <a class="nav-link" href="{{ url_for('design.experiment_builder') }}">Design</a>
44
- </li>
45
- <li class="nav-item">
46
- <a class="nav-link" href="{{ url_for('design.experiment_run') }}">Compile/Run</a>
47
- </li>
39
+ {% if enable_design %}
40
+ <li class="nav-item">
41
+ <a class="nav-link" href="{{ url_for('database.load_from_database') }}" aria-current="page">Library</a>
42
+ </li>
43
+ <li class="nav-item">
44
+ <a class="nav-link" href="{{ url_for('design.experiment_builder') }}">Design</a>
45
+ </li>
46
+ <li class="nav-item">
47
+ <a class="nav-link" href="{{ url_for('design.experiment_run') }}">Compile/Run</a>
48
+ </li>
49
+ {% endif %}
50
+ {% if enable_monitor %}
51
+ <li class="nav-item">
52
+ <a class="nav-link" href="{{ url_for('monitor.stream_feed') }}">Monitor</a></li>
53
+ </li>
54
+ {% endif %}
48
55
  <li class="nav-item">
49
56
  <a class="nav-link" href="{{ url_for('control.deck_controllers') }}">Devices</a></li>
50
57
  </li>
@@ -32,16 +32,6 @@ class User(db.Model, UserMixin):
32
32
  return self.username
33
33
 
34
34
 
35
- class Variable:
36
- def __init__(self, name: str = None, data_type=str, value=None):
37
- self.name = name
38
- self.data_type = data_type
39
- self.value = value
40
-
41
- def __str__(self):
42
- return self.name
43
-
44
-
45
35
  class Script(db.Model):
46
36
  __tablename__ = 'script'
47
37
  # id = db.Column(db.Integer, primary_key=True)
@@ -128,20 +118,8 @@ class Script(db.Model):
128
118
  if type(action['args']) is dict:
129
119
  # pass
130
120
  self.eval_list(args, arg_types)
131
-
132
121
  else:
133
122
  pass
134
- # """handle"""
135
- # args = list(args.values())[0]
136
- # if not args.startswith("#"):
137
- # if args in bool_dict.keys():
138
- # args = bool_dict[args]
139
- #
140
- # else:
141
- # if 'arg_types' in action:
142
- # arg_types = action['arg_types']
143
- # self._convert_type(args, arg_types)
144
-
145
123
  action['args'] = args
146
124
  action['return'] = output
147
125
 
@@ -186,14 +164,6 @@ class Script(db.Model):
186
164
  def currently_editing_order(self, script):
187
165
  self.id_order[self.editing_type] = script
188
166
 
189
- # @property
190
- # def editing_type(self):
191
- # return self.editing_type
192
-
193
- # @editing_type.setter
194
- # def editing_type(self, change_type):
195
- # self.editing_type = change_type
196
-
197
167
  def update_time_stamp(self):
198
168
  self.last_modified = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
199
169
 
@@ -236,6 +206,7 @@ class Script(db.Model):
236
206
  self.update_time_stamp()
237
207
 
238
208
  def add_variable(self, statement, variable, type):
209
+ variable = self.validate_function_name(variable)
239
210
  convert_type = getattr(builtins, type)
240
211
  statement = convert_type(statement)
241
212
  current_len = len(self.currently_editing_script)
@@ -266,17 +237,20 @@ class Script(db.Model):
266
237
 
267
238
  return output_variables
268
239
 
269
- def eval_variables(self, kwargs):
240
+ def validate_variables(self, kwargs):
241
+ """
242
+ Validates the kwargs passed to the Script
243
+ """
270
244
  output_variables: Dict[str, str] = self.get_variables()
271
245
  # print(output_variables)
272
246
  for key, value in kwargs.items():
273
247
  if type(value) is str and value in output_variables:
274
248
  var_type = output_variables[value]
275
- kwargs[key] = {value:var_type}
276
-
249
+ kwargs[key] = {value: var_type}
250
+ if isinstance(value, str) and value.startswith("#"):
251
+ kwargs[key] = f"#{self.validate_function_name(value[1:])}"
277
252
  return kwargs
278
253
 
279
-
280
254
  def add_logic_action(self, logic_type: str, statement):
281
255
  current_len = len(self.currently_editing_script)
282
256
  uid = uuid.uuid4().fields[-1]
@@ -294,7 +268,8 @@ class Script(db.Model):
294
268
  "while":
295
269
  [
296
270
  {"id": current_len + 1, "instrument": 'while', "action": 'while',
297
- "args": {"statement": 'False' if statement == '' else statement}, "return": '', "uuid": uid, "arg_types": {"statement": ''}},
271
+ "args": {"statement": 'False' if statement == '' else statement}, "return": '', "uuid": uid,
272
+ "arg_types": {"statement": ''}},
298
273
  {"id": current_len + 2, "instrument": 'while', "action": 'endwhile', "args": {}, "return": '',
299
274
  "uuid": uid},
300
275
  ],
@@ -308,7 +283,8 @@ class Script(db.Model):
308
283
  "repeat":
309
284
  [
310
285
  {"id": current_len + 1, "instrument": 'repeat', "action": "repeat",
311
- "args": {"statement": 1 if statement == '' else statement}, "return": '', "uuid": uid, "arg_types": {"statement": "int"}},
286
+ "args": {"statement": 1 if statement == '' else statement}, "return": '', "uuid": uid,
287
+ "arg_types": {"statement": "int"}},
312
288
  {"id": current_len + 2, "instrument": 'repeat', "action": 'endrepeat',
313
289
  "args": {}, "return": '', "uuid": uid},
314
290
  ],
@@ -319,7 +295,9 @@ class Script(db.Model):
319
295
  self.update_time_stamp()
320
296
 
321
297
  def delete_action(self, id: int):
322
-
298
+ """
299
+ Delete the action by id (step number)
300
+ """
323
301
  uid = next((action['uuid'] for action in self.currently_editing_script if action['id'] == int(id)), None)
324
302
  id_to_be_removed = [action['id'] for action in self.currently_editing_script if action['uuid'] == uid]
325
303
  order = self.currently_editing_order
@@ -330,6 +308,9 @@ class Script(db.Model):
330
308
  self.update_time_stamp()
331
309
 
332
310
  def duplicate_action(self, id: int):
311
+ """
312
+ duplicate action by id (step number), available only for non logic actions
313
+ """
333
314
  action_to_duplicate = next((action for action in self.currently_editing_script if action['id'] == int(id)),
334
315
  None)
335
316
  insert_id = action_to_duplicate.get("id")
@@ -392,15 +373,18 @@ class Script(db.Model):
392
373
  return output_str, return_list
393
374
 
394
375
  def finalize(self):
376
+ """finalize script, disable editing"""
395
377
  self.status = "finalized"
396
378
  self.update_time_stamp()
397
379
 
398
380
  def save_as(self, name):
381
+ """resave script, enable editing"""
399
382
  self.name = name
400
383
  self.status = "editing"
401
384
  self.update_time_stamp()
402
385
 
403
386
  def indent(self, unit=0):
387
+ """helper: create _ unit of indent in code string"""
404
388
  string = "\n"
405
389
  for _ in range(unit):
406
390
  string += "\t"
@@ -489,7 +473,6 @@ class Script(db.Model):
489
473
  return f"{self.indent(indent_unit)}time.sleep({statement})", indent_unit
490
474
  elif instrument == 'repeat':
491
475
  return self._process_repeat(indent_unit, action_name, statement, next_action)
492
-
493
476
  else:
494
477
  return self._process_instrument_action(indent_unit, instrument, action_name, args, save_data)
495
478
 
@@ -578,7 +561,7 @@ class Script(db.Model):
578
561
  if isinstance(args[arg], str) and args[arg].startswith("#"):
579
562
  args_str = args_str.replace(f"'#{args[arg][1:]}'", args[arg][1:])
580
563
  elif isinstance(args[arg], dict):
581
- print(args[arg])
564
+ # print(args[arg])
582
565
  variables = self.get_variables()
583
566
  value = next(iter(args[arg]))
584
567
  if value not in variables:
@@ -7,24 +7,17 @@ from flask_wtf import FlaskForm
7
7
  from wtforms import StringField, FloatField, HiddenField, BooleanField, IntegerField
8
8
  import inspect
9
9
 
10
- from ivoryos.utils.db_models import Variable, Script
11
-
12
10
 
13
11
  def find_variable(data, script):
14
- # TODO: needs to check for valid order of variables, important when editting
15
- # variable name: variable type
16
- added_variables: dict[str, str] = script.get_added_variables()
17
- output_variables: dict[str, str] = script.get_output_variables()
18
- # [action for action in script.currently_editing_script if
19
- # action["instrument"] == "variable"
20
- # # or action["return"] # TODO find returns
21
- # ]
22
- added_variables.update(output_variables)
23
- for variable_name, variable_type in added_variables.items():
12
+ """
13
+ find user defined variables and return values in the script:Script
14
+ :param data: string of input variable name
15
+ :param script:Script object
16
+ """
17
+ variables: dict[str, str] = script.get_variables()
18
+ for variable_name, variable_type in variables.items():
24
19
  if variable_name == data:
25
20
  return data, variable_type # variable_type int float str or "function_output"
26
- # if added_variable["return"] == data:
27
- # return data, None
28
21
  return None, None
29
22
 
30
23
 
@@ -45,7 +38,7 @@ class VariableOrStringField(Field):
45
38
  if self.script:
46
39
  variable, variable_type = find_variable(self.data, self.script)
47
40
  if variable:
48
- return Variable(variable, variable_type)
41
+ return variable
49
42
 
50
43
  return str(self.data) if self.data is not None else ""
51
44
 
@@ -61,7 +54,7 @@ class VariableOrFloatField(Field):
61
54
  if self.script:
62
55
  variable, variable_type = find_variable(self.data, self.script)
63
56
  if variable:
64
- return Variable(variable, variable_type)
57
+ return variable
65
58
 
66
59
  if self.raw_data:
67
60
  return self.raw_data[0]
@@ -109,7 +102,7 @@ class VariableOrIntField(Field):
109
102
  if self.script:
110
103
  variable, variable_type = find_variable(self.data, self.script)
111
104
  if variable:
112
- return Variable(variable, variable_type)
105
+ return variable
113
106
 
114
107
  if self.raw_data:
115
108
  return self.raw_data[0]
@@ -158,7 +151,7 @@ class VariableOrBoolField(BooleanField):
158
151
  if variable:
159
152
  if not variable_type == "function_output":
160
153
  raise ValueError("Not accepting boolean variables")
161
- return Variable(variable, variable_type)
154
+ return variable
162
155
 
163
156
  self.data = bool(value)
164
157
 
@@ -183,7 +176,7 @@ class VariableOrBoolField(BooleanField):
183
176
  if self.script:
184
177
  variable, variable_type = find_variable(self.raw_data, self.script)
185
178
  if variable:
186
- return Variable(variable, variable_type)
179
+ return variable
187
180
 
188
181
  if self.raw_data:
189
182
  return str(self.raw_data[0])
@@ -197,7 +190,15 @@ def format_name(name):
197
190
  return text.capitalize()
198
191
 
199
192
 
200
- def create_form_for_method(method, method_name, autofill, script=None, design=True):
193
+ def create_form_for_method(method, autofill, script=None, design=True):
194
+ """
195
+ Create forms for each method or signature
196
+ :param method: dict(docstring, signature)
197
+ :param autofill:bool if autofill is enabled
198
+ :param script:Script object
199
+ :param design: if design is enabled
200
+ """
201
+
201
202
  class DynamicForm(FlaskForm):
202
203
  pass
203
204
 
@@ -233,32 +234,60 @@ def create_form_for_method(method, method_name, autofill, script=None, design=Tr
233
234
  return DynamicForm
234
235
 
235
236
 
236
- # Create forms for each method in DummySDLDeck
237
- def create_add_form(attr, attr_name, autofill, script=None, design=True):
238
- dynamic_form = create_form_for_method(attr, attr_name, autofill, script, design)
237
+ def create_add_form(attr, attr_name, autofill: bool, script=None, design: bool = True):
238
+ """
239
+ Create forms for each method or signature
240
+ :param attr: dict(docstring, signature)
241
+ :param attr_name: method name
242
+ :param autofill:bool if autofill is enabled
243
+ :param script:Script object
244
+ :param design: if design is enabled. Design allows string input for parameter names ("#param") for all fields
245
+ """
246
+ signature = attr.get('signature', {})
247
+ docstring = attr.get('docstring', "")
248
+ dynamic_form = create_form_for_method(signature, autofill, script, design)
239
249
  if design:
240
250
  return_value = StringField(label='Save value as', render_kw={"placeholder": "Optional"})
241
251
  setattr(dynamic_form, 'return', return_value)
242
- hidden_method_name = HiddenField(name=f'hidden_name', render_kw={"value": f'{attr_name}'})
252
+ hidden_method_name = HiddenField(name=f'hidden_name', description=docstring, render_kw={"value": f'{attr_name}'})
243
253
  setattr(dynamic_form, 'hidden_name', hidden_method_name)
244
254
  return dynamic_form
245
255
 
246
256
 
247
- def create_form_from_module(sdl_module, autofill: bool, script=None, design=True):
248
- # sdl_deck = DummySDLDeck(DummyPump("COM1"), DummyBalance("COM2"))
257
+ def create_form_from_module(sdl_module, autofill: bool = False, script=None, design: bool = False):
258
+ """
259
+ Create forms for each method, used for control routes
260
+ :param sdl_module: method module
261
+ :param autofill:bool if autofill is enabled
262
+ :param script:Script object
263
+ :param design: if design is enabled
264
+ """
249
265
  method_forms = {}
250
266
  for attr_name in dir(sdl_module):
251
- attr = getattr(sdl_module, attr_name)
252
- if inspect.ismethod(attr) and not attr_name.startswith('_'):
267
+ method = getattr(sdl_module, attr_name)
268
+ if inspect.ismethod(method) and not attr_name.startswith('_'):
269
+ signature = inspect.signature(method)
270
+ docstring = inspect.getdoc(method)
271
+ attr = dict(signature=signature, docstring=docstring)
253
272
  form_class = create_add_form(attr, attr_name, autofill, script, design)
254
273
  method_forms[attr_name] = form_class()
255
274
  return method_forms
256
275
 
257
276
 
258
277
  def create_form_from_pseudo(pseudo: dict, autofill: bool, script=None, design=True):
259
- '''{'dose_liquid': < Signature(amount_in_ml: float, rate_ml_per_minute: float) >}'''
278
+ """
279
+ Create forms for pseudo method, used for design routes
280
+ :param pseudo:{'dose_liquid': {
281
+ "docstring": "some docstring",
282
+ "signature": Signature(amount_in_ml: float, rate_ml_per_minute: float) }
283
+ }
284
+ :param autofill:bool if autofill is enabled
285
+ :param script:Script object
286
+ :param design: if design is enabled
287
+ """
260
288
  method_forms = {}
261
289
  for attr_name, signature in pseudo.items():
290
+ # signature = info.get('signature', {})
262
291
  form_class = create_add_form(signature, attr_name, autofill, script, design)
263
292
  method_forms[attr_name] = form_class()
264
293
  return method_forms
@@ -266,15 +295,18 @@ def create_form_from_pseudo(pseudo: dict, autofill: bool, script=None, design=Tr
266
295
 
267
296
  def create_form_from_action(action: dict, script=None, design=True):
268
297
  '''
269
- {'action': 'dose_solid', 'arg_types': {'amount_in_mg': 'float', 'bring_in': 'bool'}, 'args': {'amount_in_mg':
270
- 5.0, 'bring_in': False}, 'id': 9, 'instrument': 'deck.sdl', 'return': '', 'uuid': 266929188668995}
298
+ Create forms for single action, used for design routes
299
+ :param action: {'action': 'dose_solid', 'arg_types': {'amount_in_mg': 'float', 'bring_in': 'bool'},
300
+ 'args': {'amount_in_mg': 5.0, 'bring_in': False}, 'id': 9,
301
+ 'instrument': 'deck.sdl', 'return': '', 'uuid': 266929188668995}
302
+ :param script:Script object
303
+ :param design: if design is enabled
304
+
271
305
  '''
272
- # print(action)
273
306
 
274
307
  arg_types = action.get("arg_types", {})
275
308
  args = action.get("args", {})
276
309
  save_as = action.get("return")
277
- action = action.get("action")
278
310
 
279
311
  class DynamicForm(FlaskForm):
280
312
  pass
@@ -314,7 +346,10 @@ def create_form_from_action(action: dict, script=None, design=True):
314
346
  return DynamicForm()
315
347
 
316
348
 
317
- def create_builtin_form(logic_type, autofill, script):
349
+ def create_builtin_form(logic_type, script):
350
+ """
351
+ Create a builtin form {if, while, variable, repeat, wait}
352
+ """
318
353
  class BuiltinFunctionForm(FlaskForm):
319
354
  pass
320
355
 
@@ -355,12 +390,22 @@ def create_builtin_form(logic_type, autofill, script):
355
390
 
356
391
 
357
392
  def create_action_button(script, stype=None):
393
+ """
394
+ Creates action buttons for design route (design canvas)
395
+ :param script: Script object
396
+ :param stype: script type (script, prep, cleanup)
397
+ """
358
398
  stype = stype or script.editing_type
359
399
  variables = script.get_variables()
360
400
  return [_action_button(i, variables) for i in script.get_script(stype)]
361
401
 
362
402
 
363
403
  def _action_button(action: dict, variables: dict):
404
+ """
405
+ Creates action button for one action
406
+ :param action: Action dict
407
+ :param variables: created variable dict
408
+ """
364
409
  style = {
365
410
  "repeat": "background-color: lightsteelblue",
366
411
  "if": "background-color: salmon",
@@ -368,7 +413,7 @@ def _action_button(action: dict, variables: dict):
368
413
  }.get(action['instrument'], "")
369
414
 
370
415
  if action['instrument'] in ['if', 'while', 'repeat']:
371
- text = f"{action['action']} {action['args']}"
416
+ text = f"{action['action']} {action['args'].get('statement', '')}"
372
417
  elif action['instrument'] == 'variable':
373
418
  text = f"{action['action']} = {action['args'].get('statement')}"
374
419
  else:
@@ -382,18 +427,13 @@ def _action_button(action: dict, variables: dict):
382
427
  for k, v in action['args'].items():
383
428
  if isinstance(v, dict):
384
429
  value = next(iter(v)) # Extract the first key if it's a dict
385
-
430
+ # show warning color for variable calling when there is no definition
386
431
  style = "background-color: khaki" if value not in variables.keys() else ""
387
-
388
432
  else:
389
433
  value = v # Keep the original value if not a dict
390
-
391
434
  arg_list.append(f"{k} = {value}") # Format the key-value pair
392
-
393
435
  arg_string = "(" + ", ".join(arg_list) + ")"
394
436
  else:
395
437
  arg_string = f"= {action['args']}"
396
-
397
438
  text = f"{prefix}{action_text} {arg_string}"
398
-
399
439
  return dict(label=text, style=style, uuid=action["uuid"], id=action["id"], instrument=action['instrument'])
@@ -7,7 +7,6 @@ import pickle
7
7
  import subprocess
8
8
  import sys
9
9
  from collections import Counter
10
- from typing import Optional, Dict, Tuple
11
10
 
12
11
  from flask import session
13
12
  from flask_socketio import SocketIO
@@ -102,20 +101,9 @@ def _inspect_class(class_object=None, debug=False):
102
101
  if not function.startswith(under_score) and not function.isupper():
103
102
  try:
104
103
  annotation = inspect.signature(method)
105
- # if doc_string:
106
104
  docstring = inspect.getdoc(method)
107
105
  functions[function] = dict(signature=annotation, docstring=docstring)
108
106
 
109
- # handle getter setters todo
110
- # if callable(att):
111
- # functions[function] = inspect.signature(att)
112
- # else:
113
- # att = getattr(class_object.__class__, function)
114
- # if isinstance(att, property) and att.fset is not None:
115
- # setter = att.fset.__annotations__
116
- # setter.pop('return', None)
117
- # if setter:
118
- # functions[function] = setter
119
107
  except Exception:
120
108
  pass
121
109
  return functions
@@ -146,23 +134,6 @@ def _get_type_from_parameters(arg, parameters):
146
134
  arg_type = annotation.__name__
147
135
  return arg_type
148
136
 
149
- # # moved to script
150
- # def find_variable_in_script(script: Script, args: Dict[str, str]) -> Optional[Tuple[Dict[str, str], Dict[str, str]]]:
151
- # # TODO: need to search for if the variable exists
152
- # added_variables: list[Dict[str, str]] = [action for action in script.currently_editing_script if
153
- # action["instrument"] == "variable"]
154
- #
155
- # possible_variable_arguments = {}
156
- # possible_variable_types = {}
157
- #
158
- # for arg_name, arg_val in args.items():
159
- # for added_variable in added_variables:
160
- # if added_variable["action"] == arg_val:
161
- # possible_variable_arguments[arg_name] = added_variable["action"]
162
- # possible_variable_types[arg_name] = "variable"
163
- #
164
- # return possible_variable_arguments, possible_variable_types
165
-
166
137
 
167
138
  def _convert_by_str(args, arg_types):
168
139
  """
@@ -203,9 +174,6 @@ def convert_config_type(args, arg_types, is_class: bool = False):
203
174
  """
204
175
  Converts an argument from str to an arg type
205
176
  """
206
- bool_dict = {"True": True, "False": False}
207
- # print(args, arg_types)
208
- # print(globals())
209
177
  if args:
210
178
  for arg in args:
211
179
  if arg not in arg_types.keys():
@@ -340,8 +308,10 @@ def ax_initiation(data, arg_types):
340
308
 
341
309
 
342
310
  def get_arg_type(args, parameters):
311
+ """get argument type from signature"""
343
312
  arg_types = {}
344
313
  # print(args, parameters)
314
+ parameters = parameters.get("signature")
345
315
  if args:
346
316
  for arg in args:
347
317
  arg_types[arg] = _get_type_from_parameters(arg, parameters)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ivoryos
3
- Version: 0.1.13
3
+ Version: 0.1.15
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
@@ -4,7 +4,6 @@ README.md
4
4
  setup.py
5
5
  ivoryos/__init__.py
6
6
  ivoryos/config.py
7
- ivoryos/version.py
8
7
  ivoryos.egg-info/PKG-INFO
9
8
  ivoryos.egg-info/SOURCES.txt
10
9
  ivoryos.egg-info/dependency_links.txt
@@ -31,6 +30,8 @@ ivoryos/routes/main/__init__.py
31
30
  ivoryos/routes/main/main.py
32
31
  ivoryos/routes/main/templates/main/help.html
33
32
  ivoryos/routes/main/templates/main/home.html
33
+ ivoryos/routes/monitor/monitor.py
34
+ ivoryos/routes/monitor/templates/monitor/monitor.html
34
35
  ivoryos/static/favicon.ico
35
36
  ivoryos/static/logo.webp
36
37
  ivoryos/static/style.css
@@ -1,5 +1,5 @@
1
1
  from setuptools import setup, find_packages
2
- from ivoryos.version import __version__ as ivoryos_version
2
+ from version import __version__ as ivoryos_version
3
3
 
4
4
  setup(
5
5
  name='ivoryos',
@@ -1 +0,0 @@
1
- __version__ = "0.1.13"
File without changes
File without changes
File without changes
File without changes
File without changes