Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[core] Plugins: Introduction of NodePluginManager #2589

Open
wants to merge 7 commits into
base: develop
Choose a base branch
from
8 changes: 8 additions & 0 deletions meshroom/_plugins/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
""" Plugins.
"""
# Plugins
from .node import NodePluginManager
from .base import Status, Pluginator


__all__ = ["NodePluginManager", "Status", "Pluginator"]
92 changes: 92 additions & 0 deletions meshroom/_plugins/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
""" Base functionality for Plugins.
"""
# Types
from typing import List

# STD
from contextlib import contextmanager
import enum
import importlib
import inspect
import logging
import pkgutil


class Status(enum.IntEnum):
""" Enum describing the state of the plugin.
"""

# UNLOADED or NOT Available - Describing that the plugin is not available in the current set of plugins
UNLOADED = -1

# ERRORED describes that the plugin exists but could not be loaded due to errors with the structure
ERRORED = 0

# LOADED describes that the plugin is currently loaded and is fully functional
LOADED = 1


class Pluginator:
""" The common plugin utilities.
"""

@staticmethod
@contextmanager
def add_to_path(_p):
""" A Context Manager to add the provided path to Python's sys.path temporarily.
"""
import sys # pylint: disable=import-outside-toplevel
old_path = sys.path
sys.path = sys.path[:]
sys.path.insert(0, _p)
try:
yield
finally:
sys.path = old_path

@staticmethod
def get(folder, packageName, classType) -> List:
""" Returns Array of Plugin, each holding the plugin and the module it belongs to.

Args:
folder (str): Path to the Directory.
packageName (str): Name of the package to import.
classType (desc.Node | BaseSubmitter): The base type of plugin which is being imported.
"""
pluginTypes = []
errors = []

# temporarily add folder to python path
with Pluginator.add_to_path(folder):
# import node package
package = importlib.import_module(packageName)
packageName = package.packageName if hasattr(package, 'packageName') else package.__name__
packageVersion = getattr(package, "__version__", None)

for importer, pluginName, ispkg in pkgutil.iter_modules(package.__path__):
pluginModuleName = '.' + pluginName

try:
pluginMod = importlib.import_module(pluginModuleName, package=package.__name__)
plugins = [plugin for name, plugin in inspect.getmembers(pluginMod, inspect.isclass)
if plugin.__module__ == '{}.{}'.format(package.__name__, pluginName)
and issubclass(plugin, classType)]
if not plugins:
logging.warning("No class defined in plugin: {}".format(pluginModuleName))

# Update the package name and version on the plugin
for p in plugins:
p.packageName = packageName
p.packageVersion = packageVersion

# Extend all of the plugins
pluginTypes.extend(plugins)
except Exception as exc:
errors.append(' * {}: {}'.format(pluginName, str(exc)))

if errors:
logging.warning('== The following "{package}" plugins could not be loaded ==\n'
'{errorMsg}\n'
.format(package=packageName, errorMsg='\n'.join(errors)))

return pluginTypes
Loading