56 lines
1.6 KiB
Python
56 lines
1.6 KiB
Python
from abc import abstractclassmethod, abstractmethod
|
|
from pronoun_update import PronounUpdate
|
|
from typing import Any, Callable, List
|
|
from importlib.metadata import entry_points
|
|
from functools import cache
|
|
|
|
|
|
class Plugin:
|
|
@classmethod
|
|
@abstractmethod
|
|
def name(cls) -> str:
|
|
pass
|
|
|
|
@abstractmethod
|
|
def __init__(self, config: dict[str, str]):
|
|
pass
|
|
|
|
@abstractmethod
|
|
async def update_bio(self, update_pronouns: PronounUpdate) -> None:
|
|
pass
|
|
|
|
|
|
@cache
|
|
def load_plugins() -> dict[str, type[Plugin]]:
|
|
plugins: dict[str, type[Plugin]] = {}
|
|
# Ignore the following two lines due to incorrect type definition for entry_points
|
|
# See: https://github.com/python/typeshed/pull/7331
|
|
for plugin in entry_points(group="pronounomatic.plugins"): # type: ignore
|
|
pl = plugin.load() # type: ignore
|
|
assert issubclass(pl, Plugin)
|
|
plugins[pl.name()] = pl
|
|
|
|
return plugins
|
|
|
|
|
|
class InvalidPluginDefinitionException(Exception):
|
|
pass
|
|
|
|
|
|
def load_plugins_for_config(config: list[dict[str, Any]]) -> List[Plugin]:
|
|
plugins = []
|
|
plugin_classes = load_plugins()
|
|
for plugin_def in config:
|
|
if not plugin_def["name"]:
|
|
raise InvalidPluginDefinitionException("Missing plugin_name field")
|
|
plugin_name = plugin_def["name"]
|
|
if not plugin_name in load_plugins():
|
|
raise InvalidPluginDefinitionException(
|
|
f"No plugin with name {plugin_name} loaded"
|
|
)
|
|
|
|
plugin_cls = plugin_classes[plugin_name]
|
|
plugin = plugin_cls(plugin_def)
|
|
plugins.append(plugin)
|
|
|
|
return plugins
|