2021-09-11 11:17:38 -07:00
|
|
|
"""Plugin manager definition."""
|
|
|
|
|
|
|
|
# Standard Library
|
2021-09-15 00:57:45 -07:00
|
|
|
import typing as t
|
2021-09-11 11:17:38 -07:00
|
|
|
from inspect import isclass
|
|
|
|
|
|
|
|
# Project
|
2021-09-11 17:55:27 -07:00
|
|
|
from hyperglass.log import log
|
2021-09-16 13:46:50 -07:00
|
|
|
from hyperglass.state import use_state
|
2021-09-11 11:17:38 -07:00
|
|
|
from hyperglass.exceptions.private import PluginError
|
|
|
|
|
|
|
|
# Local
|
|
|
|
from ._base import PluginType, HyperglassPlugin
|
2021-09-12 15:06:34 -07:00
|
|
|
from ._input import InputPlugin, InputPluginReturn
|
2021-09-13 02:37:05 -07:00
|
|
|
from ._output import OutputType, OutputPlugin
|
2021-09-12 15:06:34 -07:00
|
|
|
|
2021-09-15 00:57:45 -07:00
|
|
|
if t.TYPE_CHECKING:
|
2021-09-12 15:06:34 -07:00
|
|
|
# Project
|
2021-09-16 13:46:50 -07:00
|
|
|
from hyperglass.state import HyperglassState
|
2021-09-12 15:06:34 -07:00
|
|
|
from hyperglass.models.api.query import Query
|
2021-09-16 17:12:30 -07:00
|
|
|
from hyperglass.models.directive import Directive
|
2021-09-11 11:17:38 -07:00
|
|
|
|
2021-09-15 00:57:45 -07:00
|
|
|
PluginT = t.TypeVar("PluginT", bound=HyperglassPlugin)
|
2021-09-11 11:17:38 -07:00
|
|
|
|
|
|
|
|
2021-09-15 00:57:45 -07:00
|
|
|
class PluginManager(t.Generic[PluginT]):
|
2021-09-11 11:17:38 -07:00
|
|
|
"""Manage all plugins."""
|
|
|
|
|
|
|
|
_type: PluginType
|
2021-09-15 00:57:45 -07:00
|
|
|
_state: "HyperglassState"
|
2021-09-11 11:17:38 -07:00
|
|
|
_index: int = 0
|
|
|
|
_cache_key: str
|
|
|
|
|
|
|
|
def __init__(self: "PluginManager") -> None:
|
|
|
|
"""Initialize plugin manager."""
|
2021-09-15 00:57:45 -07:00
|
|
|
self._state = use_state()
|
2021-09-11 11:17:38 -07:00
|
|
|
self._cache_key = f"hyperglass.plugins.{self._type}"
|
|
|
|
|
|
|
|
def __init_subclass__(cls: "PluginManager", **kwargs: PluginType) -> None:
|
|
|
|
"""Set this plugin manager's type on subclass initialization."""
|
|
|
|
_type = kwargs.get("type", None) or cls._type
|
|
|
|
if _type is None:
|
2021-09-12 15:06:34 -07:00
|
|
|
raise PluginError("Plugin '{}' is missing a 'type', keyword argument", repr(cls))
|
2021-09-11 11:17:38 -07:00
|
|
|
cls._type = _type
|
|
|
|
return super().__init_subclass__()
|
|
|
|
|
|
|
|
def __iter__(self: "PluginManager") -> "PluginManager":
|
|
|
|
"""Plugin manager iterator."""
|
|
|
|
return self
|
|
|
|
|
|
|
|
def __next__(self: "PluginManager") -> PluginT:
|
|
|
|
"""Plugin manager iteration."""
|
2021-09-21 07:54:16 -07:00
|
|
|
if self._index <= len(self.plugins()):
|
|
|
|
result = self.plugins()[self._index - 1]
|
2021-09-11 11:17:38 -07:00
|
|
|
self._index += 1
|
|
|
|
return result
|
|
|
|
self._index = 0
|
|
|
|
raise StopIteration
|
|
|
|
|
2021-09-15 00:57:45 -07:00
|
|
|
def plugins(self: "PluginManager", builtins: bool = True) -> t.List[PluginT]:
|
2021-09-12 15:06:34 -07:00
|
|
|
"""Get all plugins, with built-in plugins last."""
|
2021-09-15 00:57:45 -07:00
|
|
|
plugins = self._state.plugins(self._type)
|
|
|
|
if builtins is False:
|
|
|
|
plugins = [p for p in plugins if p.__hyperglass_builtin__ is False]
|
|
|
|
|
|
|
|
# Sort plugins by their name attribute, which is the name of the class by default.
|
|
|
|
sorted_by_name = sorted(plugins, key=lambda p: str(p))
|
|
|
|
|
|
|
|
# Sort with built-in plugins last.
|
2021-09-12 15:06:34 -07:00
|
|
|
return sorted(
|
2021-09-15 00:57:45 -07:00
|
|
|
sorted_by_name,
|
2021-09-12 15:06:34 -07:00
|
|
|
key=lambda p: -1 if p.__hyperglass_builtin__ else 1, # flake8: noqa IF100
|
|
|
|
reverse=True,
|
|
|
|
)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def name(self: PluginT) -> str:
|
|
|
|
"""Get this plugin manager's name."""
|
|
|
|
return self.__class__.__name__
|
2021-09-11 11:17:38 -07:00
|
|
|
|
2021-09-15 00:57:45 -07:00
|
|
|
def methods(self: "PluginManager", name: str) -> t.Generator[t.Callable, None, None]:
|
2021-09-11 11:17:38 -07:00
|
|
|
"""Get methods of all registered plugins matching `name`."""
|
2021-09-21 07:54:16 -07:00
|
|
|
for plugin in self.plugins():
|
2021-09-11 11:17:38 -07:00
|
|
|
if hasattr(plugin, name):
|
|
|
|
method = getattr(plugin, name)
|
|
|
|
if callable(method):
|
|
|
|
yield method
|
|
|
|
|
2021-09-12 15:06:34 -07:00
|
|
|
def execute(self, *args, **kwargs) -> None:
|
|
|
|
"""Gather all plugins and execute in order."""
|
|
|
|
raise NotImplementedError(f"Plugin Manager '{self.name}' is missing an 'execute()' method.")
|
|
|
|
|
2021-09-11 11:17:38 -07:00
|
|
|
def reset(self: "PluginManager") -> None:
|
|
|
|
"""Remove all plugins."""
|
|
|
|
self._index = 0
|
2021-09-15 00:57:45 -07:00
|
|
|
self._state.reset_plugins(self._type)
|
2021-09-11 11:17:38 -07:00
|
|
|
|
|
|
|
def unregister(self: "PluginManager", plugin: PluginT) -> None:
|
|
|
|
"""Remove a plugin from currently active plugins."""
|
|
|
|
if isclass(plugin):
|
|
|
|
if issubclass(plugin, HyperglassPlugin):
|
2021-09-15 00:57:45 -07:00
|
|
|
self._state.remove_plugin(self._type, plugin)
|
|
|
|
|
2021-09-11 11:17:38 -07:00
|
|
|
return
|
|
|
|
raise PluginError("Plugin '{}' is not a valid hyperglass plugin", repr(plugin))
|
|
|
|
|
2021-09-15 00:57:45 -07:00
|
|
|
def register(self: "PluginManager", plugin: PluginT, *args: t.Any, **kwargs: t.Any) -> None:
|
2021-09-11 11:17:38 -07:00
|
|
|
"""Add a plugin to currently active plugins."""
|
|
|
|
# Create a set of plugins so duplicate plugins are not mistakenly added.
|
2021-09-11 17:55:27 -07:00
|
|
|
try:
|
2021-09-11 11:17:38 -07:00
|
|
|
if issubclass(plugin, HyperglassPlugin):
|
2021-09-12 18:27:33 -07:00
|
|
|
instance = plugin(*args, **kwargs)
|
2021-09-15 00:57:45 -07:00
|
|
|
self._state.add_plugin(self._type, instance)
|
2021-09-12 18:27:33 -07:00
|
|
|
if instance.__hyperglass_builtin__ is True:
|
|
|
|
log.debug("Registered built-in plugin '{}'", instance.name)
|
|
|
|
else:
|
|
|
|
log.success("Registered plugin '{}'", instance.name)
|
2021-09-11 11:17:38 -07:00
|
|
|
return
|
2021-09-11 17:55:27 -07:00
|
|
|
except TypeError:
|
|
|
|
raise PluginError(
|
|
|
|
"Plugin '{p}' has not defined a required method. "
|
|
|
|
"Please consult the hyperglass documentation.",
|
|
|
|
p=repr(plugin),
|
|
|
|
)
|
2021-09-12 15:06:34 -07:00
|
|
|
raise PluginError("Plugin '{p}' is not a valid hyperglass plugin", p=repr(plugin))
|
2021-09-11 11:17:38 -07:00
|
|
|
|
|
|
|
|
|
|
|
class InputPluginManager(PluginManager[InputPlugin], type="input"):
|
|
|
|
"""Manage Input Validation Plugins."""
|
|
|
|
|
2021-09-12 18:27:33 -07:00
|
|
|
def execute(
|
|
|
|
self: "InputPluginManager", *, directive: "Directive", query: "Query"
|
|
|
|
) -> InputPluginReturn:
|
2021-09-12 15:06:34 -07:00
|
|
|
"""Execute all input validation plugins.
|
|
|
|
|
|
|
|
If any plugin returns `False`, execution is halted.
|
|
|
|
"""
|
|
|
|
result = None
|
2021-09-21 07:54:16 -07:00
|
|
|
for plugin in (plugin for plugin in self.plugins() if directive.id in plugin.directives):
|
2021-09-12 15:06:34 -07:00
|
|
|
if result is False:
|
|
|
|
return result
|
|
|
|
result = plugin.validate(query)
|
|
|
|
return result
|
|
|
|
|
2021-09-11 11:17:38 -07:00
|
|
|
|
|
|
|
class OutputPluginManager(PluginManager[OutputPlugin], type="output"):
|
|
|
|
"""Manage Output Processing Plugins."""
|
2021-09-12 15:06:34 -07:00
|
|
|
|
2021-09-18 12:47:56 -07:00
|
|
|
def execute(self: "OutputPluginManager", *, output: OutputType, query: "Query") -> OutputType:
|
2021-09-12 15:06:34 -07:00
|
|
|
"""Execute all output parsing plugins.
|
|
|
|
|
|
|
|
The result of each plugin is passed to the next plugin.
|
|
|
|
"""
|
|
|
|
result = output
|
2021-09-18 12:47:56 -07:00
|
|
|
for plugin in (
|
2021-09-21 07:54:16 -07:00
|
|
|
plugin
|
|
|
|
for plugin in self.plugins()
|
|
|
|
if query.directive.id in plugin.directives and query.device.platform in plugin.platforms
|
2021-09-18 12:47:56 -07:00
|
|
|
):
|
2021-09-21 07:54:16 -07:00
|
|
|
log.debug("Output Plugin {!r} starting with\n{!r}", plugin.name, result)
|
|
|
|
result = plugin.process(output=result, query=query)
|
|
|
|
log.debug("Output Plugin {!r} completed with\n{!r}", plugin.name, result)
|
2021-09-12 15:06:34 -07:00
|
|
|
if result is False:
|
|
|
|
return result
|
|
|
|
# Pass the result of each plugin to the next plugin.
|
|
|
|
return result
|