Coverage for src/dataknobs_fsm/core/result_formatter.py: 46%
41 statements
« prev ^ index » next coverage.py v7.10.6, created at 2025-09-20 16:46 -0600
« prev ^ index » next coverage.py v7.10.6, created at 2025-09-20 16:46 -0600
1"""Utility for formatting execution results in a standardized way.
3This module provides consistent result formatting across Simple and Advanced APIs,
4eliminating code duplication and ensuring uniform output structure.
5"""
7from typing import Any, Dict, List
8from ..execution.context import ExecutionContext
11class ResultFormatter:
12 """Formatter for execution results across different processing modes."""
14 @staticmethod
15 def format_single_result(
16 context: ExecutionContext,
17 success: bool,
18 result: Any = None,
19 error: Exception | None = None
20 ) -> Dict[str, Any]:
21 """Format a single execution result.
23 Args:
24 context: The execution context
25 success: Whether execution was successful
26 result: Execution result (if any)
27 error: Exception if execution failed
29 Returns:
30 Formatted result dictionary
31 """
32 return {
33 'final_state': context.current_state,
34 'data': context.data,
35 'path': ResultFormatter._get_complete_path(context),
36 'success': success,
37 'error': str(error) if error else (str(result) if not success and result else None),
38 'metadata': context.metadata.copy() if context.metadata else {}
39 }
41 @staticmethod
42 def format_batch_result(
43 context: ExecutionContext,
44 batch_results: List[Dict[str, Any]]
45 ) -> List[Dict[str, Any]]:
46 """Format batch execution results.
48 Args:
49 context: The execution context
50 batch_results: Raw batch results
52 Returns:
53 List of formatted result dictionaries
54 """
55 formatted_results = []
57 for i, raw_result in enumerate(batch_results):
58 # Check if this item had an error
59 error = None
60 for err_idx, err_exc in context.batch_errors:
61 if err_idx == i:
62 error = err_exc
63 break
65 formatted_result = {
66 'index': i,
67 'final_state': raw_result.get('final_state', context.current_state),
68 'data': raw_result.get('data', {}),
69 'path': raw_result.get('path', []),
70 'success': error is None,
71 'error': str(error) if error else None
72 }
73 formatted_results.append(formatted_result)
75 return formatted_results
77 @staticmethod
78 def format_stream_result(
79 context: ExecutionContext,
80 chunk_result: Any,
81 chunk_index: int
82 ) -> Dict[str, Any]:
83 """Format a stream chunk result.
85 Args:
86 context: The execution context
87 chunk_result: Result from processing a chunk
88 chunk_index: Index of the processed chunk
90 Returns:
91 Formatted chunk result dictionary
92 """
93 return {
94 'chunk_index': chunk_index,
95 'chunks_processed': context.processed_chunks,
96 'current_state': context.current_state,
97 'data': chunk_result,
98 'metadata': context.metadata.copy() if context.metadata else {}
99 }
101 @staticmethod
102 def format_async_result(
103 context: ExecutionContext,
104 success: bool,
105 result: Any = None,
106 error: Exception | None = None
107 ) -> Dict[str, Any]:
108 """Format an async execution result.
110 This is identical to format_single_result but provided for clarity.
112 Args:
113 context: The execution context
114 success: Whether execution was successful
115 result: Execution result (if any)
116 error: Exception if execution failed
118 Returns:
119 Formatted result dictionary
120 """
121 return ResultFormatter.format_single_result(context, success, result, error)
123 @staticmethod
124 def format_step_result(
125 context: ExecutionContext,
126 new_state: str | None = None,
127 transition_taken: bool = False
128 ) -> Dict[str, Any]:
129 """Format a step-by-step execution result.
131 Args:
132 context: The execution context
133 new_state: The new state after the step (if any)
134 transition_taken: Whether a transition was taken
136 Returns:
137 Formatted step result dictionary
138 """
139 return {
140 'previous_state': context.previous_state,
141 'current_state': context.current_state,
142 'new_state': new_state,
143 'transition_taken': transition_taken,
144 'path': ResultFormatter._get_complete_path(context),
145 'data': context.data,
146 'metadata': context.metadata.copy() if context.metadata else {}
147 }
149 @staticmethod
150 def format_error_result(
151 context: ExecutionContext,
152 error: Exception,
153 error_state: str | None = None
154 ) -> Dict[str, Any]:
155 """Format an error result with context.
157 Args:
158 context: The execution context
159 error: The exception that occurred
160 error_state: State where error occurred (if known)
162 Returns:
163 Formatted error result dictionary
164 """
165 return {
166 'success': False,
167 'error': str(error),
168 'error_type': type(error).__name__,
169 'error_state': error_state or context.current_state,
170 'final_state': context.current_state,
171 'path': ResultFormatter._get_complete_path(context),
172 'data': context.data,
173 'metadata': context.metadata.copy() if context.metadata else {}
174 }
176 @staticmethod
177 def _get_complete_path(context: ExecutionContext) -> List[str]:
178 """Get the complete state traversal path.
180 Args:
181 context: The execution context
183 Returns:
184 List of state names in traversal order
185 """
186 # Build complete path from history plus current state
187 path = context.state_history.copy() if context.state_history else []
189 # Add current state if not already in path and if it exists
190 if context.current_state and (not path or path[-1] != context.current_state):
191 path.append(context.current_state)
193 return path
195 @staticmethod
196 def format_performance_result(
197 context: ExecutionContext,
198 result: Dict[str, Any]
199 ) -> Dict[str, Any]:
200 """Format result with performance metrics.
202 Args:
203 context: The execution context
204 result: Base result dictionary
206 Returns:
207 Result with added performance metrics
208 """
209 # Add performance stats to existing result
210 result['performance'] = context.get_performance_stats()
211 result['resource_usage'] = context.get_resource_usage()
213 return result