edda-framework 0.6.0__py3-none-any.whl → 0.8.0__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.
@@ -5,11 +5,14 @@ UI components for generating interactive Mermaid diagrams.
5
5
  import json
6
6
  from typing import Any
7
7
 
8
+ from edda.viewer_ui.theme import get_edge_color, get_mermaid_node_style, get_mermaid_style
8
9
  from edda.visualizer.ast_analyzer import WorkflowAnalyzer
9
10
  from edda.visualizer.mermaid_generator import MermaidGenerator
10
11
 
11
12
 
12
- def generate_interactive_mermaid(instance_id: str, history: list[dict[str, Any]]) -> str:
13
+ def generate_interactive_mermaid(
14
+ instance_id: str, history: list[dict[str, Any]], is_dark: bool = False
15
+ ) -> str:
13
16
  """
14
17
  Generate interactive Mermaid diagram from execution history.
15
18
 
@@ -22,6 +25,7 @@ def generate_interactive_mermaid(instance_id: str, history: list[dict[str, Any]]
22
25
  Args:
23
26
  instance_id: Workflow instance ID
24
27
  history: List of execution activity dictionaries
28
+ is_dark: Whether dark mode is enabled
25
29
 
26
30
  Returns:
27
31
  Mermaid flowchart diagram code with embedded click events
@@ -41,6 +45,20 @@ def generate_interactive_mermaid(instance_id: str, history: list[dict[str, Any]]
41
45
  activity_occurrences: dict[str, list[str]] = {} # Track activity name -> list of activity_ids
42
46
  activity_index = 0
43
47
 
48
+ # Status icon mapping
49
+ status_icons = {
50
+ "completed": "✅",
51
+ "running": "⏳",
52
+ "failed": "❌",
53
+ "waiting_for_event": "⏸️",
54
+ "waiting_for_timer": "⏱️",
55
+ "cancelled": "🚫",
56
+ "compensated": "🔄",
57
+ "compensating": "🔄",
58
+ "compensation_failed": "⚠️",
59
+ "event_received": "📨",
60
+ }
61
+
44
62
  for activity_data in history:
45
63
  activity_id = activity_data.get("activity_id")
46
64
  if not activity_id:
@@ -64,33 +82,13 @@ def generate_interactive_mermaid(instance_id: str, history: list[dict[str, Any]]
64
82
  label_suffix = f" ({occurrence_count}x)" if occurrence_count >= 2 else ""
65
83
 
66
84
  # Node label with status icon
67
- if status == "completed":
68
- label = f" {activity}{label_suffix}"
69
- style_color = "fill:#d4edda,stroke:#28a745,stroke-width:2px"
70
- elif status == "running":
71
- label = f"⏳ {activity}{label_suffix}"
72
- style_color = "fill:#fff3cd,stroke:#ffc107,stroke-width:2px"
73
- elif status == "failed":
74
- label = f"❌ {activity}{label_suffix}"
75
- style_color = "fill:#f8d7da,stroke:#dc3545,stroke-width:2px"
76
- elif status == "waiting_for_event":
77
- label = f"⏸️ {activity}{label_suffix}"
78
- style_color = "fill:#e7f3ff,stroke:#0066cc,stroke-width:2px"
79
- elif status == "cancelled":
80
- label = f"🚫 {activity}{label_suffix}"
81
- style_color = "fill:#fff4e6,stroke:#ff9800,stroke-width:2px"
82
- elif status == "compensated":
83
- label = f"🔄 {activity}{label_suffix}"
84
- style_color = "fill:#ffe6f0,stroke:#e91e63,stroke-width:2px,stroke-dasharray:5"
85
- elif status == "compensation_failed":
86
- label = f"⚠️ {activity}{label_suffix}"
87
- style_color = "fill:#ffcccc,stroke:#cc0000,stroke-width:2px"
88
- elif status == "event_received":
89
- label = f"📨 {activity}{label_suffix}"
90
- style_color = "fill:#e6f7ff,stroke:#1890ff,stroke-width:2px"
91
- else:
92
- label = f"{activity}{label_suffix}"
93
- style_color = "fill:#f0f0f0,stroke:#666,stroke-width:2px"
85
+ icon = status_icons.get(status, "")
86
+ label = f"{icon} {activity}{label_suffix}" if icon else f"{activity}{label_suffix}"
87
+
88
+ # Get themed style colors
89
+ style_color = get_mermaid_style(status, is_dark)
90
+ if status == "compensated":
91
+ style_color += ",stroke-dasharray:5"
94
92
 
95
93
  # Node definition
96
94
  lines.append(f' {node_id}["{label}"]')
@@ -133,6 +131,7 @@ class HybridMermaidGenerator(MermaidGenerator):
133
131
  compensations: dict[str, dict[str, Any]] | None = None,
134
132
  workflow_status: str = "running",
135
133
  activity_status_map: dict[str, str] | None = None,
134
+ is_dark: bool = False,
136
135
  ):
137
136
  """
