From 02783503513673574255bd1a809ce17c6f0cee6c Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sun, 17 Nov 2024 06:07:26 +0000 Subject: [PATCH] Remove plugins.txt support --- docs/docs/start/config.md | 4 +- src/backend/InvenTree/InvenTree/config.py | 31 ------ src/backend/InvenTree/InvenTree/settings.py | 4 - src/backend/InvenTree/InvenTree/tests.py | 35 ------- src/backend/InvenTree/config_template.yaml | 1 - src/backend/InvenTree/plugin/apps.py | 12 --- src/backend/InvenTree/plugin/helpers.py | 61 +++++++----- src/backend/InvenTree/plugin/installer.py | 98 ------------------- src/backend/InvenTree/plugin/registry.py | 13 --- .../templates/InvenTree/settings/plugin.html | 1 - 10 files changed, 41 insertions(+), 219 deletions(-) diff --git a/docs/docs/start/config.md b/docs/docs/start/config.md index 5320939aa9..3596f4f000 100644 --- a/docs/docs/start/config.md +++ b/docs/docs/start/config.md @@ -402,5 +402,7 @@ The following [plugin](../extend/plugins.md) configuration options are available | --- | --- | --- | --- | | INVENTREE_PLUGINS_ENABLED | plugins_enabled | Enable plugin support | False | | INVENTREE_PLUGIN_NOINSTALL | plugin_noinstall | Disable Plugin installation via API - only use plugins.txt file | False | -| INVENTREE_PLUGIN_FILE | plugins_plugin_file | Location of plugin installation file | *Not specified* | | INVENTREE_PLUGIN_DIR | plugins_plugin_dir | Location of external plugin directory | *Not specified* | + +!!! info "Plugins Directory" + If the `INVENTREE_PLUGIN_DIR` environment variable is not specified, the default location is the `plugins` directory within the InvenTree installation. diff --git a/src/backend/InvenTree/InvenTree/config.py b/src/backend/InvenTree/InvenTree/config.py index 5c62fc66c6..5f1b8ee4f6 100644 --- a/src/backend/InvenTree/InvenTree/config.py +++ b/src/backend/InvenTree/InvenTree/config.py @@ -283,37 +283,6 @@ def get_backup_dir(create=True): return bd -def get_plugin_file(): - """Returns the path of the InvenTree plugins specification file. - - Note: It will be created if it does not already exist! - """ - # Check if the plugin.txt file (specifying required plugins) is specified - plugin_file = get_setting('INVENTREE_PLUGIN_FILE', 'plugin_file') - - if not plugin_file: - # If not specified, look in the same directory as the configuration file - config_dir = get_config_file().parent - plugin_file = config_dir.joinpath('plugins.txt') - else: - # Make sure we are using a modern Path object - plugin_file = Path(plugin_file) - - if not plugin_file.exists(): - logger.warning( - 'Plugin configuration file does not exist - creating default file' - ) - logger.info("Creating plugin file at '%s'", plugin_file) - ensure_dir(plugin_file.parent) - - # If opening the file fails (no write permission, for example), then this will throw an error - plugin_file.write_text( - '# InvenTree Plugins (uses PIP framework to install)\n\n' - ) - - return plugin_file - - def get_plugin_dir(): """Returns the path of the custom plugins directory.""" return get_setting('INVENTREE_PLUGIN_DIR', 'plugin_dir') diff --git a/src/backend/InvenTree/InvenTree/settings.py b/src/backend/InvenTree/InvenTree/settings.py index 7e294b8ac8..01d5b54805 100644 --- a/src/backend/InvenTree/InvenTree/settings.py +++ b/src/backend/InvenTree/InvenTree/settings.py @@ -143,8 +143,6 @@ PLUGINS_INSTALL_DISABLED = get_boolean_setting( 'INVENTREE_PLUGIN_NOINSTALL', 'plugin_noinstall', False ) -PLUGIN_FILE = config.get_plugin_file() - # Plugin test settings PLUGIN_TESTING = get_setting( 'INVENTREE_PLUGIN_TESTING', 'PLUGIN_TESTING', TESTING @@ -160,8 +158,6 @@ PLUGIN_RETRY = get_setting( 'INVENTREE_PLUGIN_RETRY', 'PLUGIN_RETRY', 3, typecast=int ) # How often should plugin loading be tried? -PLUGIN_FILE_CHECKED = False # Was the plugin file checked? - STATICFILES_DIRS = [] # Translated Template settings diff --git a/src/backend/InvenTree/InvenTree/tests.py b/src/backend/InvenTree/InvenTree/tests.py index bf9ad84477..e0a1573b28 100644 --- a/src/backend/InvenTree/InvenTree/tests.py +++ b/src/backend/InvenTree/InvenTree/tests.py @@ -1180,23 +1180,6 @@ class TestSettings(InvenTreeTestCase): # make sure to clean up settings.TESTING_ENV = False - def test_initial_install(self): - """Test if install of plugins on startup works.""" - from plugin import registry - - if not settings.DOCKER: - # Check an install run - response = registry.install_plugin_file() - self.assertEqual(response, 'first_run') - - # Set dynamic setting to True and rerun to launch install - InvenTreeSetting.set_setting('PLUGIN_ON_STARTUP', True, self.user) - registry.reload_plugins(full_reload=True) - - # Check that there was another run - response = registry.install_plugin_file() - self.assertEqual(response, True) - def test_helpers_cfg_file(self): """Test get_config_file.""" # normal run - not configured @@ -1216,24 +1199,6 @@ class TestSettings(InvenTreeTestCase): str(config.get_config_file()).lower(), ) - def test_helpers_plugin_file(self): - """Test get_plugin_file.""" - # normal run - not configured - - valid = ['inventree/plugins.txt', 'inventree/data/plugins.txt'] - - self.assertTrue( - any(opt in str(config.get_plugin_file()).lower() for opt in valid) - ) - - # with env set - with self.in_env_context({ - 'INVENTREE_PLUGIN_FILE': '_testfolder/my_special_plugins.txt' - }): - self.assertIn( - '_testfolder/my_special_plugins.txt', str(config.get_plugin_file()) - ) - def test_helpers_setting(self): """Test get_setting.""" TEST_ENV_NAME = '123TEST' diff --git a/src/backend/InvenTree/config_template.yaml b/src/backend/InvenTree/config_template.yaml index 934fa05d69..9dd5c3e2ba 100644 --- a/src/backend/InvenTree/config_template.yaml +++ b/src/backend/InvenTree/config_template.yaml @@ -91,7 +91,6 @@ tracing: # Set this variable to True to enable InvenTree Plugins, or use the environment variable INVENTREE_PLUGINS_ENABLED plugins_enabled: False #plugin_noinstall: True -#plugin_file: '/path/to/plugins.txt' #plugin_dir: '/path/to/plugins/' # Set this variable to True to enable auto-migrations, or use the environment variable INVENTREE_AUTO_UPDATE diff --git a/src/backend/InvenTree/plugin/apps.py b/src/backend/InvenTree/plugin/apps.py index fa7801d72e..f0dd345561 100644 --- a/src/backend/InvenTree/plugin/apps.py +++ b/src/backend/InvenTree/plugin/apps.py @@ -40,18 +40,6 @@ class PluginAppConfig(AppConfig): logger.info('Loading InvenTree plugins') if not registry.is_loading: - # this is the first startup - try: - from common.models import InvenTreeSetting - - if InvenTreeSetting.get_setting( - 'PLUGIN_ON_STARTUP', create=False, cache=False - ): - # make sure all plugins are installed - registry.install_plugin_file() - except Exception: # pragma: no cover - pass - # Perform a full reload of the plugin registry registry.reload_plugins( full_reload=True, force_reload=True, collect=True diff --git a/src/backend/InvenTree/plugin/helpers.py b/src/backend/InvenTree/plugin/helpers.py index 9eee31bc39..161ef678cc 100644 --- a/src/backend/InvenTree/plugin/helpers.py +++ b/src/backend/InvenTree/plugin/helpers.py @@ -177,27 +177,37 @@ def get_modules(pkg, path=None): elif type(path) is not list: path = [path] - for finder, name, _ in pkgutil.walk_packages(path): - try: - if sys.version_info < (3, 12): - module = finder.find_module(name).load_module(name) - else: - spec = finder.find_spec(name) - module = module_from_spec(spec) - sys.modules[name] = module - spec.loader.exec_module(module) - pkg_names = getattr(module, '__all__', None) - for k, v in vars(module).items(): - if not k.startswith('_') and (pkg_names is None or k in pkg_names): - context[k] = v - context[name] = module - except AppRegistryNotReady: # pragma: no cover - pass - except Exception as error: - # this 'protects' against malformed plugin modules by more or less silently failing + try: + packages = pkgutil.walk_packages(path) + except Exception as e: + raise IntegrationPluginError(pkg.__name__, str(e)) - # log to stack - log_error({name: str(error)}, 'discovery') + try: + for finder, name, _ in packages: + try: + if sys.version_info < (3, 12): + try: + module = finder.find_module(name).load_module(name) + except Exception as e: + raise IntegrationPluginError(name, str(e)) + + else: + spec = finder.find_spec(name) + module = module_from_spec(spec) + sys.modules[name] = module + spec.loader.exec_module(module) + pkg_names = getattr(module, '__all__', None) + for k, v in vars(module).items(): + if not k.startswith('_') and (pkg_names is None or k in pkg_names): + context[k] = v + context[name] = module + except AppRegistryNotReady: # pragma: no cover + pass + except Exception as error: + # this 'protects' against malformed plugin modules by more or less silently failing + + # log to stack + log_error({name: str(error)}, 'discovery') return [v for k, v in context.items()] @@ -206,8 +216,8 @@ def get_classes(module) -> list: """Get all classes in a given module.""" try: return inspect.getmembers(module, inspect.isclass) - except Exception: - return [] + except Exception as e: + raise IntegrationPluginError(module.__name__, str(e)) def get_plugins(pkg, baseclass, path=None): @@ -223,7 +233,12 @@ def get_plugins(pkg, baseclass, path=None): # Iterate through each module in the package for mod in modules: # Iterate through each class in the module - for item in get_classes(mod): + try: + classes = get_classes(mod) + except IntegrationPluginError: + continue + + for item in classes: plugin = item[1] if issubclass(plugin, baseclass) and plugin.NAME: plugins.append(plugin) diff --git a/src/backend/InvenTree/plugin/installer.py b/src/backend/InvenTree/plugin/installer.py index e4afbc7796..1e7b977352 100644 --- a/src/backend/InvenTree/plugin/installer.py +++ b/src/backend/InvenTree/plugin/installer.py @@ -155,98 +155,6 @@ def plugins_dir(): return pd.absolute() -def install_plugins_file(): - """Install plugins from the plugins file.""" - logger.info('Installing plugins from plugins file') - - pf = settings.PLUGIN_FILE - - if not pf or not pf.exists(): - logger.warning('Plugin file %s does not exist', str(pf)) - return - - plugin_dir = plugins_dir() - - cmd = ['install', '-U', '--target', str(plugin_dir), '-r', str(pf)] - - try: - pip_command(*cmd) - except subprocess.CalledProcessError as error: - output = error.output.decode('utf-8') - logger.exception('Plugin file installation failed: %s', str(output)) - log_error('pip') - return False - except Exception as exc: - logger.exception('Plugin file installation failed: %s', exc) - log_error('pip') - return False - - # Update static files - plugin.staticfiles.collect_plugins_static_files() - plugin.staticfiles.clear_plugins_static_files() - - # At this point, the plugins file has been installed - return True - - -def update_plugins_file(install_name, remove=False): - """Add a plugin to the plugins file.""" - logger.info('Adding plugin to plugins file: %s', install_name) - - pf = settings.PLUGIN_FILE - - if not pf or not pf.exists(): - logger.warning('Plugin file %s does not exist', str(pf)) - return - - def compare_line(line: str): - """Check if a line in the file matches the installname.""" - return line.strip().split('==')[0] == install_name.split('==')[0] - - # First, read in existing plugin file - try: - with pf.open(mode='r') as f: - lines = f.readlines() - except Exception as exc: - logger.exception('Failed to read plugins file: %s', str(exc)) - return - - # Reconstruct output file - output = [] - - found = False - - # Check if plugin is already in file - for line in lines: - # Ignore processing for any commented lines - if line.strip().startswith('#'): - output.append(line) - continue - - if compare_line(line): - found = True - if not remove: - # Replace line with new install name - output.append(install_name) - else: - output.append(line) - - # Append plugin to file - if not found and not remove: - output.append(install_name) - - # Write file back to disk - try: - with pf.open(mode='w') as f: - for line in output: - f.write(line) - - if not line.endswith('\n'): - f.write('\n') - except Exception as exc: - logger.exception('Failed to add plugin to plugins file: %s', str(exc)) - - def install_plugin(url=None, packagename=None, user=None, version=None): """Install a plugin into the python virtual environment. @@ -309,9 +217,6 @@ def install_plugin(url=None, packagename=None, user=None, version=None): except subprocess.CalledProcessError as error: handle_pip_error(error, 'plugin_install') - # Save plugin to plugins file - update_plugins_file(full_pkg) - # Reload the plugin registry, to discover the new plugin from plugin.registry import registry @@ -376,9 +281,6 @@ def uninstall_plugin(cfg: plugin.models.PluginConfig, user=None, delete_config=T # No matching install target found raise ValidationError(_('Plugin installation not found')) - # Update the plugins file - update_plugins_file(package_name, remove=True) - if delete_config: # Remove the plugin configuration from the database cfg.delete() diff --git a/src/backend/InvenTree/plugin/registry.py b/src/backend/InvenTree/plugin/registry.py index 4c29e06fea..dac8ac05f4 100644 --- a/src/backend/InvenTree/plugin/registry.py +++ b/src/backend/InvenTree/plugin/registry.py @@ -428,19 +428,6 @@ class PluginsRegistry: self.mixin_modules = collected_mixins - def install_plugin_file(self): - """Make sure all plugins are installed in the current environment.""" - if settings.PLUGIN_FILE_CHECKED: - logger.info('Plugin file was already checked') - return True - - from plugin.installer import install_plugins_file - - if install_plugins_file(): - settings.PLUGIN_FILE_CHECKED = True - return 'first_run' - return False - # endregion # region general internal loading / activating / deactivating / unloading diff --git a/src/backend/InvenTree/templates/InvenTree/settings/plugin.html b/src/backend/InvenTree/templates/InvenTree/settings/plugin.html index f97c98795e..030c1e5224 100644 --- a/src/backend/InvenTree/templates/InvenTree/settings/plugin.html +++ b/src/backend/InvenTree/templates/InvenTree/settings/plugin.html @@ -25,7 +25,6 @@ {% include "InvenTree/settings/setting.html" with key="ENABLE_PLUGINS_URL" icon="fa-link" %} {% include "InvenTree/settings/setting.html" with key="ENABLE_PLUGINS_NAVIGATION" icon="fa-sitemap" %} {% include "InvenTree/settings/setting.html" with key="ENABLE_PLUGINS_APP" icon="fa-rocket" %} - {% include "InvenTree/settings/setting.html" with key="PLUGIN_ON_STARTUP" %}