import importlib import inspect import logging import sys import os log = logging.getLogger(__name__) class PluginLoadError(Exception): pass def plugin(): pass def plugin_function(): pass def plugin_hook(): pass def plugin_attachment(hookname): pass class PluginInterface(): def __init__(self): self._confspec = None @property def confspec(self): return self._confspec def find_plugins(plugin_names, plugin_dir=None): """ Looks for the list of plugin names supplied and returns their classes. Will first try for plugin modules and packages locally located in ``shepherd.plugins``, then for modules and packages prefixed ``shepherd_`` located in the supplied ``plugin_dir`` and lastly in the global import path. Args: plugin_names: List of plugin names to try and load plugin_dir: optional search path Returns: Dict of plugin classes, with their names as keys """ plugin_classes = {} for plugin_name in plugin_names: # First look for core plugins, then the plugin_dir, then in the general import path # for custom ones prefixed with "shepherd_" try: # mod = importlib.import_module("shepherd.plugins." + plugin_name) mod = importlib.import_module('.'+plugin_name, "shepherd.plugins") # TODO - ModuleNotFoundError is also triggered here if the plugin has a dependancy # that can't be found except ModuleNotFoundError: try: if plugin_dir: if os.path.isdir(plugin_dir): sys.path.append(plugin_dir) mod = importlib.import_module("shepherd_" + plugin_name) sys.path.remove(plugin_dir) else: raise PluginLoadError("plugin_dir is not a valid directory") else: mod = importlib.import_module("shepherd_" + plugin_name) except ModuleNotFoundError: raise PluginLoadError("Could not find plugin "+plugin_name) # Scan imported module for Plugin subclass def is_module_plugin(member, module=mod): return (inspect.isclass(member) and member.__module__ == module.__name__ and issubclass(member, Plugin)) class_list = inspect.getmembers(mod, is_module_plugin) if class_list: if len(class_list) > 1: log.warning( F"Plugin module {mod.__name__} has more than one shepherd.Plugin subclass.") _, plugin_classes[plugin_name] = class_list[0] log.info(F"Loading plugin {plugin_classes[plugin_name].__name__}" " from module {mod.__name__}") else: raise PluginLoadError("Imported shepherd plugin modules must contain a" " subclass of shepherd.plugin.Plugin, such as" " shepherd.plugin.SimplePlugin") return plugin_classes