138
137
  Initialize hybrid Mermaid generator.
@@ -143,6 +142,7 @@ class HybridMermaidGenerator(MermaidGenerator):
143
142
  compensations: Optional mapping of activity_id -> compensation info
144
143
  workflow_status: Status of the workflow instance (running, completed, failed, etc.)
145
144
  activity_status_map: Optional mapping of activity name to status (completed, failed, etc.)
145
+ is_dark: Whether dark mode is enabled
146
146
  """
147
147
  super().__init__()
148
148
  self.instance_id = instance_id
@@ -150,6 +150,7 @@ class HybridMermaidGenerator(MermaidGenerator):
150
150
  self.compensations = compensations or {}
151
151
  self.workflow_status = workflow_status
152
152
  self.activity_status_map = activity_status_map or {}
153
+ self.is_dark = is_dark
153
154
  self.activity_id_map: dict[str, str] = {} # Map activity name to activity_id for clicks
154
155
  self.activity_execution_counts: dict[str, int] = {} # Map activity name to execution count
155
156
  self.edge_counter = 0 # Track edge indices for linkStyle
@@ -200,9 +201,8 @@ class HybridMermaidGenerator(MermaidGenerator):
200
201
  # Add a visual separator (compensation execution section)
201
202
  comp_start_id = self._next_node_id()
202
203
  self.lines.append(f" {comp_start_id}[Compensation Execution]")
203
- self.lines.append(
204
- f" style {comp_start_id} fill:#fff3cd,stroke:#ffc107,stroke-width:2px"
205
- )
204
+ comp_header_style = get_mermaid_style("running", self.is_dark)
205
+ self.lines.append(f" style {comp_start_id} {comp_header_style}")
206
206
  self.lines.append(f" {end_id} -.->|rollback| {comp_start_id}")
207
207
  self.edge_counter += 1
208
208
 
@@ -217,9 +217,8 @@ class HybridMermaidGenerator(MermaidGenerator):
217
217
  label = f"🔄 {comp_name}"
218
218
 
219
219
  self.lines.append(f' {comp_node_id}["{label}"]')
220
- self.lines.append(
221
- f" style {comp_node_id} fill:#f8d7da,stroke:#dc3545,stroke-width:3px"
222
- )
220
+ comp_node_style = get_mermaid_style("compensating", self.is_dark)
221
+ self.lines.append(f" style {comp_node_id} {comp_node_style},stroke-width:3px")
223
222
 
224
223
  # Add click event for compensation activity
225
224
  self.lines.append(
@@ -233,10 +232,11 @@ class HybridMermaidGenerator(MermaidGenerator):
233
232
 
234
233
  prev_comp_id = comp_node_id
235
234
 
236
- # Add linkStyle for executed edges (green color)
235
+ # Add linkStyle for executed edges
237
236
  if self.executed_edges:
238
237
  edge_indices = ",".join(str(i) for i in self.executed_edges)
239
- self.lines.append(f" linkStyle {edge_indices} stroke:#28a745,stroke-width:3px")
238
+ edge_color = get_edge_color("executed", self.is_dark)
239
+ self.lines.append(f" linkStyle {edge_indices} stroke:{edge_color},stroke-width:3px")
240
240
 
241
241
  return "\n".join(self.lines)
242
242
 
@@ -296,21 +296,22 @@ class HybridMermaidGenerator(MermaidGenerator):
296
296
  if has_compensation:
297
297
  label += " ⚠"
298
298
 
299
- # Style based on status
299
+ # Style based on status using theme colors
300
300
  if status == "completed":
301
- style_color = "fill:#d4edda,stroke:#28a745,stroke-width:3px" # Green
301
+ style_color = get_mermaid_style("completed", self.is_dark) + ",stroke-width:3px"
302
302
  elif status == "failed":
303
- style_color = "fill:#f8d7da,stroke:#dc3545,stroke-width:3px" # Red
303
+ style_color = get_mermaid_style("failed", self.is_dark) + ",stroke-width:3px"
304
304
  elif status == "compensated":
305
305
  style_color = (
306
- "fill:#ffe6f0,stroke:#e91e63,stroke-width:3px,stroke-dasharray:5" # Pink
306
+ get_mermaid_style("compensating", self.is_dark)
307
+ + ",stroke-width:3px,stroke-dasharray:5"
307
308
  )
308
309
  elif status is not None:
309
- # Other executed statuses
310
- style_color = "fill:#fff3cd,stroke:#ffc107,stroke-width:3px" # Yellow
310
+ # Other executed statuses (running, waiting)
311
+ style_color = get_mermaid_style("running", self.is_dark) + ",stroke-width:3px"
311
312
  else:
312
313
  # Not executed
313
- style_color = "fill:#f5f5f5,stroke:#ccc,stroke-width:1px" # Gray
314
+ style_color = get_mermaid_style("not_executed", self.is_dark)
314
315
 
315
316
  self.lines.append(f' {node_id}["{label}"]')
316
317
  self.lines.append(f" style {node_id} {style_color}")
@@ -338,7 +339,8 @@ class HybridMermaidGenerator(MermaidGenerator):
338
339
  func_name = step.get("activity_name", step.get("function", "unknown"))
339
340
  self.lines.append(f" {node_id}[register_compensation:<br/>{func_name}]")
340
341
  self.lines.append(f" {current_id} --> {node_id}")
341
- self.lines.append(f" style {node_id} fill:#ffe6e6")
342
+ comp_reg_style = get_mermaid_style("compensating", self.is_dark)
343
+ self.lines.append(f" style {node_id} {comp_reg_style}")
342
344
 
343
345
  # Track compensation for reverse path
344
346
  self.compensation_nodes.append((current_id, node_id))
@@ -357,7 +359,8 @@ class HybridMermaidGenerator(MermaidGenerator):
357
359
 
358
360
  self.lines.append(f" {node_id}{{{{{label}}}}}")
359
361
  self.lines.append(f" {current_id} --> {node_id}")
360
- self.lines.append(f" style {node_id} fill:#fff4e6")
362
+ wait_event_style = get_mermaid_style("waiting_event", self.is_dark)
363
+ self.lines.append(f" style {node_id} {wait_event_style}")
361
364
  current_id = node_id
362
365
 
363
366
  elif step_type == "condition":
@@ -446,13 +449,15 @@ class HybridMermaidGenerator(MermaidGenerator):
446
449
  self.executed_edges.append(self.edge_counter)
447
450
  self.edge_counter += 1
448
451
 
449
- self.lines.append(f" style {cond_id} fill:#fff3e0,stroke:#ff9800,stroke-width:2px")
452
+ condition_style = get_mermaid_node_style("condition", self.is_dark)
453
+ self.lines.append(f" style {cond_id} {condition_style}")
450
454
 
451
455
  # Create merge node with invisible/minimal label
452
456
  merge_id = self._next_node_id()
453
457
  # Use a minimal circle node for merging (instead of showing "N4")
454
458
  self.lines.append(f" {merge_id}(( ))") # Small empty circle
455
- self.lines.append(f" style {merge_id} fill:#ffffff,stroke:#ddd,stroke-width:1px")
459
+ merge_style = get_mermaid_node_style("merge", self.is_dark)
460
+ self.lines.append(f" style {merge_id} {merge_style}")
456
461
 
457
462
  # If branch
458
463
  if if_branch:
@@ -555,21 +560,22 @@ class HybridMermaidGenerator(MermaidGenerator):
555
560
  else:
556
561
  label = f"{label_prefix}{func_name}"
557
562
 
558
- # Style based on status
563
+ # Style based on status using theme colors
559
564
  if status == "completed":
560
- style_color = "fill:#d4edda,stroke:#28a745,stroke-width:3px" # Green
565
+ style_color = get_mermaid_style("completed", self.is_dark) + ",stroke-width:3px"
561
566
  elif status == "failed":
562
- style_color = "fill:#f8d7da,stroke:#dc3545,stroke-width:3px" # Red
567
+ style_color = get_mermaid_style("failed", self.is_dark) + ",stroke-width:3px"
563
568
  elif status == "compensated":
564
569
  style_color = (
565
- "fill:#ffe6f0,stroke:#e91e63,stroke-width:3px,stroke-dasharray:5" # Pink
570
+ get_mermaid_style("compensating", self.is_dark)
571
+ + ",stroke-width:3px,stroke-dasharray:5"
566
572
  )
567
573
  elif status is not None:
568
- # Other executed statuses
569
- style_color = "fill:#fff3cd,stroke:#ffc107,stroke-width:3px" # Yellow
574
+ # Other executed statuses (running, waiting)
575
+ style_color = get_mermaid_style("running", self.is_dark) + ",stroke-width:3px"
570
576
  else:
571
577
  # Not executed
572
- style_color = "fill:#f5f5f5,stroke:#ccc,stroke-width:1px" # Gray
578
+ style_color = get_mermaid_style("not_executed", self.is_dark)
573
579
 
574
580
  self.lines.append(f' {node_id}["{label}"]')
575
581
  self.lines.append(f" style {node_id} {style_color}")
@@ -732,7 +738,8 @@ class HybridMermaidGenerator(MermaidGenerator):
732
738
  self.lines.append(f' {loop_id}["{label}"]')
733
739
  self.lines.append(f" {prev_id} --> {loop_id}")
734
740
  self.edge_counter += 1 # Edge from prev to loop
735
- self.lines.append(f" style {loop_id} fill:#fff0f0")
741
+ loop_style = get_mermaid_node_style("loop", self.is_dark)
742
+ self.lines.append(f" style {loop_id} {loop_style}")
736
743
 
737
744
  # Check if loop body contains executed activities
738
745
  body = loop.get("body", [])
@@ -750,7 +757,8 @@ class HybridMermaidGenerator(MermaidGenerator):
750
757
  # Create exit node as small empty circle (instead of labeled node)
751
758
  exit_id = self._next_node_id()
752
759
  self.lines.append(f" {exit_id}(( ))") # Small empty circle
753
- self.lines.append(f" style {exit_id} fill:#ffffff,stroke:#ddd,stroke-width:1px")
760
+ merge_style = get_mermaid_node_style("merge", self.is_dark)
761
+ self.lines.append(f" style {exit_id} {merge_style}")
754
762
  self.lines.append(f" {loop_id} -->|exit| {exit_id}")
755
763
  self.edge_counter += 1
756
764
 
@@ -786,12 +794,14 @@ class HybridMermaidGenerator(MermaidGenerator):
786
794
  self.lines.append(f" {match_id}{{{{match {subject}}}}}")
787
795
  self.lines.append(f" {prev_id} --> {match_id}")
788
796
  self.edge_counter += 1
789
- self.lines.append(f" style {match_id} fill:#e8f5e9,stroke:#4caf50,stroke-width:2px")
797
+ match_style = get_mermaid_node_style("match", self.is_dark)
798
+ self.lines.append(f" style {match_id} {match_style}")
790
799
 
791
800
  # Create merge node
792
801
  merge_id = self._next_node_id()
793
802
  self.lines.append(f" {merge_id}(( ))") # Small empty circle for merge
794
- self.lines.append(f" style {merge_id} fill:#ffffff,stroke:#ddd,stroke-width:1px")
803
+ merge_style = get_mermaid_node_style("merge", self.is_dark)
804
+ self.lines.append(f" style {merge_id} {merge_style}")
795
805
 
796
806
  # Process each case
797
807
  cases = match.get("cases", [])
@@ -833,9 +843,8 @@ class HybridMermaidGenerator(MermaidGenerator):
833
843
  self.executed_edges.append(self.edge_counter)
834
844
  self.edge_counter += 1
835
845
 
836
- self.lines.append(
837
- f" style {case_start_id} fill:#fff,stroke:#999,stroke-width:1px"
838
- )
846
+ case_start_style = get_mermaid_node_style("merge", self.is_dark)
847
+ self.lines.append(f" style {case_start_id} {case_start_style}")
839
848
 
840
849
  # Generate body steps starting from case_start_id
841
850
  case_end = self._generate_steps(body, case_start_id)
@@ -875,12 +884,14 @@ class HybridMermaidGenerator(MermaidGenerator):
875
884
  self.lines.append(f" {decision_id}{{{{if-elif-else}}}}")
876
885
  self.lines.append(f" {prev_id} --> {decision_id}")
877
886
  self.edge_counter += 1
878
- self.lines.append(f" style {decision_id} fill:#e8f5e9,stroke:#4caf50,stroke-width:2px")
887
+ decision_style = get_mermaid_node_style("condition", self.is_dark)
888
+ self.lines.append(f" style {decision_id} {decision_style}")
879
889
 
880
890
  # Create merge node
881
891
  merge_id = self._next_node_id()
882
892
  self.lines.append(f" {merge_id}(( ))") # Small empty circle for merge
883
- self.lines.append(f" style {merge_id} fill:#ffffff,stroke:#ddd,stroke-width:1px")
893
+ merge_style = get_mermaid_node_style("merge", self.is_dark)
894
+ self.lines.append(f" style {merge_id} {merge_style}")
884
895
 
885
896
  # Process each branch
886
897
  branches = multi_cond.get("branches", [])
@@ -950,9 +961,8 @@ class HybridMermaidGenerator(MermaidGenerator):
950
961
  self.executed_edges.append(self.edge_counter)
951
962
  self.edge_counter += 1
952
963
 
953
- self.lines.append(
954
- f" style {branch_start_id} fill:#fff,stroke:#999,stroke-width:1px"
955
- )
964
+ branch_start_style = get_mermaid_node_style("merge", self.is_dark)
965
+ self.lines.append(f" style {branch_start_id} {branch_start_style}")
956
966
 
957
967
  # Generate body steps starting from branch_start_id
958
968
  branch_end = self._generate_steps(body, branch_start_id)
@@ -1006,6 +1016,7 @@ def generate_hybrid_mermaid(
1006
1016
  source_code: str,
1007
1017
  compensations: dict[str, dict[str, Any]] | None = None,
1008
1018
  workflow_status: str = "running",
1019
+ is_dark: bool = False,
1009
1020
  ) -> str:
1010
1021
  """
