ivoryos 0.1.12__py3-none-any.whl → 0.1.18__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of ivoryos might be problematic. Click here for more details.

ivoryos/__init__.py CHANGED
@@ -1,8 +1,10 @@
1
+ import importlib
2
+ import inspect
1
3
  import os
2
4
  import sys
3
5
  from typing import Union
4
6
 
5
- from flask import Flask, redirect, url_for
7
+ from flask import Flask, redirect, url_for, Blueprint, g
6
8
 
7
9
  from ivoryos.config import Config, get_config
8
10
  from ivoryos.routes.auth.auth import auth, login_manager
@@ -10,22 +12,18 @@ from ivoryos.routes.control.control import control
10
12
  from ivoryos.routes.database.database import database
11
13
  from ivoryos.routes.design.design import design, socketio
12
14
  from ivoryos.routes.main.main import main
15
+ # from ivoryos.routes.monitor.monitor import monitor
13
16
  from ivoryos.utils import utils
14
17
  from ivoryos.utils.db_models import db
15
18
  from ivoryos.utils.global_config import GlobalConfig
16
19
  from ivoryos.utils.script_runner import ScriptRunner
17
20
  from ivoryos.version import __version__ as ivoryos_version
18
-
21
+ from importlib.metadata import entry_points
19
22
  global_config = GlobalConfig()
20
23
 
21
-
22
24
  url_prefix = os.getenv('URL_PREFIX', "/ivoryos")
23
25
  app = Flask(__name__, static_url_path=f'{url_prefix}/static', static_folder='static')
24
- app.register_blueprint(main, url_prefix=url_prefix)
25
- app.register_blueprint(auth, url_prefix=url_prefix)
26
- app.register_blueprint(design, url_prefix=url_prefix)
27
- app.register_blueprint(database, url_prefix=url_prefix)
28
- app.register_blueprint(control, url_prefix=url_prefix)
26
+
29
27
 
30
28
  def create_app(config_class=None):
31
29
  # url_prefix = os.getenv('URL_PREFIX', "/ivoryos")
@@ -55,12 +53,9 @@ def create_app(config_class=None):
55
53
  Called before
56
54
 
57
55
  """
58
- from flask import g
59
56
  g.logger = logger
60
57
  g.socketio = socketio
61
58
 
62
-
63
-
64
59
  @app.route('/')
65
60
  def redirect_to_prefix():
66
61
  return redirect(url_for('main.index', version=ivoryos_version)) # Assuming 'index' is a route in your blueprint
@@ -72,6 +67,7 @@ def run(module=None, host="0.0.0.0", port=None, debug=None, llm_server=None, mod
72
67
  config: Config = None,
73
68
  logger: Union[str, list] = None,
74
69
  logger_output_name: str = None,
70
+ enable_design=True
75
71
  ):
76
72
  """
77
73
  Start ivoryOS app server.
