2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-06-14 11:05:41 +00:00

Remove plugins.txt support

This commit is contained in:
Oliver Walters
2024-11-17 06:07:26 +00:00
parent 98f8e45218
commit 0278350351
10 changed files with 41 additions and 219 deletions

View File

@ -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.

View File

@ -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')

View File

@ -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

View File

@ -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'

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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()

View File

@ -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

View File

@ -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" %}
</tbody>
</table>
</div>