def setPluginInfoExtension(self, ext): """ DEPRECATED(>1.9): for backward compatibility. Directly configure the IPluginLocator instance instead ! .. warning:: This will only work if the strategy "info_ext" is active for locating plugins. """ try: self.getPluginLocator().setPluginInfoExtension(ext) except KeyError: log.error( "Current plugin locator doesn't support setting the plugin info extension." )
def install(self, directory, plugin_info_filename): """ Giving the plugin's info file (e.g. ``myplugin.yapsy-plugin``), and the directory where it is located, get all the files that define the plugin and copy them into the correct directory. Return ``True`` if the installation is a success, ``False`` if it is a failure. """ # start collecting essential info about the new plugin plugin_info, config_parser = self._gatherCorePluginInfo( directory, plugin_info_filename) # now determine the path of the file to execute, # depending on wether the path indicated is a # directory or a file if not (os.path.exists(plugin_info.path) or os.path.exists(plugin_info.path + ".py")): log.warning("Could not find the plugin's implementation for %s." % plugin_info.name) return False if os.path.isdir(plugin_info.path): try: shutil.copytree( plugin_info.path, os.path.join(self.install_dir, os.path.basename(plugin_info.path))) shutil.copy(os.path.join(directory, plugin_info_filename), self.install_dir) except: log.error("Could not install plugin: %s." % plugin_info.name) return False else: return True elif os.path.isfile(plugin_info.path + ".py"): try: shutil.copy(plugin_info.path + ".py", self.install_dir) shutil.copy(os.path.join(directory, plugin_info_filename), self.install_dir) except: log.error("Could not install plugin: %s." % plugin_info.name) return False else: return True else: return False
def installFromZIP(self, plugin_ZIP_filename): """ Giving the plugin's zip file (e.g. ``myplugin.zip``), check that their is a valid info file in it and correct all the plugin files into the correct directory. .. warning:: Only available for python 2.6 and later. Return ``True`` if the installation is a success, ``False`` if it is a failure. """ if not os.path.isfile(plugin_ZIP_filename): log.warning("Could not find the plugin's zip file at '%s'." % plugin_ZIP_filename) return False try: candidateZipFile = zipfile.ZipFile(plugin_ZIP_filename) first_bad_file = candidateZipFile.testzip() if first_bad_file: raise Exception("Corrupted ZIP with first bad file '%s'" % first_bad_file) except Exception as e: log.warning("Invalid zip file '%s' (error: %s)." % (plugin_ZIP_filename, e)) return False zipContent = candidateZipFile.namelist() log.info("Investigating the content of a zip file containing: '%s'" % zipContent) log.info( "Sanity checks on zip's contained files (looking for hazardous path symbols)." ) # check absence of root path and ".." shortcut that would # send the file oustide the desired directory for containedFileName in zipContent: # WARNING: the sanity checks below are certainly not # exhaustive (maybe we could do something a bit smarter by # using os.path.expanduser, os.path.expandvars and # os.path.normpath) if containedFileName.startswith("/"): log.warning( "Unsecure zip file, rejected because one of its file paths ('%s') starts with '/'" % containedFileName) return False if containedFileName.startswith( r"\\") or containedFileName.startswith("//"): log.warning( r"Unsecure zip file, rejected because one of its file paths ('%s') starts with '\\'" % containedFileName) return False if os.path.splitdrive(containedFileName)[0]: log.warning( "Unsecure zip file, rejected because one of its file paths ('%s') starts with a drive letter" % containedFileName) return False if os.path.isabs(containedFileName): log.warning( "Unsecure zip file, rejected because one of its file paths ('%s') is absolute" % containedFileName) return False pathComponent = os.path.split(containedFileName) if ".." in pathComponent: log.warning( "Unsecure zip file, rejected because one of its file paths ('%s') contains '..'" % containedFileName) return False if "~" in pathComponent: log.warning( "Unsecure zip file, rejected because one of its file paths ('%s') contains '~'" % containedFileName) return False infoFileCandidates = [ filename for filename in zipContent if os.path.dirname(filename) == "" ] if not infoFileCandidates: log.warning( "Zip file structure seems wrong in '%s', no info file found." % plugin_ZIP_filename) return False isValid = False log.info("Looking for the zipped plugin's info file among '%s'" % infoFileCandidates) for infoFileName in infoFileCandidates: infoFile = candidateZipFile.read(infoFileName) log.info("Assuming the zipped plugin info file to be '%s'" % infoFileName) pluginName, moduleName, _ = self._getPluginNameAndModuleFromStream( StringIO(str(infoFile, encoding="utf-8"))) if moduleName is None: continue log.info( "Checking existence of the expected module '%s' in the zip file" % moduleName) candidate_module_paths = [ moduleName, # Try path consistent with the platform specific one os.path.join(moduleName, "__init__.py"), # Try typical paths (unix and windows) "%s/__init__.py" % moduleName, "%s\\__init__.py" % moduleName ] for candidate in candidate_module_paths: if candidate in zipContent: isValid = True break if isValid: break if not isValid: log.warning( "Zip file structure seems wrong in '%s', " "could not match info file with the implementation of plugin '%s'." % (plugin_ZIP_filename, pluginName)) return False else: try: candidateZipFile.extractall(self.install_dir) return True except Exception as e: log.error( "Could not install plugin '%s' from zip file '%s' (exception: '%s')." % (pluginName, plugin_ZIP_filename, e)) return False
def locatePlugins(self): """ Walk through the plugins' places and look for plugins. Return the candidates and number of plugins found. """ # print "%s.locatePlugins" % self.__class__ _candidates = [] _discovered = {} for directory in map(os.path.abspath, self.plugins_places): # first of all, is it a directory :) if not os.path.isdir(directory): log.debug("%s skips %s (not a directory)" % (self.__class__.__name__, directory)) continue if self.recursive: debug_txt_mode = "recursively" walk_iter = os.walk(directory, followlinks=True) else: debug_txt_mode = "non-recursively" walk_iter = [(directory,[],os.listdir(directory))] # iteratively walks through the directory log.debug("%s walks (%s) into directory: %s" % (self.__class__.__name__, debug_txt_mode, directory)) for item in walk_iter: dirpath = item[0] for filename in item[2]: # print("testing candidate file %s" % filename) for analyzer in self._analyzers: # print("... with analyzer %s" % analyzer.name) # eliminate the obvious non plugin files if not analyzer.isValidPlugin(filename): log.debug("%s is not a valid plugin for strategy %s" % (filename, analyzer.name)) continue candidate_infofile = os.path.join(dirpath, filename) if candidate_infofile in _discovered: log.debug("%s (with strategy %s) rejected because already discovered" % (candidate_infofile, analyzer.name)) continue log.debug("%s found a candidate:\n %s" % (self.__class__.__name__, candidate_infofile)) # print candidate_infofile plugin_info = self._getInfoForPluginFromAnalyzer(analyzer, dirpath, filename) if plugin_info is None: log.debug("Plugin candidate '%s' rejected by strategy '%s'" % (candidate_infofile, analyzer.name)) break # we consider this was the good strategy to use for: it failed -> not a plugin -> don't try another strategy # now determine the path of the file to execute, # depending on wether the path indicated is a # directory or a file # print plugin_info.path # Remember all the files belonging to a discovered # plugin, so that strategies (if several in use) won't # collide if os.path.isdir(plugin_info.path): candidate_filepath = os.path.join(plugin_info.path, "__init__") # it is a package, adds all the files concerned for _file in os.listdir(plugin_info.path): if _file.endswith(".py"): self._discovered_plugins[os.path.join(plugin_info.path, _file)] = candidate_filepath _discovered[os.path.join(plugin_info.path, _file)] = candidate_filepath elif (plugin_info.path.endswith(".py") and os.path.isfile(plugin_info.path)) or os.path.isfile(plugin_info.path+".py"): candidate_filepath = plugin_info.path if candidate_filepath.endswith(".py"): candidate_filepath = candidate_filepath[:-3] # it is a file, adds it self._discovered_plugins[".".join((plugin_info.path, "py"))] = candidate_filepath _discovered[".".join((plugin_info.path, "py"))] = candidate_filepath else: log.error("Plugin candidate rejected: cannot find the file or directory module for '%s'" % (candidate_infofile)) break # print candidate_filepath _candidates.append((candidate_infofile, candidate_filepath, plugin_info)) # finally the candidate_infofile must not be discovered again _discovered[candidate_infofile] = candidate_filepath self._discovered_plugins[candidate_infofile] = candidate_filepath # print "%s found by strategy %s" % (candidate_filepath, analyzer.name) return _candidates, len(_candidates)
def loadPlugins(self, callback=None, callback_after=None): """ Load the candidate plugins that have been identified through a previous call to locatePlugins. For each plugin candidate look for its category, load it and store it in the appropriate slot of the ``category_mapping``. You can specify 2 callbacks: callback, and callback_after. If either of these are passed a function, (in the case of callback), it will get called before each plugin load attempt and (for callback_after), after each attempt. The ``plugin_info`` instance is passed as an argument to each callback. This is meant to facilitate code that needs to run for each plugin, such as adding the directory it resides in to sys.path (so imports of other files in the plugin's directory work correctly). You can use callback_after to remove anything you added to the path. """ # print "%s.loadPlugins" % self.__class__ if not hasattr(self, '_candidates'): raise ValueError("locatePlugins must be called before loadPlugins") processed_plugins = [] for candidate_infofile, candidate_filepath, plugin_info in self._candidates: # make sure to attribute a unique module name to the one # that is about to be loaded plugin_module_name_template = NormalizePluginNameForModuleName( "yapsy_loaded_plugin_" + plugin_info.name) + "_%d" for plugin_name_suffix in range(len(sys.modules)): plugin_module_name = plugin_module_name_template % plugin_name_suffix if plugin_module_name not in sys.modules: break # tolerance on the presence (or not) of the py extensions if candidate_filepath.endswith(".py"): candidate_filepath = candidate_filepath[:-3] # if a callback exists, call it before attempting to load # the plugin so that a message can be displayed to the # user if callback is not None: callback(plugin_info) # cover the case when the __init__ of a package has been # explicitely indicated if "__init__" in os.path.basename(candidate_filepath): candidate_filepath = os.path.dirname(candidate_filepath) try: candidate_module = PluginManager._importModule( plugin_module_name, candidate_filepath) except Exception: exc_info = sys.exc_info() log.error("Unable to import plugin: %s" % candidate_filepath, exc_info=exc_info) plugin_info.error = exc_info processed_plugins.append(plugin_info) continue processed_plugins.append(plugin_info) if "__init__" in os.path.basename(candidate_filepath): sys.path.remove(plugin_info.path) # now try to find and initialise the first subclass of the correct plugin interface last_failed_attempt_message = None for element, element_name in ((getattr(candidate_module, name), name) for name in dir(candidate_module)): plugin_info_reference = None for category_name in self.categories_interfaces: try: is_correct_subclass = issubclass( element, self.categories_interfaces[category_name]) except Exception: exc_info = sys.exc_info() log.debug( "correct subclass tests failed for: %s in %s" % (element_name, candidate_filepath), exc_info=exc_info) continue if is_correct_subclass and element is not self.categories_interfaces[ category_name]: current_category = category_name if candidate_infofile not in self._category_file_mapping[ current_category]: # we found a new plugin: initialise it and search for the next one if not plugin_info_reference: try: plugin_info.plugin_object = self.instanciateElementWithImportInfo( element, element_name, plugin_module_name, candidate_filepath) plugin_info_reference = plugin_info except Exception: exc_info = sys.exc_info() last_failed_attempt_message = "Unable to create plugin object: %s" % candidate_filepath log.debug(last_failed_attempt_message, exc_info=exc_info) plugin_info.error = exc_info break # If it didn't work once it wont again else: last_failed_attempt_message = None plugin_info.categories.append(current_category) self.category_mapping[current_category].append( plugin_info_reference) self._category_file_mapping[ current_category].append(candidate_infofile) # Everything is loaded and instantiated for this plugin now if callback_after is not None: callback_after(plugin_info) else: if last_failed_attempt_message: log.error(last_failed_attempt_message, exc_info=plugin_info.error) # Remove candidates list since we don't need them any more and # don't need to take up the space delattr(self, '_candidates') return processed_plugins