builder / plugins.py
mgbam's picture
Update plugins.py
9fb92de verified
raw
history blame
5.17 kB
# plugins.py
# ------------------------------------------------------------------
# Generic plugin framework for AnyCoder AI
# ------------------------------------------------------------------
from __future__ import annotations
import importlib
import os
from abc import ABC, abstractmethod
from typing import Dict, List, Type
# ------------------------------------------------------------------ #
# 1  Base plugin interface
# ------------------------------------------------------------------ #
class Plugin(ABC):
"""
Abstract base class for AnyCoder runtime plugins.
Required attributes (class‑level):
----------------------------------
name : str – unique key (used to invoke the plugin)
description : str – short human description
"""
name: str
description: str
# ---- lifecycle ----------------------------------------------------
@abstractmethod
def initialize(self, config: Dict | None = None) -> None:
"""Called once at start‑up. Use for auth / heavy setup."""
...
@abstractmethod
def execute(self, **kwargs) -> Dict:
"""
Execute plugin action. Must return a JSON‑serialisable dict.
`**kwargs` are passed from the caller verbatim.
"""
...
# ------------------------------------------------------------------ #
# 2  Plugin manager
# ------------------------------------------------------------------ #
class PluginManager:
"""
Discovers *.py files under `plugins_dir`, registers concrete Plugin
subclasses, initialises them (once), and lets the app invoke them.
"""
def __init__(self, plugins_dir: str = "plugins") -> None:
self.plugins_dir = plugins_dir
self._registry: Dict[str, Type[Plugin]] = {}
self._instances: Dict[str, Plugin] = {}
# ---------- discovery ---------------------------------------------
def discover(self) -> None:
"""Import every *.py file in `plugins_dir` (non‑private)."""
if not os.path.isdir(self.plugins_dir):
return
for filename in os.listdir(self.plugins_dir):
if filename.startswith("_") or not filename.endswith(".py"):
continue
module_path = f"{self.plugins_dir}.{filename[:-3]}"
try:
module = importlib.import_module(module_path)
except Exception as exc: # pragma: no cover
print(f"[PLUGIN] Failed to import {module_path}: {exc}")
continue
# Register any subclasses of Plugin
for attr in dir(module):
obj = getattr(module, attr)
if (
isinstance(obj, type)
and issubclass(obj, Plugin)
and obj is not Plugin
):
self.register(obj)
# ---------- registry ----------------------------------------------
def register(self, plugin_cls: Type[Plugin]) -> None:
key = plugin_cls.name
if not key:
raise ValueError("Plugin class missing `.name` attribute.")
self._registry[key] = plugin_cls
def initialize_all(self, config: Dict | None = None) -> None:
for name, cls in self._registry.items():
try:
inst = cls()
inst.initialize(config or {})
self._instances[name] = inst
except Exception as exc: # pragma: no cover
print(f"[PLUGIN] Init failed for {name}: {exc}")
# ---------- public API --------------------------------------------
def list_plugins(self) -> List[str]:
return list(self._registry)
def execute(self, name: str, **kwargs) -> Dict:
if name not in self._instances:
raise ValueError(f"Plugin '{name}' is not initialised.")
return self._instances[name].execute(**kwargs)
# ------------------------------------------------------------------ #
# 3  Example built‑in plugin
# ------------------------------------------------------------------ #
class VSCodeSnippetPlugin(Plugin):
"""Generate VSCode snippet JSON for quick copy‑paste."""
name = "vscode_snippets"
description = "Produces VS Code snippet templates."
def initialize(self, config: Dict | None = None) -> None:
cfg = config or {}
self.snippet_dir = cfg.get("snippet_dir", "./snippets")
def execute(self, *, language: str = "python", snippet_name: str) -> Dict:
path = os.path.join(self.snippet_dir, f"{language}.{snippet_name}.json")
if os.path.isfile(path):
with open(path, "r", encoding="utf-8") as fh:
content = fh.read()
else:
content = '{ "prefix": "todo", "body": ["// add your snippet here"] }'
return {"plugin": self.name, "snippet": content}
# ------------------------------------------------------------------ #
# 4  Global manager instance (auto‑discover on import)
# ------------------------------------------------------------------ #
plugin_manager = PluginManager()
plugin_manager.discover()
plugin_manager.initialize_all()