Coverage for openhcs/core/components/metaprogramming.py: 0.0%

101 statements  

« prev     ^ index     » next       coverage.py v7.10.7, created at 2025-10-01 18:33 +0000

1""" 

2Metaprogramming system for dynamic interface generation based on component enums. 

3 

4This module provides a metaprogramming framework that dynamically generates interface 

5classes based on the contents of component enums, enabling truly generic component 

6processing without hardcoded method names or component assumptions. 

7""" 

8 

9import logging 

10from abc import ABC, abstractmethod 

11from typing import Any, Dict, Type, TypeVar, Generic, Optional, Set, Callable, Union 

12from enum import Enum 

13import inspect 

14 

15logger = logging.getLogger(__name__) 

16 

17T = TypeVar('T', bound=Enum) 

18 

19 

20class MethodSignature: 

21 """Represents a dynamically generated method signature.""" 

22 

23 def __init__(self, name: str, return_type: Type = Any, **kwargs): 

24 self.name = name 

25 self.return_type = return_type 

26 self.parameters = kwargs 

27 

28 def __repr__(self): 

29 params = ", ".join(f"{k}: {v.__name__ if hasattr(v, '__name__') else v}" 

30 for k, v in self.parameters.items()) 

31 return f"{self.name}({params}) -> {self.return_type.__name__ if hasattr(self.return_type, '__name__') else self.return_type}" 

32 

33 

34class ComponentMethodRegistry: 

35 """Registry for tracking dynamically generated methods.""" 

36 

37 def __init__(self): 

38 self._methods: Dict[str, Dict[str, MethodSignature]] = {} 

39 

40 def register_method(self, interface_name: str, method: MethodSignature): 

41 """Register a method for an interface.""" 

42 if interface_name not in self._methods: 

43 self._methods[interface_name] = {} 

44 self._methods[interface_name][method.name] = method 

45 logger.debug(f"Registered method {method.name} for interface {interface_name}") 

46 

47 def get_methods(self, interface_name: str) -> Dict[str, MethodSignature]: 

48 """Get all methods for an interface.""" 

49 return self._methods.get(interface_name, {}) 

50 

51 def has_method(self, interface_name: str, method_name: str) -> bool: 

52 """Check if an interface has a specific method.""" 

53 return (interface_name in self._methods and 

54 method_name in self._methods[interface_name]) 

55 

56 

57# Global method registry 

58_method_registry = ComponentMethodRegistry() 

59 

60 

61class DynamicInterfaceMeta(type): 

62 """ 

63 Metaclass that dynamically generates interface methods based on component enums. 

64  

65 This metaclass inspects the component enum during class creation and generates 

66 abstract methods for each component, enabling truly generic component processing. 

67 """ 

68 

69 def __new__(mcs, name, bases, namespace, component_enum=None, method_patterns=None, **kwargs): 

70 """ 

71 Create a new interface class with dynamically generated methods. 

72  

73 Args: 

74 name: Class name 

75 bases: Base classes 

76 namespace: Class namespace 

77 component_enum: Enum class to generate methods from 

78 method_patterns: List of method patterns to generate 

79 **kwargs: Additional arguments 

80 """ 

81 # Default method patterns if not specified 

82 if method_patterns is None: 

83 method_patterns = ['process', 'validate', 'get_keys'] 

84 

85 # Generate methods if component_enum is provided 

86 if component_enum is not None: 

87 logger.info(f"Generating dynamic interface {name} for enum {component_enum.__name__}") 

88 mcs._generate_methods(namespace, component_enum, method_patterns, name) 

89 

90 # Create the class 

91 cls = super().__new__(mcs, name, bases, namespace) 

92 

93 # Register the interface 

94 if component_enum is not None: 

95 cls._component_enum = component_enum 

96 cls._method_patterns = method_patterns 

97 logger.info(f"Created dynamic interface {name} with {len(_method_registry.get_methods(name))} methods") 

98 

99 return cls 

100 

101 @staticmethod 

102 def _generate_methods(namespace: Dict[str, Any], component_enum: Type[Enum], 

103 method_patterns: list, interface_name: str): 

104 """Generate abstract methods for each component and pattern combination.""" 

105 for component in component_enum: 

106 component_name = component.value 

107 

108 for pattern in method_patterns: 

109 method_name = f"{pattern}_{component_name}" 

110 

111 # Create method signature 

112 signature = MethodSignature( 

113 name=method_name, 

114 return_type=Any, 

115 context=Any, 

116 **{f"{component_name}_value": str} 

117 ) 

118 

119 # Register the method 

120 _method_registry.register_method(interface_name, signature) 

121 

122 # Create abstract method 

123 def create_abstract_method(method_name=method_name): 

124 @abstractmethod 

125 def abstract_method(self, context: Any, **kwargs) -> Any: 

126 """Dynamically generated abstract method.""" 

127 raise NotImplementedError(f"Method {method_name} must be implemented") 

128 

129 abstract_method.__name__ = method_name 

