|
|
@@ -0,0 +1,67 @@
|
|
|
+import functools
|
|
|
+import importlib
|
|
|
+from collections import namedtuple
|
|
|
+from importlib import resources
|
|
|
+from typing import Type, Any, List
|
|
|
+
|
|
|
+# Basic structure for storing information about one plugin
|
|
|
+Plugin = namedtuple("Plugin", ("name", "func"))
|
|
|
+
|
|
|
+# Dictionary with information about all registered plugins
|
|
|
+_PLUGINS = {}
|
|
|
+
|
|
|
+# From https://realpython.com/python-import/#example-a-package-of-plugins
|
|
|
+
|
|
|
+
|
|
|
+def register(func):
|
|
|
+ """Decorator for registering a new plugin"""
|
|
|
+ package, _, plugin = func.__module__.rpartition(".")
|
|
|
+ pkg_info = _PLUGINS.setdefault(package, {})
|
|
|
+ pkg_info[plugin] = Plugin(name=plugin, func=func)
|
|
|
+ return func
|
|
|
+
|
|
|
+
|
|
|
+def names(package: str) -> List[str]:
|
|
|
+ """List all plugins in one package"""
|
|
|
+ _import_all(package)
|
|
|
+ return sorted(_PLUGINS[package])
|
|
|
+
|
|
|
+
|
|
|
+def get(package: str, plugin: str) -> Type[Any]:
|
|
|
+ """Get a given plugin"""
|
|
|
+ _import(package, plugin)
|
|
|
+ return _PLUGINS[package][plugin].func
|
|
|
+
|
|
|
+
|
|
|
+def call(package: str, plugin: str, *args, **kwargs) -> Any:
|
|
|
+ """Call the given plugin"""
|
|
|
+ plugin_func = get(package, plugin)
|
|
|
+ return plugin_func(*args, **kwargs)
|
|
|
+
|
|
|
+
|
|
|
+def _import(package: str, plugin: str) -> None:
|
|
|
+ """Import the given plugin file from a package"""
|
|
|
+ importlib.import_module(f"{package}.{plugin}")
|
|
|
+
|
|
|
+
|
|
|
+def _import_all(package: str) -> None:
|
|
|
+ """Import all plugins in a package"""
|
|
|
+ files = resources.contents(package)
|
|
|
+ plugins = [f[:-3] for f in files if f.endswith(".py") and f[0] != "_"]
|
|
|
+ for plugin in plugins:
|
|
|
+ _import(package, plugin)
|
|
|
+
|
|
|
+
|
|
|
+def names_factory(package: str):
|
|
|
+ """Create a names() function for one package"""
|
|
|
+ return functools.partial(names, package)
|
|
|
+
|
|
|
+
|
|
|
+def get_factory(package: str):
|
|
|
+ """Create a get() function for one package"""
|
|
|
+ return functools.partial(get, package)
|
|
|
+
|
|
|
+
|
|
|
+def call_factory(package: str):
|
|
|
+ """Create a call() function for one package"""
|
|
|
+ return functools.partial(call, package)
|