Coverage for fastblocks/mcp/registry.py: 36%

137 statements  

« prev     ^ index     » next       coverage.py v7.10.7, created at 2025-10-09 00:47 -0700

1"""Central registry for FastBlocks adapter management.""" 

2 

3from contextlib import suppress 

4from typing import Any 

5from uuid import UUID 

6 

7from acb.depends import depends 

8 

9from .discovery import AdapterDiscoveryServer, AdapterInfo 

10 

11 

12class AdapterRegistry: 

13 """Central registry for managing FastBlocks adapters.""" 

14 

15 def __init__(self) -> None: 

16 """Initialize adapter registry.""" 

17 self.discovery = AdapterDiscoveryServer() 

18 self._active_adapters: dict[str, Any] = {} 

19 self._adapter_dependencies: dict[str, set[str]] = {} 

20 self._adapter_config: dict[str, dict[str, Any]] = {} 

21 

22 async def initialize(self) -> None: 

23 """Initialize the registry by discovering all adapters.""" 

24 await self.discovery.discover_adapters() 

25 

26 async def register_adapter(self, adapter_name: str, adapter_instance: Any) -> bool: 

27 """Register an adapter instance in the registry.""" 

28 try: 

29 # Register with ACB system 

30 with suppress(Exception): 

31 depends.set(adapter_instance) 

32 

33 # Track in our registry 

34 self._active_adapters[adapter_name] = adapter_instance 

35 

36 return True 

37 except Exception: 

38 return False 

39 

40 async def unregister_adapter(self, adapter_name: str) -> bool: 

41 """Unregister an adapter from the registry.""" 

42 try: 

43 if adapter_name in self._active_adapters: 

44 del self._active_adapters[adapter_name] 

45 

46 # TODO: Remove from ACB registry if possible 

47 return True 

48 except Exception: 

49 return False 

50 

51 async def get_adapter(self, adapter_name: str) -> Any | None: 

52 """Get an adapter instance by name.""" 

53 # Try active registry first 

54 if adapter_name in self._active_adapters: 

55 return self._active_adapters[adapter_name] 

56 

57 # Try ACB registry 

58 with suppress(Exception): # Try to instantiate from discovery 

59 adapter = depends.get(adapter_name) 

60 if adapter: 

61 self._active_adapters[adapter_name] = adapter 

62 return adapter 

63 

64 adapter = await self.discovery.instantiate_adapter(adapter_name) 

65 if adapter: 

66 await self.register_adapter(adapter_name, adapter) 

67 return adapter 

68 

69 return None 

70 

71 async def get_adapter_info(self, adapter_name: str) -> AdapterInfo | None: 

72 """Get adapter information by name.""" 

73 return await self.discovery.get_adapter_by_name(adapter_name) 

74 

75 async def list_available_adapters(self) -> dict[str, AdapterInfo]: 

76 """List all available adapters.""" 

77 return await self.discovery.discover_adapters() 

78 

79 async def list_active_adapters(self) -> dict[str, Any]: 

80 """List currently active (instantiated) adapters.""" 

81 return self._active_adapters.copy() 

82 

83 async def get_adapters_by_category(self, category: str) -> list[AdapterInfo]: 

84 """Get all adapters in a specific category.""" 

85 return await self.discovery.get_adapters_by_category(category) 

86 

87 async def get_categories(self) -> list[str]: 

88 """Get all available adapter categories.""" 

89 return await self.discovery.get_all_categories() 

90 

91 def _validate_module_id(self, adapter: Any, result: dict[str, Any]) -> None: 

92 """Validate adapter MODULE_ID attribute.""" 

93 if not hasattr(adapter, "MODULE_ID"): 

94 result["warnings"].append("Missing MODULE_ID attribute") 

95 elif not isinstance(adapter.MODULE_ID, UUID): 

96 result["errors"].append("MODULE_ID is not a valid UUID") 

97 

98 def _validate_module_status(self, adapter: Any, result: dict[str, Any]) -> None: 

99 """Validate adapter MODULE_STATUS attribute.""" 

100 if not hasattr(adapter, "MODULE_STATUS"): 

101 result["warnings"].append("Missing MODULE_STATUS attribute") 

102 elif adapter.MODULE_STATUS not in ( 

103 "stable", 

104 "beta", 

105 "alpha", 

106 "experimental", 

107 ): 

108 result["warnings"].append(f"Unknown MODULE_STATUS: {adapter.MODULE_STATUS}") 

109 

110 def _validate_settings(self, adapter: Any, result: dict[str, Any]) -> None: 

111 """Validate adapter settings configuration.""" 

112 if hasattr(adapter, "settings"): 

113 result["info"]["has_settings"] = True 

114 settings = adapter.settings 

