from __future__ import annotations import importlib import importlib.util import logging import sys from pathlib import Path logger = logging.getLogger(__name__) class PluginLoader: async def load(self, entry_file: str) -> object: try: path = Path(entry_file) if path.exists() and path.is_file(): module_name = entry_file.replace("/", ".").replace("\\", ".").removesuffix(".py") if module_name in sys.modules: return sys.modules[module_name] spec = importlib.util.spec_from_file_location(module_name, str(path)) if spec and spec.loader: module = importlib.util.module_from_spec(spec) sys.modules[module_name] = module spec.loader.exec_module(module) return module module_name = entry_file.replace("/", ".").replace("\\", ".").removesuffix(".py") return importlib.import_module(module_name) except Exception as exc: logger.error("failed to load plugin module %s: %s", entry_file, exc) raise def extract_adapter_class(self, module: object) -> type | None: for attr_name in dir(module): if attr_name.startswith("_"): continue attr = getattr(module, attr_name, None) if attr is None or not isinstance(attr, type): continue if not hasattr(attr, "get_default_config"): continue if not hasattr(attr, "capabilities"): continue if not hasattr(attr, "open") or not hasattr(attr, "close"): continue return attr return None