Source code for lwe.core.plugin_manager

import os
import importlib.util
import importlib.metadata

from lwe.core.config import Config
from lwe.core.logger import Logger
from lwe.core.cache_manager import CacheManager
import lwe.core.util as util

PLUGIN_PREFIX = "lwe_"


[docs] class PluginManager: def __init__( self, config=None, backend=None, cache_manager=None, search_path=None, additional_plugins=None, ): additional_plugins = additional_plugins or [] self.config = config or Config() self.log = Logger(self.__class__.__name__, self.config) self.backend = backend self.cache_manager = cache_manager or CacheManager(self.config) self.search_path = search_path if search_path else self.get_default_plugin_paths() self.plugins = {} self.package_plugins = {} self.plugin_list = list(set(config.get("plugins.enabled") + additional_plugins)) self.load_package_plugins(self.plugin_list) self.load_plugins(self.plugin_list)
[docs] def get_default_plugin_paths(self): user_plugin_dirs = ( self.config.args.plugins_dir or util.get_environment_variable_list("plugin_dir") or self.config.get("directories.plugins") ) system_plugin_dirs = [ os.path.join(util.get_package_root(self), "plugins"), ] plugin_paths = user_plugin_dirs + system_plugin_dirs self.log.debug(f"Plugin paths: {plugin_paths}") return plugin_paths
[docs] def inject_plugin(self, plugin_name, plugin_class): plugin_instance = plugin_class(self.config, cache_manager=self.cache_manager) self.setup_plugin(plugin_name, plugin_instance) self.plugins[plugin_name] = plugin_instance
[docs] def reload_plugin(self, plugin_name): if plugin_name in self.plugin_list: plugin_instance = self.load_plugin(plugin_name) if plugin_instance is not None: self.plugins[plugin_name] = plugin_instance message = f"Plugin {plugin_name} reloaded successfully" self.log.info(message) return True, plugin_instance, message return False, None, f"Failed to reload plugin {plugin_name}" else: message = f"Plugin {plugin_name} not found in plugin list" self.log.error(message) return False, None, message
[docs] def load_plugins(self, plugin_list): for plugin_name in plugin_list: plugin_instance = self.load_plugin(plugin_name) if plugin_instance is not None: self.plugins[plugin_name] = plugin_instance else: self.log.error(f"Plugin {plugin_name} not found in search path")
[docs] def merge_plugin_config(self, plugin_instance): config_key = f"plugins.{plugin_instance.name}" default_config = plugin_instance.default_config() user_config = self.config.get(config_key) or {} self.log.debug( f"Merging plugin {config_key} config, default: {default_config}, user: {user_config}" ) plugin_config = util.merge_dicts(default_config, user_config) self.config.set(config_key, plugin_config)
[docs] def load_package_plugins(self, plugin_list): self.log.info("Scanning for package plugins") entry_point_group = f"{PLUGIN_PREFIX}plugins" try: entry_points = importlib.metadata.entry_points().select(group=entry_point_group) except AttributeError: # TODO: Python 3.9 compatibility, remove when we drop support for 3.9. entry_points = importlib.metadata.entry_points().get(entry_point_group, []) for entry_point in entry_points: try: package_name = entry_point.dist.metadata["Name"] except AttributeError: # TODO: Python 3.9 compatibility, remove when we drop support for 3.9. package_name = entry_point.name plugin_name = util.dash_to_underscore(package_name[len(f"{PLUGIN_PREFIX}plugin_") :]) if plugin_name in plugin_list: try: klass = entry_point.load() plugin_instance = klass(self.config, cache_manager=self.cache_manager) self.log.info( f"Loaded plugin: {entry_point.name}, from package: {package_name}" ) self.package_plugins[plugin_name] = plugin_instance except Exception as e: self.log.error( f"Failed to load plugin {entry_point.name}, from package: {package_name}: {e}" ) else: self.log.info( f"Skip loading: {entry_point.name}, from package: {package_name}, reason: not enabled" )
[docs] def setup_plugin(self, plugin_name, plugin_instance): plugin_instance.set_name(plugin_name) plugin_instance.set_backend(self.backend) if self.backend.name in plugin_instance.incompatible_backends(): self.log.error( f"Plugin {plugin_name} is incompatible with backend {self.backend.name}, remove it from configuration" ) return False self.merge_plugin_config(plugin_instance) plugin_instance.setup() return True
[docs] def load_plugin(self, plugin_name): plugin_instance = None for path in self.search_path: plugin_file = os.path.join(path, plugin_name + ".py") self.log.debug(f"Searching for plugin file {plugin_file}") if os.path.exists(plugin_file): try: self.log.info(f"Loading plugin {plugin_name} from {plugin_file}") spec = importlib.util.spec_from_file_location(plugin_name, plugin_file) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) plugin_class_name = util.snake_to_class(plugin_name) plugin_class = getattr(module, plugin_class_name) plugin_instance = plugin_class(self.config, cache_manager=self.cache_manager) break except Exception as e: self.log.error(f"Error loading plugin {plugin_name} from {plugin_file}: {e}") return None if plugin_instance is None and plugin_name in self.package_plugins: self.log.info(f"Using package plugin for {plugin_name}") plugin_instance = self.package_plugins[plugin_name] if plugin_instance: if not self.setup_plugin(plugin_name, plugin_instance): return None return plugin_instance
[docs] def get_plugins(self): return self.plugins