MrDraw/src/octoprint/plugin/core.py

403 lines
12 KiB
Python

# coding=utf-8
from __future__ import absolute_import
__author__ = "Gina Häußge <osd@foosel.net>"
__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
__copyright__ = "Copyright (C) 2014 The OctoPrint Project - Released under terms of the AGPLv3 License"
import os
import imp
from collections import defaultdict
import logging
class PluginInfo(object):
attr_name = '__plugin_name__'
attr_description = '__plugin_description__'
attr_version = '__plugin_version__'
attr_author = '__plugin_author__'
attr_url = '__plugin_url__'
attr_license = '__plugin_license__'
attr_hooks = '__plugin_hooks__'
attr_implementations = '__plugin_implementations__'
attr_helpers = '__plugin_helpers__'
attr_check = '__plugin_check__'
attr_init = '__plugin_init__'
def __init__(self, key, location, instance, name=None, version=None, description=None, author=None, url=None, license=None):
self.key = key
self.location = location
self.instance = instance
self.origin = None
self.enabled = True
self.bundled = False
self._name = name
self._version = version
self._description = description
self._author = author
self._url = url
self._license = license
def __str__(self):
return "{name} ({version})".format(name=self.name, version=self.version if self.version else "unknown")
def get_hook(self, hook):
if not hook in self.hooks:
return None
return self.hooks[hook]
def get_implementations(self, *types):
result = set()
for implementation in self.implementations:
matches_all = True
for type in types:
if not isinstance(implementation, type):
matches_all = False
if matches_all:
result.add(implementation)
return result
@property
def name(self):
return self._get_instance_attribute(self.__class__.attr_name, defaults=(self._name, self.key))
@property
def description(self):
return self._get_instance_attribute(self.__class__.attr_description, default=self._description)
@property
def version(self):
return self._version if self._version is not None else self._get_instance_attribute(self.__class__.attr_version, default=self._version)
@property
def author(self):
return self._get_instance_attribute(self.__class__.attr_author, default=self._author)
@property
def url(self):
return self._get_instance_attribute(self.__class__.attr_url, default=self._url)
@property
def license(self):
return self._get_instance_attribute(self.__class__.attr_license, default=self._license)
@property
def hooks(self):
return self._get_instance_attribute(self.__class__.attr_hooks, default={})
@property
def implementations(self):
return self._get_instance_attribute(self.__class__.attr_implementations, default=[])
@property
def helpers(self):
return self._get_instance_attribute(self.__class__.attr_helpers, default={})
@property
def check(self):
return self._get_instance_attribute(self.__class__.attr_check, default=lambda: True)
@property
def init(self):
return self._get_instance_attribute(self.__class__.attr_init, default=lambda: True)
def _get_instance_attribute(self, attr, default=None, defaults=None):
if not hasattr(self.instance, attr):
if defaults is not None:
for value in defaults:
if value is not None:
return value
return default
return getattr(self.instance, attr)
class PluginManager(object):
def __init__(self, plugin_folders, plugin_types, plugin_entry_points, logging_prefix=None, plugin_disabled_list=None):
self.logger = logging.getLogger(__name__)
if logging_prefix is None:
logging_prefix = ""
if plugin_disabled_list is None:
plugin_disabled_list = []
self.plugin_folders = plugin_folders
self.plugin_types = plugin_types
self.plugin_entry_points = plugin_entry_points
self.plugin_disabled_list = plugin_disabled_list
self.logging_prefix = logging_prefix
self.plugins = dict()
self.plugin_hooks = defaultdict(list)
self.plugin_implementations = defaultdict(set)
self.plugin_implementations_by_type = defaultdict(list)
self.disabled_plugins = dict()
self.registered_clients = []
self.reload_plugins()
def _find_plugins(self):
plugins = dict()
disabled_plugins = dict()
if self.plugin_folders:
self._add_plugins_from_folders(self.plugin_folders, plugins, disabled_plugins)
if self.plugin_entry_points:
self._add_plugins_from_entry_points(self.plugin_entry_points, plugins, disabled_plugins)
return plugins, disabled_plugins
def _add_plugins_from_folders(self, folders, plugins, disabled_plugins):
for folder in folders:
readonly = False
if isinstance(folder, (list, tuple)):
if len(folder) == 2:
folder, readonly = folder
else:
continue
if not os.path.exists(folder):
self.logger.warn("Plugin folder {folder} could not be found, skipping it".format(folder=folder))
continue
entries = os.listdir(folder)
for entry in entries:
path = os.path.join(folder, entry)
if os.path.isdir(path) and os.path.isfile(os.path.join(path, "__init__.py")):
key = entry
elif os.path.isfile(path) and entry.endswith(".py"):
key = entry[:-3] # strip off the .py extension
else:
continue
if key in plugins:
# plugin is already defined, ignore it
continue
plugin = self._load_plugin_from_module(key, folder=folder)
if plugin:
plugin.origin = ("folder", folder)
if readonly:
plugin.bundled = True
if self._is_plugin_disabled(key):
plugin.enabled = False
disabled_plugins[key] = plugin
else:
plugins[key] = plugin
return plugins, disabled_plugins
def _add_plugins_from_entry_points(self, groups, plugins, disabled_plugins):
import pkg_resources
import pkginfo
if not isinstance(groups, (list, tuple)):
groups = [groups]
for group in groups:
for entry_point in pkg_resources.iter_entry_points(group=group, name=None):
key = entry_point.name
module_name = entry_point.module_name
version = entry_point.dist.version
if key in plugins:
# plugin is already defined, ignore it
continue
kwargs = dict(module_name=module_name, version=version)
try:
module_pkginfo = pkginfo.Installed(module_name)
except:
self.logger.exception("Something went wrong while retrieving package info data for module %s" % module_name)
else:
kwargs.update(dict(
name=module_pkginfo.name,
summary=module_pkginfo.summary,
author=module_pkginfo.author,
url=module_pkginfo.home_page,
license=module_pkginfo.license
))
plugin = self._load_plugin_from_module(key, **kwargs)
if plugin:
plugin.origin = ("entry_point", group, module_name)
if self._is_plugin_disabled(key):
plugin.enabled = False
disabled_plugins[key] = plugin
else:
plugins[key] = plugin
return plugins, disabled_plugins
def _load_plugin_from_module(self, key, folder=None, module_name=None, name=None, version=None, summary=None, author=None, url=None, license=None):
# TODO error handling
try:
if folder:
module = imp.find_module(key, [folder])
elif module_name:
module = imp.find_module(module_name)
else:
return None
except:
self.logger.warn("Could not locate plugin {key}")
return None
plugin = self._load_plugin(key, *module, name=name, version=version, summary=summary, author=author, url=url, license=license)
if plugin:
if plugin.check():
return plugin
else:
self.logger.warn("Plugin \"{plugin}\" did not pass check".format(plugin=str(plugin)))
return None
def _load_plugin(self, key, f, filename, description, name=None, version=None, summary=None, author=None, url=None, license=None):
try:
instance = imp.load_module(key, f, filename, description)
return PluginInfo(key, filename, instance, name=name, version=version, description=summary, author=author, url=url, license=license)
except:
self.logger.exception("Error loading plugin {key}, disabling it".format(key=key))
return None
def _is_plugin_disabled(self, key):
return key in self.plugin_disabled_list or key.endswith('disabled')
def reload_plugins(self):
self.logger.info("Loading plugins from {folders} and installed plugin packages...".format(folders=", ".join(self.plugin_folders)))
self.plugins, self.disabled_plugins = self._find_plugins()
for name, plugin in self.plugins.items():
# initialize the plugin
plugin.init()
# evaluate registered hooks
for hook, callback in plugin.hooks.items():
self.plugin_hooks[hook].append((name, callback))
# evaluate registered implementations
for plugin_type in self.plugin_types:
implementations = plugin.get_implementations(plugin_type)
self.plugin_implementations_by_type[plugin_type] += ( (name, implementation) for implementation in implementations )
self.plugin_implementations[name].update(plugin.get_implementations())
self.log_registered_plugins()
def initialize_implementations(self, additional_injects=None, additional_inject_factories=None):
if additional_injects is None:
additional_injects = dict()
if additional_inject_factories is None:
additional_inject_factories = []
for name, implementations in self.plugin_implementations.items():
plugin = self.plugins[name]
for implementation in implementations:
try:
kwargs = dict(additional_injects)
kwargs.update(dict(
identifier=name,
plugin_name=plugin.name,
plugin_version=plugin.version,
basefolder=os.path.realpath(plugin.location),
logger=logging.getLogger(self.logging_prefix + name),
))
# inject the additional_injects
for arg, value in kwargs.items():
setattr(implementation, "_" + arg, value)
# inject any injects produced in the additional_inject_factories
for factory in additional_inject_factories:
try:
return_value = factory(name, implementation)
except:
self.logger.exception("Exception while executing injection factory %r" % factory)
else:
if return_value is not None:
if isinstance(return_value, dict):
for arg, value in return_value.items():
setattr(implementation, "_" + arg, value)
implementation.initialize()
except:
self.logger.exception("Exception while initializing plugin")
# TODO disable plugin!
self.logger.info("Initialized {count} plugin(s)".format(count=len(self.plugin_implementations)))
def log_registered_plugins(self):
if len(self.plugins) <= 0:
self.logger.info("No plugins found")
else:
self.logger.info("Found {count} plugin(s): {plugins}".format(count=len(self.plugins), plugins=", ".join(map(lambda x: str(x), self.plugins.values()))))
if len(self.disabled_plugins) > 0:
self.logger.info("{count} plugin(s) currently disabled: {plugins}".format(count=len(self.disabled_plugins), plugins=", ".join(map(lambda x: str(x), self.disabled_plugins.values()))))
def get_plugin(self, name):
if not name in self.plugins:
return None
return self.plugins[name].instance
def get_hooks(self, hook):
if not hook in self.plugin_hooks:
return dict()
return {hook[0]: hook[1] for hook in self.plugin_hooks[hook]}
def get_implementations(self, *types):
result = None
for t in types:
implementations = self.plugin_implementations_by_type[t]
if result is None:
result = set(implementations)
else:
result = result.intersection(implementations)
if result is None:
return dict()
return {impl[0]: impl[1] for impl in result}
def get_helpers(self, name, *helpers):
if not name in self.plugins:
return None
plugin = self.plugins[name]
all_helpers = plugin.helpers
if len(helpers):
return dict((k, v) for (k, v) in all_helpers.items() if k in helpers)
else:
return all_helpers
def register_client(self, client):
if client is None:
return
self.registered_clients.append(client)
def unregister_client(self, client):
self.registered_clients.remove(client)
def send_plugin_message(self, plugin, data):
for client in self.registered_clients:
try: client.sendPluginMessage(plugin, data)
except: self.logger.exception("Exception while sending plugin data to client")
class Plugin(object):
def initialize(self):
pass