130 abstract_method.__qualname__ = f"{interface_name}.{method_name}" 

131 return abstract_method 

132 

133 # Add method to namespace 

134 namespace[method_name] = create_abstract_method() 

135 logger.debug(f"Generated abstract method {method_name} for {interface_name}") 

136 

137 

138class ComponentProcessorInterface(metaclass=DynamicInterfaceMeta): 

139 """ 

140 Base interface for component processors with dynamically generated methods. 

141  

142 This class uses the DynamicInterfaceMeta metaclass to automatically generate 

143 abstract methods based on the component enum, providing a truly generic 

144 interface that adapts to any component configuration. 

145 """ 

146 

147 def __init__(self, component_enum: Type[T]): 

148 """ 

149 Initialize the processor interface. 

150  

151 Args: 

152 component_enum: The component enum to process 

153 """ 

154 self.component_enum = component_enum 

155 self._validate_implementation() 

156 

157 def _validate_implementation(self): 

158 """Validate that all required methods are implemented.""" 

159 interface_name = self.__class__.__name__ 

160 required_methods = _method_registry.get_methods(interface_name) 

161 

162 for method_name, signature in required_methods.items(): 

163 if not hasattr(self, method_name): 

164 raise NotImplementedError( 

165 f"Class {self.__class__.__name__} must implement method {method_name}" 

166 ) 

167 

168 method = getattr(self, method_name) 

169 if not callable(method): 

170 raise TypeError( 

171 f"Attribute {method_name} in {self.__class__.__name__} must be callable" 

172 ) 

173 

174 logger.debug(f"Validated implementation of {interface_name} with {len(required_methods)} methods") 

175 

176 def get_available_methods(self) -> Dict[str, MethodSignature]: 

177 """Get all available methods for this interface.""" 

178 return _method_registry.get_methods(self.__class__.__name__) 

179 

180 def has_method_for_component(self, component: T, pattern: str) -> bool: 

181 """Check if a method exists for a specific component and pattern.""" 

182 method_name = f"{pattern}_{component.value}" 

183 return _method_registry.has_method(self.__class__.__name__, method_name) 

184 

185 def call_component_method(self, component: T, pattern: str, context: Any, **kwargs) -> Any: 

186 """Dynamically call a component-specific method.""" 

187 method_name = f"{pattern}_{component.value}" 

188 

189 if not hasattr(self, method_name): 

190 raise AttributeError( 

191 f"Method {method_name} not found in {self.__class__.__name__}" 

192 ) 

193 

194 method = getattr(self, method_name) 

195 return method(context, **kwargs) 

196 

197 

198class InterfaceGenerator: 

199 """ 

200 Factory for creating component-specific interfaces dynamically. 

201  

202 This class provides a high-level API for generating interfaces based on 

203 component enums, with caching and type safety features. 

204 """ 

205 

206 def __init__(self): 

207 self._interface_cache: Dict[str, Type] = {} 

208 

209 def create_interface(self, 

210 component_enum: Type[T], 

211 interface_name: Optional[str] = None, 

212 method_patterns: Optional[list] = None, 

213 base_classes: Optional[tuple] = None) -> Type[ComponentProcessorInterface]: 

214 """ 

215 Create a component-specific interface class. 

216  

217 Args: 

218 component_enum: The component enum to generate interface for 

219 interface_name: Optional custom interface name 

220 method_patterns: Optional custom method patterns 

221 base_classes: Optional additional base classes 

222  

223 Returns: 

224 Dynamically generated interface class 

225 """ 

226 # Generate interface name if not provided 

227 if interface_name is None: 

228 interface_name = f"{component_enum.__name__}ProcessorInterface" 

229 

230 # Check cache 

231 cache_key = f"{interface_name}_{id(component_enum)}" 

232 if cache_key in self._interface_cache: 

233 logger.debug(f"Returning cached interface {interface_name}") 

234 return self._interface_cache[cache_key] 

235 

236 # Set default base classes 

237 if base_classes is None: 

238 base_classes = (ComponentProcessorInterface,) 

239 

240 # Create the interface class dynamically 

241 interface_class = DynamicInterfaceMeta( 

242 interface_name, 

243 base_classes, 

244 {}, 

245 component_enum=component_enum, 

246 method_patterns=method_patterns 

247 ) 

248 

249 # Cache the interface 

250 self._interface_cache[cache_key] = interface_class 

251 

252 logger.info(f"Created interface {interface_name} for {component_enum.__name__}") 

253 return interface_class 

254 

255 def get_cached_interface(self, interface_name: str) -> Optional[Type]: 

256 """Get a cached interface by name.""" 

257 for key, interface in self._interface_cache.items(): 

258 if key.startswith(interface_name): 

259 return interface 

260 return None 

261 

262 def clear_cache(self): 

263 """Clear the interface cache.""" 

264 self._interface_cache.clear() 

265 logger.debug("Cleared interface cache") 

266 

267 

268# Global interface generator instance 

269interface_generator = InterfaceGenerator()