@@ -85,22 +81,43 @@ def run(module=None, host="0.0.0.0", port=None, debug=None, llm_server=None, mod
85
81
  :param config: config class, defaults to None
86
82
  :param logger: logger name of list of logger names, defaults to None
87
83
  :param logger_output_name: log file save name of logger, defaults to None, and will use "default.log"
88
-
84
+ :param enable_design: enable design canvas, database and workflow execution
85
+ :param stream_address:
89
86
  """
90
87
  app = create_app(config_class=config or get_config()) # Create app instance using factory function
91
88
 
89
+ app.register_blueprint(main, url_prefix=url_prefix)
90
+ app.register_blueprint(auth, url_prefix=url_prefix)
91
+ app.register_blueprint(control, url_prefix=url_prefix)
92
+
93
+ if enable_design:
94
+ app.register_blueprint(design, url_prefix=url_prefix)
95
+ app.register_blueprint(database, url_prefix=url_prefix)
96
+
97
+ plugins = load_plugins(app, socketio)
98
+
99
+ def inject_nav_config():
100
+ """Make NAV_CONFIG available globally to all templates."""
101
+ return dict(
102
+ enable_design=enable_design,
103
+ plugins=plugins,
104
+ )
105
+
106
+ app.context_processor(inject_nav_config)
92
107
  port = port or int(os.environ.get("PORT", 8000))
93
108
  debug = debug if debug is not None else app.config.get('DEBUG', True)
94
109
 
95
110
  app.config["LOGGERS"] = logger
96
- app.config["LOGGERS_PATH"] = logger_output_name or app.config["LOGGERS_PATH"] # default.log
111
+ app.config["LOGGERS_PATH"] = logger_output_name or app.config["LOGGERS_PATH"] # default.log
97
112
  logger_path = os.path.join(app.config["OUTPUT_FOLDER"], app.config["LOGGERS_PATH"])
98
113
 
99
114
  if module:
100
115
  app.config["MODULE"] = module
101
116
  app.config["OFF_LINE"] = False
102
117
  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)
118
+ # global_config.heinsight = HeinsightAPI("http://127.0.0.1:8080")
119
+ global_config.deck_snapshot = utils.create_deck_snapshot(global_config.deck,
120
+ output_path=app.config["DUMMY_DECK"], save=True)
104
121
  # global_config.runner = ScriptRunner(globals())
105
122
  else:
106
123
  app.config["OFF_LINE"] = True
@@ -120,3 +137,22 @@ def run(module=None, host="0.0.0.0", port=None, debug=None, llm_server=None, mod
120
137
  for log in logger:
121
138
  utils.start_logger(socketio, log_filename=logger_path, logger_name=log)
122
139
  socketio.run(app, host=host, port=port, debug=debug, use_reloader=False, allow_unsafe_werkzeug=True)
140
+ # return app
141
+
142
+
143
+ def load_plugins(app, socketio):
144
+ """
145
+ Dynamically load installed plugins and attach Flask-SocketIO.
146
+ """
147
+ plugin_names = []
148
+ for entry_point in entry_points().get("ivoryos.plugins", []):
149
+ plugin = entry_point.load()
150
+
151
+ # If the plugin has an `init_socketio()` function, pass socketio
152
+ if hasattr(plugin, 'init_socketio'):
153
+ plugin.init_socketio(socketio)
154
+
155
+ plugin_names.append(entry_point.name)
156
+ app.register_blueprint(getattr(plugin, entry_point.name), url_prefix=f"{url_prefix}/{entry_point.name}")
157
+
158
+ return plugin_names
@@ -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>
@@ -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
 
@@ -99,25 +100,29 @@ def experiment_builder(instrument=None):
99
100
 
100
101
  deck_list = utils.available_pseudo_deck(current_app.config["DUMMY_DECK"])
101
102
 
102
- functions = []
103
+ functions = {}
103
104
  if deck:
104
105
  deck_variables = global_config.deck_snapshot.keys()
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
108
- 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:
109
113
  if instrument in ['if', 'while', 'variable', 'wait', 'repeat']:
110
- forms = create_builtin_form(instrument, autofill=autofill, script=script)
114
+ forms = create_builtin_form(instrument, script=script)
111
115
  else:
112
116
  if deck:
113
- function_metadata = global_config.deck_snapshot.get(instrument, {})
117
+ functions = global_config.deck_snapshot.get(instrument, {})
114
118
  elif pseudo_deck:
115
- function_metadata = pseudo_deck.get(instrument, {})
116
- 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()}
117
122
  forms = create_form_from_pseudo(pseudo=functions, autofill=autofill, script=script)
118
123
  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)
124
+ # all_kwargs = request.form.copy()
125
+ method_name = request.form.get("hidden_name", None)
121
126
  # if method_name is not None:
122
127
  form = forms.get(method_name)
123
128
  kwargs = {field.name: field.data for field in form if field.name != 'csrf_token'}
@@ -125,29 +130,15 @@ def experiment_builder(instrument=None):
125
130
  if form and form.validate_on_submit():
126
131
  function_name = kwargs.pop("hidden_name")
127
132
  save_data = kwargs.pop('return', '')
128
- variable_kwargs = {}
129
- variable_kwargs_types = {}
130
133
 
131
- try:
132
- variable_kwargs, variable_kwargs_types = utils.find_variable_in_script(script, kwargs)
133
-
134
- for name in variable_kwargs.keys():
135
- del kwargs[name]
136
- primitive_arg_types = utils.get_arg_type(kwargs, functions[function_name])
137
-
138
- except:
139
- primitive_arg_types = utils.get_arg_type(kwargs, functions[function_name])
140
-
141
- kwargs.update(variable_kwargs)
142
- arg_types = {}
143
- arg_types.update(variable_kwargs_types)
144
- arg_types.update(primitive_arg_types)
145
- all_kwargs.update(variable_kwargs)
134
+ primitive_arg_types = utils.get_arg_type(kwargs, functions[function_name])
146
135
 
136
+ script.eval_list(kwargs, primitive_arg_types)
137
+ kwargs = script.validate_variables(kwargs)
147
138
  action = {"instrument": instrument, "action": function_name,
148
- "args": {name: arg for (name, arg) in kwargs.items()},
139
+ "args": kwargs,
149
140
  "return": save_data,
150
- 'arg_types': arg_types}
141
+ 'arg_types': primitive_arg_types}
151
142
  script.add_action(action=action)
152
143
  else:
153
144
  flash(form.errors)
@@ -155,9 +146,13 @@ def experiment_builder(instrument=None):
155
146
  elif request.method == 'POST' and "builtin_name" in request.form:
156
147
  kwargs = {field.name: field.data for field in forms if field.name != 'csrf_token'}
157
148
  if forms.validate_on_submit():
149
+ # print(kwargs)
158
150
  logic_type = kwargs.pop('builtin_name')
159
151
  if 'variable' in kwargs:
160
- script.add_variable(**kwargs)
152
+ try:
153
+ script.add_variable(**kwargs)
154
+ except ValueError:
155
+ flash("Invalid variable type")
161
156
  else:
162
157
  script.add_logic_action(logic_type=logic_type, **kwargs)
163
158
  else:
@@ -168,12 +163,13 @@ def experiment_builder(instrument=None):
168
163
  autofill = not autofill
169
164
  forms = create_form_from_pseudo(functions, autofill=autofill, script=script)
170
165
  session['autofill'] = autofill
166
+
171
167
  utils.post_script_file(script)
172
- design_buttons = [create_action_button(i) for i in script.currently_editing_script]
168
+ design_buttons = create_action_button(script)
173
169
  return render_template('experiment_builder.html', off_line=off_line, instrument=instrument, history=deck_list,
174
170
  script=script, defined_variables=deck_variables,
175
171
  local_variables=global_config.defined_variables,
176
- functions=functions, forms=forms, buttons=design_buttons, format_name=format_name,
172
+ forms=forms, buttons=design_buttons, format_name=format_name,
177
173
  use_llm=enable_llm)
178
174
 
179
175
 
@@ -250,10 +246,14 @@ def experiment_run():
250
246
  # module = current_app.config.get('MODULE', '')
251
247
  # deck = sys.modules[module] if module else None
252
248
  # 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}
249
+ design_buttons = {stype: create_action_button(script, stype) for stype in script.stypes}
254
250
  config_preview = []
255
251
  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'])
252
+ try:
253
+ exec_string = script.compile(current_app.config['SCRIPT_FOLDER'])
254
+ except ValueError as e:
255
+ flash(e.__str__())
256
+ return redirect(url_for("design.experiment_builder"))
257
257
  # print(exec_string)
258
258
  config_file = request.args.get("filename")
259
259
  config = []
@@ -492,14 +492,20 @@ def edit_action(uuid: str):
492
492
  script = utils.get_script_file()
493
493
  action = script.find_by_uuid(uuid)
494
494
  session['edit_action'] = action
495
- if request.method == "POST":
495
+
496
+ if request.method == "POST" and action is not None:
497
+ forms = create_form_from_action(action, script=script)
496
498
  if "back" not in request.form:
497
- args = request.form.to_dict()
498
- save_as = args.pop('return', '')
499
- try:
500
- script.update_by_uuid(uuid=uuid, args=args, output=save_as)
501
- except Exception as e:
502
- flash(e.__str__())
499
+ kwargs = {field.name: field.data for field in forms if field.name != 'csrf_token'}
500
+ # print(kwargs)
501
+ if forms and forms.validate_on_submit():
502
+ save_as = kwargs.pop('return', '')
503
+ kwargs = script.validate_variables(kwargs)
504
+ # try:
505
+ script.update_by_uuid(uuid=uuid, args=kwargs, output=save_as)
506
+ # except Exception as e:
507
+ else:
508
+ flash(forms.errors)
503
509
  session.pop('edit_action')
504
510
  return redirect(url_for('design.experiment_builder'))
505
511
 
@@ -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 %}
@@ -139,12 +150,12 @@
139
150
  {{ format_name(name) }}
140
151
  </button>
141
152
  </h2>
153
+
142
154
  <div id="{{name}}" class="accordion-collapse collapse" data-bs-parent="#accordionActions">
143
155
  <div class="accordion-body">
144
156
  <form role="form" method='POST' name="add" id="add">
145
157
  <div class="form-group">
146
158
  {{ form.hidden_tag() }}
147
- {# {{ form.hidden_name() }}#}
148
159
  {% for field in form %}
149
160
  {% if field.type not in ['CSRFTokenField', 'HiddenField'] %}
150
161
  <div class="input-group mb-3">
@@ -161,6 +172,8 @@
161
172
  {% endfor %}
162
173
  </div>
163
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
+
164
177
  </form>
165
178
 
166
179
 
@@ -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">
@@ -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,18 @@
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
+
48
51
  <li class="nav-item">
49
52
  <a class="nav-link" href="{{ url_for('control.deck_controllers') }}">Devices</a></li>
50
53
  </li>
@@ -54,6 +57,13 @@
54
57
  <li class="nav-item">
55
58
  <a class="nav-link" href="{{ url_for('main.help_info') }}">About</a>
56
59
  </li>
60
+ {% if plugins %}
61
+ {% for plugin in plugins %}
62
+ <li class="nav-item">
63
+ <a class="nav-link" href="{{ url_for(plugin+'.main') }}">{{ plugin.capitalize() }}</a></li>
64
+ </li>
65
+ {% endfor %}
66
+ {% endif %}
57
67
  </ul>
58
68
  <ul class="navbar-nav ms-auto">
59
69