From f87b7644b7e20df9b03cbe208b2a540088f3312b Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sun, 17 Nov 2024 04:14:02 +0000 Subject: [PATCH] Use plugins dir when installing from plugins.txt --- src/backend/InvenTree/plugin/installer.py | 69 ++++++++++++++++++----- src/backend/InvenTree/plugin/models.py | 2 +- src/backend/InvenTree/plugin/registry.py | 27 ++++----- 3 files changed, 70 insertions(+), 28 deletions(-) diff --git a/src/backend/InvenTree/plugin/installer.py b/src/backend/InvenTree/plugin/installer.py index bbbaba0044..5843a3f1e1 100644 --- a/src/backend/InvenTree/plugin/installer.py +++ b/src/backend/InvenTree/plugin/installer.py @@ -65,6 +65,29 @@ def handle_pip_error(error, path: str) -> list: raise ValidationError(errors[0]) +def check_plugins_path(packagename: str) -> bool: + """Determine if the package is installed in the plugins directory.""" + # Remove version information + for c in '<>=! ': + packagename = packagename.split(c)[0] + + plugin_dir = get_plugin_dir() + + if not plugin_dir: + return False + + plugin_dir_path = pathlib.Path(plugin_dir) + + if not plugin_dir_path.exists(): + return False + + result = pip_command('freeze', '--path', plugin_dir_path.absolute()) + output = result.decode('utf-8').split('\n') + + # Check if the package is installed in the plugins directory + return any(re.match(f'^{packagename}==', line.strip()) for line in output) + + def check_package_path(packagename: str): """Determine the install path of a particular package. @@ -100,6 +123,31 @@ def check_package_path(packagename: str): return False +def plugins_dir(): + """Return the path to the InvenTree custom plugins director. + + Returns: + pathlib.Path: Path to the custom plugins directory + + Raises: + ValidationError: If the plugins directory is not specified, or does not exist + """ + pd = get_plugin_dir() + + if not pd: + raise ValidationError(_('Plugins directory not specified')) + + pd = pathlib.Path(pd) + + if not pd.exists(): + try: + pd.mkdir(parents=True, exist_ok=True) + except Exception: + raise ValidationError(_('Failed to create plugin directory')) + + return pd.absolute() + + def install_plugins_file(): """Install plugins from the plugins file.""" logger.info('Installing plugins from plugins file') @@ -110,8 +158,12 @@ def install_plugins_file(): 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('install', '-r', str(pf)) + pip_command(*cmd) except subprocess.CalledProcessError as error: output = error.output.decode('utf-8') logger.exception('Plugin file installation failed: %s', str(output)) @@ -212,21 +264,10 @@ def install_plugin(url=None, packagename=None, user=None, version=None): if not in_venv: logger.warning('InvenTree is not running in a virtual environment') - plugin_dir = get_plugin_dir() - - if not plugin_dir: - raise ValidationError(_('Plugins directory not specified')) - - plugin_dir_path = pathlib.Path(plugin_dir) - - if not plugin_dir_path.exists(): - try: - plugin_dir_path.mkdir(parents=True, exist_ok=True) - except Exception: - raise ValidationError(_('Failed to create plugin directory')) + plugin_dir = plugins_dir() # build up the command - install_name = ['install', '-U', '--target', plugin_dir_path.absolute()] + install_name = ['install', '-U', '--target', str(plugin_dir)] full_pkg = '' diff --git a/src/backend/InvenTree/plugin/models.py b/src/backend/InvenTree/plugin/models.py index 58c08101fd..95263caf52 100644 --- a/src/backend/InvenTree/plugin/models.py +++ b/src/backend/InvenTree/plugin/models.py @@ -70,7 +70,7 @@ class PluginConfig(InvenTree.models.MetadataMixin, models.Model): """Nice name for printing.""" name = f'{self.name} - {self.key}' if not self.active: - name += '(not active)' + name += ' (not active)' return name # extra attributes from the registry diff --git a/src/backend/InvenTree/plugin/registry.py b/src/backend/InvenTree/plugin/registry.py index 68b77da66f..de9e3bbeeb 100644 --- a/src/backend/InvenTree/plugin/registry.py +++ b/src/backend/InvenTree/plugin/registry.py @@ -365,31 +365,32 @@ class PluginsRegistry: collected_plugins = [] # Collect plugins from paths - for plugin in self.plugin_dirs(): - logger.debug("Loading plugins from directory '%s'", plugin) + for plugin_dir in self.plugin_dirs(): + logger.debug("Loading plugins from directory '%s'", plugin_dir) parent_path = None - parent_obj = Path(plugin) + parent_obj = Path(plugin_dir) # If a "path" is provided, some special handling is required - if parent_obj.name is not plugin and len(parent_obj.parts) > 1: + if parent_obj.name is not plugin_dir and len(parent_obj.parts) > 1: # Ensure PosixPath object is converted to a string, before passing to get_plugins parent_path = str(parent_obj.parent) - plugin = parent_obj.name + plugin_dir = parent_obj.name # Gather Modules if parent_path: # On python 3.12 use new loader method if sys.version_info < (3, 12): raw_module = _load_source( - plugin, str(parent_obj.joinpath('__init__.py')) + plugin_dir, str(parent_obj.joinpath('__init__.py')) ) else: raw_module = SourceFileLoader( - plugin, str(parent_obj.joinpath('__init__.py')) + plugin_dir, str(parent_obj.joinpath('__init__.py')) ).load_module() else: - raw_module = importlib.import_module(plugin) + raw_module = importlib.import_module(plugin_dir) + modules = get_plugins(raw_module, InvenTreePlugin, path=parent_path) for item in modules or []: @@ -404,11 +405,11 @@ class PluginsRegistry: # Collect plugins from setup entry points for entry in get_entrypoints(): try: - plugin = entry.load() - plugin.is_package = True - plugin.package_name = getattr(entry.dist, 'name', None) - plugin._get_package_metadata() - collected_plugins.append(plugin) + plugin_dir = entry.load() + plugin_dir.is_package = True + plugin_dir.package_name = getattr(entry.dist, 'name', None) + plugin_dir._get_package_metadata() + collected_plugins.append(plugin_dir) except Exception as error: # pragma: no cover handle_error(error, do_raise=False, log_name='discovery')