import importlib import os import warnings import logging MAIN_MODULE = '__init__' HERE = os.path.abspath(os.path.dirname(__file__)) DEFAULT_PLUGIN_PATH = os.path.join(HERE, 'default') def search_plugin_dirs(plugin_dirs, include_default=True): """ Search through, and load from, a list of plugin directories. Args: plugin_dirs (list): List of strings of plugin directories. include_default (bool): Also load plugins from the module default directory (DEFAULT_PLUGIN_PATH) """ global DEFAULT_PLUGIN_PATH if include_default: # If including default plugins plugin_dirs.append(DEFAULT_PLUGIN_PATH) # Add default directory to the search paths logging.debug(plugin_dirs) plugins = [] # List of loaded plugins for plugin_dir in plugin_dirs: # For each plugin directory logging.debug("Searching {}".format(plugin_dir)) plugins.extend(find_plugins(plugin_dir)) # Find plugin folders, and load into list return plugins def find_plugins(plugin_dir): """ Find all plugins residing within a directory Args: plugin_dir (str): String of directory to be searched """ plugins = [] plugins_folders = os.listdir(plugin_dir) loader_details = ( importlib.machinery.SourceFileLoader, importlib.machinery.SOURCE_SUFFIXES ) for i in plugins_folders: plugin_folder = os.path.join(plugin_dir, i) logging.info(plugin_folder) if not os.path.isdir(plugin_folder): continue if not MAIN_MODULE + '.py' in os.listdir(plugin_folder): continue module_spec = importlib.machinery.FileFinder(plugin_folder, loader_details).find_spec(MAIN_MODULE) plugins.append(module_spec) return plugins def load_plugin(module_spec): """ Load a source file from a given spec. Args: module_spec: Module spec of module to be returned """ module = importlib.util.module_from_spec(module_spec) module_spec.loader.exec_module(module) return module class PluginMount(object): """ A mount-point for all loaded plugins. Attaches to a Microscope object. Args: parent (:py:class:`openflexure_microscope.microscope.Microscope`): The parent Microscope object to attach to. """ def __init__(self, parent): self.parent = parent print("Creating plugin mount") def attach(self, plugin_module): if not hasattr(plugin_module, 'PLUGINS') or not isinstance(plugin_module.PLUGINS, dict): raise Exception("No falid PLUGINS dictionary found in {}".format(plugin_module)) for plugin_name, plugin_class in plugin_module.PLUGINS.items(): plugin_object = plugin_class() if hasattr(self, plugin_name): warnings.warn("A plugin named {} has already been loaded. Skipping {}.".format(plugin_name, plugin_class)) else: setattr(self, plugin_name, plugin_object) # Grant plugin access to the hardware assert(isinstance(plugin_object, MicroscopePlugin)) plugin_object.camera = self.parent.camera plugin_object.stage = self.parent.stage print("Adding plugin: {}".format(plugin_name)) class MicroscopePlugin(): """ Parent class for all microscope plugins. Initially only defines an empty object for camera and stage. All plugins must be an instance of this class to successfully attach to PluginMount. """ def __init__(self): self.camera = None #: :py:class:`openflexure_microscope.camera.pi.StreamingCamera`: Picamera object self.stage = None #: :py:class:`openflexure_stage.stage.OpenFlexureStage`: OpenFlexure stage object