Coverage for openhcs/textual_tui/widgets/shared/unified_parameter_analyzer.py: 0.0%

83 statements  

« prev     ^ index     » next       coverage.py v7.10.3, created at 2025-08-14 05:57 +0000

1"""Unified parameter analysis interface for all parameter sources in OpenHCS TUI. 

2 

3This module provides a single, consistent interface for analyzing parameters from: 

4- Functions and methods 

5- Dataclasses and their fields 

6- Nested dataclass structures 

7- Any callable or type with parameters 

8 

9Replaces the fragmented approach of SignatureAnalyzer vs FieldIntrospector. 

10""" 

11 

12import inspect 

13import dataclasses 

14from typing import Dict, Union, Callable, Type, Any, Optional 

15from dataclasses import dataclass 

16 

17from .signature_analyzer import SignatureAnalyzer, ParameterInfo, DocstringExtractor 

18 

19 

20@dataclass 

21class UnifiedParameterInfo: 

22 """Unified parameter information that works for all parameter sources.""" 

23 name: str 

24 param_type: Type 

25 default_value: Any 

26 is_required: bool 

27 description: Optional[str] = None 

28 source_type: str = "unknown" # "function", "dataclass", "nested" 

29 

30 @classmethod 

31 def from_parameter_info(cls, param_info: ParameterInfo, source_type: str = "function") -> "UnifiedParameterInfo": 

32 """Convert from existing ParameterInfo to unified format.""" 

33 return cls( 

34 name=param_info.name, 

35 param_type=param_info.param_type, 

36 default_value=param_info.default_value, 

37 is_required=param_info.is_required, 

38 description=param_info.description, 

39 source_type=source_type 

40 ) 

41 

42 

43class UnifiedParameterAnalyzer: 

44 """Single interface for analyzing parameters from any source. 

45  

46 This class provides a unified way to extract parameter information 

47 from functions, dataclasses, and other parameter sources, ensuring 

48 consistent behavior across the entire application. 

49 """ 

50 

51 @staticmethod 

52 def analyze(target: Union[Callable, Type, object]) -> Dict[str, UnifiedParameterInfo]: 

53 """Analyze parameters from any source. 

54  

55 Args: 

56 target: Function, method, dataclass type, or instance to analyze 

57  

58 Returns: 

59 Dictionary mapping parameter names to UnifiedParameterInfo objects 

60  

61 Examples: 

62 # Function analysis 

63 param_info = UnifiedParameterAnalyzer.analyze(my_function) 

64  

65 # Dataclass analysis 

66 param_info = UnifiedParameterAnalyzer.analyze(MyDataclass) 

67  

68 # Instance analysis 

69 param_info = UnifiedParameterAnalyzer.analyze(my_instance) 

70 """ 

71 if target is None: 

72 return {} 

73 

74 # Determine the type of target and route to appropriate analyzer 

75 if inspect.isfunction(target) or inspect.ismethod(target): 

76 return UnifiedParameterAnalyzer._analyze_callable(target) 

77 elif inspect.isclass(target): 

78 if dataclasses.is_dataclass(target): 

79 return UnifiedParameterAnalyzer._analyze_dataclass_type(target) 

80 else: 

81 # Try to analyze constructor 

82 return UnifiedParameterAnalyzer._analyze_callable(target.__init__) 

83 elif dataclasses.is_dataclass(target): 

84 # Instance of dataclass 

85 return UnifiedParameterAnalyzer._analyze_dataclass_instance(target) 

86 else: 

87 # Try to analyze as callable 

88 if callable(target): 

89 return UnifiedParameterAnalyzer._analyze_callable(target) 

90 else: 

91 return {} 

92 

93 @staticmethod 

94 def _analyze_callable(callable_obj: Callable) -> Dict[str, UnifiedParameterInfo]: 

95 """Analyze a callable (function, method, etc.).""" 

96 try: 

97 # Use existing SignatureAnalyzer for callables 

98 param_info_dict = SignatureAnalyzer.analyze(callable_obj) 

99 

100 # Convert to unified format 

101 unified_params = {} 

102 for name, param_info in param_info_dict.items(): 

