diff --git a/src/octoprint/plugin/core.py b/src/octoprint/plugin/core.py index d8a58e9e..4231b56c 100644 --- a/src/octoprint/plugin/core.py +++ b/src/octoprint/plugin/core.py @@ -551,53 +551,69 @@ class PluginManager(object): result = OrderedDict() if self.plugin_folders: - result.update(self._find_plugins_from_folders(self.plugin_folders, existing, ignored_uninstalled=ignore_uninstalled)) + try: + result.update(self._find_plugins_from_folders(self.plugin_folders, + existing, + ignored_uninstalled=ignore_uninstalled)) + except: + self.logger.exception("Error fetching plugins from folders") if self.plugin_entry_points: existing.update(result) - result.update(self._find_plugins_from_entry_points(self.plugin_entry_points, existing, ignore_uninstalled=ignore_uninstalled)) + try: + result.update(self._find_plugins_from_entry_points(self.plugin_entry_points, + existing, + ignore_uninstalled=ignore_uninstalled)) + except: + self.logger.exception("Error fetching plugins from entry points") return result def _find_plugins_from_folders(self, folders, existing, ignored_uninstalled=True): result = OrderedDict() for folder in folders: - flagged_readonly = False - if isinstance(folder, (list, tuple)): - if len(folder) == 2: - folder, flagged_readonly = folder - else: - continue - actual_readonly = not os.access(folder, os.W_OK) - - if not os.path.exists(folder): - self.logger.warn("Plugin folder {folder} could not be found, skipping it".format(folder=folder)) - continue - - for entry in scandir(folder): - if entry.is_dir() and os.path.isfile(os.path.join(entry.path, "__init__.py")): - key = entry.name - elif entry.is_file() and entry.name.endswith(".py"): - key = entry.name[:-3] # strip off the .py extension - if key.startswith("__"): - # might be an __init__.py in our plugins folder, or something else we don't want - # to handle + try: + flagged_readonly = False + if isinstance(folder, (list, tuple)): + if len(folder) == 2: + folder, flagged_readonly = folder + else: continue - else: + actual_readonly = not os.access(folder, os.W_OK) + + if not os.path.exists(folder): + self.logger.warn("Plugin folder {folder} could not be found, skipping it".format(folder=folder)) continue - if key in existing or key in result or (ignored_uninstalled and key in self.marked_plugins["uninstalled"]): - # plugin is already defined, ignore it - continue + for entry in scandir(folder): + try: + if entry.is_dir() and os.path.isfile(os.path.join(entry.path, "__init__.py")): + key = entry.name + elif entry.is_file() and entry.name.endswith(".py"): + key = entry.name[:-3] # strip off the .py extension + if key.startswith("__"): + # might be an __init__.py in our plugins folder, or something else we don't want + # to handle + continue + else: + continue - plugin = self._import_plugin_from_module(key, folder=folder) - if plugin: - plugin.origin = FolderOrigin("folder", folder) - plugin.managable = not flagged_readonly and not actual_readonly - plugin.bundled = flagged_readonly + if key in existing or key in result or (ignored_uninstalled and key in self.marked_plugins["uninstalled"]): + # plugin is already defined, ignore it + continue - plugin.enabled = False + plugin = self._import_plugin_from_module(key, folder=folder) + if plugin: + plugin.origin = FolderOrigin("folder", folder) + plugin.managable = not flagged_readonly and not actual_readonly + plugin.bundled = flagged_readonly - result[key] = plugin + plugin.enabled = False + + result[key] = plugin + except: + self.logger.exception("Error processing folder entry {!r} from folder {}".format(entry, folder)) + except: + self.logger.exception("Error processing folder {}".format(folder)) return result @@ -619,51 +635,65 @@ class PluginManager(object): if not isinstance(groups, (list, tuple)): groups = [groups] + def wrapped(gen): + # to protect against some issues in installed packages that make iteration over entry points + # fall on its face - e.g. https://groups.google.com/forum/#!msg/octoprint/DyXdqhR0U7c/kKMUsMmIBgAJ + try: + yield next(gen) + except StopIteration: + raise + except: + self.logger.exception("Something went wrong while processing the entry points of a package in the " + "Python environment - broken entry_points.txt in some package?") + for group in groups: - for entry_point in working_set.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 existing or key in result or (ignore_uninstalled and key in self.marked_plugins["uninstalled"]): - # plugin is already defined or marked as uninstalled, ignore it - continue - - kwargs = dict(module_name=module_name, version=version) - package_name = None + for entry_point in wrapped(working_set.iter_entry_points(group=group, name=None)): try: - module_pkginfo = InstalledEntryPoint(entry_point) + key = entry_point.name + module_name = entry_point.module_name + version = entry_point.dist.version + + if key in existing or key in result or (ignore_uninstalled and key in self.marked_plugins["uninstalled"]): + # plugin is already defined or marked as uninstalled, ignore it + continue + + kwargs = dict(module_name=module_name, version=version) + package_name = None + try: + module_pkginfo = InstalledEntryPoint(entry_point) + 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 + )) + package_name = module_pkginfo.name + + plugin = self._import_plugin_from_module(key, **kwargs) + if plugin: + plugin.origin = EntryPointOrigin("entry_point", group, module_name, package_name, version) + + # plugin is manageable if its location is writable and OctoPrint + # is either not running from a virtual env or the plugin is + # installed in that virtual env - the virtual env's pip will not + # allow us to uninstall stuff that is installed outside + # of the virtual env, so this check is necessary + plugin.managable = os.access(plugin.location, os.W_OK) \ + and (not self._python_virtual_env + or is_sub_path_of(plugin.location, self._python_prefix) + or is_editable_install(self._python_install_dir, + package_name, + module_name, + plugin.location)) + + plugin.enabled = False + result[key] = plugin 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 - )) - package_name = module_pkginfo.name - - plugin = self._import_plugin_from_module(key, **kwargs) - if plugin: - plugin.origin = EntryPointOrigin("entry_point", group, module_name, package_name, version) - - # plugin is manageable if its location is writable and OctoPrint - # is either not running from a virtual env or the plugin is - # installed in that virtual env - the virtual env's pip will not - # allow us to uninstall stuff that is installed outside - # of the virtual env, so this check is necessary - plugin.managable = os.access(plugin.location, os.W_OK) \ - and (not self._python_virtual_env - or is_sub_path_of(plugin.location, self._python_prefix) - or is_editable_install(self._python_install_dir, - package_name, - module_name, - plugin.location)) - - plugin.enabled = False - result[key] = plugin + self.logger.exception("Error processing entry point {!r} for group {}".format(entry_point, group)) return result @@ -717,10 +747,10 @@ class PluginManager(object): entry_key, entry_version = entry return entry_key == key and entry_version == version return False - + return any(map(lambda entry: matches_plugin(entry), self.plugin_blacklist)) - + def reload_plugins(self, startup=False, initialize_implementations=True, force_reload=None): self.logger.info("Loading plugins from {folders} and installed plugin packages...".format( folders=", ".join(map(lambda x: x[0] if isinstance(x, tuple) else str(x), self.plugin_folders))