From a6dbe185c64c151b0e4f39e9d5e35f460c6a0080 Mon Sep 17 00:00:00 2001 From: Oliver Date: Thu, 5 Oct 2023 00:38:40 +1100 Subject: [PATCH] Allow reload of plugin registry when config changes (#5662) * Allow reload of plugin registry when config changes - Any global settings which control plugin behaviour trigger reload - Plugin registry hash includes the value of these settings - Plugin registry hash is now cached * typo --- InvenTree/common/models.py | 20 ++++++++--- .../plugin/base/integration/ScheduleMixin.py | 2 +- InvenTree/plugin/registry.py | 34 ++++++++++++++++--- 3 files changed, 45 insertions(+), 11 deletions(-) diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index f78458ad82..0531d886dd 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -951,6 +951,16 @@ def update_exchange_rates(setting): InvenTree.tasks.update_exchange_rates() +def reload_plugin_registry(setting): + """When a core plugin setting is changed, reload the plugin registry""" + + from plugin import registry + + logger.info("Reloading plugin registry due to change in setting '%s'", setting.key) + + registry.reload_plugins(full_reload=True, force_reload=True, collect=True) + + class InvenTreeSetting(BaseInvenTreeSetting): """An InvenTreeSetting object is a key:value pair used for storing single values (e.g. one-off settings values). @@ -1704,7 +1714,7 @@ class InvenTreeSetting(BaseInvenTreeSetting): 'description': _('Enable plugins to add URL routes'), 'default': False, 'validator': bool, - 'requires_restart': True, + 'after_save': reload_plugin_registry, }, 'ENABLE_PLUGINS_NAVIGATION': { @@ -1712,7 +1722,7 @@ class InvenTreeSetting(BaseInvenTreeSetting): 'description': _('Enable plugins to integrate into navigation'), 'default': False, 'validator': bool, - 'requires_restart': True, + 'after_save': reload_plugin_registry, }, 'ENABLE_PLUGINS_APP': { @@ -1720,7 +1730,7 @@ class InvenTreeSetting(BaseInvenTreeSetting): 'description': _('Enable plugins to add apps'), 'default': False, 'validator': bool, - 'requires_restart': True, + 'after_save': reload_plugin_registry, }, 'ENABLE_PLUGINS_SCHEDULE': { @@ -1728,7 +1738,7 @@ class InvenTreeSetting(BaseInvenTreeSetting): 'description': _('Enable plugins to run scheduled tasks'), 'default': False, 'validator': bool, - 'requires_restart': True, + 'after_save': reload_plugin_registry, }, 'ENABLE_PLUGINS_EVENTS': { @@ -1736,7 +1746,7 @@ class InvenTreeSetting(BaseInvenTreeSetting): 'description': _('Enable plugins to respond to internal events'), 'default': False, 'validator': bool, - 'requires_restart': True, + 'after_save': reload_plugin_registry, }, "PROJECT_CODES_ENABLED": { diff --git a/InvenTree/plugin/base/integration/ScheduleMixin.py b/InvenTree/plugin/base/integration/ScheduleMixin.py index 250979abc7..1db3a339de 100644 --- a/InvenTree/plugin/base/integration/ScheduleMixin.py +++ b/InvenTree/plugin/base/integration/ScheduleMixin.py @@ -56,7 +56,7 @@ class ScheduleMixin: @classmethod def _activate_mixin(cls, registry, plugins, *args, **kwargs): - """Activate scheudles from plugins with the ScheduleMixin.""" + """Activate schedules from plugins with the ScheduleMixin.""" logger.debug('Activating plugin tasks') from common.models import InvenTreeSetting diff --git a/InvenTree/plugin/registry.py b/InvenTree/plugin/registry.py index c2e27b633b..c7d5fbd8fa 100644 --- a/InvenTree/plugin/registry.py +++ b/InvenTree/plugin/registry.py @@ -54,6 +54,9 @@ class PluginsRegistry: self.plugins_inactive: Dict[str, InvenTreePlugin] = {} # List of inactive instances self.plugins_full: Dict[str, InvenTreePlugin] = {} # List of all plugin instances + # Keep an internal hash of the plugin registry state + self.registry_hash = None + self.plugin_modules: List[InvenTreePlugin] = [] # Holds all discovered plugins self.mixin_modules: Dict[str, Any] = {} # Holds all discovered mixins @@ -633,17 +636,17 @@ class PluginsRegistry: from common.models import InvenTreeSetting - plg_hash = self.calculate_plugin_hash() + self.registry_hash = self.calculate_plugin_hash() try: old_hash = InvenTreeSetting.get_setting("_PLUGIN_REGISTRY_HASH", "", create=False, cache=False) except Exception: old_hash = "" - if old_hash != plg_hash: + if old_hash != self.registry_hash: try: - logger.debug("Updating plugin registry hash: %s", str(plg_hash)) - InvenTreeSetting.set_setting("_PLUGIN_REGISTRY_HASH", plg_hash, change_user=None) + logger.debug("Updating plugin registry hash: %s", str(self.registry_hash)) + InvenTreeSetting.set_setting("_PLUGIN_REGISTRY_HASH", self.registry_hash, change_user=None) except Exception as exc: logger.exception("Failed to update plugin registry hash: %s", str(exc)) @@ -656,6 +659,8 @@ class PluginsRegistry: from hashlib import md5 + from common.models import InvenTreeSetting + data = md5() # Hash for all loaded plugins @@ -664,6 +669,21 @@ class PluginsRegistry: data.update(str(plug.version).encode()) data.update(str(plug.is_active()).encode()) + # Also hash for all config settings which define plugin behavior + keys = [ + 'ENABLE_PLUGINS_URL', + 'ENABLE_PLUGINS_NAVIGATION', + 'ENABLE_PLUGINS_APP', + 'ENABLE_PLUGINS_SCHEDULE', + 'ENABLE_PLUGINS_EVENTS' + ] + + for k in keys: + try: + data.update(str(InvenTreeSetting.get_setting(k, False, cache=False, create=False)).encode()) + except Exception: + pass + return str(data.hexdigest()) def check_reload(self): @@ -677,13 +697,17 @@ class PluginsRegistry: logger.debug("Checking plugin registry hash") + # If not already cached, calculate the hash + if not self.registry_hash: + self.registry_hash = self.calculate_plugin_hash() + try: reg_hash = InvenTreeSetting.get_setting("_PLUGIN_REGISTRY_HASH", "", create=False, cache=False) except Exception as exc: logger.exception("Failed to retrieve plugin registry hash: %s", str(exc)) return - if reg_hash and reg_hash != self.calculate_plugin_hash(): + if reg_hash and reg_hash != self.registry_hash: logger.info("Plugin registry hash has changed - reloading") self.reload_plugins(full_reload=True, force_reload=True, collect=True)