ivoryos 1.0.3__py3-none-any.whl → 1.0.4__py3-none-any.whl

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

Potentially problematic release.


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

ivoryos/__init__.py CHANGED
@@ -12,7 +12,7 @@ from ivoryos.routes.design.design import design, socketio
12
12
  from ivoryos.routes.main.main import main
13
13
  # from ivoryos.routes.monitor.monitor import monitor
14
14
  from ivoryos.utils import utils
15
- from ivoryos.utils.db_models import db
15
+ from ivoryos.utils.db_models import db, User
16
16
  from ivoryos.utils.global_config import GlobalConfig
17
17
  from ivoryos.utils.script_runner import ScriptRunner
18
18
  from ivoryos.version import __version__ as ivoryos_version
@@ -40,6 +40,15 @@ app.register_blueprint(control, url_prefix=url_prefix)
40
40
  app.register_blueprint(design, url_prefix=url_prefix)
41
41
  app.register_blueprint(database, url_prefix=url_prefix)
42
42
 
43
+ @login_manager.user_loader
44
+ def load_user(user_id):
45
+ """
46
+ This function is called by Flask-Login on every request to get the
47
+ current user object from the user ID stored in the session.
48
+ """
49
+ # The correct implementation is to fetch the user from the database.
50
+ return db.session.get(User, user_id)
51
+
43
52
 
44
53
  def create_app(config_class=None):
45
54
  """
@@ -85,8 +94,8 @@ def run(module=None, host="0.0.0.0", port=None, debug=None, llm_server=None, mod
85
94
  logger: Union[str, list] = None,
86
95
  logger_output_name: str = None,
87
96
  enable_design: bool = True,
88
- blueprint_plugins: Union[list[Blueprint], Blueprint] = [],
89
- exclude_names: list[str] = [],
97
+ blueprint_plugins: Union[list, Blueprint] = [],
98
+ exclude_names: list = [],
90
99
  ):
91
100
  """
92
101
  Start ivoryOS app server.
