mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
There shouldn't be any blank lines after the function docstring. Remove the blank lines to fix this issue. Co-authored-by: deepsource-autofix[bot] <62050782+deepsource-autofix[bot]@users.noreply.github.com>
223 lines
6.2 KiB
Python
223 lines
6.2 KiB
Python
"""Install a plugin into the python virtual environment"""
|
|
|
|
import logging
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
|
|
from django.conf import settings
|
|
from django.core.exceptions import ValidationError
|
|
from django.utils.translation import gettext_lazy as _
|
|
|
|
logger = logging.getLogger('inventree')
|
|
|
|
|
|
def pip_command(*args):
|
|
"""Build and run a pip command using using the current python executable
|
|
|
|
returns: subprocess.check_output
|
|
throws: subprocess.CalledProcessError
|
|
"""
|
|
python = sys.executable
|
|
|
|
command = [python, '-m', 'pip']
|
|
command.extend(args)
|
|
|
|
command = [str(x) for x in command]
|
|
|
|
logger.info("Running pip command: %s", ' '.join(command))
|
|
|
|
return subprocess.check_output(
|
|
command,
|
|
cwd=settings.BASE_DIR.parent,
|
|
stderr=subprocess.STDOUT,
|
|
)
|
|
|
|
|
|
def check_package_path(packagename: str):
|
|
"""Determine the install path of a particular package
|
|
|
|
- If installed, return the installation path
|
|
- If not installed, return False
|
|
"""
|
|
logger.debug("check_package_path: %s", packagename)
|
|
|
|
# Remove version information
|
|
for c in '<>=! ':
|
|
packagename = packagename.split(c)[0]
|
|
|
|
try:
|
|
result = pip_command('show', packagename)
|
|
|
|
output = result.decode('utf-8').split("\n")
|
|
|
|
for line in output:
|
|
# Check if line matches pattern "Location: ..."
|
|
match = re.match(r'^Location:\s+(.+)$', line.strip())
|
|
|
|
if match:
|
|
return match.group(1)
|
|
|
|
except subprocess.CalledProcessError as error:
|
|
output = error.output.decode('utf-8')
|
|
logger.exception("Plugin lookup failed: %s", str(output))
|
|
return False
|
|
|
|
# If we get here, the package is not installed
|
|
return False
|
|
|
|
|
|
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
|
|
|
|
try:
|
|
pip_command('install', '-r', str(pf))
|
|
except subprocess.CalledProcessError as error:
|
|
output = error.output.decode('utf-8')
|
|
logger.exception("Plugin file installation failed: %s", str(output))
|
|
return False
|
|
except Exception as exc:
|
|
logger.exception("Plugin file installation failed: %s", exc)
|
|
return False
|
|
|
|
# At this point, the plugins file has been installed
|
|
return True
|
|
|
|
|
|
def add_plugin_to_file(install_name):
|
|
"""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
|
|
|
|
# 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
|
|
|
|
# Check if plugin is already in file
|
|
for line in lines:
|
|
if line.strip() == install_name:
|
|
logger.debug("Plugin already exists in file")
|
|
return
|
|
|
|
# Append plugin to file
|
|
lines.append(f'{install_name}')
|
|
|
|
# Write file back to disk
|
|
try:
|
|
with pf.open(mode='w') as f:
|
|
for line in lines:
|
|
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):
|
|
"""Install a plugin into the python virtual environment:
|
|
|
|
- A staff user account is required
|
|
- We must detect that we are running within a virtual environment
|
|
"""
|
|
if user and not user.is_staff:
|
|
raise ValidationError(_("Permission denied: only staff users can install plugins"))
|
|
|
|
logger.debug("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")
|
|
|
|
# build up the command
|
|
install_name = ['install', '-U']
|
|
|
|
full_pkg = ''
|
|
|
|
if url:
|
|
# use custom registration / VCS
|
|
if True in [identifier in url for identifier in ['git+https', 'hg+https', 'svn+svn', ]]:
|
|
# using a VCS provider
|
|
if packagename:
|
|
full_pkg = f'{packagename}@{url}'
|
|
else:
|
|
full_pkg = url
|
|
else: # pragma: no cover
|
|
# using a custom package repositories
|
|
# This is only for pypa compliant directory services (all current are tested above)
|
|
# and not covered by tests.
|
|
if url:
|
|
install_name.append('-i')
|
|
full_pkg = url
|
|
elif packagename:
|
|
full_pkg = packagename
|
|
|
|
elif packagename:
|
|
# use pypi
|
|
full_pkg = packagename
|
|
|
|
install_name.append(full_pkg)
|
|
|
|
ret = {}
|
|
|
|
# Execute installation via pip
|
|
try:
|
|
result = pip_command(*install_name)
|
|
|
|
ret['result'] = ret['success'] = _("Installed plugin successfully")
|
|
ret['output'] = str(result, 'utf-8')
|
|
|
|
if packagename:
|
|
if path := check_package_path(packagename):
|
|
# Override result information
|
|
ret['result'] = _(f"Installed plugin into {path}")
|
|
|
|
except subprocess.CalledProcessError as error:
|
|
# If an error was thrown, we need to parse the output
|
|
|
|
output = error.output.decode('utf-8')
|
|
logger.exception("Plugin installation failed: %s", str(output))
|
|
|
|
errors = [
|
|
_("Plugin installation failed"),
|
|
]
|
|
|
|
for msg in output.split("\n"):
|
|
msg = msg.strip()
|
|
|
|
if msg:
|
|
errors.append(msg)
|
|
|
|
if len(errors) > 1:
|
|
raise ValidationError(errors)
|
|
else:
|
|
raise ValidationError(errors[0])
|
|
|
|
# Save plugin to plugins file
|
|
add_plugin_to_file(full_pkg)
|
|
|
|
# Reload the plugin registry, to discover the new plugin
|
|
from plugin.registry import registry
|
|
registry.reload_plugins(full_reload=True, force_reload=True, collect=True)
|
|
|
|
return ret
|