plugins: Add dependencies support

Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
This commit is contained in:
Maxime “pep” Buquet 2020-04-11 18:11:24 +02:00
parent 497706582a
commit fcdef2d96c
Signed by: pep
GPG key ID: DEDA74AEECA9D0F2
3 changed files with 52 additions and 3 deletions

View file

@ -518,10 +518,10 @@ class Core:
plugins = config.get('plugins_autoload')
if ':' in plugins:
for plugin in plugins.split(':'):
self.plugin_manager.load(plugin)
self.plugin_manager.load(plugin, unload_first=False)
else:
for plugin in plugins.split():
self.plugin_manager.load(plugin)
self.plugin_manager.load(plugin, unload_first=False)
self.plugins_autoloaded = True
def start(self):

View file

@ -3,6 +3,8 @@ Define the PluginConfig and Plugin classes, plus the SafetyMetaclass.
These are used in the plugin system added in poezio 0.7.5
(see plugin_manager.py)
"""
from typing import Set
from asyncio import iscoroutinefunction
from functools import partial
from configparser import RawConfigParser
@ -399,7 +401,11 @@ class BasePlugin(object, metaclass=SafetyMetaclass):
Class that all plugins derive from.
"""
# Internal use only
_unloading = False
default_config = None
dependencies: Set[str] = set()
def __init__(self, name, plugin_api, core, plugins_conf_dir):
self.__name = name

View file

@ -7,6 +7,7 @@ plugin env.
import logging
import os
from typing import Dict, Set
from importlib import import_module, machinery
from pathlib import Path
from os import path
@ -27,6 +28,8 @@ class PluginManager:
And keeps track of everything the plugin has done through the API.
"""
rdeps: Dict[str, Set[str]] = {}
def __init__(self, core):
self.core = core
# module name -> module object
@ -58,10 +61,25 @@ class PluginManager:
for plugin in set(self.plugins.keys()):
self.unload(plugin, notify=False)
def load(self, name: str, notify=True):
def set_rdeps(self, name):
"""
Runs through plugin dependencies to build the reverse dependencies table.
"""
if name not in self.rdeps:
self.rdeps[name] = set()
for dep in self.plugins[name].dependencies:
if dep not in self.rdeps:
self.rdeps[dep] = {name}
else:
self.rdeps[dep].add(name)
def load(self, name: str, notify=True, unload_first=True):
"""
Load a plugin.
"""
if not unload_first and name in self.plugins:
return None
if name in self.plugins:
self.unload(name)
@ -109,8 +127,20 @@ class PluginManager:
self.event_handlers[name] = []
try:
self.plugins[name] = None
for dep in module.Plugin.dependencies:
self.load(dep, unload_first=False)
if dep not in self.plugins:
log.debug(
'Plugin %s couldn\'t load because of dependency %s',
name, dep
)
return None
self.plugins[name] = module.Plugin(name, self.plugin_api, self.core,
self.plugins_conf_dir)
self.set_rdeps(name)
except Exception as e:
log.error('Error while loading the plugin %s', name, exc_info=True)
if notify:
@ -122,8 +152,20 @@ class PluginManager:
self.core.information('Plugin %s loaded' % name, 'Info')
def unload(self, name: str, notify=True):
"""
Unloads plugin as well as plugins depending on it.
"""
if name in self.plugins:
try:
self.plugins[name]._unloading = True # Prevents loops
for rdep in self.rdeps[name].copy():
if rdep in self.plugins and not self.plugins[rdep]._unloading:
self.unload(rdep)
if rdep in self.plugins:
log.debug('Failed to unload reverse dependency %s first.', rdep)
return None
for command in self.commands[name].keys():
del self.core.commands[command]
for key in self.keys[name].keys():
@ -143,6 +185,7 @@ class PluginManager:
if self.plugins[name] is not None:
self.plugins[name].unload()
del self.plugins[name]
del self.rdeps[name]
del self.commands[name]
del self.keys[name]
del self.tab_commands[name]