@@ -101,8 +110,8 @@ def run(module=None, host="0.0.0.0", port=None, debug=None, llm_server=None, mod
101
110
  :param logger: logger name of list of logger names, defaults to None
102
111
  :param logger_output_name: log file save name of logger, defaults to None, and will use "default.log"
103
112
  :param enable_design: enable design canvas, database and workflow execution
104
- :param blueprint_plugins: custom Blueprint pages
105
- :param exclude_names: module names to exclude from parsing
113
+ :param blueprint_plugins: Union[list[Blueprint], Blueprint] custom Blueprint pages
114
+ :param exclude_names: list[str] module names to exclude from parsing
106
115
  """
107
116
  app = create_app(config_class=config or get_config()) # Create app instance using factory function
108
117
 
@@ -153,6 +162,13 @@ def run(module=None, host="0.0.0.0", port=None, debug=None, llm_server=None, mod
153
162
  elif type(logger) is list:
154
163
  for log in logger:
155
164
  utils.start_logger(socketio, log_filename=logger_path, logger_name=log)
165
+
166
+ # in case Python 3.12 or higher doesn't log URL
167
+ if sys.version_info >= (3, 12):
168
+ ip = utils.get_ip_address()
169
+ print(f"Server running at http://localhost:{port}")
170
+ if not ip == "127.0.0.1":
171
+ print(f"Server running at http://{ip}:{port}")
156
172
  socketio.run(app, host=host, port=port, debug=debug, use_reloader=False, allow_unsafe_werkzeug=True)
157
173
  # return app
158
174
 
@@ -175,7 +191,7 @@ def load_installed_plugins(app, socketio):
175
191
  return plugin_names
176
192
 
177
193
 
178
- def load_plugins(blueprints: Union[list[Blueprint], Blueprint], app, socketio):
194
+ def load_plugins(blueprints: Union[list, Blueprint], app, socketio):
179
195
  """
180
196
  Dynamically load installed plugins and attach Flask-SocketIO.
181
197
  """
@@ -69,16 +69,18 @@ def signup():
69
69
  if request.method == 'POST':
70
70
  username = request.form.get('username')
71
71
  password = request.form.get('password')
72
- salt = bcrypt.gensalt()
73
- hashed = bcrypt.hashpw(password.encode('utf-8'), salt)
74
- user = User(username, hashed)
75
- try:
76
- db.session.add(user)
77
- db.session.commit()
78
- return redirect(url_for('auth.login'))
79
- except Exception:
80
- flash("username exists :(", "error")
72
+
73
+ # Query the database to see if the user already exists.
74
+ existing_user = User.query.filter_by(username=username).first()
75
+
76
+ if existing_user:
77
+ flash("User already exists :(", "error")
81
78
  return render_template('signup.html'), 409
79
+ hashed = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt())
80
+ user = User(username, hashed)
81
+ db.session.add(user)
82
+ db.session.commit()
83
+ return redirect(url_for('auth.login'))
82
84
  return render_template('signup.html')
83
85
 
84
86
 
@@ -92,9 +94,4 @@ def logout():
92
94
  """
93
95
  logout_user()
94
96
  session.clear()
95
- return redirect(url_for('auth.login'))
96
-
97
-
98
- @login_manager.user_loader
99
- def load_user(username):
100
- return User(username, password=None)
97
+ return redirect(url_for('auth.login'))
@@ -20,7 +20,7 @@ def edit_workflow(script_name:str):
20
20
 
21
21
  :param script_name: script name
22
22
  :type script_name: str
23
- :status 302: redirect to :http:get:`/ivoryos/experiment/build/`
23
+ :status 302: redirect to :http:get:`/ivoryos/design/script/`
24
24
  """
25
25
  row = Script.query.get(script_name)
26
26
  script = Script(**row.as_dict())
@@ -244,7 +244,7 @@ def get_workflow_steps(workflow_id:int):
244
244
  .. http:get:: /database/workflows/<int:workflow_id>
245
245
 
246
246
  """
247
- workflow = WorkflowRun.query.get_or_404(workflow_id)
247
+ workflow = db.session.get(WorkflowRun, workflow_id)
248
248
  steps = WorkflowStep.query.filter_by(workflow_id=workflow_id).order_by(WorkflowStep.start_time).all()
249
249
 
250
250
  # Use full objects for template rendering
@@ -120,7 +120,7 @@ def experiment_builder(instrument=None):
120
120
  if deck and script.deck is None:
121
121
  script.deck = os.path.splitext(os.path.basename(deck.__file__))[
122
122
  0] if deck.__name__ == "__main__" else deck.__name__
123
- script.sort_actions()
123
+ # script.sort_actions()
124
124
 
125
125
  pseudo_deck_name = session.get('pseudo_deck', '')
126
126
  pseudo_deck_path = os.path.join(current_app.config["DUMMY_DECK"], pseudo_deck_name)
@@ -202,11 +202,11 @@ def experiment_builder(instrument=None):
202
202
  logic_type = kwargs.pop('builtin_name')
203
203
  if 'variable' in kwargs:
204
204
  try:
205
- script.add_variable(**kwargs, insert_position=insert_position)
205
+ script.add_variable(insert_position=insert_position, **kwargs)
206
206
  except ValueError:
207
207
  flash("Invalid variable type")
208
208
  else:
209
- script.add_logic_action(logic_type=logic_type, **kwargs, insert_position=insert_position)
209
+ script.add_logic_action(logic_type=logic_type, insert_position=insert_position, **kwargs)
210
210
  else:
211
211
  flash(form.errors)
212
212
  elif request.method == 'POST' and "workflow_name" in request.form:
@@ -240,6 +240,10 @@ def experiment_builder(instrument=None):
240
240
  forms = create_form_from_pseudo(functions, autofill=autofill, script=script)
241
241
 
242
242
  utils.post_script_file(script)
243
+
244
+ exec_string = script.python_script if script.python_script else script.compile(current_app.config['SCRIPT_FOLDER'])
245
+ session['python_code'] = exec_string
246
+
243
247
  design_buttons = create_action_button(script)
244
248
  return render_template('experiment_builder.html', off_line=off_line, instrument=instrument, history=deck_list,
245
249
  script=script, defined_variables=deck_variables,
@@ -313,7 +317,8 @@ def experiment_run():
313
317
  """
314
318
  deck = global_config.deck
315
319
  script = utils.get_script_file()
316
- script.sort_actions()
320
+
321
+ # script.sort_actions() # handled in update list
317
322
  off_line = current_app.config["OFF_LINE"]
318
323
  deck_list = utils.import_history(os.path.join(current_app.config["OUTPUT_FOLDER"], 'deck_history.txt'))
319
324
  # if not off_line and deck is None:
@@ -345,7 +350,7 @@ def experiment_run():
345
350
  if filename:
346
351
  # config_preview = list(csv.DictReader(open(os.path.join(current_app.config['CSV_FOLDER'], filename))))
347
352
  config = list(csv.DictReader(open(os.path.join(current_app.config['CSV_FOLDER'], filename))))
348
- config_preview = config[1:6]
353
+ config_preview = config[1:]
349
354
  arg_type = config.pop(0) # first entry is types
350
355
  try:
351
356
  for key, func_str in exec_string.items():
@@ -439,14 +444,24 @@ def toggle_script_type(stype=None):
439
444
  return redirect(url_for('design.experiment_builder'))
440
445
 
441
446
 
442
- @design.route("/updateList", methods=['GET', 'POST'])
447
+ @design.route("/updateList", methods=['POST'])
443
448
  @login_required
444
449
  def update_list():
445
450
  order = request.form['order']
446
451
  script = utils.get_script_file()
447
452
  script.currently_editing_order = order.split(",", len(script.currently_editing_script))
453
+ script.sort_actions()
454
+ exec_string = script.compile(current_app.config['SCRIPT_FOLDER'])
448
455
  utils.post_script_file(script)
449
- return jsonify('Successfully Updated')
456
+ session['python_code'] = exec_string
457
+
458
+ return jsonify({'success': True})
459
+
460
+
461
+ @design.route("/toggle_show_code", methods=["POST"])
462
+ def toggle_show_code():
463
+ session["show_code"] = not session.get("show_code", False)
464
+ return redirect(request.referrer or url_for("design.experiment_builder"))
450
465
 
451
466
 
452
467
  # --------------------handle all the import/export and download/upload--------------------------
@@ -3,6 +3,23 @@
3
3
 
4
4
  {% block body %}
5
5
  {# overlay block for text-to-code gen #}
6
+ <style>
7
+ .code-overlay {
8
+ position: absolute;
9
+ top: 0;
10
+ right: -50%;
11
+ height: 100%;
12
+ width: 50%;
13
+ z-index: 100;
14
+ background: #f8f9fa;
15
+ box-shadow: -2px 0 6px rgba(0, 0, 0, 0.1);
16
+ transition: right 0.3s ease;
17
+ overflow-y: auto;
18
+ }
19
+ .code-overlay.show {
20
+ right: 0;
21
+ }
22
+ </style>
6
23
  <div id="overlay" class="overlay">
7
24
  <div>
8
25
  <h3 id="overlay-text">Generating design, please wait...</h3>
@@ -252,6 +269,13 @@
252
269
  <li class="nav-item"><a class="{{'nav-link active' if script.editing_type=='script' else 'nav-link'}}" aria-current="page" href="{{url_for('design.toggle_script_type', stype='script') }}">Experiment</a></li>
253
270
  <li class="nav-item"><a class="{{'nav-link active' if script.editing_type=='cleanup' else 'nav-link'}}" aria-current="page" href="{{url_for('design.toggle_script_type', stype='cleanup') }}">Clean up</a></li>
254
271
  </ul>
272
+ <form method="POST" action="{{ url_for('design.toggle_show_code') }}" class="ms-3">
273
+ <div class="form-check form-switch">
274
+ <input class="form-check-input" type="checkbox" id="showPythonCodeSwitch" name="show_code"
275
+ onchange="this.form.submit()" {% if session.get('show_code') %}checked{% endif %}>
276
+ <label class="form-check-label" for="showPythonCodeSwitch">Show Python Code</label>
277
+ </div>
278
+ </form>
255
279
 
256
280
  <div class="form-check form-switch ms-auto">
257
281
  <input class="form-check-input" type="checkbox" id="toggleLineNumbers" onchange="toggleLineNumbers()">
@@ -259,54 +283,74 @@
259
283
  </div>
260
284
 
261
285
  </div>
262
-
263
- <div class="canvas" droppable="true">
264
- <div class="collapse" id="info">
265
- <table class="table script-table">
266
- <tbody>
267
- <tr><th scope="row">Deck Name</th><td>{{script.deck}}</td></tr>
268
- <tr><th scope="row">Script Name</th><td>{{ script.name }}</td></tr>
269
- <tr>
270
- <th scope="row">Editing status <a role="button" data-bs-toggle="popover" data-bs-title="How to use:" data-bs-content="You can choose to disable editing, so the script is finalized and cannot be edited. Use save as to rename the script"><i class="bi bi-info-circle"></i></a></th>
271
- <td>{{script.status}}</td>
272
- </tr>
273
- <tr>
274
- <th scope="row">Output Values <a role="button" data-bs-toggle="popover" data-bs-title="How to use:" data-bs-content="This will be your output data. If the return data is not a value, it will save as None is the result file"><i class="bi bi-info-circle"></i></a></th>
275
- <td>
276
- {% for i in script.config_return()[1] %}
277
- <input type="checkbox">{{i}}
278
- {% endfor %}
279
- </td>
280
- </tr>
281
- <tr>
282
- <th scope="row">Config Variables <a role="button" data-bs-toggle="popover" data-bs-title="How to use:" data-bs-content="This shows variables you want to configure later using .csv file"><i class="bi bi-info-circle"></i></a></th>
283
- <td>
284
- <ul>
285
- {% for i in script.config("script")[0] %}
286
- <li>{{i}}</li>
286
+ <div class="canvas-wrapper position-relative">
287
+ <div class="canvas" droppable="true">
288
+ <div class="collapse" id="info">
289
+ <table class="table script-table">
290
+ <tbody>
291
+ <tr><th scope="row">Deck Name</th><td>{{script.deck}}</td></tr>
292
+ <tr><th scope="row">Script Name</th><td>{{ script.name }}</td></tr>
293
+ <tr>
294
+ <th scope="row">Editing status <a role="button" data-bs-toggle="popover" data-bs-title="How to use:" data-bs-content="You can choose to disable editing, so the script is finalized and cannot be edited. Use save as to rename the script"><i class="bi bi-info-circle"></i></a></th>
295
+ <td>{{script.status}}</td>
296
+ </tr>
297
+ <tr>
298
+ <th scope="row">Output Values <a role="button" data-bs-toggle="popover" data-bs-title="How to use:" data-bs-content="This will be your output data. If the return data is not a value, it will save as None is the result file"><i class="bi bi-info-circle"></i></a></th>
299
+ <td>
300
+ {% for i in script.config_return()[1] %}
301
+ <input type="checkbox">{{i}}
287
302
  {% endfor %}
288
- </ul>
289
- </td>
290
- </tr>
291
- </tbody>
292
- </table>
293
- </div>
294
- <div class="list-group" id="list" style="margin-top: 20px">
295
- <ul class="reorder">
296
- {% for button in buttons %}
297
- <li id="{{ button['id'] }}" style="list-style-type: none;">
298
- <span class="line-number d-none">{{ button['id'] }}.</span>
299
- <a href="{{ url_for('design.edit_action', uuid=button['uuid']) }}" type="button" class="btn btn-light" style="{{ button['style'] }}">{{ button['label'] }}</a>
300
- {% if not button["instrument"] in ["if","while","repeat"] %}
301
- <a href="{{ url_for('design.duplicate_action', id=button['id']) }}" type="button" class="btn btn-light"><span class="bi bi-copy"></span></a>
302
- {% endif %}
303
- <a href="{{ url_for('design.delete_action', id=button['id']) }}" type="button" class="btn btn-light"><span class="bi bi-trash"></span></a>
304
- </li>
305
- {% endfor %}
306
- </ul>
303
+ </td>
304
+ </tr>
305
+ <tr>
306
+ <th scope="row">Config Variables <a role="button" data-bs-toggle="popover" data-bs-title="How to use:" data-bs-content="This shows variables you want to configure later using .csv file"><i class="bi bi-info-circle"></i></a></th>
307
+ <td>
308
+ <ul>
309
+ {% for i in script.config("script")[0] %}
310
+ <li>{{i}}</li>
311
+ {% endfor %}
312
+ </ul>
313
+ </td>
314
+ </tr>
315
+ </tbody>
316
+ </table>
317
+ </div>
318
+
319
+ <div class="list-group" id="list" style="margin-top: 20px">
320
+ <ul class="reorder">
321
+ {% for button in buttons %}
322
+ <li id="{{ button['id'] }}" style="list-style-type: none;">
323
+ <span class="line-number d-none">{{ button['id'] }}.</span>
324
+ <a href="{{ url_for('design.edit_action', uuid=button['uuid']) }}" type="button" class="btn btn-light" style="{{ button['style'] }}">{{ button['label'] }}</a>
325
+ {% if not button["instrument"] in ["if","while","repeat"] %}
326
+ <a href="{{ url_for('design.duplicate_action', id=button['id']) }}" type="button" class="btn btn-light"><span class="bi bi-copy"></span></a>
327
+ {% endif %}
328
+ <a href="{{ url_for('design.delete_action', id=button['id']) }}" type="button" class="btn btn-light"><span class="bi bi-trash"></span></a>
329
+ </li>
330
+ {% endfor %}
331
+ </ul>
307
332
 
333
+ <!-- Python Code Overlay -->
334
+ <!-- Right side: Python code (conditionally shown) -->
335
+ <!-- Python Code Slide-Over Panel -->
336
+ {% if session.get('show_code') %}
337
+ <div id="pythonCodeOverlay" class="code-overlay bg-light border-start show">
338
+ <div class="overlay-header d-flex justify-content-between align-items-center px-3 py-2 border-bottom">
339
+ <strong>Python Code</strong>
340
+ <button class="btn btn-sm btn-outline-secondary" onclick="toggleCodeOverlay(false)">
341
+ <i class="bi bi-x-lg"></i>
342
+ </button>
343
+ </div>
344
+ <div class="overlay-content p-3">
345
+ {% for stype, script in session['python_code'].items() %}
346
+ <pre><code class="language-python">{{ script }}</code></pre>
347
+ {% endfor %}
348
+ <a href="{{ url_for('design.download', filetype='python') }}">Download <i class="bi bi-download"></i></a>
349
+ </div>
350
+ </div>
351
+ {% endif %}
352
+ </div>
308
353
  </div>
309
- </div>
310
354
  <div>
311
355
  <a class="btn btn-dark {{ 'disabled' if not script.name or script.status == "finalized" else ''}}" href="{{url_for('database.publish')}}">Quick Save</a>
312
356
  <a class="btn btn-dark " href="{{ url_for('design.experiment_run') }}">Compile and Run</a>
@@ -445,6 +489,17 @@
445
489
  }
446
490
  }
447
491
 
492
+ function toggleCodeOverlay() {
493
+ const overlay = document.getElementById("pythonCodeOverlay");
494
+ const toggleBtn = document.getElementById("codeToggleBtn");
495
+ overlay.classList.toggle("show");
496
+
497
+ // Change arrow icon
498
+ const icon = toggleBtn.querySelector("i");
499
+ icon.classList.toggle("bi-chevron-left");
500
+ icon.classList.toggle("bi-chevron-right");
501
+ }
502
+
448
503
  // Restore state on page load
449
504
  document.addEventListener('DOMContentLoaded', () => {
450
505
  const savedState = localStorage.getItem('showLineNumbers');