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

1"""Utility for formatting execution results in a standardized way. 

2 

3This module provides consistent result formatting across Simple and Advanced APIs, 

4eliminating code duplication and ensuring uniform output structure. 

5""" 

6 

7from typing import Any, Dict, List 

8from ..execution.context import ExecutionContext 

9 

10 

11class ResultFormatter: 

12 """Formatter for execution results across different processing modes.""" 

13 

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. 

22  

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 

28  

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 } 

40 

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. 

47  

48 Args: 

49 context: The execution context 

50 batch_results: Raw batch results 

51  

52 Returns: 

53 List of formatted result dictionaries 

54 """ 

55 formatted_results = [] 

56 

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 

64 

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) 

74 

75 return formatted_results 

76 

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. 

84  

85 Args: 

86 context: The execution context 

87 chunk_result: Result from processing a chunk 

88 chunk_index: Index of the processed chunk 

89  

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 } 

100 

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. 

109  

110 This is identical to format_single_result but provided for clarity. 

111  

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 

117  

118 Returns: 

119 Formatted result dictionary 

120 """ 

121 return ResultFormatter.format_single_result(context, success, result, error) 

122 

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. 

130  

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 

135  

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 } 

148 

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. 

156  

157 Args: 

158 context: The execution context 

159 error: The exception that occurred 

160 error_state: State where error occurred (if known) 

161  

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 } 

175 

176 @staticmethod 

177 def _get_complete_path(context: ExecutionContext) -> List[str]: 

178 """Get the complete state traversal path. 

179  

180 Args: 

181 context: The execution context 

182  

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 [] 

188 

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) 

192 

193 return path 

194 

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. 

201  

202 Args: 

203 context: The execution context 

204 result: Base result dictionary 

205  

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() 

212 

213 return result