115 if hasattr(settings, "__dict__"): 

116 result["info"]["settings_properties"] = list(settings.__dict__.keys()) 

117 else: 

118 result["warnings"].append("No settings attribute found") 

119 

120 async def validate_adapter(self, adapter_name: str) -> dict[str, Any]: 

121 """Validate an adapter's configuration and functionality.""" 

122 result: dict[str, Any] = { 

123 "valid": False, 

124 "errors": [], 

125 "warnings": [], 

126 "info": {}, 

127 } 

128 

129 try: 

130 # Get adapter info 

131 info = await self.get_adapter_info(adapter_name) 

132 if not info: 

133 result["errors"].append(f"Adapter '{adapter_name}' not found") 

134 return result 

135 

136 result["info"] = info.to_dict() 

137 

138 # Try to get/instantiate adapter 

139 adapter = await self.get_adapter(adapter_name) 

140 if not adapter: 

141 result["errors"].append( 

142 f"Failed to instantiate adapter '{adapter_name}'" 

143 ) 

144 return result 

145 

146 # Validate adapter attributes 

147 self._validate_module_id(adapter, result) 

148 self._validate_module_status(adapter, result) 

149 self._validate_settings(adapter, result) 

150 

151 # If we got here without errors, adapter is valid 

152 if not result["errors"]: 

153 result["valid"] = True 

154 

155 except Exception as e: 

156 result["errors"].append(f"Validation error: {e}") 

157 

158 return result 

159 

160 async def auto_register_available_adapters(self) -> dict[str, bool]: 

161 """Automatically register all available adapters.""" 

162 results = {} 

163 available_adapters = await self.list_available_adapters() 

164 

165 for adapter_name in available_adapters: 

166 try: 

167 adapter = await self.discovery.instantiate_adapter(adapter_name) 

168 if adapter: 

169 success = await self.register_adapter(adapter_name, adapter) 

170 results[adapter_name] = success 

171 else: 

172 results[adapter_name] = False 

173 except Exception: 

174 results[adapter_name] = False 

175 

176 return results 

177 

178 async def get_adapter_statistics(self) -> dict[str, Any]: 

179 """Get comprehensive statistics about adapters.""" 

180 available = await self.list_available_adapters() 

181 active = await self.list_active_adapters() 

182 categories = await self.get_categories() 

183 

184 stats: dict[str, Any] = { 

185 "total_available": len(available), 

186 "total_active": len(active), 

187 "total_categories": len(categories), 

188 "categories": {}, 

189 "status_breakdown": {}, 

190 "active_adapters": list(active.keys()), 

191 "inactive_adapters": [name for name in available if name not in active], 

192 } 

193 

194 # Category breakdown 

195 categories_dict: dict[str, dict[str, Any]] = {} 

196 for category in categories: 

197 category_adapters = await self.get_adapters_by_category(category) 

198 categories_dict[category] = { 

199 "total": len(category_adapters), 

200 "adapters": [adapter.name for adapter in category_adapters], 

201 } 

202 stats["categories"] = categories_dict 

203 

204 # Status breakdown 

205 status_breakdown: dict[str, int] = {} 

206 for adapter_info in available.values(): 

207 status = adapter_info.module_status 

208 if status not in status_breakdown: 

209 status_breakdown[status] = 0 

210 status_breakdown[status] += 1 

211 stats["status_breakdown"] = status_breakdown 

212 

213 return stats 

214 

215 def configure_adapter(self, adapter_name: str, config: dict[str, Any]) -> None: 

216 """Configure an adapter with custom settings.""" 

217 self._adapter_config[adapter_name] = config 

218 

219 def get_adapter_config(self, adapter_name: str) -> dict[str, Any]: 

220 """Get configuration for an adapter.""" 

221 return self._adapter_config.get(adapter_name, {}) 

222 

223 def add_adapter_dependency(self, adapter_name: str, dependency: str) -> None: 

224 """Add a dependency relationship between adapters.""" 

225 if adapter_name not in self._adapter_dependencies: 

226 self._adapter_dependencies[adapter_name] = set() 

227 self._adapter_dependencies[adapter_name].add(dependency) 

228 

229 def get_adapter_dependencies(self, adapter_name: str) -> set[str]: 

230 """Get dependencies for an adapter.""" 

231 return self._adapter_dependencies.get(adapter_name, set()) 

232 

233 async def resolve_dependencies(self, adapter_name: str) -> list[str]: 

234 """Resolve and load all dependencies for an adapter.""" 

235 dependencies = self.get_adapter_dependencies(adapter_name) 

236 loaded = [] 

237 

238 for dep in dependencies: 

239 adapter = await self.get_adapter(dep) 

240 if adapter: 

241 loaded.append(dep) 

242 

243 return loaded