ivoryos 0.1.20__py3-none-any.whl → 0.1.22__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.

@@ -1,36 +1,105 @@
1
- $(document).ready(function(){
2
- function slideout(){
3
- setTimeout(function(){
4
- $("#response").slideUp("slow", function () {});
5
- }, 2000);
6
- }
1
+ $(document).ready(function () {
2
+ let dropTargetId = ""; // Store the ID of the drop target
3
+
4
+ $("#list ul").sortable({
5
+ cancel: ".unsortable",
6
+ opacity: 0.8,
7
+ cursor: "move",
8
+ placeholder: "drop-placeholder",
9
+ update: function () {
10
+ var item_order = [];
11
+ $("ul.reorder li").each(function () {
12
+ item_order.push($(this).attr("id"));
13
+ });
14
+ var order_string = "order=" + item_order.join(",");
15
+
16
+ $.ajax({
17
+ method: "POST",
18
+ url: updateListUrl,
19
+ data: order_string,
20
+ cache: false,
21
+ success: function (data) {
22
+ $("#response").html(data);
23
+ $("#response").slideDown("slow");
24
+ slideout();
25
+ }
26
+ });
27
+ }
28
+ });
29
+
30
+ // Make Entire Accordion Item Draggable
31
+ $(".accordion-item").on("dragstart", function (event) {
32
+ let formHtml = $(this).find(".accordion-body").html(); // Get the correct form
33
+ event.originalEvent.dataTransfer.setData("form", formHtml || ""); // Store form HTML
34
+ event.originalEvent.dataTransfer.setData("action", $(this).find(".draggable-action").data("action"));
35
+ event.originalEvent.dataTransfer.setData("id", $(this).find(".draggable-action").attr("id"));
36
+
37
+ $(this).addClass("dragging");
38
+ });
39
+
40
+
41
+ $("#list ul, .canvas").on("dragover", function (event) {
42
+ event.preventDefault();
43
+ let $target = $(event.target).closest("li");
44
+
45
+ // If we're over a valid <li> element in the list
46
+ if ($target.length) {
47
+ dropTargetId = $target.attr("id") || ""; // Store the drop target ID
48
+
49
+ $(".drop-placeholder").remove(); // Remove existing placeholders
50
+ $("<li class='drop-placeholder'></li>").insertBefore($target); // Insert before the target element
51
+ } else if (!$("#list ul").children().length && $(this).hasClass("canvas")) {
52
+ $(".drop-placeholder").remove(); // Remove any placeholder
53
+ // $("#list ul").append("<li class='drop-placeholder'></li>"); // Append placeholder to canvas
54
+ } else {
55
+ dropTargetId = ""; // Append placeholder to canvas
56
+ }
57
+ });
7
58
 
8
- $("#response").hide();
9
-
10
- $(function() {
11
- $("#list ul").sortable({
12
- cancel: ".unsortable",
13
- opacity: 0.8,
14
- cursor: 'move',
15
- update: function() {
16
- var item_order = [];
17
- $('ul.reorder li').each(function() {
18
- item_order.push($(this).attr("id"));
19
- });
20
- var order_string = 'order=' + item_order.join(',');
21
-
22
- $.ajax({
23
- method: "POST",
24
- url: updateListUrl, // updateListUrl should be set dynamically in your HTML template
25
- data: order_string,
26
- cache: false,
27
- success: function(data){
28
- $("#response").html(data);
29
- $("#response").slideDown('slow');
30
- slideout();
31
- }
32
- });
33
- }
34
- });
59
+ $("#list ul, .canvas").on("dragleave", function () {
60
+ $(".drop-placeholder").remove(); // Remove placeholder on leave
35
61
  });
62
+
63
+ $("#list ul, .canvas").on("drop", function (event) {
64
+ event.preventDefault();
65
+
66
+ var actionName = event.originalEvent.dataTransfer.getData("action");
67
+ var actionId = event.originalEvent.dataTransfer.getData("id");
68
+ var formHtml = event.originalEvent.dataTransfer.getData("form"); // Retrieve form HTML
69
+ let listLength = $("ul.reorder li").length;
70
+ dropTargetId = dropTargetId || listLength + 1; // Assign a "last" ID or unique identifier
71
+ $(".drop-placeholder").remove();
72
+ // Trigger the modal with the appropriate action
73
+ triggerModal(formHtml, actionName, actionId, dropTargetId);
74
+
75
+ });
76
+
77
+ // Function to trigger the modal (same for both buttons and accordion items)
78
+ function triggerModal(formHtml, actionName, actionId, dropTargetId) {
79
+ if (formHtml && formHtml.trim() !== "") {
80
+ var $form = $("<div>").html(formHtml); // Convert HTML string to jQuery object
81
+
82
+ // Create a hidden input for the drop target ID
83
+ var $hiddenInput = $("<input>")
84
+ .attr("type", "hidden")
85
+ .attr("name", "drop_target_id")
86
+ .attr("id", "dropTargetInput")
87
+ .val(dropTargetId);
88
+
89
+ // Insert before the submit button
90
+ $form.find("button[type='submit']").before($hiddenInput);
91
+
92
+ $("#modalFormFields").empty().append($form.children());
93
+ $("#dropModal").modal("show"); // Show modal
94
+
95
+ // Store and display drop target ID in the modal
96
+ $("#modalDropTarget").text(dropTargetId || "N/A");
97
+
98
+ $("#modalFormFields").data("action-id", actionId);
99
+ $("#modalFormFields").data("action-name", actionName);
100
+ $("#modalFormFields").data("drop-target-id", dropTargetId);
101
+ } else {
102
+ console.error("Form HTML is undefined or empty!");
103
+ }
104
+ }
36
105
  });
ivoryos/static/style.css CHANGED
@@ -199,4 +199,13 @@ hr.vertical {
199
199
  color: white;
200
200
  font-size: 24px;
201
201
  padding-top: 20%;
202
+ }
203
+ .drop-placeholder {
204
+ height: 2px !important; /* Keep it very thin */
205
+ min-height: 2px !important;
206
+ margin: 0 !important;
207
+ padding: 0 !important;
208
+ background: rgba(0, 0, 0, 0.2); /* Slight visibility */
209
+ border-radius: 2px;
210
+ list-style: none; /* Remove any default list styling */
202
211
  }
@@ -46,17 +46,20 @@
46
46
  <li class="nav-item">
47
47
  <a class="nav-link" href="{{ url_for('design.experiment_run') }}">Compile/Run</a>
48
48
  </li>
49
+ <li class="nav-item">
50
+ <a class="nav-link" href="{{ url_for('database.list_workflows') }}">Data</a>
51
+ </li>
49
52
  {% endif %}
50
53
 
51
54
  <li class="nav-item">
52
55
  <a class="nav-link" href="{{ url_for('control.deck_controllers') }}">Devices</a></li>
53
56
  </li>
54
- <li class="nav-item">
55
- <a class="nav-link" href="{{ url_for('control.controllers_home') }}">Temp Devices</a></li>
56
- </li>
57
- <li class="nav-item">
58
- <a class="nav-link" href="{{ url_for('main.help_info') }}">About</a>
59
- </li>
57
+ {# <li class="nav-item">#}
58
+ {# <a class="nav-link" href="{{ url_for('control.controllers_home') }}">Temp Devices</a></li>#}
59
+ {# </li>#}
60
+ {# <li class="nav-item">#}
61
+ {# <a class="nav-link" href="{{ url_for('main.help_info') }}">About</a>#}
62
+ {# </li>#}
60
63
  {% if plugins %}
61
64
  {% for plugin in plugins %}
62
65
  <li class="nav-item">
@@ -0,0 +1,57 @@
1
+ # import argparse
2
+ import os
3
+
4
+ # import requests
5
+
6
+ # session = requests.Session()
7
+
8
+
9
+ # Function to create class and methods dynamically
10
+ def create_function(url, class_name, functions):
11
+ class_template = f'class {class_name.capitalize()}:\n url = "{url}ivoryos/backend_control/deck.{class_name}"\n'
12
+
13
+ for function_name, details in functions.items():
14
+ signature = details['signature']
15
+ docstring = details.get('docstring', '')
16
+
17
+ # Creating the function definition
18
+ method = f' def {function_name}{signature}:\n'
19
+ if docstring:
20
+ method += f' """{docstring}"""\n'
21
+
22
+ # Generating the session.post code for sending data
23
+ method += ' return session.post(self.url, data={'
24
+ method += f'"hidden_name": "{function_name}"'
25
+
26
+ # Extracting the parameters from the signature string for the data payload
27
+ param_str = signature[6:-1] # Remove the "(self" and final ")"
28
+ params = [param.strip() for param in param_str.split(',')] if param_str else []
29
+
30
+ for param in params:
31
+ param_name = param.split(':')[0].strip() # Split on ':' and get parameter name
32
+ method += f', "{param_name}": {param_name}'
33
+
34
+ method += '}).json()\n'
35
+ class_template += method + '\n'
36
+
37
+ return class_template
38
+
39
+ # Function to export the generated classes to a Python script
40
+ def export_to_python(class_definitions, path):
41
+ with open(os.path.join(path, "generated_proxy.py"), 'w') as f:
42
+ # Writing the imports at the top of the script
43
+ f.write('import requests\n\n')
44
+ f.write('session = requests.Session()\n\n')
45
+
46
+ # Writing each class definition to the file
47
+ for class_name, class_def in class_definitions.items():
48
+ f.write(class_def)
49
+ f.write('\n')
50
+
51
+ # Creating instances of the dynamically generated classes
52
+ for class_name in class_definitions.keys():
53
+ instance_name = class_name.lower() # Using lowercase for instance names
54
+ f.write(f'{instance_name} = {class_name.capitalize()}()\n')
55
+
56
+
57
+
@@ -44,9 +44,12 @@ class Script(db.Model):
44
44
  id_order = db.Column(JSONType, nullable=True)
45
45
  editing_type = db.Column(db.String(50), nullable=True)
46
46
  author = db.Column(db.String(50), nullable=False)
47
+ # registered = db.Column(db.Boolean, nullable=True, default=False)
47
48
 
48
49
  def __init__(self, name=None, deck=None, status=None, script_dict: dict = None, id_order: dict = None,
49
- time_created=None, last_modified=None, editing_type=None, author: str = None):
50
+ time_created=None, last_modified=None, editing_type=None, author: str = None,
51
+ registered:bool=False,
52
+ ):
50
53
  if script_dict is None:
51
54
  script_dict = {"prep": [], "script": [], "cleanup": []}
52
55
  elif type(script_dict) is not dict:
@@ -73,6 +76,7 @@ class Script(db.Model):
73
76
  self.id_order = id_order
74
77
  self.editing_type = editing_type
75
78
  self.author = author
79
+ # self.r = registered
76
80
 
77
81
  def as_dict(self):
78
82
  dict = self.__dict__
@@ -196,28 +200,37 @@ class Script(db.Model):
196
200
  for i in self.stypes:
197
201
  self._sort(i)
198
202
 
199
- def add_action(self, action: dict):
203
+ def add_action(self, action: dict, insert_position=None):
200
204
  current_len = len(self.currently_editing_script)
201
205
  action_to_add = action.copy()
202
206
  action_to_add['id'] = current_len + 1
203
207
  action_to_add['uuid'] = uuid.uuid4().fields[-1]
204
208
  self.currently_editing_script.append(action_to_add)
205
- self.currently_editing_order.append(str(current_len + 1))
209
+ self._insert_action(insert_position, current_len)
206
210
  self.update_time_stamp()
207
211
 
208
- def add_variable(self, statement, variable, type):
212
+ def add_variable(self, statement, variable, type, insert_position=None):
209
213
  variable = self.validate_function_name(variable)
210
214
  convert_type = getattr(builtins, type)
211
215
  statement = convert_type(statement)
212
216
  current_len = len(self.currently_editing_script)
213
217
  uid = uuid.uuid4().fields[-1]
214
- action_list = [{"id": current_len + 1, "instrument": 'variable', "action": variable,
218
+ action = {"id": current_len + 1, "instrument": 'variable', "action": variable,
215
219
  "args": {"statement": 'None' if statement == '' else statement}, "return": '', "uuid": uid,
216
- "arg_types": {"statement": type}}]
217
- self.currently_editing_script.extend(action_list)
218
- self.currently_editing_order.extend([str(current_len + i + 1) for i in range(len(action_list))])
220
+ "arg_types": {"statement": type}}
221
+ self.currently_editing_script.append(action)
222
+ self._insert_action(insert_position, current_len)
219
223
  self.update_time_stamp()
220
224
 
225
+ def _insert_action(self, insert_position, current_len, action_len:int=1):
226
+
227
+ if insert_position is None:
228
+ self.currently_editing_order.extend([str(current_len + i + 1) for i in range(action_len)])
229
+ else:
230
+ index = int(insert_position) - 1
231
+ self.currently_editing_order[index:index] = [str(current_len + i + 1) for i in range(action_len)]
232
+ self.sort_actions()
233
+
221
234
  def get_added_variables(self):
222
235
  added_variables: Dict[str, str] = {action["action"]: action["arg_types"]["statement"] for action in
223
236
  self.currently_editing_script if action["instrument"] == "variable"}
@@ -251,7 +264,7 @@ class Script(db.Model):
251
264
  kwargs[key] = f"#{self.validate_function_name(value[1:])}"
252
265
  return kwargs
253
266
 
254
- def add_logic_action(self, logic_type: str, statement):
267
+ def add_logic_action(self, logic_type: str, statement, insert_position=None):
255
268
  current_len = len(self.currently_editing_script)
256
269
  uid = uuid.uuid4().fields[-1]
257
270
  logic_dict = {
@@ -291,7 +304,7 @@ class Script(db.Model):
291
304
  }
292
305
  action_list = logic_dict[logic_type]
293
306
  self.currently_editing_script.extend(action_list)
294
- self.currently_editing_order.extend([str(current_len + i + 1) for i in range(len(action_list))])
307
+ self._insert_action(insert_position, current_len, len(action_list))
295
308
  self.update_time_stamp()
296
309
 
297
310
  def delete_action(self, id: int):
@@ -446,7 +459,8 @@ class Script(db.Model):
446
459
  configure = [param + f":{param_type}" if not param_type == "any" else "" for param, param_type in
447
460
  config_type.items()]
448
461
 
449
- function_header = f"def {run_name}_{stype}("
462
+ script_type = f"_{stype}" if stype != "script" else ""
463
+ function_header = f"def {run_name}{script_type}("
450
464
 
451
465
  if stype == "script":
452
466
  function_header += ", ".join(configure)
@@ -492,6 +506,9 @@ class Script(db.Model):
492
506
  return f"{self.indent(indent_unit)}time.sleep({statement})", indent_unit
493
507
  elif instrument == 'repeat':
494
508
  return self._process_repeat(indent_unit, action_name, statement, next_action)
509
+ #todo
510
+ # elif instrument == 'registered_workflows':
511
+ # return inspect.getsource(my_function)
495
512
  else:
496
513
  return self._process_instrument_action(indent_unit, instrument, action_name, args, save_data)
497
514
 
@@ -618,6 +635,44 @@ class Script(db.Model):
618
635
  for i in exec_string.values():
619
636
  s.write(f"\n\n\n{i}")
620
637
 
638
+ class WorkflowRun(db.Model):
639
+ __tablename__ = 'workflow_runs'
640
+
641
+ id = db.Column(db.Integer, primary_key=True)
642
+ name = db.Column(db.String(128), nullable=False)
643
+ platform = db.Column(db.String(128), nullable=False)
644
+ start_time = db.Column(db.DateTime, default=datetime.now())
645
+ end_time = db.Column(db.DateTime)
646
+ data_path = db.Column(db.String(256))
647
+ steps = db.relationship(
648
+ 'WorkflowStep',
649
+ backref='workflow_runs',
650
+ cascade='all, delete-orphan',
651
+ passive_deletes=True
652
+ )
653
+ def as_dict(self):
654
+ dict = self.__dict__
655
+ dict.pop('_sa_instance_state', None)
656
+ return dict
657
+
658
+ class WorkflowStep(db.Model):
659
+ __tablename__ = 'workflow_steps'
660
+
661
+ id = db.Column(db.Integer, primary_key=True)
662
+ workflow_id = db.Column(db.Integer, db.ForeignKey('workflow_runs.id', ondelete='CASCADE'), nullable=False)
663
+
664
+ phase = db.Column(db.String(64), nullable=False) # 'prep', 'main', 'cleanup'
665
+ repeat_index = db.Column(db.Integer, default=0) # Only applies to 'main' phase
666
+ step_index = db.Column(db.Integer, default=0)
667
+ method_name = db.Column(db.String(128), nullable=False)
668
+ start_time = db.Column(db.DateTime)
669
+ end_time = db.Column(db.DateTime)
670
+ run_error = db.Column(db.Boolean, default=False)
671
+
672
+ def as_dict(self):
673
+ dict = self.__dict__
674
+ dict.pop('_sa_instance_state', None)
675
+ return dict
621
676
 
622
677
  if __name__ == "__main__":
623
678
  a = Script()
ivoryos/utils/form.py CHANGED
@@ -7,6 +7,10 @@ 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 Script
11
+ from ivoryos.utils.global_config import GlobalConfig
12
+
13
+ global_config = GlobalConfig()
10
14
 
11
15
  def find_variable(data, script):
12
16
  """
@@ -345,6 +349,13 @@ def create_form_from_action(action: dict, script=None, design=True):
345
349
  setattr(DynamicForm, 'return', return_value)
346
350
  return DynamicForm()
347
351
 
352
+ def create_all_builtin_forms(script):
353
+ all_builtin_forms = {}
354
+ for logic_name in ['if', 'while', 'variable', 'wait', 'repeat']:
355
+ # signature = info.get('signature', {})
356
+ form_class = create_builtin_form(logic_name, script)
357
+ all_builtin_forms[logic_name] = form_class()
358
+ return all_builtin_forms
348
359
 
349
360
  def create_builtin_form(logic_type, script):
350
361
  """
@@ -379,14 +390,51 @@ def create_builtin_form(logic_type, script):
379
390
  render_kw=render_kwargs)
380
391
  type_field = SelectField(
381
392
  'Select Input Type',
382
- choices=[('int', 'Integer'), ('float', 'Float'), ('str', 'String')],
393
+ choices=[('int', 'Integer'), ('float', 'Float'), ('str', 'String'), ('bool', 'Boolean')],
383
394
  default='str' # Optional default value
384
395
  )
385
396
  setattr(BuiltinFunctionForm, "variable", variable_field)
386
397
  setattr(BuiltinFunctionForm, "type", type_field)
387
398
  hidden_field = HiddenField(name=f'builtin_name', render_kw={"value": f'{logic_type}'})
388
399
  setattr(BuiltinFunctionForm, "builtin_name", hidden_field)
389
- return BuiltinFunctionForm()
400
+ return BuiltinFunctionForm
401
+
402
+
403
+ def get_method_from_workflow(function_string):
404
+ """Creates a function from a string and assigns it a new name."""
405
+
406
+ namespace = {}
407
+ exec(function_string, globals(), namespace) # Execute the string in a safe namespace
408
+ func_name = next(iter(namespace))
409
+ # Get the function name dynamically
410
+ return namespace[func_name]
411
+
412
+
413
+ def create_workflow_forms(script, autofill: bool = False, design: bool = False):
414
+ workflow_forms = {}
415
+ functions = {}
416
+ class RegisteredWorkflows:
417
+ pass
418
+
419
+ deck_name = script.deck
420
+ workflows = Script.query.filter(Script.deck==deck_name, Script.name != script.name).all()
421
+ for workflow in workflows:
422
+ compiled_strs = workflow.compile().get('script', "")
423
+ method = get_method_from_workflow(compiled_strs)
424
+ functions[workflow.name] = dict(signature=inspect.signature(method), docstring=inspect.getdoc(method))
425
+ setattr(RegisteredWorkflows, workflow.name, method)
426
+
427
+ form_class = create_form_for_method(method, autofill, script, design)
428
+
429
+ hidden_method_name = HiddenField(name=f'hidden_name', description="",
430
+ render_kw={"value": f'{workflow.name}'})
431
+ if design:
432
+ return_value = StringField(label='Save value as', render_kw={"placeholder": "Optional"})
433
+ setattr(form_class, 'return', return_value)
434
+ setattr(form_class, 'workflow_name', hidden_method_name)
435
+ workflow_forms[workflow.name] = form_class()
436
+ global_config.registered_workflows = RegisteredWorkflows
437
+ return workflow_forms, functions
390
438
 
391
439
 
392
440
  def create_action_button(script, stype=None):
@@ -8,6 +8,7 @@ class GlobalConfig:
8
8
  if cls._instance is None:
9
9
  cls._instance = super(GlobalConfig, cls).__new__(cls, *args, **kwargs)
10
10
  cls._instance._deck = None
11
+ cls._instance._registered_workflows = None
11
12
  cls._instance._agent = None
12
13
  cls._instance._defined_variables = {}
13
14
  cls._instance._api_variables = set()
@@ -24,6 +25,15 @@ class GlobalConfig:
24
25
  if self._deck is None:
25
26
  self._deck = value
26
27
 
28
+ @property
29
+ def registered_workflows(self):
30
+ return self._registered_workflows
31
+
32
+ @registered_workflows.setter
33
+ def registered_workflows(self, value):
34
+ if self._registered_workflows is None:
35
+ self._registered_workflows = value
36
+
27
37
 
28
38
  @property
29
39
  def deck_snapshot(self):
@@ -162,7 +162,7 @@ can you also help find the default value you can't find the info from my request
162
162
 
163
163
  if __name__ == "__main__":
164
164
  from pprint import pprint
165
- from example.sdl_example.abstract_sdl import deck
165
+ from example.abstract_sdl_example.abstract_sdl import deck
166
166
 
167
167
  from utils import parse_functions
168
168