mirror of
https://github.com/inventree/InvenTree.git
synced 2025-06-16 20:15:44 +00:00
Improve check for plugins file
This commit is contained in:
@ -1,7 +1,6 @@
|
|||||||
"""Install a plugin into the python virtual environment."""
|
"""Install a plugin into the python virtual environment."""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import pathlib
|
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
@ -12,7 +11,6 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
|
|
||||||
import plugin.models
|
import plugin.models
|
||||||
import plugin.staticfiles
|
import plugin.staticfiles
|
||||||
from InvenTree.config import get_plugin_dir
|
|
||||||
from InvenTree.exceptions import log_error
|
from InvenTree.exceptions import log_error
|
||||||
|
|
||||||
logger = logging.getLogger('inventree')
|
logger = logging.getLogger('inventree')
|
||||||
@ -68,91 +66,41 @@ def handle_pip_error(error, path: str) -> list:
|
|||||||
raise ValidationError(errors[0])
|
raise ValidationError(errors[0])
|
||||||
|
|
||||||
|
|
||||||
def check_plugins_path(packagename: str) -> bool:
|
def get_install_info(packagename: str) -> str:
|
||||||
"""Determine if the package is installed in the plugins directory."""
|
"""Determine the install information for a particular package.
|
||||||
# Remove version information
|
|
||||||
for c in '<>=! ':
|
|
||||||
packagename = packagename.split(c)[0]
|
|
||||||
|
|
||||||
plugin_dir = get_plugin_dir()
|
- Uses 'pip show' to determine the install location of a package.
|
||||||
|
|
||||||
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(rf'^{packagename}[\s=@]', line.strip()) for line in output)
|
|
||||||
|
|
||||||
|
|
||||||
def check_package_path(packagename: str) -> str:
|
|
||||||
"""Determine the install path of a particular package.
|
|
||||||
|
|
||||||
- If installed, return the installation path
|
|
||||||
- If not installed, return an empty string
|
|
||||||
"""
|
"""
|
||||||
logger.debug('check_package_path: %s', packagename)
|
logger.debug('get_install_info: %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]
|
||||||
|
|
||||||
|
info = {}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
result = pip_command('show', packagename)
|
result = pip_command('show', packagename)
|
||||||
|
|
||||||
output = result.decode('utf-8').split('\n')
|
output = result.decode('utf-8').split('\n')
|
||||||
|
|
||||||
for line in output:
|
for line in output:
|
||||||
# Check if line matches pattern "Location: ..."
|
parts = line.split(':')
|
||||||
match = re.match(r'^Location:\s+(.+)$', line.strip())
|
|
||||||
|
|
||||||
if match:
|
if len(parts) >= 2:
|
||||||
return match.group(1)
|
key = str(parts[0].strip().lower().replace('-', '_'))
|
||||||
|
value = str(parts[1].strip())
|
||||||
|
|
||||||
|
info[key] = value
|
||||||
|
|
||||||
except subprocess.CalledProcessError as error:
|
except subprocess.CalledProcessError as error:
|
||||||
log_error('check_package_path')
|
log_error('get_install_info')
|
||||||
|
|
||||||
output = error.output.decode('utf-8')
|
output = error.output.decode('utf-8')
|
||||||
|
info['error'] = output
|
||||||
logger.exception('Plugin lookup failed: %s', str(output))
|
logger.exception('Plugin lookup failed: %s', str(output))
|
||||||
return False
|
|
||||||
|
|
||||||
# If we get here, the package is not installed
|
return info
|
||||||
return ''
|
|
||||||
|
|
||||||
|
|
||||||
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():
|
def install_plugins_file():
|
||||||
@ -165,9 +113,7 @@ def install_plugins_file():
|
|||||||
logger.warning('Plugin file %s does not exist', str(pf))
|
logger.warning('Plugin file %s does not exist', str(pf))
|
||||||
return
|
return
|
||||||
|
|
||||||
plugin_dir = plugins_dir()
|
cmd = ['install', '-U', '-r', str(pf)]
|
||||||
|
|
||||||
cmd = ['install', '-U', '--target', str(plugin_dir), '-r', str(pf)]
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
pip_command(*cmd)
|
pip_command(*cmd)
|
||||||
@ -189,10 +135,19 @@ def install_plugins_file():
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def update_plugins_file(install_name, remove=False):
|
def update_plugins_file(install_name, full_package=None, version=None, remove=False):
|
||||||
"""Add a plugin to the plugins file."""
|
"""Add a plugin to the plugins file."""
|
||||||
|
if remove:
|
||||||
|
logger.info('Removing plugin from plugins file: %s', install_name)
|
||||||
|
else:
|
||||||
logger.info('Adding plugin to plugins file: %s', install_name)
|
logger.info('Adding plugin to plugins file: %s', install_name)
|
||||||
|
|
||||||
|
# If a full package name is provided, use that instead
|
||||||
|
if full_package and full_package != install_name:
|
||||||
|
new_value = full_package
|
||||||
|
else:
|
||||||
|
new_value = f'{install_name}=={version}' if version else install_name
|
||||||
|
|
||||||
pf = settings.PLUGIN_FILE
|
pf = settings.PLUGIN_FILE
|
||||||
|
|
||||||
if not pf or not pf.exists():
|
if not pf or not pf.exists():
|
||||||
@ -201,7 +156,7 @@ def update_plugins_file(install_name, remove=False):
|
|||||||
|
|
||||||
def compare_line(line: str):
|
def compare_line(line: str):
|
||||||
"""Check if a line in the file matches the installname."""
|
"""Check if a line in the file matches the installname."""
|
||||||
return line.strip().split('==')[0] == install_name.split('==')[0]
|
return re.match(rf'^{install_name}[\s=@]', line.strip())
|
||||||
|
|
||||||
# First, read in existing plugin file
|
# First, read in existing plugin file
|
||||||
try:
|
try:
|
||||||
@ -227,13 +182,13 @@ def update_plugins_file(install_name, remove=False):
|
|||||||
found = True
|
found = True
|
||||||
if not remove:
|
if not remove:
|
||||||
# Replace line with new install name
|
# Replace line with new install name
|
||||||
output.append(install_name)
|
output.append(new_value)
|
||||||
else:
|
else:
|
||||||
output.append(line)
|
output.append(line)
|
||||||
|
|
||||||
# Append plugin to file
|
# Append plugin to file
|
||||||
if not found and not remove:
|
if not found and not remove:
|
||||||
output.append(install_name)
|
output.append(new_value)
|
||||||
|
|
||||||
# Write file back to disk
|
# Write file back to disk
|
||||||
try:
|
try:
|
||||||
@ -264,10 +219,8 @@ 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)
|
||||||
|
|
||||||
plugin_dir = plugins_dir()
|
|
||||||
|
|
||||||
# build up the command
|
# build up the command
|
||||||
install_name = ['install', '-U', '--target', str(plugin_dir)]
|
install_name = ['install', '-U']
|
||||||
|
|
||||||
full_pkg = ''
|
full_pkg = ''
|
||||||
|
|
||||||
@ -302,15 +255,17 @@ def install_plugin(url=None, packagename=None, user=None, version=None):
|
|||||||
ret['result'] = ret['success'] = _('Installed plugin successfully')
|
ret['result'] = ret['success'] = _('Installed plugin successfully')
|
||||||
ret['output'] = str(result, 'utf-8')
|
ret['output'] = str(result, 'utf-8')
|
||||||
|
|
||||||
if packagename and (path := check_package_path(packagename)):
|
if packagename and (info := get_install_info(packagename)):
|
||||||
# Override result information
|
if path := info.get('location'):
|
||||||
ret['result'] = _(f'Installed plugin into {path}')
|
ret['result'] = _(f'Installed plugin into {path}')
|
||||||
|
ret['version'] = info.get('version')
|
||||||
|
|
||||||
except subprocess.CalledProcessError as error:
|
except subprocess.CalledProcessError as error:
|
||||||
handle_pip_error(error, 'plugin_install')
|
handle_pip_error(error, 'plugin_install')
|
||||||
|
|
||||||
|
if version := ret.get('version'):
|
||||||
# Save plugin to plugins file
|
# Save plugin to plugins file
|
||||||
update_plugins_file(full_pkg)
|
update_plugins_file(packagename, full_package=full_pkg, version=version)
|
||||||
|
|
||||||
# Reload the plugin registry, to discover the new plugin
|
# Reload the plugin registry, to discover the new plugin
|
||||||
from plugin.registry import registry
|
from plugin.registry import registry
|
||||||
@ -364,10 +319,12 @@ def uninstall_plugin(cfg: plugin.models.PluginConfig, user=None, delete_config=T
|
|||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
if check_package_path(package_name):
|
pkg_info = get_install_info(package_name)
|
||||||
|
|
||||||
|
if path := pkg_info.get('location'):
|
||||||
# Uninstall the plugin using pip
|
# Uninstall the plugin using pip
|
||||||
|
logger.info('Uninstalling plugin: %s from %s', package_name, path)
|
||||||
try:
|
try:
|
||||||
pip_command('uninstall', '-y', package_name)
|
pip_command('uninstall', '-y', package_name)
|
||||||
except subprocess.CalledProcessError as error:
|
except subprocess.CalledProcessError as error:
|
||||||
|
Reference in New Issue
Block a user