Verified Commit fcdef2d9 authored by Maxime Buquet's avatar Maxime Buquet
Browse files

plugins: Add dependencies support

Signed-off-by: Maxime Buquet's avatarMaxime “pep” Buquet <>
parent 49770658
......@@ -518,10 +518,10 @@ class Core:
plugins = config.get('plugins_autoload')
if ':' in plugins:
for plugin in plugins.split(':'):
self.plugin_manager.load(plugin, unload_first=False)
for plugin in plugins.split():
self.plugin_manager.load(plugin, unload_first=False)
self.plugins_autoloaded = True
def start(self):
......@@ -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
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
......@@ -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}
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:
......@@ -109,8 +127,20 @@ class PluginManager:
self.event_handlers[name] = []
self.plugins[name] = None
for dep in module.Plugin.dependencies:
self.load(dep, unload_first=False)
if dep not in self.plugins:
'Plugin %s couldn\'t load because of dependency %s',
name, dep
return None
self.plugins[name] = module.Plugin(name, self.plugin_api, self.core,
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:
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:
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:
del self.plugins[name]
del self.rdeps[name]
del self.commands[name]
del self.keys[name]
del self.tab_commands[name]
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment