ivoryos 0.1.11__tar.gz → 0.1.13__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 (53) hide show
  1. {ivoryos-0.1.11/ivoryos.egg-info → ivoryos-0.1.13}/PKG-INFO +1 -1
  2. {ivoryos-0.1.11 → ivoryos-0.1.13}/ivoryos/__init__.py +5 -5
  3. {ivoryos-0.1.11 → ivoryos-0.1.13}/ivoryos/routes/control/control.py +1 -1
  4. {ivoryos-0.1.11 → ivoryos-0.1.13}/ivoryos/routes/design/design.py +42 -29
  5. {ivoryos-0.1.11 → ivoryos-0.1.13}/ivoryos/routes/design/templates/design/experiment_builder.html +24 -13
  6. {ivoryos-0.1.11 → ivoryos-0.1.13}/ivoryos/utils/db_models.py +152 -48
  7. {ivoryos-0.1.11 → ivoryos-0.1.13}/ivoryos/utils/form.py +154 -71
  8. {ivoryos-0.1.11 → ivoryos-0.1.13}/ivoryos/utils/utils.py +38 -25
  9. ivoryos-0.1.13/ivoryos/version.py +1 -0
  10. {ivoryos-0.1.11 → ivoryos-0.1.13/ivoryos.egg-info}/PKG-INFO +1 -1
  11. ivoryos-0.1.11/ivoryos/version.py +0 -1
  12. {ivoryos-0.1.11 → ivoryos-0.1.13}/LICENSE +0 -0
  13. {ivoryos-0.1.11 → ivoryos-0.1.13}/MANIFEST.in +0 -0
  14. {ivoryos-0.1.11 → ivoryos-0.1.13}/README.md +0 -0
  15. {ivoryos-0.1.11 → ivoryos-0.1.13}/ivoryos/config.py +0 -0
  16. {ivoryos-0.1.11 → ivoryos-0.1.13}/ivoryos/routes/__init__.py +0 -0
  17. {ivoryos-0.1.11 → ivoryos-0.1.13}/ivoryos/routes/auth/__init__.py +0 -0
  18. {ivoryos-0.1.11 → ivoryos-0.1.13}/ivoryos/routes/auth/auth.py +0 -0
  19. {ivoryos-0.1.11 → ivoryos-0.1.13}/ivoryos/routes/auth/templates/auth/login.html +0 -0
  20. {ivoryos-0.1.11 → ivoryos-0.1.13}/ivoryos/routes/auth/templates/auth/signup.html +0 -0
  21. {ivoryos-0.1.11 → ivoryos-0.1.13}/ivoryos/routes/control/__init__.py +0 -0
  22. {ivoryos-0.1.11 → ivoryos-0.1.13}/ivoryos/routes/control/templates/control/controllers.html +0 -0
  23. {ivoryos-0.1.11 → ivoryos-0.1.13}/ivoryos/routes/control/templates/control/controllers_home.html +0 -0
  24. {ivoryos-0.1.11 → ivoryos-0.1.13}/ivoryos/routes/control/templates/control/controllers_new.html +0 -0
  25. {ivoryos-0.1.11 → ivoryos-0.1.13}/ivoryos/routes/database/__init__.py +0 -0
  26. {ivoryos-0.1.11 → ivoryos-0.1.13}/ivoryos/routes/database/database.py +0 -0
  27. {ivoryos-0.1.11 → ivoryos-0.1.13}/ivoryos/routes/database/templates/database/experiment_database.html +0 -0
  28. {ivoryos-0.1.11 → ivoryos-0.1.13}/ivoryos/routes/design/__init__.py +0 -0
  29. {ivoryos-0.1.11 → ivoryos-0.1.13}/ivoryos/routes/design/templates/design/experiment_run.html +0 -0
  30. {ivoryos-0.1.11 → ivoryos-0.1.13}/ivoryos/routes/main/__init__.py +0 -0
  31. {ivoryos-0.1.11 → ivoryos-0.1.13}/ivoryos/routes/main/main.py +0 -0
  32. {ivoryos-0.1.11 → ivoryos-0.1.13}/ivoryos/routes/main/templates/main/help.html +0 -0
  33. {ivoryos-0.1.11 → ivoryos-0.1.13}/ivoryos/routes/main/templates/main/home.html +0 -0
  34. {ivoryos-0.1.11 → ivoryos-0.1.13}/ivoryos/static/favicon.ico +0 -0
  35. {ivoryos-0.1.11 → ivoryos-0.1.13}/ivoryos/static/gui_annotation/Slide1.png +0 -0
  36. {ivoryos-0.1.11 → ivoryos-0.1.13}/ivoryos/static/gui_annotation/Slide2.PNG +0 -0
  37. {ivoryos-0.1.11 → ivoryos-0.1.13}/ivoryos/static/js/overlay.js +0 -0
  38. {ivoryos-0.1.11 → ivoryos-0.1.13}/ivoryos/static/js/socket_handler.js +0 -0
  39. {ivoryos-0.1.11 → ivoryos-0.1.13}/ivoryos/static/js/sortable_card.js +0 -0
  40. {ivoryos-0.1.11 → ivoryos-0.1.13}/ivoryos/static/js/sortable_design.js +0 -0
  41. {ivoryos-0.1.11 → ivoryos-0.1.13}/ivoryos/static/logo.webp +0 -0
  42. {ivoryos-0.1.11 → ivoryos-0.1.13}/ivoryos/static/style.css +0 -0
  43. {ivoryos-0.1.11 → ivoryos-0.1.13}/ivoryos/templates/base.html +0 -0
  44. {ivoryos-0.1.11 → ivoryos-0.1.13}/ivoryos/utils/__init__.py +0 -0
  45. {ivoryos-0.1.11 → ivoryos-0.1.13}/ivoryos/utils/global_config.py +0 -0
  46. {ivoryos-0.1.11 → ivoryos-0.1.13}/ivoryos/utils/llm_agent.py +0 -0
  47. {ivoryos-0.1.11 → ivoryos-0.1.13}/ivoryos/utils/script_runner.py +0 -0
  48. {ivoryos-0.1.11 → ivoryos-0.1.13}/ivoryos.egg-info/SOURCES.txt +0 -0
  49. {ivoryos-0.1.11 → ivoryos-0.1.13}/ivoryos.egg-info/dependency_links.txt +0 -0
  50. {ivoryos-0.1.11 → ivoryos-0.1.13}/ivoryos.egg-info/requires.txt +0 -0
  51. {ivoryos-0.1.11 → ivoryos-0.1.13}/ivoryos.egg-info/top_level.txt +0 -0
  52. {ivoryos-0.1.11 → ivoryos-0.1.13}/setup.cfg +0 -0
  53. {ivoryos-0.1.11 → ivoryos-0.1.13}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ivoryos
