Spaces:
Running
Running
Download app/services/plugin_system.py from Samad14/bio-nexus-api: direct link, hf CLI and curl.
- Browser
- Download file 6.23 kB
-
https://huggingface.co/spaces/Samad14/bio-nexus-api/resolve/main/app/services/plugin_system.py
- Command line
-
hf download hf://spaces/Samad14/bio-nexus-api/app/services/plugin_system.py
-
curl -L -o plugin_system.py https://huggingface.co/spaces/Samad14/bio-nexus-api/resolve/main/app/services/plugin_system.py
6.23 kB
| """Plugin System (BioNexus 2.0, Component 20). | |
| Extends the platform at runtime: lightweight Python plugins declare hook | |
| callbacks that are dispatched by the PluginManager around engine validation, | |
| exports and pipeline events. Plugins live in BIONEXUS_PLUGINS_DIR (default | |
| app/plugins/) as plain .py files; each subclass of BioNexusPlugin is loaded, | |
| registered and enabled. All plugin failures are contained (never break the | |
| host operation). | |
| """ | |
| from __future__ import annotations | |
| import importlib.util | |
| import logging | |
| import os | |
| import threading | |
| from pathlib import Path | |
| from typing import Any | |
| logger = logging.getLogger(__name__) | |
| PLUGIN_DIR = os.environ.get("BIONEXUS_PLUGINS_DIR") | |
| if not PLUGIN_DIR: | |
| PLUGIN_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "plugins") | |
| class BioNexusPlugin: | |
| """Override the hook methods you care about. `name`/`version` are required.""" | |
| name: str = "unnamed" | |
| version: str = "0.0.0" | |
| description: str = "" | |
| #: lifecycle + pipeline hooks (all optional, all contained-safe) | |
| def on_register(self) -> None: | |
| pass | |
| def on_disable(self) -> None: | |
| pass | |
| def before_validate(self, engine_name: str, report: dict, result: Any = None) -> list[dict]: | |
| """Return extra validation checks [{name, passed, detail}] appended to | |
| the engine's native PASS/FAIL report. `result` is the parsed EngineResult.""" | |
| return [] | |
| def after_export(self, engine_name: str, payload: dict) -> dict | None: | |
| """Optionally decorate an exported artifact (returned dict is merged).""" | |
| return None | |
| def on_event(self, event: str, payload: dict) -> list[dict]: | |
| """Receive arbitrary platform events as a trace record.""" | |
| return [] | |
| class PluginManager: | |
| def __init__(self, plugin_dir: str | None = None) -> None: | |
| self.dir = plugin_dir or PLUGIN_DIR | |
| self.plugins: dict[str, BioNexusPlugin] = {} | |
| self.enabled: set[str] = set() | |
| self.trace: list[dict] = [] | |
| self._lock = threading.Lock() | |
| # --- discovery ---------------------------------------------------------- | |
| def load_dir(self, directory: str | None = None) -> list[str]: | |
| """Import every *.py plugin in the (default) plugin directory.""" | |
| scan_dir = directory or self.dir | |
| loaded: list[str] = [] | |
| if not os.path.isdir(scan_dir): | |
| return loaded | |
| for path in sorted(Path(scan_dir).glob("*.py")): | |
| if path.name.startswith("_"): | |
| continue | |
| before = set(self.plugins) | |
| self._load_file(path) | |
| if set(self.plugins) - before: | |
| loaded.append(path.stem) | |
| return loaded | |
| def _load_file(self, path: Path) -> None: | |
| try: | |
| spec = importlib.util.spec_from_file_location(f"bionexus_plugin_{path.stem}", path) | |
| mod = importlib.util.module_from_spec(spec) | |
| spec.loader.exec_module(mod) | |
| except Exception as e: | |
| logger.warning("plugin %s import failed: %s", path.name, e) | |
| return | |
| added = False | |
| for obj in vars(mod).values(): | |
| if isinstance(obj, type) and issubclass(obj, BioNexusPlugin) and obj is not BioNexusPlugin: | |
| plugin = obj() | |
| try: | |
| plugin.on_register() | |
| self.plugins[plugin.name] = plugin | |
| self.enabled.add(plugin.name) | |
| except Exception as e: | |
| logger.warning("plugin %s register failed: %s", plugin.name, e) | |
| def reload(self) -> list[str]: | |
| with self._lock: | |
| self.plugins = {} | |
| self.enabled = set() | |
| return self.load_dir() | |
| # --- state -------------------------------------------------------------- | |
| def list_plugins(self) -> list[dict]: | |
| with self._lock: | |
| return [{"name": p.name, "version": p.version, "description": p.description, | |
| "enabled": p.name in self.enabled} | |
| for p in self.plugins.values()] | |
| def set_enabled(self, name: str, enabled: bool) -> bool: | |
| with self._lock: | |
| if name not in self.plugins: | |
| return False | |
| if enabled: | |
| self.enabled.add(name) | |
| else: | |
| self.enabled.discard(name) | |
| try: | |
| self.plugins[name].on_disable() | |
| except Exception as e: | |
| logger.warning("plugin %s on_disable failed: %s", name, e) | |
| return True | |
| # --- hooks -------------------------------------------------------------- | |
| def _active(self) -> list[BioNexusPlugin]: | |
| with self._lock: | |
| return [p for n, p in self.plugins.items() if n in self.enabled] | |
| def before_validate(self, engine_name: str, report: dict, result: Any = None) -> list[dict]: | |
| """Collect extra checks from every active plugin, containing failures.""" | |
| extra: list[dict] = [] | |
| for p in self._active(): | |
| try: | |
| extra.extend(p.before_validate(engine_name, report, result)) | |
| except Exception as e: | |
| extra.append({"name": f"{p.name}:validate_error", "passed": False, "detail": str(e)}) | |
| return extra | |
| def after_export(self, engine_name: str, payload: dict) -> dict: | |
| merged = dict(payload) | |
| for p in self._active(): | |
| try: | |
| decor = p.after_export(engine_name, payload) | |
| if isinstance(decor, dict): | |
| merged.update(decor) | |
| except Exception as e: | |
| logger.warning("plugin %s.after_export failed: %s", p.name, e) | |
| return merged | |
| def run_event(self, event: str, payload: dict) -> list[dict]: | |
| records: list[dict] = [] | |
| for p in self._active(): | |
| try: | |
| records.extend(p.on_event(event, dict(payload))) | |
| except Exception as e: | |
| records.append({"plugin": p.name, "event": event, "error": str(e)}) | |
| self.trace.append({"event": event, "payload": payload, "records": records}) | |
| return records | |
| plugin_manager = PluginManager() |