mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Allow plugin loading from external directory (#3364)
* Load custom plugin directories * Allow plugins to be loaded from an external directory * Handle exception when path is not relative to base path * Fix typo * Use pathlib * Move plugin directory code into registry.py - Allows us to reload plugins without having to reload the server itself Co-authored-by: Matthias Mair <code@mjmair.com>
This commit is contained in:
parent
805accb479
commit
b98ae25583
@ -935,17 +935,6 @@ PLUGINS_ENABLED = _is_true(get_setting(
|
||||
|
||||
PLUGIN_FILE = get_plugin_file()
|
||||
|
||||
# Plugin Directories (local plugins will be loaded from these directories)
|
||||
PLUGIN_DIRS = ['plugin.builtin', ]
|
||||
|
||||
if not TESTING:
|
||||
# load local deploy directory in prod
|
||||
PLUGIN_DIRS.append('plugins') # pragma: no cover
|
||||
|
||||
if DEBUG or TESTING:
|
||||
# load samples in debug mode
|
||||
PLUGIN_DIRS.append('plugin.samples')
|
||||
|
||||
# Plugin test settings
|
||||
PLUGIN_TESTING = get_setting('PLUGIN_TESTING', TESTING) # are plugins beeing tested?
|
||||
PLUGIN_TESTING_SETUP = get_setting('PLUGIN_TESTING_SETUP', False) # load plugins from setup hooks in testing?
|
||||
|
@ -172,10 +172,16 @@ class GitStatus:
|
||||
|
||||
|
||||
# region plugin finders
|
||||
def get_modules(pkg):
|
||||
def get_modules(pkg, path=None):
|
||||
"""Get all modules in a package."""
|
||||
context = {}
|
||||
for loader, name, _ in pkgutil.walk_packages(pkg.__path__):
|
||||
|
||||
if path is None:
|
||||
path = pkg.__path__
|
||||
elif type(path) is not list:
|
||||
path = [path]
|
||||
|
||||
for loader, name, _ in pkgutil.walk_packages(path):
|
||||
try:
|
||||
module = loader.find_module(name).load_module(name)
|
||||
pkg_names = getattr(module, '__all__', None)
|
||||
@ -199,7 +205,7 @@ def get_classes(module):
|
||||
return inspect.getmembers(module, inspect.isclass)
|
||||
|
||||
|
||||
def get_plugins(pkg, baseclass):
|
||||
def get_plugins(pkg, baseclass, path=None):
|
||||
"""Return a list of all modules under a given package.
|
||||
|
||||
- Modules must be a subclass of the provided 'baseclass'
|
||||
@ -207,7 +213,7 @@ def get_plugins(pkg, baseclass):
|
||||
"""
|
||||
plugins = []
|
||||
|
||||
modules = get_modules(pkg)
|
||||
modules = get_modules(pkg, path=path)
|
||||
|
||||
# Iterate through each module in the package
|
||||
for mod in modules:
|
||||
|
@ -275,7 +275,11 @@ class InvenTreePlugin(MixinBase, MetaBase):
|
||||
"""Path to the plugin."""
|
||||
if self._is_package:
|
||||
return self.__module__ # pragma: no cover
|
||||
return pathlib.Path(self.def_path).relative_to(settings.BASE_DIR)
|
||||
|
||||
try:
|
||||
return pathlib.Path(self.def_path).relative_to(settings.BASE_DIR)
|
||||
except ValueError:
|
||||
return pathlib.Path(self.def_path)
|
||||
|
||||
@property
|
||||
def settings_url(self):
|
||||
|
@ -187,6 +187,53 @@ class PluginsRegistry:
|
||||
|
||||
logger.info('Finished reloading plugins')
|
||||
|
||||
def plugin_dirs(self):
|
||||
"""Construct a list of directories from where plugins can be loaded"""
|
||||
|
||||
dirs = ['plugin.builtin', ]
|
||||
|
||||
if settings.TESTING or settings.DEBUG:
|
||||
# If in TEST or DEBUG mode, load plugins from the 'samples' directory
|
||||
dirs.append('plugin.samples')
|
||||
|
||||
if settings.TESTING:
|
||||
custom_dirs = os.getenv('INVENTREE_PLUGIN_TEST_DIR', None)
|
||||
else:
|
||||
custom_dirs = os.getenv('INVENTREE_PLUGIN_DIR', None)
|
||||
|
||||
# Load from user specified directories (unless in testing mode)
|
||||
dirs.append('plugins')
|
||||
|
||||
if custom_dirs is not None:
|
||||
# Allow multiple plugin directories to be specified
|
||||
for pd_text in custom_dirs.split(','):
|
||||
pd = pathlib.Path(pd_text.strip()).absolute()
|
||||
|
||||
# Attempt to create the directory if it does not already exist
|
||||
if not pd.exists():
|
||||
try:
|
||||
pd.mkdir(exist_ok=True)
|
||||
except Exception:
|
||||
logger.error(f"Could not create plugin directory '{pd}'")
|
||||
continue
|
||||
|
||||
# Ensure the directory has an __init__.py file
|
||||
init_filename = pd.joinpath('__init__.py')
|
||||
|
||||
if not init_filename.exists():
|
||||
try:
|
||||
init_filename.write_text("# InvenTree plugin directory\n")
|
||||
except Exception:
|
||||
logger.error(f"Could not create file '{init_filename}'")
|
||||
continue
|
||||
|
||||
if pd.exists() and pd.is_dir():
|
||||
# By this point, we have confirmed that the directory at least exists
|
||||
logger.info(f"Added plugin directory: '{pd}'")
|
||||
dirs.append(pd)
|
||||
|
||||
return dirs
|
||||
|
||||
def collect_plugins(self):
|
||||
"""Collect plugins from all possible ways of loading."""
|
||||
if not settings.PLUGINS_ENABLED:
|
||||
@ -196,8 +243,21 @@ class PluginsRegistry:
|
||||
self.plugin_modules = [] # clear
|
||||
|
||||
# Collect plugins from paths
|
||||
for plugin in settings.PLUGIN_DIRS:
|
||||
modules = get_plugins(importlib.import_module(plugin), InvenTreePlugin)
|
||||
for plugin in self.plugin_dirs():
|
||||
|
||||
print(f"Loading plugins from directory '{plugin}'")
|
||||
|
||||
parent_path = None
|
||||
parent_obj = pathlib.Path(plugin)
|
||||
|
||||
# If a "path" is provided, some special handling is required
|
||||
if parent_obj.name is not plugin and len(parent_obj.parts) > 1:
|
||||
print("loading from a qualified path:", plugin)
|
||||
parent_path = parent_obj.parent
|
||||
plugin = parent_obj.name
|
||||
|
||||
modules = get_plugins(importlib.import_module(plugin), InvenTreePlugin, path=parent_path)
|
||||
|
||||
if modules:
|
||||
[self.plugin_modules.append(item) for item in modules]
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user