2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-06-18 13:05:42 +00:00

Implement removal of plugin from plugins dir

This commit is contained in:
Oliver Walters
2024-11-17 04:57:09 +00:00
parent f87b7644b7
commit 83b35eb7a1
2 changed files with 60 additions and 28 deletions

View File

@ -3,6 +3,7 @@
import logging import logging
import pathlib import pathlib
import re import re
import shutil
import subprocess import subprocess
import sys import sys
@ -21,8 +22,10 @@ logger = logging.getLogger('inventree')
def pip_command(*args): def pip_command(*args):
"""Build and run a pip command using using the current python executable. """Build and run a pip command using using the current python executable.
returns: subprocess.check_output Returns: The output of the pip command
throws: subprocess.CalledProcessError
Raises:
subprocess.CalledProcessError: If the pip command fails
""" """
python = sys.executable python = sys.executable
@ -88,14 +91,18 @@ def check_plugins_path(packagename: str) -> bool:
return any(re.match(f'^{packagename}==', line.strip()) for line in output) return any(re.match(f'^{packagename}==', line.strip()) for line in output)
def check_package_path(packagename: str): def check_package_path(packagename: str) -> str:
"""Determine the install path of a particular package. """Determine the install path of a particular package.
- If installed, return the installation path - If installed, return the installation path
- If not installed, return False - If not installed, return an empty string
""" """
logger.debug('check_package_path: %s', packagename) logger.debug('check_package_path: %s', packagename)
# First check if the package is installed in the plugins directory
if check_plugins_path(packagename):
return f'plugins/{packagename}'
# Remove version information # Remove version information
for c in '<>=! ': for c in '<>=! ':
packagename = packagename.split(c)[0] packagename = packagename.split(c)[0]
@ -120,7 +127,7 @@ def check_package_path(packagename: str):
return False return False
# If we get here, the package is not installed # If we get here, the package is not installed
return False return ''
def plugins_dir(): def plugins_dir():
@ -257,13 +264,6 @@ def install_plugin(url=None, packagename=None, user=None, version=None):
logger.info('install_plugin: %s, %s', url, packagename) logger.info('install_plugin: %s, %s', url, packagename)
# Check if we are running in a virtual environment
# For now, just log a warning
in_venv = sys.prefix != sys.base_prefix
if not in_venv:
logger.warning('InvenTree is not running in a virtual environment')
plugin_dir = plugins_dir() plugin_dir = plugins_dir()
# build up the command # build up the command
@ -359,23 +359,22 @@ def uninstall_plugin(cfg: plugin.models.PluginConfig, user=None, delete_config=T
_('Plugin cannot be uninstalled as it is currently active') _('Plugin cannot be uninstalled as it is currently active')
) )
if not cfg.is_installed():
raise ValidationError(_('Plugin is not installed'))
validate_package_plugin(cfg, user) validate_package_plugin(cfg, user)
package_name = cfg.package_name package_name = cfg.package_name
logger.info('Uninstalling plugin: %s', package_name) logger.info('Uninstalling plugin: %s', package_name)
cmd = ['uninstall', '-y', package_name] if check_plugins_path(package_name):
# Uninstall the plugin from the plugins directory
try: uninstall_from_plugins_dir(cfg)
result = pip_command(*cmd) elif check_package_path(package_name):
# Uninstall the plugin using pip
ret = { uninstall_from_pip(cfg)
'result': _('Uninstalled plugin successfully'), else:
'success': True, # No matching install target found
'output': str(result, 'utf-8'), raise ValidationError(_('Plugin installation not found'))
}
except subprocess.CalledProcessError as error:
handle_pip_error(error, 'plugin_uninstall')
# Update the plugins file # Update the plugins file
update_plugins_file(package_name, remove=True) update_plugins_file(package_name, remove=True)
@ -390,4 +389,36 @@ def uninstall_plugin(cfg: plugin.models.PluginConfig, user=None, delete_config=T
# Reload the plugin registry # Reload the plugin registry
registry.reload_plugins(full_reload=True, force_reload=True, collect=True) registry.reload_plugins(full_reload=True, force_reload=True, collect=True)
return ret return {'result': _('Uninstalled plugin successfully'), 'success': True}
def uninstall_from_plugins_dir(cfg: plugin.models.PluginConfig):
"""Uninstall a plugin from the plugins directory."""
package_name = cfg.package_name
logger.debug('Uninstalling plugin from plugins directory: %s', package_name)
plugin_install_dir = plugins_dir()
plugin_dir = cfg.plugin.path()
if plugin_dir.is_relative_to(plugin_install_dir):
# Find the top-most relative path
while plugin_dir.parent and plugin_dir.parent != plugin_install_dir:
plugin_dir = plugin_dir.parent
if plugin_dir and plugin_dir.is_relative_to(plugin_install_dir):
shutil.rmtree(plugin_dir)
def uninstall_from_pip(cfg: plugin.models.PluginConfig):
"""Uninstall a plugin using pip."""
package_name = cfg.package_name
logger.debug('Uninstalling plugin via PIP: %s', package_name)
cmd = ['uninstall', '-y', package_name]
try:
pip_command(*cmd)
except subprocess.CalledProcessError as error:
handle_pip_error(error, 'plugin_uninstall')

View File

@ -239,9 +239,10 @@ class InvenTreePlugin(VersionMixin, MixinBase, MetaBase):
"""File that contains plugin definition.""" """File that contains plugin definition."""
return Path(inspect.getfile(cls)) return Path(inspect.getfile(cls))
def path(self) -> Path: @classmethod
def path(cls) -> Path:
"""Path to plugins base folder.""" """Path to plugins base folder."""
return self.file().parent return cls.file().parent
def _get_value(self, meta_name: str, package_name: str) -> str: def _get_value(self, meta_name: str, package_name: str) -> str:
"""Extract values from class meta or package info. """Extract values from class meta or package info.