From 6ccad67477a214abc56d95c23743c2abda6b23a6 Mon Sep 17 00:00:00 2001 From: Florian Strzelecki Date: Sat, 16 Feb 2019 20:31:01 +0100 Subject: [PATCH] plugin: bot's new interface --- sopel/bot.py | 71 +++++++++++++++++++++++++++++++++++++++ sopel/plugins/handlers.py | 14 +++++++- 2 files changed, 84 insertions(+), 1 deletion(-) diff --git a/sopel/bot.py b/sopel/bot.py index b7b516f758..6812fbe4ae 100644 --- a/sopel/bot.py +++ b/sopel/bot.py @@ -8,6 +8,7 @@ from __future__ import unicode_literals, absolute_import, print_function, division import collections +import itertools import os import re import sys @@ -58,6 +59,7 @@ def __init__(self, config, daemon=False): 'medium': collections.defaultdict(list), 'low': collections.defaultdict(list) } + self._plugins = {} self.config = config """The :class:`sopel.config.Config` for the current Sopel instance.""" self.doc = {} @@ -221,6 +223,75 @@ def setup(self): else: stderr("Warning: Couldn't load any modules") + def reload_plugin(self, name): + if name not in self._plugins: + # TODO: raise more specific exception + raise Exception('Plugin %s is not loaded' % name) + + plugin = self._plugins[name] + # tear down + plugin.shutdown(self) + plugin.unregister(self) + print('Unloaded: %s' % name) + # reload & setup + plugin.reload() + plugin.setup(self) + plugin.register(self) + print('Reloaded: %s' % name) + + def reload_plugins(self): + """Reload all plugins + + First, it shutdown & unregister all plugins, then it reload, setup, and + register all of them. + """ + registered = list(self._plugins.items()) + # tear down all plugins + for name, plugin in registered: + plugin.shutdown(self) + plugin.unregister(self) + print('Unloaded: %s' % name) + + # reload & setup all plugins + for name, plugin in registered: + plugin.reload() + plugin.setup(self) + plugin.register(self) + print('Reloaded: %s' % name) + + def add_plugin(self, plugin, callables, jobs, shutdowns, urls): + """Add a loaded plugin to the bot's registry""" + self._plugins[plugin.name] = plugin + self.register(callables, jobs, shutdowns, urls) + + def remove_plugin(self, plugin, callables, jobs, shutdowns, urls): + """Remove a loaded plugin from the bot's registry""" + name = plugin.name + if name not in self._plugins: + # TODO: raise more specific exception + raise Exception('Plugin %s is not loaded' % name) + + try: + # remove commands, jobs, and shutdown functions + for func in itertools.chain(callables, jobs, shutdowns): + self.unregister(func) + + # remove URL callback handlers + for func in urls: + regex = func.url_regex + if func == self.memory['url_callbacks'].get(regex): + del self.memory['url_callbacks'][regex] + except: # noqa + # TODO: consider logging? + raise # re-raised + else: + # remove plugin from registry + del self._plugins[name] + + def has_plugin(self, name): + """Tell if the bot has registered this plugin by its name""" + return name in self._plugins + def unregister(self, obj): if not callable(obj): return diff --git a/sopel/plugins/handlers.py b/sopel/plugins/handlers.py index 8ebe51e59d..26f98ef567 100644 --- a/sopel/plugins/handlers.py +++ b/sopel/plugins/handlers.py @@ -118,6 +118,14 @@ def register(self, bot): """ raise NotImplementedError + def unregister(self, bot): + """Unregister the plugin from the ``bot`` + + :param bot: instance of Sopel + :type bot: :class:`sopel.bot.Sopel` + """ + raise NotImplementedError + def shutdown(self, bot): """Take action on bot's shutdown @@ -216,7 +224,11 @@ def has_setup(self): def register(self, bot): relevant_parts = loader.clean_module(self._module, bot.config) - bot.register(*relevant_parts) + bot.add_plugin(self, *relevant_parts) + + def unregister(self, bot): + relevant_parts = loader.clean_module(self._module, bot.config) + bot.remove_plugin(self, *relevant_parts) def shutdown(self, bot): if self.has_shutdown():