3
- Version: 0.1.11
3
+ Version: 0.1.13
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
@@ -18,7 +18,6 @@ from ivoryos.version import __version__ as ivoryos_version
18
18
 
19
19
  global_config = GlobalConfig()
20
20
 
21
-
22
21
  url_prefix = os.getenv('URL_PREFIX', "/ivoryos")
23
22
  app = Flask(__name__, static_url_path=f'{url_prefix}/static', static_folder='static')
24
23
  app.register_blueprint(main, url_prefix=url_prefix)
@@ -27,6 +26,7 @@ app.register_blueprint(design, url_prefix=url_prefix)
27
26
  app.register_blueprint(database, url_prefix=url_prefix)
28
27
  app.register_blueprint(control, url_prefix=url_prefix)
29
28
 
29
+
30
30
  def create_app(config_class=None):
31
31
  # url_prefix = os.getenv('URL_PREFIX', "/ivoryos")
32
32
  # app = Flask(__name__, static_url_path=f'{url_prefix}/static', static_folder='static')
@@ -59,8 +59,6 @@ def create_app(config_class=None):
59
59
  g.logger = logger
60
60
  g.socketio = socketio
61
61
 
62
-
63
-
64
62
  @app.route('/')
65
63
  def redirect_to_prefix():
66
64
  return redirect(url_for('main.index', version=ivoryos_version)) # Assuming 'index' is a route in your blueprint
@@ -93,14 +91,15 @@ def run(module=None, host="0.0.0.0", port=None, debug=None, llm_server=None, mod
93
91
  debug = debug if debug is not None else app.config.get('DEBUG', True)
94
92
 
95
93
  app.config["LOGGERS"] = logger
96
- app.config["LOGGERS_PATH"] = logger_output_name or app.config["LOGGERS_PATH"] # default.log
94
+ app.config["LOGGERS_PATH"] = logger_output_name or app.config["LOGGERS_PATH"] # default.log
97
95
  logger_path = os.path.join(app.config["OUTPUT_FOLDER"], app.config["LOGGERS_PATH"])
98
96
 
99
97
  if module:
100
98
  app.config["MODULE"] = module
101
99
  app.config["OFF_LINE"] = False
102
100
  global_config.deck = sys.modules[module]
103
- global_config.deck_snapshot = utils.create_deck_snapshot(global_config.deck, output_path=app.config["DUMMY_DECK"], save=True)
101
+ global_config.deck_snapshot = utils.create_deck_snapshot(global_config.deck,
102
+ output_path=app.config["DUMMY_DECK"], save=True)
104
103
  # global_config.runner = ScriptRunner(globals())
105
104
  else:
106
105
  app.config["OFF_LINE"] = True
@@ -120,3 +119,4 @@ def run(module=None, host="0.0.0.0", port=None, debug=None, llm_server=None, mod
120
119
  for log in logger:
121
120
  utils.start_logger(socketio, log_filename=logger_path, logger_name=log)
122
121
  socketio.run(app, host=host, port=port, debug=debug, use_reloader=False, allow_unsafe_werkzeug=True)
122
+ # return app
@@ -187,7 +187,7 @@ def backend_control(instrument: str=None):
187
187
  return json_output, 400
188
188
  else:
189
189
  return "instrument not exist", 400
190
- return '', 200
190
+ return json_output, 200
191
191
 
192
192
 
193
193
  @control.route("/backend_control", methods=['GET'])
@@ -12,7 +12,8 @@ from werkzeug.utils import secure_filename
12
12
 
13
13
  from ivoryos.utils import utils
14
14
  from ivoryos.utils.global_config import GlobalConfig
15
- from ivoryos.utils.form import create_builtin_form, create_action_button, format_name, create_form_from_pseudo
15
+ from ivoryos.utils.form import create_builtin_form, create_action_button, format_name, create_form_from_pseudo, \
16
+ create_form_from_action
16
17
  from ivoryos.utils.db_models import Script
17
18
  from ivoryos.utils.script_runner import ScriptRunner
18
19
 
@@ -105,9 +106,10 @@ def experiment_builder(instrument=None):
105
106
  else:
106
107
  deck_variables = list(pseudo_deck.keys()) if pseudo_deck else []
107
108
  deck_variables.remove("deck_name") if len(deck_variables) > 0 else deck_variables
109
+ edit_action = session.get("edit_action")
108
110
  if instrument:
109
- if instrument in ['if', 'while', 'variable', 'wait']:
110
- forms = create_builtin_form(instrument)
111
+ if instrument in ['if', 'while', 'variable', 'wait', 'repeat']:
112
+ forms = create_builtin_form(instrument, autofill=autofill, script=script)
111
113
  else:
112
114
  if deck:
113
115
  function_metadata = global_config.deck_snapshot.get(instrument, {})
@@ -116,8 +118,8 @@ def experiment_builder(instrument=None):
116
118
  functions = {key: data.get('signature', {}) for key, data in function_metadata.items()}
117
119
  forms = create_form_from_pseudo(pseudo=functions, autofill=autofill, script=script)
118
120
  if request.method == 'POST' and "hidden_name" in request.form:
119
- all_kwargs = request.form.copy()
120
- method_name = all_kwargs.pop("hidden_name", None)
121
+ # all_kwargs = request.form.copy()
122
+ method_name = request.form.get("hidden_name", None)
121
123
  # if method_name is not None:
122
124
  form = forms.get(method_name)
123
125
  kwargs = {field.name: field.data for field in form if field.name != 'csrf_token'}
@@ -125,25 +127,18 @@ def experiment_builder(instrument=None):
125
127
  if form and form.validate_on_submit():
126
128
  function_name = kwargs.pop("hidden_name")
127
129
  save_data = kwargs.pop('return', '')
128
- variable_kwargs = {}
129
- variable_kwargs_types = {}
130
130
 
131
- try:
132
- variable_kwargs, variable_kwargs_types = utils.find_variable_in_script(script, kwargs)
133
131
 
134
- for name in variable_kwargs.keys():
135
- del kwargs[name]
136
- primitive_arg_types = utils.get_arg_type(kwargs, functions[function_name])
137
132
 
138
- except:
139
- primitive_arg_types = utils.get_arg_type(kwargs, functions[function_name])
133
+ primitive_arg_types = utils.get_arg_type(kwargs, functions[function_name])
140
134
 
141
- kwargs.update(variable_kwargs)
135
+ # print(script.get_added_variables(), script.get_output_variables())
142
136
  arg_types = {}
143
- arg_types.update(variable_kwargs_types)
137
+ # arg_types.update(variable_kwargs_types)
144
138
  arg_types.update(primitive_arg_types)
145
- all_kwargs.update(variable_kwargs)
146
-
139
+ # all_kwargs.update(variable_kwargs)
140
+ script.eval_list(kwargs, arg_types)
141
+ kwargs = script.eval_variables(kwargs)
147
142
  action = {"instrument": instrument, "action": function_name,
148
143
  "args": {name: arg for (name, arg) in kwargs.items()},
149
144
  "return": save_data,
@@ -155,9 +150,13 @@ def experiment_builder(instrument=None):
155
150
  elif request.method == 'POST' and "builtin_name" in request.form:
156
151
  kwargs = {field.name: field.data for field in forms if field.name != 'csrf_token'}
157
152
  if forms.validate_on_submit():
153
+ # print(kwargs)
158
154
  logic_type = kwargs.pop('builtin_name')
159
155
  if 'variable' in kwargs:
160
- script.add_variable(**kwargs)
156
+ try:
157
+ script.add_variable(**kwargs)
158
+ except ValueError:
159
+ flash("Invalid variable type")
161
160
  else:
162
161
  script.add_logic_action(logic_type=logic_type, **kwargs)
163
162
  else:
@@ -168,8 +167,10 @@ def experiment_builder(instrument=None):
168
167
  autofill = not autofill
169
168
  forms = create_form_from_pseudo(functions, autofill=autofill, script=script)
170
169
  session['autofill'] = autofill
170
+ elif edit_action:
171
+ forms = create_form_from_action(edit_action, script=script)
171
172
  utils.post_script_file(script)
172
- design_buttons = [create_action_button(i) for i in script.currently_editing_script]
173
+ design_buttons = create_action_button(script)
173
174
  return render_template('experiment_builder.html', off_line=off_line, instrument=instrument, history=deck_list,
174
175
  script=script, defined_variables=deck_variables,
175
176
  local_variables=global_config.defined_variables,
@@ -250,10 +251,14 @@ def experiment_run():
250
251
  # module = current_app.config.get('MODULE', '')
251
252
  # deck = sys.modules[module] if module else None
252
253
  # script.deck = os.path.splitext(os.path.basename(deck.__file__))[0]
253
- design_buttons = {stype: [create_action_button(i) for i in script.get_script(stype)] for stype in script.stypes}
254
+ design_buttons = {stype: create_action_button(script, stype) for stype in script.stypes}
254
255
  config_preview = []
255
256
  config_file_list = [i for i in os.listdir(current_app.config["CSV_FOLDER"]) if not i == ".gitkeep"]
256
- exec_string = script.compile(current_app.config['SCRIPT_FOLDER'])
257
+ try:
258
+ exec_string = script.compile(current_app.config['SCRIPT_FOLDER'])
259
+ except ValueError as e:
260
+ flash(e.__str__())
261
+ return redirect(url_for("design.experiment_builder"))
257
262
  # print(exec_string)
258
263
  config_file = request.args.get("filename")
259
264
  config = []
@@ -306,6 +311,8 @@ def experiment_run():
306
311
  logger=g.logger, socketio=g.socketio, repeat_count=repeat,
307
312
  output_path=datapath
308
313
  )
314
+ if utils.check_config_duplicate(config):
315
+ flash(f"WARNING: Duplicate in config entries.")
309
316
  except Exception as e:
310
317
  flash(e)
311
318
  return render_template('experiment_run.html', script=script.script_dict, filename=filename, dot_py=exec_string,
@@ -490,14 +497,20 @@ def edit_action(uuid: str):
490
497
  script = utils.get_script_file()
491
498
  action = script.find_by_uuid(uuid)
492
499
  session['edit_action'] = action
493
- if request.method == "POST":
500
+
501
+ if request.method == "POST" and action is not None:
502
+ forms = create_form_from_action(action, script=script)
494
503
  if "back" not in request.form:
495
- args = request.form.to_dict()
496
- save_as = args.pop('return', '')
497
- try:
498
- script.update_by_uuid(uuid=uuid, args=args, output=save_as)
499
- except Exception as e:
500
- flash(e.__str__())
504
+ kwargs = {field.name: field.data for field in forms if field.name != 'csrf_token'}
505
+ # print(kwargs)
506
+ if forms and forms.validate_on_submit():
507
+ save_as = kwargs.pop('return', '')
508
+ kwargs = script.eval_variables(kwargs)
509
+ # try:
510
+ script.update_by_uuid(uuid=uuid, args=kwargs, output=save_as)
511
+ # except Exception as e:
512
+ else:
513
+ flash(forms.errors)
501
514
  session.pop('edit_action')
502
515
  return redirect(url_for('design.experiment_builder'))
503
516
 
@@ -30,6 +30,7 @@
30
30
 
31
31
  {# edit action #}
32
32
  {% if session["edit_action"] %}
33
+ {# {{ session["edit_action"] }}#}
33
34
  {% with action = session["edit_action"] %}
34
35
  <h5> {{ format_name(action['action']) }} </h5>
35
36
  <form role="form" method='POST' name="{{instrument}}" action="{{ url_for('design.edit_action', uuid=session["edit_action"]['uuid']) }}">
@@ -41,16 +42,26 @@
41
42
  <input class="form-control" type="text" id="arg" name="arg" placeholder="{{ action['arg_types']}}" value="{{ action['args'] }}" aria-labelledby="variableHelpBlock">
42
43
  </div>
43
44
  {% else %}
44
- {% for arg in action['args'] %}
45
- <div class="input-group mb-3">
46
- <label class="input-group-text">{{ format_name(arg) }}</label>
47
- <input class="form-control" type="text" id="{{ arg }}" name="{{ arg }}" placeholder="{{ action['arg_types'][arg] }}" value="{{ action['args'][arg] }}" aria-labelledby="variableHelpBlock">
48
- </div>
45
+ {# {% for arg in action['args'] %}#}
46
+ {# <div class="input-group mb-3">#}
47
+ {# <label class="input-group-text">{{ format_name(arg) }}</label>#}
48
+ {# <input class="form-control" type="text" id="{{ arg }}" name="{{ arg }}" placeholder="{{ action['arg_types'][arg] }}" value="{{ action['args'][arg] }}" aria-labelledby="variableHelpBlock">#}
49
+ {# </div>#}
50
+ {# {% endfor %}#}
51
+ {# <div class="input-group mb-3">#}
52
+ {# <label class="input-group-text">Save Output?</label>#}
53
+ {# <input class="form-control" type="text" id="return" name="return" value="{{ action['return'] }}" aria-labelledby="variableHelpBlock">#}
54
+ {# </div>#}
55
+ {{ forms.hidden_tag() }}
56
+ {% for field in forms %}
57
+ {% if field.type not in ['CSRFTokenField'] %}
58
+ <div class="input-group mb-3">
59
+ <label class="input-group-text">{{ field.label.text }}</label>
60
+ {{ field(class="form-control") }}
61
+ <div class="form-text">{{ field.description }} </div>
62
+ </div>
63
+ {% endif %}
49
64
  {% endfor %}
50
- <div class="input-group mb-3">
51
- <label class="input-group-text">Save Output?</label>
52
- <input class="form-control" type="text" id="return" name="return" value="{{ action['return'] }}" aria-labelledby="variableHelpBlock">
53
- </div>
54
65
  {% endif %}
55
66
  </div>
56
67
  {% endif %}
@@ -72,7 +83,7 @@
72
83
  <!-- <div class="rounded flex-fill" style="height: 30px;background-color: aliceblue">-->
73
84
  <!-- <h6 style=" text-align: center; ">{{ format_name(instrument) }}</h6>-->
74
85
  <!-- </div>-->
75
- {% if instrument in ['if' , 'while' , 'variable' ,'wait'] %}
86
+ {% if instrument in ['if' , 'while' , 'variable' ,'wait', 'repeat'] %}
76
87
  {# form for builtin functions #}
77
88
  <form role="form" method='POST' name="{{instrument}}" action="{{url_for('design.experiment_builder',instrument=instrument)}}" >
78
89
  <div class="form-group">
@@ -185,7 +196,7 @@
185
196
  </h5>
186
197
  <div class="accordion-collapse collapse show" id="advanced">
187
198
  <ul class="list-group">
188
- {% for instrument in ['if', 'while', 'variable', 'wait'] %}
199
+ {% for instrument in ['if', 'while', 'variable', 'wait', 'repeat'] %}
189
200
  <form role="form" method='GET' name="device" action="{{url_for('design.experiment_builder',instrument=instrument)}}">
190
201
  <div class="form-group">
191
202
  <button class="list-group-item list-group-item-action" aria-current="true" type="submit">{{instrument}}</button>
@@ -302,7 +313,7 @@
302
313
  {% for button in buttons %}
303
314
  <li id="{{ button['id'] }}" style="list-style-type: none;">
304
315
  <a href="{{ url_for('design.edit_action', uuid=button['uuid']) }}" type="button" class="btn btn-light" style="{{ button['style'] }}">{{ button['label'] }}</a>
305
- {% if not button["instrument"] in ["if","while"] %}
316
+ {% if not button["instrument"] in ["if","while","repeat"] %}
306
317
  <a href="{{ url_for('design.duplicate_action', id=button['id']) }}" type="button" class="btn btn-light"><span class="bi bi-copy"></span></a>
307
318
  {% endif %}
308
319
  <a href="{{ url_for('design.delete_action', id=button['id']) }}" type="button" class="btn btn-light"><span class="bi bi-trash"></span></a>
@@ -403,7 +414,7 @@
403
414
  </div>
404
415
  </div>
405
416
 
406
- {% if instrument and not instrument in ['if' , 'while' , 'variable' ,'wait'] and use_llm %}
417
+ {% if instrument and not instrument in ['if' , 'while' , 'variable' ,'wait', 'repeat'] and use_llm %}
407
418
  <script>
408
419
  const buttonIds = {{ ['generate'] | tojson }};
409
420
  </script>
@@ -1,8 +1,11 @@
1
+ import ast
2
+ import builtins
1
3
  import json
2
4
  import keyword
3
5
  import re
4
6
  import uuid
5
7
  from datetime import datetime
8
+ from typing import Dict
6
9
 
7
10
  from flask_login import UserMixin
8
11
  from flask_sqlalchemy import SQLAlchemy
@@ -29,6 +32,16 @@ class User(db.Model, UserMixin):
29
32
  return self.username
30
33
 
31
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
+
32
45
  class Script(db.Model):
33
46
  __tablename__ = 'script'
34
47
  # id = db.Column(db.Integer, primary_key=True)
@@ -89,52 +102,70 @@ class Script(db.Model):
89
102
  return action
90
103
 
91
104
  def _convert_type(self, args, arg_types):
105
+ if arg_types in ["list", "tuple", "set"]:
106
+ try:
107
+ args = ast.literal_eval(args)
108
+ return args
109
+ except Exception:
110
+ pass
92
111
  if type(arg_types) is not list:
93
112
  arg_types = [arg_types]
94
113
  for arg_type in arg_types:
95
114
  try:
115
+ # print(arg_type)
96
116
  args = eval(f"{arg_type}('{args}')")
97
117
  return
98
118
  except Exception:
119
+
99
120
  pass
100
121
  raise TypeError(f"Input type error: cannot convert '{args}' to {arg_type}.")
101
122
 
102
123
  def update_by_uuid(self, uuid, args, output):
103
- bool_dict = {"True": True, "False": False}
104
124
  action = self.find_by_uuid(uuid)
125
+ if not action:
126
+ return
127
+ arg_types = action['arg_types']
105
128
  if type(action['args']) is dict:
106
- for arg in action['args']:
107
- if not args[arg].startswith("#"):
108
-
109
- if args[arg] in bool_dict.keys():
110
- args[arg] = bool_dict[args[arg]]
111
- elif args[arg] == "None" or args[arg] == "":
112
- args[arg] = None
113
- else:
114
- if arg in action['arg_types']:
115
- arg_types = action['arg_types'][arg]
116
- self._convert_type(args[arg], arg_types)
117
- else:
118
- try:
119
- args[arg] = eval(args[arg])
120
- except Exception:
121
- pass
122
- else:
123
- args = list(args.values())[0]
124
- if not args.startswith("#"):
125
- if args in bool_dict.keys():
126
- args = bool_dict[args]
129
+ # pass
130
+ self.eval_list(args, arg_types)
127
131
 
128
- else:
129
- if 'arg_types' in action:
130
- arg_types = action['arg_types']
131
- self._convert_type(args, arg_types)
132
+ else:
133
+ 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)
132
144
 
133
- # print(args)
134
145
  action['args'] = args
135
- # print(action)
136
146
  action['return'] = output
137
147
 
148
+ @staticmethod
149
+ def eval_list(args, arg_types):
150
+ for arg in args:
151
+ arg_type = arg_types[arg]
152
+ if arg_type in ["list", "tuple", "set"]:
153
+
154
+ if type(arg) is str and not args[arg].startswith("#"):
155
+ # arg_types = arg_types[arg]
156
+ # if arg_types in ["list", "tuple", "set"]:
157
+ convert_type = getattr(builtins, arg_type) # Handle unknown types s
158
+ try:
159
+ output = ast.literal_eval(args[arg])
160
+ if type(output) not in [list, tuple, set]:
161
+ output = [output]
162
+ args[arg] = convert_type(output)
163
+ # return args
164
+ except ValueError:
165
+ _list = ''.join(args[arg]).split(',')
166
+ # convert_type = getattr(builtins, arg_types) # Handle unknown types s
167
+ args[arg] = convert_type([s.strip() for s in _list])
168
+
138
169
  @property
139
170
  def stypes(self):
140
171
  return list(self.script_dict.keys())
@@ -204,15 +235,48 @@ class Script(db.Model):
204
235
  self.currently_editing_order.append(str(current_len + 1))
205
236
  self.update_time_stamp()
206
237
 
207
- def add_variable(self, statement, variable):
238
+ def add_variable(self, statement, variable, type):
239
+ convert_type = getattr(builtins, type)
240
+ statement = convert_type(statement)
208
241
  current_len = len(self.currently_editing_script)
209
242
  uid = uuid.uuid4().fields[-1]
210
243
  action_list = [{"id": current_len + 1, "instrument": 'variable', "action": variable,
211
- "args": 'None' if statement == '' else statement, "return": '', "uuid": uid, "arg_types": ''}]
244
+ "args": {"statement": 'None' if statement == '' else statement}, "return": '', "uuid": uid,
245
+ "arg_types": {"statement": type}}]
212
246
  self.currently_editing_script.extend(action_list)
213
247
  self.currently_editing_order.extend([str(current_len + i + 1) for i in range(len(action_list))])
214
248
  self.update_time_stamp()
215
249
 
250
+ def get_added_variables(self):
251
+ added_variables: Dict[str, str] = {action["action"]: action["arg_types"]["statement"] for action in
252
+ self.currently_editing_script if action["instrument"] == "variable"}
253
+
254
+ return added_variables
255
+
256
+ def get_output_variables(self):
257
+ output_variables: Dict[str, str] = {action["return"]: "function_output" for action in
258
+ self.currently_editing_script if action["return"]}
259
+
260
+ return output_variables
261
+
262
+ def get_variables(self):
263
+ output_variables: Dict[str, str] = self.get_output_variables()
264
+ added_variables = self.get_added_variables()
265
+ output_variables.update(added_variables)
266
+
267
+ return output_variables
268
+
269
+ def eval_variables(self, kwargs):
270
+ output_variables: Dict[str, str] = self.get_variables()
271
+ # print(output_variables)
272
+ for key, value in kwargs.items():
273
+ if type(value) is str and value in output_variables:
274
+ var_type = output_variables[value]
275
+ kwargs[key] = {value:var_type}
276
+
277
+ return kwargs
278
+
279
+
216
280
  def add_logic_action(self, logic_type: str, statement):
217
281
  current_len = len(self.currently_editing_script)
218
282
  uid = uuid.uuid4().fields[-1]
@@ -220,26 +284,33 @@ class Script(db.Model):
220
284
  "if":
221
285
  [
222
286
  {"id": current_len + 1, "instrument": 'if', "action": 'if',
223
- "args": 'True' if statement == '' else statement,
224
- "return": '', "uuid": uid, "arg_types": ''},
225
- {"id": current_len + 2, "instrument": 'if', "action": 'else', "args": '', "return": '',
287
+ "args": {"statement": 'True' if statement == '' else statement},
288
+ "return": '', "uuid": uid, "arg_types": {"statement": ''}},
289
+ {"id": current_len + 2, "instrument": 'if', "action": 'else', "args": {}, "return": '',
226
290
  "uuid": uid},
227
- {"id": current_len + 3, "instrument": 'if', "action": 'endif', "args": '', "return": '',
291
+ {"id": current_len + 3, "instrument": 'if', "action": 'endif', "args": {}, "return": '',
228
292
  "uuid": uid},
229
293
  ],
230
294
  "while":
231
295
  [
232
296
  {"id": current_len + 1, "instrument": 'while', "action": 'while',
233
- "args": 'False' if statement == '' else statement, "return": '', "uuid": uid, "arg_types": ''},
234
- {"id": current_len + 2, "instrument": 'while', "action": 'endwhile', "args": '', "return": '',
297
+ "args": {"statement": 'False' if statement == '' else statement}, "return": '', "uuid": uid, "arg_types": {"statement": ''}},
298
+ {"id": current_len + 2, "instrument": 'while', "action": 'endwhile', "args": {}, "return": '',
235
299
  "uuid": uid},
236
300
  ],
237
301
 
238
302
  "wait":
239
303
  [
240
304
  {"id": current_len + 1, "instrument": 'wait', "action": "wait",
241
- "args": '0' if statement == '' else statement,
242
- "return": '', "uuid": uid, "arg_types": "float"},
305
+ "args": {"statement": 1 if statement == '' else statement},
306
+ "return": '', "uuid": uid, "arg_types": {"statement": "float"}},
307
+ ],
308
+ "repeat":
309
+ [
310
+ {"id": current_len + 1, "instrument": 'repeat', "action": "repeat",
311
+ "args": {"statement": 1 if statement == '' else statement}, "return": '', "uuid": uid, "arg_types": {"statement": "int"}},
312
+ {"id": current_len + 2, "instrument": 'repeat', "action": 'endrepeat',
313
+ "args": {}, "return": '', "uuid": uid},
243
314
  ],
244
315
  }
245
316
  action_list = logic_dict[logic_type]
@@ -259,7 +330,8 @@ class Script(db.Model):
259
330
  self.update_time_stamp()
260
331
 
261
332
  def duplicate_action(self, id: int):
262
- action_to_duplicate = next((action for action in self.currently_editing_script if action['id'] == int(id)), None)
333
+ action_to_duplicate = next((action for action in self.currently_editing_script if action['id'] == int(id)),
334
+ None)
263
335
  insert_id = action_to_duplicate.get("id")
264
336
  self.add_action(action_to_duplicate)
265
337
  # print(self.currently_editing_script)
@@ -366,11 +438,15 @@ class Script(db.Model):
366
438
  """
367
439
  Generate the function header.
368
440
  """
369
- configure, _ = self.config(stype)
441
+ configure, config_type = self.config(stype)
442
+
443
+ configure = [param + f":{param_type}" if not param_type == "any" else "" for param, param_type in
444
+ config_type.items()]
445
+
370
446
  function_header = f"\n\ndef {run_name}_{stype}("
371
447
 
372
448
  if stype == "script":
373
- function_header += ",".join(configure)
449
+ function_header += ", ".join(configure)
374
450
 
375
451
  function_header += "):"
376
452
  function_header += self.indent(1) + f"global {run_name}_{stype}"
@@ -389,7 +465,6 @@ class Script(db.Model):
389
465
  return_str, return_list = self.config_return()
390
466
  if return_list and stype == "script":
391
467
  body += self.indent(indent_unit) + return_str
392
-
393
468
  return body
394
469
 
395
470
  def _process_action(self, indent_unit, action, index, stype):
@@ -397,18 +472,24 @@ class Script(db.Model):
397
472
  Process each action within the script dictionary.
398
473
  """
399
474
  instrument = action['instrument']
475
+ statement = action['args'].get('statement')
400
476
  args = self._process_args(action['args'])
477
+
401
478
  save_data = action['return']
402
479
  action_name = action['action']
403
480
  next_action = self._get_next_action(stype, index)
481
+ # print(args)
404
482
  if instrument == 'if':
405
- return self._process_if(indent_unit, action_name, args, next_action)
483
+ return self._process_if(indent_unit, action_name, statement, next_action)
406
484
  elif instrument == 'while':
407
- return self._process_while(indent_unit, action_name, args, next_action)
485
+ return self._process_while(indent_unit, action_name, statement, next_action)
408
486
  elif instrument == 'variable':
409
- return self.indent(indent_unit) + f"{action_name} = {args}", indent_unit
487
+ return self.indent(indent_unit) + f"{action_name} = {statement}", indent_unit
410
488
  elif instrument == 'wait':
411
- return f"{self.indent(indent_unit)}time.sleep({args})", indent_unit
489
+ return f"{self.indent(indent_unit)}time.sleep({statement})", indent_unit
490
+ elif instrument == 'repeat':
491
+ return self._process_repeat(indent_unit, action_name, statement, next_action)
492
+
412
493
  else:
413
494
  return self._process_instrument_action(indent_unit, instrument, action_name, args, save_data)
414
495
 
@@ -456,10 +537,25 @@ class Script(db.Model):
456
537
  indent_unit -= 1
457
538
  return exec_string, indent_unit
458
539
 
540
+ def _process_repeat(self, indent_unit, action, args, next_action):
541
+ """
542
+ Process 'while' and 'endwhile' actions.
543
+ """
544
+ exec_string = ""
545
+ if action == 'repeat':
546
+ exec_string += self.indent(indent_unit) + f"for _ in range({args}):"
547
+ indent_unit += 1
548
+ if next_action and next_action['instrument'] == 'repeat':
549
+ exec_string += self.indent(indent_unit) + "pass"
550
+ elif action == 'endrepeat':
551
+ indent_unit -= 1
552
+ return exec_string, indent_unit
553
+
459
554
  def _process_instrument_action(self, indent_unit, instrument, action, args, save_data):
460
555
  """
461
556
  Process actions related to instruments.
462
557
  """
558
+
463
559
  if isinstance(args, dict):
464
560
  args_str = self._process_dict_args(args)
465
561
  single_line = f"{instrument}.{action}(**{args_str})"
@@ -481,8 +577,16 @@ class Script(db.Model):
481
577
  for arg in args:
482
578
  if isinstance(args[arg], str) and args[arg].startswith("#"):
483
579
  args_str = args_str.replace(f"'#{args[arg][1:]}'", args[arg][1:])
484
- elif self._is_variable(arg):
485
- args_str = args_str.replace(f"'{args[arg]}'", args[arg])
580
+ elif isinstance(args[arg], dict):
581
+ print(args[arg])
582
+ variables = self.get_variables()
583
+ value = next(iter(args[arg]))
584
+ if value not in variables:
585
+ raise ValueError(f"Variable ({value}) is not defined.")
586
+ args_str = args_str.replace(f"{args[arg]}", next(iter(args[arg])))
587
+ # elif self._is_variable(arg):
588
+ # print("is variable")
589
+ # args_str = args_str.replace(f"'{args[arg]}'", args[arg])
486
590
  return args_str
487
591
 
488
592
  def _get_next_action(self, stype, index):
@@ -1,3 +1,4 @@
1
+ from wtforms.fields.choices import SelectField
1
2
  from wtforms.fields.core import Field
2
3
  from wtforms.validators import InputRequired
3
4
  from wtforms.widgets.core import TextInput
@@ -6,16 +7,22 @@ from flask_wtf import FlaskForm
6
7
  from wtforms import StringField, FloatField, HiddenField, BooleanField, IntegerField
7
8
  import inspect
8
9
 
10
+ from ivoryos.utils.db_models import Variable, Script
11
+
9
12
 
10
13
  def find_variable(data, script):
11
14
  # TODO: needs to check for valid order of variables, important when editting
12
- added_variables: list[dict[str, str]] = [action for action in script.currently_editing_script if
13
- action["instrument"] == "variable"
14
- # or action["return"] # TODO find returns
15
- ]
16
- for added_variable in added_variables:
17
- if added_variable["action"] == data:
18
- return data, added_variable["args"]
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():
24
+ if variable_name == data:
25
+ return data, variable_type # variable_type int float str or "function_output"
19
26
  # if added_variable["return"] == data:
20
27
  # return data, None
21
28
  return None, None
@@ -36,9 +43,9 @@ class VariableOrStringField(Field):
36
43
 
37
44
  def _value(self):
38
45
  if self.script:
39
- variable, value = find_variable(self.data, self.script)
46
+ variable, variable_type = find_variable(self.data, self.script)
40
47
  if variable:
41
- return variable
48
+ return Variable(variable, variable_type)
42
49
 
43
50
  return str(self.data) if self.data is not None else ""
44
51
 
@@ -52,9 +59,9 @@ class VariableOrFloatField(Field):
52
59
 
53
60
  def _value(self):
54
61
  if self.script:
55
- variable, value = find_variable(self.data, self.script)
62
+ variable, variable_type = find_variable(self.data, self.script)
56
63
  if variable:
57
- return variable
64
+ return Variable(variable, variable_type)
58
65
 
59
66
  if self.raw_data:
60
67
  return self.raw_data[0]
@@ -73,14 +80,15 @@ class VariableOrFloatField(Field):
73
80
  try:
74
81
  if self.script:
75
82
  try:
76
- variable, value = find_variable(valuelist[0], self.script)
83
+ variable, variable_type = find_variable(valuelist[0], self.script)
77
84
  if variable:
78
- float(value)
79
- self.data = str(variable)
85
+ if not variable_type == "function_output":
86
+ if variable_type not in ["float", "int"]:
87
+ raise ValueError("Variable is not a valid float")
88
+ self.data = variable
80
89
  return
81
90
  except ValueError:
82
91
  pass
83
-
84
92
  self.data = float(valuelist[0])
85
93
  except ValueError as exc:
86
94
  self.data = None
@@ -99,9 +107,9 @@ class VariableOrIntField(Field):
99
107
 
100
108
  def _value(self):
101
109
  if self.script:
102
- variable, value = find_variable(self.data, self.script)
110
+ variable, variable_type = find_variable(self.data, self.script)
103
111
  if variable:
104
- return variable
112
+ return Variable(variable, variable_type)
105
113
 
106
114
  if self.raw_data:
107
115
  return self.raw_data[0]
@@ -109,34 +117,16 @@ class VariableOrIntField(Field):
109
117
  return str(self.data)
110
118
  return ""
111
119
 
112
- # def process_data(self, value):
113
- #
114
- # if self.script:
115
- # variable, var_value = find_variable(value, self.script)
116
- # if variable:
117
- # try:
118
- # int(var_value)
119
- # self.data = str(variable)
120
- # return
121
- # except ValueError:
122
- # pass
123
- # if value is None or value is unset_value:
124
- # self.data = None
125
- # return
126
- # try:
127
- # self.data = int(value)
128
- # except (ValueError, TypeError) as exc:
129
- # self.data = None
130
- # raise ValueError(self.gettext("Not a valid integer value.")) from exc
131
-
132
120
  def process_formdata(self, valuelist):
133
121
  if not valuelist:
134
122
  return
135
123
  if self.script:
136
- variable, var_value = find_variable(valuelist[0], self.script)
124
+ variable, variable_type = find_variable(valuelist[0], self.script)
137
125
  if variable:
138
126
  try:
139
- int(var_value)
127
+ if not variable_type == "function_output":
128
+ if not variable_type == "int":
129
+ raise ValueError("Not a valid integer value")
140
130
  self.data = str(variable)
141
131
  return
142
132
  except ValueError:
@@ -155,6 +145,7 @@ class VariableOrIntField(Field):
155
145
 
156
146
  class VariableOrBoolField(BooleanField):
157
147
  widget = TextInput()
148
+ false_values = (False, "false", "", "False", "f", "F")
158
149
 
159
150
  def __init__(self, label='', validators=None, script=None, **kwargs):
160
151
  super(VariableOrBoolField, self).__init__(label, validators, **kwargs)
@@ -163,32 +154,36 @@ class VariableOrBoolField(BooleanField):
163
154
  def process_data(self, value):
164
155
 
165
156
  if self.script:
166
- variable, var_value = find_variable(value, self.script)
157
+ variable, variable_type = find_variable(value, self.script)
167
158
  if variable:
168
- try:
169
- bool(var_value)
170
- return variable
171
- except ValueError:
172
- return
159
+ if not variable_type == "function_output":
160
+ raise ValueError("Not accepting boolean variables")
161
+ return Variable(variable, variable_type)
173
162
 
174
163
  self.data = bool(value)
175
164
 
176
165
  def process_formdata(self, valuelist):
177
- if not valuelist or type(valuelist) is list and valuelist[0] == '':
166
+ # todo
167
+ # print(valuelist)
168
+ if not valuelist or not type(valuelist) is list:
178
169
  self.data = False
179
- elif valuelist and valuelist[0].startswith("#"):
180
- if not self.script.editing_type == "script":
181
- raise ValueError(self.gettext("Variable is not supported in prep/cleanup"))
182
- self.data = valuelist[0]
183
170
  else:
184
- self.data = True
171
+ value = valuelist[0] if type(valuelist) is list else valuelist
172
+ if value.startswith("#"):
173
+ if not self.script.editing_type == "script":
174
+ raise ValueError(self.gettext("Variable is not supported in prep/cleanup"))
175
+ self.data = valuelist[0]
176
+ elif value in self.false_values:
177
+ self.data = False
178
+ else:
179
+ self.data = True
185
180
 
186
181
  def _value(self):
187
182
 
188
183
  if self.script:
189
- variable, value = find_variable(self.raw_data, self.script)
184
+ variable, variable_type = find_variable(self.raw_data, self.script)
190
185
  if variable:
191
- return variable
186
+ return Variable(variable, variable_type)
192
187
 
193
188
  if self.raw_data:
194
189
  return str(self.raw_data[0])
@@ -269,17 +264,76 @@ def create_form_from_pseudo(pseudo: dict, autofill: bool, script=None, design=Tr
269
264
  return method_forms
270
265
 
271
266
 
272
- def create_builtin_form(logic_type):
267
+ def create_form_from_action(action: dict, script=None, design=True):
268
+ '''
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}
271
+ '''
272
+ # print(action)
273
+
274
+ arg_types = action.get("arg_types", {})
275
+ args = action.get("args", {})
276
+ save_as = action.get("return")
277
+ action = action.get("action")
278
+
279
+ class DynamicForm(FlaskForm):
280
+ pass
281
+
282
+ annotation_mapping = {
283
+ "int": (VariableOrIntField if design else IntegerField, 'Enter integer value'),
284
+ "float": (VariableOrFloatField if design else FloatField, 'Enter numeric value'),
285
+ "str": (VariableOrStringField if design else StringField, 'Enter text'),
286
+ "bool": (VariableOrBoolField if design else BooleanField, 'Empty for false')
287
+ }
288
+
289
+ for name, param_type in arg_types.items():
290
+ formatted_param_name = format_name(name)
291
+ value = args.get(name, "")
292
+ if type(value) is dict:
293
+ value = next(iter(value))
294
+ field_kwargs = {
295
+ "label": formatted_param_name,
296
+ "default": f'{value}',
297
+ "validators": [InputRequired()],
298
+ **({"script": script})
299
+ }
300
+ param_type = param_type if type(param_type) is str else f"{param_type}"
301
+ field_class, placeholder_text = annotation_mapping.get(
302
+ param_type,
303
+ (VariableOrStringField if design else StringField, f'Enter {param_type} value')
304
+ )
305
+ render_kwargs = {"placeholder": placeholder_text}
306
+
307
+ # Create the field with additional rendering kwargs for placeholder text
308
+ field = field_class(**field_kwargs, render_kw=render_kwargs)
309
+ setattr(DynamicForm, name, field)
310
+
311
+ if design:
312
+ return_value = StringField(label='Save value as', default=f"{save_as}", render_kw={"placeholder": "Optional"})
313
+ setattr(DynamicForm, 'return', return_value)
314
+ return DynamicForm()
315
+
316
+
317
+ def create_builtin_form(logic_type, autofill, script):
273
318
  class BuiltinFunctionForm(FlaskForm):
274
319
  pass
275
320
 
276
- placeholder_text = f'Enter numbers' if logic_type == 'wait' else f'Enter statement'
277
- description_text = f'Your variable can be numbers, boolean (True or False) or text ("text")' if logic_type == 'variable' else ''
278
- field_class = FloatField if logic_type == 'wait' else StringField # Default to StringField as a fallback
321
+ placeholder_text = {
322
+ 'wait': 'Enter second',
323
+ 'repeat': 'Enter an integer'
324
+ }.get(logic_type, 'Enter statement')
325
+ description_text = {
326
+ 'variable': 'Your variable can be numbers, boolean (True or False) or text ("text")',
327
+ }.get(logic_type, '')
328
+ field_class = {
329
+ 'wait': VariableOrFloatField,
330
+ 'repeat': VariableOrIntField
331
+ }.get(logic_type, VariableOrStringField) # Default to StringField as a fallback
279
332
  field_kwargs = {
280
333
  "label": f'statement',
281
334
  "validators": [InputRequired()] if logic_type in ['wait', "variable"] else [],
282
335
  "description": description_text,
336
+ "script": script
283
337
  }
284
338
  render_kwargs = {"placeholder": placeholder_text}
285
339
  field = field_class(**field_kwargs, render_kw=render_kwargs)
@@ -288,29 +342,58 @@ def create_builtin_form(logic_type):
288
342
  variable_field = StringField(label=f'variable', validators=[InputRequired()],
289
343
  description="Your variable name cannot include space",
290
344
  render_kw=render_kwargs)
345
+ type_field = SelectField(
346
+ 'Select Input Type',
347
+ choices=[('int', 'Integer'), ('float', 'Float'), ('str', 'String')],
348
+ default='str' # Optional default value
349
+ )
291
350
  setattr(BuiltinFunctionForm, "variable", variable_field)
351
+ setattr(BuiltinFunctionForm, "type", type_field)
292
352
  hidden_field = HiddenField(name=f'builtin_name', render_kw={"value": f'{logic_type}'})
293
353
  setattr(BuiltinFunctionForm, "builtin_name", hidden_field)
294
354
  return BuiltinFunctionForm()
295
355
 
296
356
 
297
- def create_action_button(s: dict):
298
- style = ""
299
- if s['instrument'] in ['if', 'while']:
300
- text = f"{s['action']} {s['args']}"
301
- style = "background-color: tomato"
302
- elif s['instrument'] == 'variable':
303
- text = f"{s['action']} = {s['args']}"
357
+ def create_action_button(script, stype=None):
358
+ stype = stype or script.editing_type
359
+ variables = script.get_variables()
360
+ return [_action_button(i, variables) for i in script.get_script(stype)]
361
+
362
+
363
+ def _action_button(action: dict, variables: dict):
364
+ style = {
365
+ "repeat": "background-color: lightsteelblue",
366
+ "if": "background-color: salmon",
367
+ "while": "background-color: salmon",
368
+ }.get(action['instrument'], "")
369
+
370
+ if action['instrument'] in ['if', 'while', 'repeat']:
371
+ text = f"{action['action']} {action['args']}"
372
+ elif action['instrument'] == 'variable':
373
+ text = f"{action['action']} = {action['args'].get('statement')}"
304
374
  else:
305
375
  # regular action button
306
- prefix = f"{s['return']} = " if s['return'] else ""
307
- action_text = f"{s['instrument'].split('.')[-1] if s['instrument'].startswith('deck') else s['instrument']}.{s['action']}"
376
+ prefix = f"{action['return']} = " if action['return'] else ""
377
+ action_text = f"{action['instrument'].split('.')[-1] if action['instrument'].startswith('deck') else action['instrument']}.{action['action']}"
308
378
  arg_string = ""
309
- if s['args']:
310
- if type(s['args']) is dict:
311
- arg_string = "(" + ", ".join([f"{k} = {v}" for k, v in s['args'].items()]) + ")"
379
+ if action['args']:
380
+ if type(action['args']) is dict:
381
+ arg_list = []
382
+ for k, v in action['args'].items():
383
+ if isinstance(v, dict):
384
+ value = next(iter(v)) # Extract the first key if it's a dict
385
+
386
+ style = "background-color: khaki" if value not in variables.keys() else ""
387
+
388
+ else:
389
+ value = v # Keep the original value if not a dict
390
+
391
+ arg_list.append(f"{k} = {value}") # Format the key-value pair
392
+
393
+ arg_string = "(" + ", ".join(arg_list) + ")"
312
394
  else:
313
- arg_string = f"= {s['args']}"
395
+ arg_string = f"= {action['args']}"
314
396
 
315
397
  text = f"{prefix}{action_text} {arg_string}"
316
- return dict(label=text, style=style, uuid=s["uuid"], id=s["id"], instrument=s['instrument'])
398
+
399
+ return dict(label=text, style=style, uuid=action["uuid"], id=action["id"], instrument=action['instrument'])
@@ -6,6 +6,7 @@ import os
6
6
  import pickle
7
7
  import subprocess
8
8
  import sys
9
+ from collections import Counter
9
10
  from typing import Optional, Dict, Tuple
10
11
 
11
12
  from flask import session
@@ -83,7 +84,6 @@ def available_pseudo_deck(path):
83
84
  """
84
85
  load pseudo deck (snapshot) from connection history
85
86
  """
86
- import os
87
87
  return os.listdir(path)
88
88
 
89
89
 
@@ -131,34 +131,37 @@ def _get_type_from_parameters(arg, parameters):
131
131
  if annotation is not inspect._empty:
132
132
  # print(p[arg].annotation)
133
133
  if annotation.__module__ == 'typing':
134
- if hasattr(annotation, '_name') and annotation._name in ["Optional", "Union"]:
135
- # print(p[arg].annotation.__args__)
136
- arg_type = [i.__name__ for i in annotation.__args__]
137
- elif hasattr(annotation, '__origin__'):
138
- arg_type = annotation.__origin__.__name__
139
- else:
140
- # TODO
141
- pass
134
+
135
+ if hasattr(annotation, '__origin__'):
136
+ origin = annotation.__origin__
137
+ if hasattr(origin, '_name') and origin._name in ["Optional", "Union"]:
138
+ arg_type = [i.__name__ for i in annotation.__args__]
139
+ elif hasattr(origin, '__name__'):
140
+ arg_type = origin.__name__
141
+ # todo other types
142
+ elif annotation.__module__ == 'types':
143
+ arg_type = [i.__name__ for i in annotation.__args__]
144
+
142
145
  else:
143
146
  arg_type = annotation.__name__
144
147
  return arg_type
145
148
 
146
-
147
- def find_variable_in_script(script: Script, args: Dict[str, str]) -> Optional[Tuple[Dict[str, str], Dict[str, str]]]:
148
- # TODO: need to search for if the variable exists
149
- added_variables: list[Dict[str, str]] = [action for action in script.currently_editing_script if
150
- action["instrument"] == "variable"]
151
-
152
- possible_variable_arguments = {}
153
- possible_variable_types = {}
154
-
155
- for arg_name, arg_val in args.items():
156
- for added_variable in added_variables:
157
- if added_variable["action"] == arg_val:
158
- possible_variable_arguments[arg_name] = added_variable["action"]
159
- possible_variable_types[arg_name] = "variable"
160
-
161
- return possible_variable_arguments, possible_variable_types
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
162
165
 
163
166
 
164
167
  def _convert_by_str(args, arg_types):
@@ -424,3 +427,13 @@ def load_deck(pkl_name: str):
424
427
  return pseudo_deck
425
428
  except FileNotFoundError:
426
429
  return None
430
+
431
+
432
+ def check_config_duplicate(config):
433
+ """
434
+ Checks if the config entry has any duplicate
435
+ :param config: [{"arg": 1}, {"arg": 1}, {"arg": 1}]
436
+ :return: [True, False]
437
+ """
438
+ hashable_data = [tuple(sorted(d.items())) for d in config]
439
+ return any(count > 1 for count in Counter(hashable_data).values())
@@ -0,0 +1 @@
1
+ __version__ = "0.1.13"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ivoryos
3
- Version: 0.1.11
3
+ Version: 0.1.13
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
@@ -1 +0,0 @@
1
- __version__ = "0.1.11"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes