|
|
|
@ -4,6 +4,8 @@ import inspect
|
|
|
|
import logging
|
|
|
|
import logging
|
|
|
|
import sys
|
|
|
|
import sys
|
|
|
|
import pkgutil
|
|
|
|
import pkgutil
|
|
|
|
|
|
|
|
from collections import namedtuple
|
|
|
|
|
|
|
|
from types import MappingProxyType
|
|
|
|
import pkg_resources
|
|
|
|
import pkg_resources
|
|
|
|
from configspec import ConfigSpecification
|
|
|
|
from configspec import ConfigSpecification
|
|
|
|
from .. import base_plugins
|
|
|
|
from .. import base_plugins
|
|
|
|
@ -11,6 +13,12 @@ from .. import base_plugins
|
|
|
|
|
|
|
|
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_loaded_plugins = {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
plugin_interfaces = {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
plugin_functions = {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class PluginLoadError(Exception):
|
|
|
|
class PluginLoadError(Exception):
|
|
|
|
pass
|
|
|
|
pass
|
|
|
|
@ -36,6 +44,16 @@ def plugin_attachment(hookname):
|
|
|
|
return wrapped
|
|
|
|
return wrapped
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class InterfaceFunction():
|
|
|
|
|
|
|
|
def __init__(self, func):
|
|
|
|
|
|
|
|
if not callable(func):
|
|
|
|
|
|
|
|
raise TypeError("Argument to InterfaceFunction must be callable")
|
|
|
|
|
|
|
|
self.func = func
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def __call__(self, *args, **kwargs):
|
|
|
|
|
|
|
|
return self.func(*args, **kwargs)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class PluginInterface():
|
|
|
|
class PluginInterface():
|
|
|
|
@staticmethod
|
|
|
|
@staticmethod
|
|
|
|
def _load_interface(module, plugin_name) -> "PluginInterface":
|
|
|
|
def _load_interface(module, plugin_name) -> "PluginInterface":
|
|
|
|
@ -58,6 +76,14 @@ class PluginInterface():
|
|
|
|
interface._plugin_name = plugin_name
|
|
|
|
interface._plugin_name = plugin_name
|
|
|
|
return interface
|
|
|
|
return interface
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
|
|
|
|
self._confspec = None
|
|
|
|
|
|
|
|
self._loaded = False
|
|
|
|
|
|
|
|
self._functions = {}
|
|
|
|
|
|
|
|
self.config = None
|
|
|
|
|
|
|
|
self.plugins = None
|
|
|
|
|
|
|
|
self._plugin_name = "<not yet loaded>"
|
|
|
|
|
|
|
|
|
|
|
|
def _load_confspec(self, module):
|
|
|
|
def _load_confspec(self, module):
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
If not already registered, looks for a ConfigSpecification instance in a plugin,
|
|
|
|
If not already registered, looks for a ConfigSpecification instance in a plugin,
|
|
|
|
@ -82,11 +108,6 @@ class PluginInterface():
|
|
|
|
def _load_pluginclass(self, module):
|
|
|
|
def _load_pluginclass(self, module):
|
|
|
|
pass
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
|
|
|
|
self._confspec = None
|
|
|
|
|
|
|
|
self._loaded = False
|
|
|
|
|
|
|
|
self._plugin_name = "<not yet loaded>"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _load_guard(self):
|
|
|
|
def _load_guard(self):
|
|
|
|
if self._loaded:
|
|
|
|
if self._loaded:
|
|
|
|
raise PluginLoadError("Cannot call interface register functions once"
|
|
|
|
raise PluginLoadError("Cannot call interface register functions once"
|
|
|
|
@ -98,6 +119,17 @@ class PluginInterface():
|
|
|
|
raise PluginLoadError("confspec must be an instance of ConfigSpecification")
|
|
|
|
raise PluginLoadError("confspec must be an instance of ConfigSpecification")
|
|
|
|
self._confspec = confspec
|
|
|
|
self._confspec = confspec
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def register_function(self, func, name=None):
|
|
|
|
|
|
|
|
self._load_guard()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if not name:
|
|
|
|
|
|
|
|
name = func.__name__
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if name in self._functions:
|
|
|
|
|
|
|
|
raise PluginLoadError(F"Interface function with name '{name}' already exists")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
self._functions[name] = InterfaceFunction(func)
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
@property
|
|
|
|
def confspec(self):
|
|
|
|
def confspec(self):
|
|
|
|
return self._confspec
|
|
|
|
return self._confspec
|
|
|
|
@ -132,9 +164,6 @@ def discover_installed_plugins():
|
|
|
|
return [entrypoint.name for entrypoint in pkg_resources.iter_entry_points('shepherd.plugins')]
|
|
|
|
return [entrypoint.name for entrypoint in pkg_resources.iter_entry_points('shepherd.plugins')]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
loaded_plugins = {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def load_plugin(plugin_name, plugin_dir=None):
|
|
|
|
def load_plugin(plugin_name, plugin_dir=None):
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
Finds a Shepherd plugin, loads it, and returns the resulting PluginInterface object.
|
|
|
|
Finds a Shepherd plugin, loads it, and returns the resulting PluginInterface object.
|
|
|
|
@ -153,8 +182,8 @@ def load_plugin(plugin_name, plugin_dir=None):
|
|
|
|
Returns: The PluginInterface for the loaded plugin
|
|
|
|
Returns: The PluginInterface for the loaded plugin
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
if plugin_name in loaded_plugins:
|
|
|
|
if plugin_name in _loaded_plugins:
|
|
|
|
return loaded_plugins[plugin_name]
|
|
|
|
return _loaded_plugins[plugin_name]
|
|
|
|
|
|
|
|
|
|
|
|
# Each of the 3 plugin sources have different import mechanisms. Discovery is broken out to
|
|
|
|
# Each of the 3 plugin sources have different import mechanisms. Discovery is broken out to
|
|
|
|
# allow them to be listed. Using a try/except block wouldn't be able to tell the difference
|
|
|
|
# allow them to be listed. Using a try/except block wouldn't be able to tell the difference
|
|
|
|
@ -185,10 +214,29 @@ def load_plugin(plugin_name, plugin_dir=None):
|
|
|
|
interface._load_confspec(mod)
|
|
|
|
interface._load_confspec(mod)
|
|
|
|
|
|
|
|
|
|
|
|
# TODO Populate plugin interface
|
|
|
|
# TODO Populate plugin interface
|
|
|
|
|
|
|
|
interface._loaded = True
|
|
|
|
|
|
|
|
|
|
|
|
loaded_plugins[plugin_name] = interface
|
|
|
|
_loaded_plugins[plugin_name] = interface
|
|
|
|
return interface
|
|
|
|
return interface
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def init_plugins(plugin_configs, core_config):
|
|
|
|
def init_plugins(plugin_configs, core_config):
|
|
|
|
pass
|
|
|
|
"""
|
|
|
|
|
|
|
|
Initialise plugins named as keys in plugin_configs. Plugins must already be loaded.
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
# Run plugin init and init hooks
|
|
|
|
|
|
|
|
plugin_names = plugin_configs.keys()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for plugin_name in plugin_names:
|
|
|
|
|
|
|
|
plugin_interfaces[plugin_name] = _loaded_plugins[plugin_name]
|
|
|
|
|
|
|
|
plugin_functions[plugin_name] = _loaded_plugins[plugin_name]._functions
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
plugin_functions_tuples = {}
|
|
|
|
|
|
|
|
for name, functions in plugin_functions.items():
|
|
|
|
|
|
|
|
plugin_functions_tuples[name] = namedtuple(
|
|
|
|
|
|
|
|
F'{name}_interface_functions', functions.keys())(**functions)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
plugin_functions_view = MappingProxyType(plugin_functions_tuples)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for name, plugin_interface in plugin_interfaces.items():
|
|
|
|
|
|
|
|
plugin_interface.plugins = plugin_functions_view
|
|
|
|
|