""" RMI Plugin Architecture - Extensible Backend System ==================================================== Plugin system for adding new features without modifying core code. Features: • Plugin Registry - discover, load, and manage plugins • Plugin Types - connectors, scanners, analyzers, notifiers, exporters • Hot Reload - reload plugins without restart • Sandboxed Execution - isolated plugin environments • Plugin API - standardized interface for all plugins • Configuration Management - per-plugin config with validation • Health Checks - monitor plugin status and performance • Dependency Management - handle plugin dependencies Plugin Types: connector - Data sources (exchanges, APIs, oracles) scanner - Security scanners (contract, wallet, token) analyzer - Analysis engines (risk, sentiment, on-chain) notifier - Alert channels (email, telegram, webhook) exporter - Data export (CSV, PDF, API, webhook) wallet - Wallet integrations (hardware, custodial) payment - Payment processors (x402, stripe, crypto) ml - ML models (fraud detection, prediction) Author: RMI Platform Team Date: 2026-05-31 """ import importlib import importlib.util import logging import os import time from abc import ABC, abstractmethod from collections.abc import Callable from datetime import UTC, datetime from enum import StrEnum from typing import Any logger = logging.getLogger("rmi_plugin_system") # ── Enums ───────────────────────────────────────────────────── class PluginType(StrEnum): CONNECTOR = "connector" SCANNER = "scanner" ANALYZER = "analyzer" NOTIFIER = "notifier" EXPORTER = "exporter" WALLET = "wallet" PAYMENT = "payment" ML = "ml" SECURITY = "security" ANALYTICS = "analytics" class PluginStatus(StrEnum): ACTIVE = "active" INACTIVE = "inactive" ERROR = "error" LOADING = "loading" DEPRECATED = "deprecated" # ── Base Plugin Interface ───────────────────────────────────── class Plugin(ABC): """Base class for all plugins.""" def __init__(self, config: dict[str, Any] | None = None): self.config = config or {} self.status = PluginStatus.LOADING.value self.last_error = "" self.load_time = datetime.now(UTC).isoformat() self.call_count = 0 self.error_count = 0 @property @abstractmethod def name(self) -> str: """Plugin name.""" pass @property @abstractmethod def version(self) -> str: """Plugin version.""" pass @property @abstractmethod def plugin_type(self) -> PluginType: """Plugin type.""" pass @property @abstractmethod def description(self) -> str: """Plugin description.""" pass def initialize(self) -> bool: """Initialize plugin. Return True if successful.""" try: self._setup() self.status = PluginStatus.ACTIVE.value return True except Exception as e: self.status = PluginStatus.ERROR.value self.last_error = str(e) logger.error(f"Plugin {self.name} initialization failed: {e}") return False def _setup(self): # noqa: B027 """Override for setup logic.""" pass def health_check(self) -> dict[str, Any]: """Return health status.""" return { "status": self.status, "call_count": self.call_count, "error_count": self.error_count, "error_rate": self.error_count / max(self.call_count, 1), "last_error": self.last_error, "uptime_seconds": int(time.time() - datetime.fromisoformat(self.load_time).timestamp()), } def shutdown(self): """Cleanup on shutdown.""" self.status = PluginStatus.INACTIVE.value def to_dict(self) -> dict: return { "name": self.name, "version": self.version, "type": self.plugin_type.value, "description": self.description, "status": self.status, "config": self.config, "health": self.health_check(), } # ── Plugin Registry ─────────────────────────────────────────── class PluginRegistry: """ Central registry for all plugins. Manages plugin lifecycle, discovery, and execution. """ PLUGIN_DIR = "/root/backend/plugins" def __init__(self): self._plugins: dict[str, Plugin] = {} self._hooks: dict[str, list[Callable]] = {} self._ensure_plugin_dir() def _ensure_plugin_dir(self): """Ensure plugin directory exists.""" os.makedirs(self.PLUGIN_DIR, exist_ok=True) for ptype in PluginType: os.makedirs(os.path.join(self.PLUGIN_DIR, ptype.value), exist_ok=True) def register(self, plugin: Plugin) -> bool: """Register a plugin instance.""" if plugin.name in self._plugins: logger.warning(f"Plugin {plugin.name} already registered - replacing") if plugin.initialize(): self._plugins[plugin.name] = plugin logger.info(f"Plugin registered: {plugin.name} v{plugin.version}") return True return False def unregister(self, name: str) -> bool: """Unregister a plugin.""" plugin = self._plugins.pop(name, None) if plugin: plugin.shutdown() logger.info(f"Plugin unregistered: {name}") return True return False def get(self, name: str) -> Plugin | None: """Get plugin by name.""" return self._plugins.get(name) def list_plugins(self, plugin_type: PluginType | None = None) -> list[Plugin]: """List all plugins, optionally filtered by type.""" plugins = list(self._plugins.values()) if plugin_type: plugins = [p for p in plugins if p.plugin_type == plugin_type] return plugins def get_by_type(self, plugin_type: PluginType) -> list[Plugin]: """Get all plugins of a specific type.""" return [p for p in self._plugins.values() if p.plugin_type == plugin_type] def discover_plugins(self) -> list[str]: """Discover plugins from plugin directory.""" discovered = [] for ptype in PluginType: type_dir = os.path.join(self.PLUGIN_DIR, ptype.value) if os.path.exists(type_dir): for fname in os.listdir(type_dir): if fname.endswith(".py") and not fname.startswith("_"): discovered.append(os.path.join(type_dir, fname)) return discovered def load_plugin_from_file(self, filepath: str) -> Plugin | None: """Load a plugin from a Python file.""" try: spec = importlib.util.spec_from_file_location("plugin", filepath) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) # Find Plugin subclass for attr_name in dir(module): attr = getattr(module, attr_name) if isinstance(attr, type) and issubclass(attr, Plugin) and attr != Plugin: instance = attr() if self.register(instance): return instance logger.warning(f"No Plugin subclass found in {filepath}") return None except Exception as e: logger.error(f"Failed to load plugin from {filepath}: {e}") return None def reload_plugin(self, name: str) -> bool: """Reload a plugin.""" plugin = self._plugins.get(name) if not plugin: return False # Unregister and re-register self.unregister(name) return plugin.initialize() and self.register(plugin) def get_stats(self) -> dict[str, Any]: """Get plugin registry statistics.""" by_type = {} by_status = {} for p in self._plugins.values(): by_type[p.plugin_type.value] = by_type.get(p.plugin_type.value, 0) + 1 by_status[p.status] = by_status.get(p.status, 0) + 1 return { "total_plugins": len(self._plugins), "by_type": by_type, "by_status": by_status, "plugins": [p.to_dict() for p in self._plugins.values()], } # ── Hook System ──────────────────────────────────────── def register_hook(self, event: str, callback: Callable): """Register a hook for an event.""" if event not in self._hooks: self._hooks[event] = [] self._hooks[event].append(callback) def trigger_hook(self, event: str, *args, **kwargs) -> list[Any]: """Trigger all hooks for an event.""" results = [] for callback in self._hooks.get(event, []): try: result = callback(*args, **kwargs) results.append(result) except Exception as e: logger.error(f"Hook error for {event}: {e}") return results # ── Built-in Plugin Examples ────────────────────────────────── class PrometheusExporterPlugin(Plugin): """Export metrics to Prometheus.""" @property def name(self) -> str: return "prometheus_exporter" @property def version(self) -> str: return "1.0.0" @property def plugin_type(self) -> PluginType: return PluginType.EXPORTER @property def description(self) -> str: return "Export metrics in Prometheus format" def export(self, metrics: dict[str, Any]) -> str: """Export metrics to Prometheus format.""" lines = [] for name, value in metrics.items(): prom_name = f"rmi_{name}" lines.append(f"# HELP {prom_name} Metric") lines.append(f"# TYPE {prom_name} gauge") lines.append(f"{prom_name} {value}") return "\n".join(lines) class WebhookNotifierPlugin(Plugin): """Send notifications via webhook.""" @property def name(self) -> str: return "webhook_notifier" @property def version(self) -> str: return "1.0.0" @property def plugin_type(self) -> PluginType: return PluginType.NOTIFIER @property def description(self) -> str: return "Send notifications to configured webhooks" def _setup(self): self.webhook_url = self.config.get("webhook_url", "") self.headers = self.config.get("headers", {}) async def notify(self, event: str, data: dict[str, Any]) -> bool: """Send notification to webhook.""" if not self.webhook_url: return False try: import httpx async with httpx.AsyncClient() as client: payload = { "event": event, "timestamp": datetime.now(UTC).isoformat(), "data": data, } r = await client.post( self.webhook_url, json=payload, headers=self.headers, timeout=10, ) return r.status_code < 400 except Exception as e: logger.error(f"Webhook notification failed: {e}") self.error_count += 1 return False class RedisCachePlugin(Plugin): """Redis caching plugin.""" @property def name(self) -> str: return "redis_cache" @property def version(self) -> str: return "1.0.0" @property def plugin_type(self) -> PluginType: return PluginType.CONNECTOR @property def description(self) -> str: return "Redis caching and pub/sub connector" def _setup(self): import redis.asyncio as redis_lib self.redis = redis_lib.Redis( host=self.config.get("host", "localhost"), port=self.config.get("port", 6379), password=self.config.get("password", ""), decode_responses=True, ) async def get(self, key: str) -> str | None: return await self.redis.get(key) async def set(self, key: str, value: str, ttl: int = 3600): await self.redis.setex(key, ttl, value) async def publish(self, channel: str, message: str): await self.redis.publish(channel, message) # ── Singleton ───────────────────────────────────────────────── _plugin_registry: PluginRegistry | None = None def get_plugin_registry() -> PluginRegistry: """Get or create plugin registry.""" global _plugin_registry if _plugin_registry is None: _plugin_registry = PluginRegistry() # Auto-register built-in plugins _plugin_registry.register(PrometheusExporterPlugin()) _plugin_registry.register(WebhookNotifierPlugin()) _plugin_registry.register(RedisCachePlugin()) return _plugin_registry