103 unified_params[name] = UnifiedParameterInfo.from_parameter_info( 

104 param_info, 

105 source_type="function" 

106 ) 

107 

108 return unified_params 

109 

110 except Exception: 

111 return {} 

112 

113 @staticmethod 

114 def _analyze_dataclass_type(dataclass_type: Type) -> Dict[str, UnifiedParameterInfo]: 

115 """Analyze a dataclass type.""" 

116 try: 

117 # Extract docstring information 

118 docstring_info = DocstringExtractor.extract(dataclass_type) 

119 

120 # Get field information 

121 fields = dataclasses.fields(dataclass_type) 

122 unified_params = {} 

123 

124 for field in fields: 

125 # Get field description from docstring 

126 field_description = docstring_info.parameters.get(field.name) 

127 

128 # Determine if field is required 

129 is_required = field.default == dataclasses.MISSING and field.default_factory == dataclasses.MISSING 

130 

131 # Get default value 

132 if field.default != dataclasses.MISSING: 

133 default_value = field.default 

134 elif field.default_factory != dataclasses.MISSING: 

135 default_value = field.default_factory() 

136 else: 

137 default_value = None 

138 

139 unified_params[field.name] = UnifiedParameterInfo( 

140 name=field.name, 

141 param_type=field.type, 

142 default_value=default_value, 

143 is_required=is_required, 

144 description=field_description, 

145 source_type="dataclass" 

146 ) 

147 

148 return unified_params 

149 

150 except Exception: 

151 return {} 

152 

153 @staticmethod 

154 def _analyze_dataclass_instance(instance: object) -> Dict[str, UnifiedParameterInfo]: 

155 """Analyze a dataclass instance.""" 

156 try: 

157 # Get the type and analyze it 

158 dataclass_type = type(instance) 

159 unified_params = UnifiedParameterAnalyzer._analyze_dataclass_type(dataclass_type) 

160 

161 # Update default values with current instance values 

162 for name, param_info in unified_params.items(): 

163 if hasattr(instance, name): 

164 current_value = getattr(instance, name) 

165 # Create new UnifiedParameterInfo with current value as default 

166 unified_params[name] = UnifiedParameterInfo( 

167 name=param_info.name, 

168 param_type=param_info.param_type, 

169 default_value=current_value, 

170 is_required=param_info.is_required, 

171 description=param_info.description, 

172 source_type="dataclass_instance" 

173 ) 

174 

175 return unified_params 

176 

177 except Exception: 

178 return {} 

179 

180 @staticmethod 

181 def analyze_nested(target: Union[Callable, Type, object], parent_info: Dict[str, UnifiedParameterInfo] = None) -> Dict[str, UnifiedParameterInfo]: 

182 """Analyze parameters with nested dataclass support. 

183  

184 This method provides enhanced analysis that can handle nested dataclasses 

185 and maintain parent context information. 

186  

187 Args: 

188 target: The target to analyze 

189 parent_info: Optional parent parameter information for context 

190  

191 Returns: 

192 Dictionary of unified parameter information with nested support 

193 """ 

194 base_params = UnifiedParameterAnalyzer.analyze(target) 

195 

196 # For each parameter, check if it's a nested dataclass 

197 enhanced_params = {} 

198 for name, param_info in base_params.items(): 

199 enhanced_params[name] = param_info 

200 

201 # If this parameter is a dataclass, mark it as having nested structure 

202 if dataclasses.is_dataclass(param_info.param_type): 

203 # Update source type to indicate nesting capability 

204 enhanced_params[name] = UnifiedParameterInfo( 

205 name=param_info.name, 

206 param_type=param_info.param_type, 

207 default_value=param_info.default_value, 

208 is_required=param_info.is_required, 

209 description=param_info.description, 

210 source_type=f"{param_info.source_type}_nested" 

211 ) 

212 

213 return enhanced_params 

214 

215 

216# Backward compatibility aliases 

217# These allow existing code to continue working while migration happens 

218ParameterAnalyzer = UnifiedParameterAnalyzer 

219analyze_parameters = UnifiedParameterAnalyzer.analyze