1011
1022
  Generate hybrid Mermaid diagram combining static analysis and execution history.
@@ -1017,6 +1028,7 @@ def generate_hybrid_mermaid(
1017
1028
  source_code: Source code of the workflow function
1018
1029
  compensations: Optional mapping of activity_id -> compensation info
1019
1030
  workflow_status: Status of the workflow instance (running, completed, failed, etc.)
1031
+ is_dark: Whether dark mode is enabled
1020
1032
 
1021
1033
  Returns:
1022
1034
  Mermaid flowchart diagram code with execution highlighting
@@ -1030,7 +1042,7 @@ def generate_hybrid_mermaid(
1030
1042
 
1031
1043
  if not workflows:
1032
1044
  # Fallback to history-only diagram
1033
- return generate_interactive_mermaid(instance_id, history)
1045
+ return generate_interactive_mermaid(instance_id, history, is_dark)
1034
1046
 
1035
1047
  workflow_structure = workflows[0]
1036
1048
 
@@ -1079,6 +1091,7 @@ def generate_hybrid_mermaid(
1079
1091
  compensations,
1080
1092
  workflow_status,
1081
1093
  activity_status_map,
1094
+ is_dark,
1082
1095
  )
1083
1096
  generator.activity_id_map = activity_id_map
1084
1097
  generator.activity_execution_counts = activity_execution_counts
@@ -1089,7 +1102,7 @@ def generate_hybrid_mermaid(
1089
1102
  except Exception as e:
1090
1103
  # Fallback to history-only diagram on any error
1091
1104
  print(f"Warning: Hybrid diagram generation failed, falling back to history-only: {e}")
1092
- return generate_interactive_mermaid(instance_id, history)
1105
+ return generate_interactive_mermaid(instance_id, history, is_dark)
1093
1106
 
1094
1107
 
1095
1108
  def format_json_for_display(data: Any) -> str:
@@ -5,7 +5,8 @@ Data service for retrieving workflow instance data from storage.
5
5
  import inspect
6
6
  import json
7
7
  import logging
8
- from typing import Any
8
+ from datetime import datetime
9
+ from typing import Any, cast
9
10
 
10
11
  from edda.pydantic_utils import is_pydantic_model
11
12
  from edda.storage.protocol import StorageProtocol
@@ -36,8 +37,46 @@ class WorkflowDataService:
36
37
  Returns:
37
38
  List of workflow instance dictionaries
38
39
  """
39
- instances = await self.storage.list_instances(limit=limit)
40
- return instances
40
+ result = await self.storage.list_instances(limit=limit)
41
+ return cast(list[dict[str, Any]], result["instances"])
42
+
43
+ async def get_instances_paginated(
44
+ self,
45
+ page_size: int = 20,
46
+ page_token: str | None = None,
47
+ status_filter: str | None = None,
48
+ search_query: str | None = None,
49
+ started_after: datetime | None = None,
50
+ started_before: datetime | None = None,
51
+ ) -> dict[str, Any]:
52
+ """
53
+ Get workflow instances with cursor-based pagination and filtering.
54
+
55
+ Args:
56
+ page_size: Number of instances per page (10, 20, or 50)
57
+ page_token: Cursor for pagination (from previous response)
58
+ status_filter: Filter by status (e.g., "running", "completed", "failed")
59
+ search_query: Search by workflow name or instance ID (partial match)
60
+ started_after: Filter instances started after this datetime
61
+ started_before: Filter instances started before this datetime
62
+
63
+ Returns:
64
+ Dictionary containing:
65
+ - instances: List of workflow instances
66
+ - next_page_token: Cursor for next page, or None
67
+ - has_more: Boolean indicating if more pages exist
68
+ """
69
+ # Use search_query for both workflow_name_filter and instance_id_filter
70
+ result = await self.storage.list_instances(
71
+ limit=page_size,
72
+ page_token=page_token,
73
+ status_filter=status_filter,
74
+ workflow_name_filter=search_query,
75
+ instance_id_filter=search_query,
76
+ started_after=started_after,
77
+ started_before=started_before,
78
+ )
79
+ return result
41
80
 
42
81
  async def get_workflow_compensations(self, instance_id: str) -> dict[str, dict[str, Any]]:
43
82
  """
@@ -228,7 +267,7 @@ class WorkflowDataService:
228
267
  except (OSError, TypeError) as e:
229
268
  # OSError: source not available (e.g., interactive shell)
230
269
  # TypeError: not a module, class, method, function, etc.
231
- print(f"Warning: Could not get source for {workflow_name}: {e}")
270
+ logger.warning("Could not get source for %s: %s", workflow_name, e)
232
271
  return None
233
272
 
234
273
  async def get_activity_executions(
@@ -293,8 +332,8 @@ class WorkflowDataService:
293
332
  import httpx
294
333
 
295
334
  try:
296
- print(f"[Cancel] Attempting to cancel workflow: {instance_id}")
297
- print(f"[Cancel] API URL: {edda_app_url}/cancel/{instance_id}")
335
+ logger.debug("Attempting to cancel workflow: %s", instance_id)
336
+ logger.debug("API URL: %s/cancel/%s", edda_app_url, instance_id)
298
337
 
299
338
  async with httpx.AsyncClient() as client:
300
339
  response = await client.post(
@@ -302,8 +341,8 @@ class WorkflowDataService:
302
341
  timeout=10.0,
303
342
  )
304
343
 
305
- print(f"[Cancel] Response status: {response.status_code}")
306
- print(f"[Cancel] Response body: {response.text}")
344
+ logger.debug("Response status: %s", response.status_code)
345
+ logger.debug("Response body: %s", response.text)
307
346
 
308
347
  if 200 <= response.status_code < 300:
309
348
  return True, "Workflow cancelled successfully"
@@ -317,17 +356,17 @@ class WorkflowDataService:
317
356
 
318
357
  except httpx.ConnectError as e:
319
358
  error_msg = f"Cannot connect to EddaApp at {edda_app_url}. Is it running?"
320
- print(f"[Cancel] Connection error: {e}")
359
+ logger.warning("Connection error: %s", e)
321
360
  return False, error_msg
322
361
 
323
362
  except httpx.TimeoutException as e:
324
363
  error_msg = "Request timed out. The server may be busy."
325
- print(f"[Cancel] Timeout error: {e}")
364
+ logger.warning("Timeout error: %s", e)
326
365
  return False, error_msg
327
366
 
328
367
  except Exception as e:
329
368
  error_msg = f"Unexpected error: {type(e).__name__}: {str(e)}"
330
- print(f"[Cancel] Unexpected error: {e}")
369
+ logger.error("Unexpected error: %s", e, exc_info=True)
331
370
  return False, error_msg
332
371
 
333
372
  def get_all_workflows(self) -> dict[str, Any]:
@@ -481,7 +520,7 @@ class WorkflowDataService:
481
520
  }
482
521
  except Exception as e:
483
522
  # Fallback to JSON if schema generation fails
484
- print(f"Warning: Failed to generate JSON Schema for {annotation}: {e}")
523
+ logger.warning("Failed to generate JSON Schema for %s: %s", annotation, e)
485
524
  return {"type": "json"}
486
525
 
487
526
  # Check if it's an Enum
@@ -817,9 +856,9 @@ class WorkflowDataService:
817
856
  from cloudevents.http import CloudEvent
818
857
 
819
858
  try:
820
- print(f"[StartWorkflow] Attempting to start workflow: {workflow_name}")
821
- print(f"[StartWorkflow] Sending CloudEvent to: {edda_app_url}")
822
- print(f"[StartWorkflow] Params: {params}")
859
+ logger.debug("Attempting to start workflow: %s", workflow_name)
860
+ logger.debug("Sending CloudEvent to: %s", edda_app_url)
861
+ logger.debug("Params: %s", params)
823
862
 
824
863
  # Verify workflow exists
825
864
  all_workflows = self.get_all_workflows()
@@ -837,8 +876,8 @@ class WorkflowDataService:
837
876
  # Convert to HTTP format (structured content mode)
838
877
  headers, body = to_structured(event)
839
878
 
840
- print(f"[StartWorkflow] CloudEvent ID: {attributes['id']}")
841
- print(f"[StartWorkflow] CloudEvent type: {workflow_name}")
879
+ logger.debug("CloudEvent ID: %s", attributes["id"])
880
+ logger.debug("CloudEvent type: %s", workflow_name)
842
881
 
843
882
  # Send CloudEvent to EddaApp
844
883
  async with httpx.AsyncClient() as client:
@@ -849,8 +888,8 @@ class WorkflowDataService:
849
888
  timeout=10.0,
850
889
  )
851
890
 
852
- print(f"[StartWorkflow] Response status: {response.status_code}")
853
- print(f"[StartWorkflow] Response body: {response.text}")
891
+ logger.debug("Response status: %s", response.status_code)
892
+ logger.debug("Response body: %s", response.text)
854
893
 
855
894
  if 200 <= response.status_code < 300:
856
895
  # CloudEvent accepted (200 OK or 202 Accepted)
@@ -863,18 +902,15 @@ class WorkflowDataService:
863
902
 
864
903
  except httpx.ConnectError as e:
865
904
  error_msg = f"Cannot connect to EddaApp at {edda_app_url}. Is it running?"
866
- print(f"[StartWorkflow] Connection error: {e}")
905
+ logger.warning("Connection error: %s", e)
867
906
  return False, error_msg, None
868
907
 
869
908
  except httpx.TimeoutException as e:
870
909
  error_msg = "Request timed out. The server may be busy."
871
- print(f"[StartWorkflow] Timeout error: {e}")
910
+ logger.warning("Timeout error: %s", e)
872
911
  return False, error_msg, None
873
912
 
874
913
  except Exception as e:
875
914
  error_msg = f"Unexpected error: {type(e).__name__}: {str(e)}"
876
- print(f"[StartWorkflow] Unexpected error: {e}")
877
- import traceback
878
-
879
- traceback.print_exc()
915
+ logger.error("Unexpected error: %s", e, exc_info=True)
880
916
  return False, error_msg, None