Yapsy-1.12.0/0000775000175000017500000000000013343011254014310 5ustar thibauldthibauld00000000000000Yapsy-1.12.0/LICENSE.txt0000664000175000017500000000326412575022002016137 0ustar thibauldthibauld00000000000000Yapsy is provided under the BSD-2 clause license (see text below), with the following two exceptions: - the "yapsy" icons in artwork/ is licensed under the Creative Commons Attribution-Share Alike 3.0 by Thibauld Nion (see artwork/LICENSE.txt) - the compat.py file is licensed under the ISC License by Kenneth Reitz (see yapsy/compat.py). -------------------- BSD 2-clause license -------------------- Copyright (c) 2007-2015, Thibauld Nion All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Yapsy-1.12.0/yapsy/0000775000175000017500000000000013343011254015455 5ustar thibauldthibauld00000000000000Yapsy-1.12.0/yapsy/__init__.py0000664000175000017500000000564513343007234017603 0ustar thibauldthibauld00000000000000# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- """ Overview ======== Yapsy's main purpose is to offer a way to easily design a plugin system in Python, and motivated by the fact that many other Python plugin system are either too complicated for a basic use or depend on a lot of libraries. Yapsy only depends on Python's standard library. |yapsy| basically defines two core classes: - a fully functional though very simple ``PluginManager`` class - an interface ``IPlugin`` which defines the interface of plugin instances handled by the ``PluginManager`` Getting started =============== The basic classes defined by |yapsy| should work "as is" and enable you to load and activate your plugins. So that the following code should get you a fully working plugin management system:: from yapsy.PluginManager import PluginManager # Build the manager simplePluginManager = PluginManager() # Tell it the default place(s) where to find plugins simplePluginManager.setPluginPlaces(["path/to/myplugins"]) # Load all plugins simplePluginManager.collectPlugins() # Activate all loaded plugins for pluginInfo in simplePluginManager.getAllPlugins(): simplePluginManager.activatePluginByName(pluginInfo.name) .. note:: The ``plugin_info`` object (typically an instance of ``IPlugin``) plays as *the entry point of each plugin*. That's also where |yapsy| ceases to guide you: it's up to you to define what your plugins can do and how you want to talk to them ! Talking to your plugin will then look very much like the following:: # Trigger 'some action' from the loaded plugins for pluginInfo in simplePluginManager.getAllPlugins(): pluginInfo.plugin_object.doSomething(...) """ __version__="1.12.0" # tell epydoc that the documentation is in the reStructuredText format __docformat__ = "restructuredtext en" # provide a default named log for package-wide use import logging log = logging.getLogger('yapsy') # Some constants concerning the plugins PLUGIN_NAME_FORBIDEN_STRING=";;" """ .. warning:: This string (';;' by default) is forbidden in plugin names, and will be usable to describe lists of plugins for instance (see :doc:`ConfigurablePluginManager`) """ import re from yapsy.compat import is_py2, str if is_py2: RE_NON_ALPHANUM = re.compile("\W", re.U) else: RE_NON_ALPHANUM = re.compile("\W") def NormalizePluginNameForModuleName(pluginName): """ Normalize a plugin name into a safer name for a module name. .. note:: may do a little more modifications than strictly necessary and is not optimized for speed. """ if is_py2: pluginName = str(pluginName, 'utf-8') if len(pluginName)==0: return "_" if pluginName[0].isdigit(): pluginName = "_" + pluginName ret = RE_NON_ALPHANUM.sub("_",pluginName) if is_py2: ret = ret.encode('utf-8') return ret Yapsy-1.12.0/yapsy/IMultiprocessPlugin.py0000664000175000017500000000207713343003466022024 0ustar thibauldthibauld00000000000000# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- """ Role ==== Defines the basic interfaces for multiprocessed plugins. Extensibility ============= In your own software, you'll probably want to build derived classes of the ``IMultiprocessPlugin`` class as it is a mere interface with no specific functionality. Your software's plugins should then inherit your very own plugin class (itself derived from ``IMultiprocessPlugin``). Override the `run` method to include your code. Use the `self.parent_pipe` to send and receive data with the parent process or create your own communication mecanism. Where and how to code these plugins is explained in the section about the :doc:`PluginManager`. API === """ from yapsy.IPlugin import IPlugin class IMultiprocessPlugin(IPlugin): """ Base class for multiprocessed plugin. """ def __init__(self, parent_pipe): IPlugin.__init__(self) self.parent_pipe = parent_pipe def run(self): """ Override this method in your implementation """ return Yapsy-1.12.0/yapsy/PluginInfo.py0000664000175000017500000001474613043444543020125 0ustar thibauldthibauld00000000000000# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- """ Role ==== Encapsulate a plugin instance as well as some metadata. API === """ from yapsy.compat import ConfigParser from distutils.version import StrictVersion class PluginInfo(object): """Representation of the most basic set of information related to a given plugin such as its name, author, description... Any additional information can be stored ad retrieved in a PluginInfo, when this one is created with a ``ConfigParser.ConfigParser`` instance. This typically means that when metadata is read from a text file (the original way for yapsy to describe plugins), all info that is not part of the basic variables (name, path, version etc), can still be accessed though the ``details`` member variables that behaves like Python's ``ConfigParser.ConfigParser``. Warning: the instance associated with the ``details`` member variable is never copied and used to store all plugin infos. If you set it to a custom instance, it will be modified as soon as another member variale of the plugin info is changed. Alternatively, if you change the instance "outside" the plugin info, it will also change the plugin info. Ctor Arguments: *plugin_name* is a simple string describing the name of the plugin. *plugin_path* describe the location where the plugin can be found. .. warning:: The ``path`` attribute is the full path to the plugin if it is organised as a directory or the full path to a file without the ``.py`` extension if the plugin is defined by a simple file. In the later case, the actual plugin is reached via ``plugin_info.path+'.py'``. """ def __init__(self, plugin_name, plugin_path): self.__details = ConfigParser() self.name = plugin_name self.path = plugin_path self._ensureDetailsDefaultsAreBackwardCompatible() # Storage for stuff created during the plugin lifetime self.plugin_object = None self.categories = [] self.error = None def __setDetails(self,cfDetails): """ Fill in all details by storing a ``ConfigParser`` instance. .. warning: The values for ``plugin_name`` and ``plugin_path`` given a init time will superseed any value found in ``cfDetails`` in section 'Core' for the options 'Name' and 'Module' (this is mostly for backward compatibility). """ bkp_name = self.name bkp_path = self.path self.__details = cfDetails self.name = bkp_name self.path = bkp_path self._ensureDetailsDefaultsAreBackwardCompatible() def __getDetails(self): return self.__details def __getName(self): return self.details.get("Core","Name") def __setName(self, name): if not self.details.has_section("Core"): self.details.add_section("Core") self.details.set("Core","Name",name) def __getPath(self): return self.details.get("Core","Module") def __setPath(self,path): if not self.details.has_section("Core"): self.details.add_section("Core") self.details.set("Core","Module",path) def __getVersion(self): return StrictVersion(self.details.get("Documentation","Version")) def setVersion(self, vstring): """ Set the version of the plugin. Used by subclasses to provide different handling of the version number. """ if isinstance(vstring,StrictVersion): vstring = str(vstring) if not self.details.has_section("Documentation"): self.details.add_section("Documentation") self.details.set("Documentation","Version",vstring) def __getAuthor(self): return self.details.get("Documentation","Author") def __setAuthor(self,author): if not self.details.has_section("Documentation"): self.details.add_section("Documentation") self.details.set("Documentation","Author",author) def __getCopyright(self): return self.details.get("Documentation","Copyright") def __setCopyright(self,copyrightTxt): if not self.details.has_section("Documentation"): self.details.add_section("Documentation") self.details.set("Documentation","Copyright",copyrightTxt) def __getWebsite(self): return self.details.get("Documentation","Website") def __setWebsite(self,website): if not self.details.has_section("Documentation"): self.details.add_section("Documentation") self.details.set("Documentation","Website",website) def __getDescription(self): return self.details.get("Documentation","Description") def __setDescription(self,description): if not self.details.has_section("Documentation"): self.details.add_section("Documentation") return self.details.set("Documentation","Description",description) def __getCategory(self): """ DEPRECATED (>1.9): Mimic former behaviour when what is noz the first category was considered as the only one the plugin belonged to. """ if self.categories: return self.categories[0] else: return "UnknownCategory" def __setCategory(self,c): """ DEPRECATED (>1.9): Mimic former behaviour by making so that if a category is set as if it were the only category to which the plugin belongs, then a __getCategory will return this newly set category. """ self.categories = [c] + self.categories name = property(fget=__getName,fset=__setName) path = property(fget=__getPath,fset=__setPath) version = property(fget=__getVersion,fset=setVersion) author = property(fget=__getAuthor,fset=__setAuthor) copyright = property(fget=__getCopyright,fset=__setCopyright) website = property(fget=__getWebsite,fset=__setWebsite) description = property(fget=__getDescription,fset=__setDescription) details = property(fget=__getDetails,fset=__setDetails) # deprecated (>1.9): plugins are not longer associated to a # single category ! category = property(fget=__getCategory,fset=__setCategory) def _getIsActivated(self): """ Return the activated state of the plugin object. Makes it possible to define a property. """ return self.plugin_object.is_activated is_activated = property(fget=_getIsActivated) def _ensureDetailsDefaultsAreBackwardCompatible(self): """ Internal helper function. """ if not self.details.has_option("Documentation","Author"): self.author = "Unknown" if not self.details.has_option("Documentation","Version"): self.version = "0.0" if not self.details.has_option("Documentation","Website"): self.website = "None" if not self.details.has_option("Documentation","Copyright"): self.copyright = "Unknown" if not self.details.has_option("Documentation","Description"): self.description = "" Yapsy-1.12.0/yapsy/FilteredPluginManager.py0000664000175000017500000000772312575022002022247 0ustar thibauldthibauld00000000000000# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- """ Role ==== Defines the basic mechanisms to have a plugin manager filter the available list of plugins after locating them and before loading them. One use fo this would be to prevent untrusted plugins from entering the system. To use it properly you must reimplement or monkey patch the ``IsPluginOk`` method, as in the following example:: # define a plugin manager (with you prefered options) pm = PluginManager(...) # decorate it with the Filtering mechanics pm = FilteredPluginManager(pm) # define a custom predicate that filters out plugins without descriptions pm.isPluginOk = lambda x: x.description!="" API === """ from yapsy.IPlugin import IPlugin from yapsy.PluginManagerDecorator import PluginManagerDecorator class FilteredPluginManager(PluginManagerDecorator): """ Base class for decorators which filter the plugins list before they are loaded. """ def __init__(self, decorated_manager=None, categories_filter=None, directories_list=None, plugin_info_ext="yapsy-plugin"): if categories_filter is None: categories_filter = {"Default":IPlugin} # Create the base decorator class PluginManagerDecorator.__init__(self,decorated_manager, categories_filter, directories_list, plugin_info_ext) # prepare the mapping of the latest version of each plugin self.rejectedPlugins = [ ] def filterPlugins(self): """ Go through the currently available candidates, and and either leaves them, or moves them into the list of rejected Plugins. Can be overridden if overriding ``isPluginOk`` sentinel is not powerful enough. """ self.rejectedPlugins = [ ] for candidate_infofile, candidate_filepath, plugin_info in self._component.getPluginCandidates(): if not self.isPluginOk( plugin_info): self.rejectPluginCandidate((candidate_infofile, candidate_filepath, plugin_info) ) def rejectPluginCandidate(self,pluginTuple): """ Move a plugin from the candidates list to the rejected List. """ if pluginTuple in self.getPluginCandidates(): self._component.removePluginCandidate(pluginTuple) if not pluginTuple in self.rejectedPlugins: self.rejectedPlugins.append(pluginTuple) def unrejectPluginCandidate(self,pluginTuple): """ Move a plugin from the rejected list to into the candidates list. """ if not pluginTuple in self.getPluginCandidates(): self._component.appendPluginCandidate(pluginTuple) if pluginTuple in self.rejectedPlugins: self.rejectedPlugins.remove(pluginTuple) def removePluginCandidate(self,pluginTuple): """ Remove a plugin from the list of candidates. """ if pluginTuple in self.getPluginCandidates(): self._component.removePluginCandidate(pluginTuple) if pluginTuple in self.rejectedPlugins: self.rejectedPlugins.remove(pluginTuple) def appendPluginCandidate(self,pluginTuple): """ Add a new candidate. """ if self.isPluginOk(pluginTuple[2]): if pluginTuple not in self.getPluginCandidates(): self._component.appendPluginCandidate(pluginTuple) else: if not pluginTuple in self.rejectedPlugins: self.rejectedPlugins.append(pluginTuple) def isPluginOk(self,info): """ Sentinel function to detect if a plugin should be filtered. ``info`` is an instance of a ``PluginInfo`` and this method is expected to return True if the corresponding plugin can be accepted, and False if it must be filtered out. Subclasses should override this function and return false for any plugin which they do not want to be loadable. """ return True def locatePlugins(self): """ locate and filter plugins. """ #Reset Catalogue self.setCategoriesFilter(self._component.categories_interfaces) #Reread and filter. self._component.locatePlugins() self.filterPlugins() return len(self._component.getPluginCandidates()) def getRejectedPlugins(self): """ Return the list of rejected plugins. """ return self.rejectedPlugins[:] Yapsy-1.12.0/yapsy/AutoInstallPluginManager.py0000664000175000017500000001676013343003466022760 0ustar thibauldthibauld00000000000000# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- """ Role ==== Defines plugin managers that can handle the installation of plugin files into the right place. Then the end-user does not have to browse to the plugin directory to install them. API === """ import os import shutil import zipfile from yapsy.IPlugin import IPlugin from yapsy.PluginManagerDecorator import PluginManagerDecorator from yapsy import log from yapsy.compat import StringIO, str class AutoInstallPluginManager(PluginManagerDecorator): """ A plugin manager that also manages the installation of the plugin files into the appropriate directory. Ctor Arguments: ``plugin_install_dir`` The directory where new plugins to be installed will be copied. .. warning:: If ``plugin_install_dir`` does not correspond to an element of the ``directories_list``, it is appended to the later. """ def __init__(self, plugin_install_dir=None, decorated_manager=None, # The following args will only be used if we need to # create a default PluginManager categories_filter=None, directories_list=None, plugin_info_ext="yapsy-plugin"): if categories_filter is None: categories_filter = {"Default":IPlugin} # Create the base decorator class PluginManagerDecorator.__init__(self, decorated_manager, categories_filter, directories_list, plugin_info_ext) # set the directory for new plugins self.plugins_places=[] self.setInstallDir(plugin_install_dir) def setInstallDir(self,plugin_install_dir): """ Set the directory where to install new plugins. """ if not (plugin_install_dir in self.plugins_places): self.plugins_places.append(plugin_install_dir) self.install_dir = plugin_install_dir def getInstallDir(self): """ Return the directory where new plugins should be installed. """ return self.install_dir 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 Yapsy-1.12.0/yapsy/MultiprocessPluginProxy.py0000664000175000017500000000214512575022002022742 0ustar thibauldthibauld00000000000000# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- """ Role ==== The ``MultiprocessPluginProxy`` is instanciated by the MultiprocessPluginManager to replace the real implementation that is run in a different process. You cannot access your plugin directly from the parent process. You should use the child_pipe to communicate with your plugin. The `MultiprocessPluginProxy`` role is to keep reference of the communication pipe to the child process as well as the process informations. API === """ from yapsy.IPlugin import IPlugin class MultiprocessPluginProxy(IPlugin): """ This class contains two members that are initialized by the :doc:`MultiprocessPluginManager`. self.proc is a reference that holds the multiprocessing.Process instance of the child process. self.child_pipe is a reference that holds the multiprocessing.Pipe instance to communicate with the child. """ def __init__(self): IPlugin.__init__(self) self.proc = None # This attribute holds the multiprocessing.Process instance self.child_pipe = None # This attribute holds the multiprocessing.Pipe instance Yapsy-1.12.0/yapsy/PluginManager.py0000664000175000017500000006213613343003466020576 0ustar thibauldthibauld00000000000000# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- """ Role ==== The ``PluginManager`` loads plugins that enforce the `Plugin Description Policy`_, and offers the most simple methods to activate and deactivate the plugins once they are loaded. .. note:: It may also classify the plugins in various categories, but this behaviour is optional and if not specified elseway all plugins are stored in the same default category. .. note:: It is often more useful to have the plugin manager behave like singleton, this functionality is provided by ``PluginManagerSingleton`` Plugin Description Policy ========================= When creating a ``PluginManager`` instance, one should provide it with a list of directories where plugins may be found. In each directory, a plugin should contain the following elements: For a *Standard* plugin: ``myplugin.yapsy-plugin`` A *plugin info file* identical to the one previously described. ``myplugin`` A directory ontaining an actual Python plugin (ie with a ``__init__.py`` file that makes it importable). The upper namespace of the plugin should present a class inheriting the ``IPlugin`` interface (the same remarks apply here as in the previous case). For a *Single file* plugin: ``myplugin.yapsy-plugin`` A *plugin info file* which is identified thanks to its extension, see the `Plugin Info File Format`_ to see what should be in this file. The extension is customisable at the ``PluginManager``'s instanciation, since one may usually prefer the extension to bear the application name. ``myplugin.py`` The source of the plugin. This file should at least define a class inheriting the ``IPlugin`` interface. This class will be instanciated at plugin loading and it will be notified the activation/deactivation events. Plugin Info File Format ----------------------- The plugin info file is a text file *encoded in ASCII or UTF-8* and gathering, as its name suggests, some basic information about the plugin. - it gives crucial information needed to be able to load the plugin - it provides some documentation like information like the plugin author's name and a short description fo the plugin functionality. Here is an example of what such a file should contain:: [Core] Name = My plugin Name Module = the_name_of_the_pluginto_load_with_no_py_ending [Documentation] Description = What my plugin broadly does Author = My very own name Version = the_version_number_of_the_plugin Website = My very own website .. note:: From such plugin descriptions, the ``PluginManager`` will built its own representations of the plugins as instances of the :doc:`PluginInfo` class. Changing the default behaviour ============================== The default behaviour for locating and loading plugins can be changed using the various options exposed on the interface via getters. The plugin detection, in particular, can be fully customized by settting a custom plugin locator. See ``IPluginLocator`` for more details on this. Extensibility ============= Several mechanisms have been put up to help extending the basic functionalities of the proivided classes. A few *hints* to help you extend those classes: If the new functionalities do not overlap the ones already implemented, then they should be implemented as a Decorator class of the base plugin. This should be done by inheriting the ``PluginManagerDecorator``. If this previous way is not possible, then the functionalities should be added as a subclass of ``PluginManager``. .. note:: The first method is highly prefered since it makes it possible to have a more flexible design where one can pick several functionalities and litterally *add* them to get an object corresponding to one's precise needs. API === """ import sys import os import imp from yapsy import log from yapsy import NormalizePluginNameForModuleName from yapsy.IPlugin import IPlugin from yapsy.IPluginLocator import IPluginLocator # The follozing two imports are used to implement the default behaviour from yapsy.PluginFileLocator import PluginFileAnalyzerWithInfoFile from yapsy.PluginFileLocator import PluginFileLocator # imported for backward compatibility (this variable was defined here # before 1.10) from yapsy import PLUGIN_NAME_FORBIDEN_STRING # imported for backward compatibility (this PluginInfo was imported # here before 1.10) from yapsy.PluginInfo import PluginInfo class PluginManager(object): """ Manage several plugins by ordering them in categories. The mechanism for searching and loading the plugins is already implemented in this class so that it can be used directly (hence it can be considered as a bit more than a mere interface) The file describing a plugin must be written in the syntax compatible with Python's ConfigParser module as in the `Plugin Info File Format`_ About the __init__: Initialize the mapping of the categories and set the list of directories where plugins may be. This can also be set by direct call the methods: - ``setCategoriesFilter`` for ``categories_filter`` - ``setPluginPlaces`` for ``directories_list`` - ``setPluginInfoExtension`` for ``plugin_info_ext`` You may look at these function's documentation for the meaning of each corresponding arguments. """ def __init__(self, categories_filter=None, directories_list=None, plugin_info_ext=None, plugin_locator=None): # as a good practice we don't use mutable objects as default # values (these objects would become like static variables) # for function/method arguments, but rather use None. if categories_filter is None: categories_filter = {"Default":IPlugin} self.setCategoriesFilter(categories_filter) plugin_locator = self._locatorDecide(plugin_info_ext, plugin_locator) # plugin_locator could be either a dict defining strategies, or directly # an IPluginLocator object self.setPluginLocator(plugin_locator, directories_list) def _locatorDecide(self, plugin_info_ext, plugin_locator): """ For backward compatibility, we kept the *plugin_info_ext* argument. Thus we may use it if provided. Returns the (possibly modified) *plugin_locator*. """ specific_info_ext = plugin_info_ext is not None specific_locator = plugin_locator is not None if not specific_info_ext and not specific_locator: # use the default behavior res = PluginFileLocator() elif not specific_info_ext and specific_locator: # plugin_info_ext not used res = plugin_locator elif not specific_locator and specific_info_ext: # plugin_locator not used, and plugin_info_ext provided # -> compatibility mode res = PluginFileLocator() res.setAnalyzers([PluginFileAnalyzerWithInfoFile("info_ext",plugin_info_ext)]) elif specific_info_ext and specific_locator: # both provided... issue a warning that tells "plugin_info_ext" # will be ignored msg = ("Two incompatible arguments (%s) provided:", "'plugin_info_ext' and 'plugin_locator'). Ignoring", "'plugin_info_ext'.") raise ValueError(" ".join(msg) % self.__class__.__name__) return res def setCategoriesFilter(self, categories_filter): """ Set the categories of plugins to be looked for as well as the way to recognise them. The ``categories_filter`` first defines the various categories in which the plugins will be stored via its keys and it also defines the interface tha has to be inherited by the actual plugin class belonging to each category. """ self.categories_interfaces = categories_filter.copy() # prepare the mapping from categories to plugin lists self.category_mapping = {} # also maps the plugin info files (useful to avoid loading # twice the same plugin...) self._category_file_mapping = {} for categ in categories_filter: self.category_mapping[categ] = [] self._category_file_mapping[categ] = [] def setPluginPlaces(self, directories_list): """ DEPRECATED(>1.9): directly configure the IPluginLocator instance instead ! Convenience method (actually call the IPluginLocator method) """ self.getPluginLocator().setPluginPlaces(directories_list) def updatePluginPlaces(self, directories_list): """ DEPRECATED(>1.9): directly configure the IPluginLocator instance instead ! Convenience method (actually call the IPluginLocator method) """ self.getPluginLocator().updatePluginPlaces(directories_list) 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 setPluginInfoClass(self, picls, strategies=None): """ DEPRECATED(>1.9): directly configure the IPluginLocator instance instead ! Convenience method (actually call self.getPluginLocator().setPluginInfoClass) When using a ``PluginFileLocator`` you may restrict the strategies to which the change of PluginInfo class will occur by just giving the list of strategy names in the argument "strategies" """ if strategies: for name in strategies: self.getPluginLocator().setPluginInfoClass(picls, name) else: self.getPluginLocator().setPluginInfoClass(picls) def getPluginInfoClass(self): """ DEPRECATED(>1.9): directly control that with the IPluginLocator instance instead ! Get the class that holds PluginInfo. """ return self.getPluginLocator().getPluginInfoClass() def setPluginLocator(self, plugin_locator, dir_list=None, picls=None): """ Sets the strategy used to locate the basic information. .. note: If a dir_list is provided it overrides the directory list that may have been previously set in the locator. See ``IPluginLocator`` for the policy that plugin_locator must enforce. """ if isinstance(plugin_locator, IPluginLocator): self._plugin_locator = plugin_locator if dir_list is not None: self._plugin_locator.setPluginPlaces(dir_list) if picls is not None: self.setPluginInfoClass(picls) else: raise TypeError("Unexpected format for plugin_locator ('%s' is not an instance of IPluginLocator)" % plugin_locator) def getPluginLocator(self): """ Grant direct access to the plugin locator. """ return self._plugin_locator def _gatherCorePluginInfo(self, directory, plugin_info_filename): """ DEPRECATED(>1.9): please use a specific plugin locator if you need such information. Gather the core information (name, and module to be loaded) about a plugin described by it's info file (found at 'directory/filename'). Return an instance of ``PluginInfo`` and the config_parser used to gather the core data *in a tuple*, if the required info could be localised, else return ``(None,None)``. .. note:: This is supposed to be used internally by subclasses and decorators. """ return self.getPluginLocator().gatherCorePluginInfo(directory,plugin_info_filename) def _getPluginNameAndModuleFromStream(self,infoFileObject,candidate_infofile=""): """ DEPRECATED(>1.9): please use a specific plugin locator if you need such information. Extract the name and module of a plugin from the content of the info file that describes it and which is stored in infoFileObject. .. note:: Prefer using ``_gatherCorePluginInfo`` instead, whenever possible... .. warning:: ``infoFileObject`` must be a file-like object: either an opened file for instance or a string buffer wrapped in a StringIO instance as another example. .. note:: ``candidate_infofile`` must be provided whenever possible to get better error messages. Return a 3-uple with the name of the plugin, its module and the config_parser used to gather the core data *in a tuple*, if the required info could be localised, else return ``(None,None,None)``. .. note:: This is supposed to be used internally by subclasses and decorators. """ return self.getPluginLocator().getPluginNameAndModuleFromStream(infoFileObject, candidate_infofile) def getCategories(self): """ Return the list of all categories. """ return list(self.category_mapping.keys()) def removePluginFromCategory(self, plugin,category_name): """ Remove a plugin from the category where it's assumed to belong. """ self.category_mapping[category_name].remove(plugin) def appendPluginToCategory(self, plugin, category_name): """ Append a new plugin to the given category. """ self.category_mapping[category_name].append(plugin) def getPluginsOfCategory(self, category_name): """ Return the list of all plugins belonging to a category. """ return self.category_mapping[category_name][:] def getAllPlugins(self): """ Return the list of all plugins (belonging to all categories). """ allPlugins = set() for pluginsOfOneCategory in self.category_mapping.values(): allPlugins.update(pluginsOfOneCategory) return list(allPlugins) def getPluginsOf(self, **kwargs): """ Returns a set of plugins whose properties match the named arguments provided here along with their correspoding values. """ selectedPLugins = set() for plugin in self.getAllPlugins(): for attrName in kwargs: if not hasattr(plugin, attrName): break attrValue = kwargs[attrName] pluginValue = getattr(plugin, attrName) if pluginValue == attrValue: continue if type(pluginValue) == type(attrValue): break try: if attrValue in pluginValue: continue except: break else: selectedPLugins.add(plugin) return selectedPLugins def getPluginCandidates(self): """ Return the list of possible plugins. Each possible plugin (ie a candidate) is described by a 3-uple: (info file path, python file path, plugin info instance) .. warning: locatePlugins must be called before ! """ if not hasattr(self, '_candidates'): raise RuntimeError("locatePlugins must be called before getPluginCandidates") return self._candidates[:] def removePluginCandidate(self,candidateTuple): """ Remove a given candidate from the list of plugins that should be loaded. The candidate must be represented by the same tuple described in ``getPluginCandidates``. .. warning: locatePlugins must be called before ! """ if not hasattr(self, '_candidates'): raise ValueError("locatePlugins must be called before removePluginCandidate") self._candidates.remove(candidateTuple) def appendPluginCandidate(self, candidateTuple): """ Append a new candidate to the list of plugins that should be loaded. The candidate must be represented by the same tuple described in ``getPluginCandidates``. .. warning: locatePlugins must be called before ! """ if not hasattr(self, '_candidates'): raise ValueError("locatePlugins must be called before removePluginCandidate") self._candidates.append(candidateTuple) def locatePlugins(self): """ Convenience method (actually call the IPluginLocator method) """ self._candidates, npc = self.getPluginLocator().locatePlugins() 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 @staticmethod def _importModule(plugin_module_name, candidate_filepath): """ Import a module, trying either to find it as a single file or as a directory. :note: Isolated and provided to be reused, but not to be reimplemented ! """ # use imp to correctly load the plugin as a module if os.path.isdir(candidate_filepath): candidate_module = imp.load_module(plugin_module_name,None,candidate_filepath,("py","r",imp.PKG_DIRECTORY)) else: with open(candidate_filepath+".py","r") as plugin_file: candidate_module = imp.load_module(plugin_module_name,plugin_file,candidate_filepath+".py",("py","r",imp.PY_SOURCE)) return candidate_module def instanciateElementWithImportInfo(self, element, element_name, plugin_module_name, candidate_filepath): """Override this method to customize how plugins are instanciated. :note: This methods recieves the 'element' that is a candidate as the plugin's main file, but also enough information to reload its containing module and this element. """ return self.instanciateElement(element) def instanciateElement(self, element): """ DEPRECATED(>1.11): reimplement instead `instanciateElementWithImportInfo` ! Override this method to customize how plugins are instanciated. :warning: This method is called only if `instanciateElementWithImportInfo` has not been reimplemented ! """ return element() def collectPlugins(self): """ Walk through the plugins' places and look for plugins. Then for each plugin candidate look for its category, load it and stores it in the appropriate slot of the category_mapping. """ # print "%s.collectPlugins" % self.__class__ self.locatePlugins() self.loadPlugins() def getPluginByName(self,name,category="Default"): """ Get the plugin correspoding to a given category and name """ if category in self.category_mapping: for item in self.category_mapping[category]: if item.name == name: return item return None def activatePluginByName(self,name,category="Default"): """ Activate a plugin corresponding to a given category + name. """ pta_item = self.getPluginByName(name,category) if pta_item is not None: plugin_to_activate = pta_item.plugin_object if plugin_to_activate is not None: log.debug("Activating plugin: %s.%s"% (category,name)) plugin_to_activate.activate() return plugin_to_activate return None def deactivatePluginByName(self,name,category="Default"): """ Desactivate a plugin corresponding to a given category + name. """ if category in self.category_mapping: plugin_to_deactivate = None for item in self.category_mapping[category]: if item.name == name: plugin_to_deactivate = item.plugin_object break if plugin_to_deactivate is not None: log.debug("Deactivating plugin: %s.%s"% (category,name)) plugin_to_deactivate.deactivate() return plugin_to_deactivate return None class PluginManagerSingleton(object): """ Singleton version of the most basic plugin manager. Being a singleton, this class should not be initialised explicitly and the ``get`` classmethod must be called instead. To call one of this class's methods you have to use the ``get`` method in the following way: ``PluginManagerSingleton.get().themethodname(theargs)`` To set up the various coonfigurables variables of the PluginManager's behaviour please call explicitly the following methods: - ``setCategoriesFilter`` for ``categories_filter`` - ``setPluginPlaces`` for ``directories_list`` - ``setPluginInfoExtension`` for ``plugin_info_ext`` """ __instance = None __decoration_chain = None def __init__(self): if self.__instance is not None: raise Exception("Singleton can't be created twice !") def setBehaviour(self,list_of_pmd): """ Set the functionalities handled by the plugin manager by giving a list of ``PluginManager`` decorators. This function shouldn't be called several time in a same process, but if it is only the first call will have an effect. It also has an effect only if called before the initialisation of the singleton. In cases where the function is indeed going to change anything the ``True`` value is return, in all other cases, the ``False`` value is returned. """ if self.__decoration_chain is None and self.__instance is None: log.debug("Setting up a specific behaviour for the PluginManagerSingleton") self.__decoration_chain = list_of_pmd return True else: log.debug("Useless call to setBehaviour: the singleton is already instanciated of already has a behaviour.") return False setBehaviour = classmethod(setBehaviour) def get(self): """ Actually create an instance """ if self.__instance is None: if self.__decoration_chain is not None: # Get the object to be decorated # print self.__decoration_chain pm = self.__decoration_chain[0]() for cls_item in self.__decoration_chain[1:]: # print cls_item pm = cls_item(decorated_manager=pm) # Decorate the whole object self.__instance = pm else: # initialise the 'inner' PluginManagerDecorator self.__instance = PluginManager() log.debug("PluginManagerSingleton initialised") return self.__instance get = classmethod(get) # For backward compatility import the most basic decorator (it changed # place as of v1.8) from yapsy.PluginManagerDecorator import PluginManagerDecorator Yapsy-1.12.0/yapsy/PluginManagerDecorator.py0000664000175000017500000000701612575022002022426 0ustar thibauldthibauld00000000000000# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- """ Role ==== Provide an easy way to build a chain of decorators extending the functionalities of the default plugin manager, when it comes to activating, deactivating or looking into loaded plugins. The ``PluginManagerDecorator`` is the base class to be inherited by each element of the chain of decorator. .. warning:: If you want to customise the way the plugins are detected and loaded, you should not try to do it by implementing a new ``PluginManagerDecorator``. Instead, you'll have to reimplement the :doc:`PluginManager` itself. And if you do so by enforcing the ``PluginManager`` interface, just giving an instance of your new manager class to the ``PluginManagerDecorator`` should be transparent to the "stantard" decorators. API === """ import os from yapsy.IPlugin import IPlugin from yapsy import log class PluginManagerDecorator(object): """ Add several responsibilities to a plugin manager object in a more flexible way than by mere subclassing. This is indeed an implementation of the Decorator Design Patterns. There is also an additional mechanism that allows for the automatic creation of the object to be decorated when this object is an instance of PluginManager (and not an instance of its subclasses). This way we can keep the plugin managers creation simple when the user don't want to mix a lot of 'enhancements' on the base class. About the __init__: Mimics the PluginManager's __init__ method and wraps an instance of this class into this decorator class. - *If the decorated_object is not specified*, then we use the PluginManager class to create the 'base' manager, and to do so we will use the arguments: ``categories_filter``, ``directories_list``, and ``plugin_info_ext`` or their default value if they are not given. - *If the decorated object is given*, these last arguments are simply **ignored** ! All classes (and especially subclasses of this one) that want to be a decorator must accept the decorated manager as an object passed to the init function under the exact keyword ``decorated_object``. """ def __init__(self, decorated_object=None, # The following args will only be used if we need to # create a default PluginManager categories_filter=None, directories_list=None, plugin_info_ext="yapsy-plugin"): if directories_list is None: directories_list = [os.path.dirname(__file__)] if categories_filter is None: categories_filter = {"Default": IPlugin} if decorated_object is None: log.debug("Creating a default PluginManager instance to be decorated.") from yapsy.PluginManager import PluginManager decorated_object = PluginManager(categories_filter, directories_list, plugin_info_ext) self._component = decorated_object def __getattr__(self,name): """ Decorator trick copied from: http://www.pasteur.fr/formation/infobio/python/ch18s06.html """ # print "looking for %s in %s" % (name, self.__class__) return getattr(self._component,name) def collectPlugins(self): """ This function will usually be a shortcut to successively call ``self.locatePlugins`` and then ``self.loadPlugins`` which are very likely to be redefined in each new decorator. So in order for this to keep on being a "shortcut" and not a real pain, I'm redefining it here. """ self.locatePlugins() self.loadPlugins() Yapsy-1.12.0/yapsy/IPlugin.py0000664000175000017500000000224212575022002017375 0ustar thibauldthibauld00000000000000# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- """ Role ==== Defines the basic interfaces for a plugin. These interfaces are inherited by the *core* class of a plugin. The *core* class of a plugin is then the one that will be notified the activation/deactivation of a plugin via the ``activate/deactivate`` methods. For simple (near trivial) plugin systems, one can directly use the following interfaces. Extensibility ============= In your own software, you'll probably want to build derived classes of the ``IPlugin`` class as it is a mere interface with no specific functionality. Your software's plugins should then inherit your very own plugin class (itself derived from ``IPlugin``). Where and how to code these plugins is explained in the section about the :doc:`PluginManager`. API === """ class IPlugin(object): """ The most simple interface to be inherited when creating a plugin. """ def __init__(self): self.is_activated = False def activate(self): """ Called at plugin activation. """ self.is_activated = True def deactivate(self): """ Called when the plugin is disabled. """ self.is_activated = False Yapsy-1.12.0/yapsy/IMultiprocessChildPlugin.py0000664000175000017500000000221513343003466022762 0ustar thibauldthibauld00000000000000# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- """ Role ==== Originally defined the basic interfaces for multiprocessed plugins. Deprecation Note ================ This class is deprecated and replaced by :doc:`IMultiprocessChildPlugin`. Child classes of `IMultiprocessChildPlugin` used to be an `IPlugin` as well as a `multiprocessing.Process`, possibly playing with the functionalities of both, which make maintenance harder than necessary. And indeed following a bug fix to make multiprocess plugins work on Windows, instances of IMultiprocessChildPlugin inherit Process but are not exactly the running process (there is a new wrapper process). API === """ from multiprocessing import Process from yapsy.IMultiprocessPlugin import IMultiprocessPlugin class IMultiprocessChildPlugin(IMultiprocessPlugin, Process): """ Base class for multiprocessed plugin. DEPRECATED(>1.11): Please use IMultiProcessPluginBase instead ! """ def __init__(self, parent_pipe): IMultiprocessPlugin.__init__(self, parent_pipe) Process.__init__(self) def run(self): """ Override this method in your implementation """ return Yapsy-1.12.0/yapsy/PluginFileLocator.py0000664000175000017500000004617113343003466021430 0ustar thibauldthibauld00000000000000# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- """ Role ==== The ``PluginFileLocator`` locates plugins when they are accessible via the filesystem. It's default behaviour is to look for text files with the '.yapsy-plugin' extensions and to read the plugin's decription in them. Customization ------------- The behaviour of a ``PluginFileLocator`` can be customized by instanciating it with a specific 'analyzer'. Two analyzers are already implemented and provided here: ``PluginFileAnalyzerWithInfoFile`` the default 'analyzer' that looks for plugin 'info files' as text file with a predefined extension. This implements the way yapsy looks for plugin since version 1. ``PluginFileAnalyzerMathingRegex`` look for files matching a regex and considers them as being the plugin itself. All analyzers must enforce the It enforces the ``plugin locator`` policy as defined by ``IPluginLocator`` and used by ``PluginManager``. ``info_ext`` expects a plugin to be discovered through its *plugin info file*. User just needs to provide an extension (without '.') to look for *plugin_info_file*. ``regexp`` looks for file matching the given regular pattern expression. User just needs to provide the regular pattern expression. All analyzers must enforce the policy represented by the ``IPluginFileAnalyzer`` interface. API === """ import os import re from yapsy import log from yapsy.compat import ConfigParser, is_py2, basestring from yapsy.PluginInfo import PluginInfo from yapsy import PLUGIN_NAME_FORBIDEN_STRING from yapsy.IPluginLocator import IPluginLocator class IPluginFileAnalyzer(object): """ Define the methods expected by PluginFileLocator for its 'analyzer'. """ def __init__(self,name): self.name = name def isValidPlugin(self, filename): """ Check if the resource found at filename is a valid plugin. """ raise NotImplementedError("'isValidPlugin' must be reimplemented by %s" % self) def getInfosDictFromPlugin(self, dirpath, filename): """ Returns the extracted plugin informations as a dictionary. This function ensures that "name" and "path" are provided. *dirpath* is the full path to the directory where the plugin file is *filename* is the name (ie the basename) of the plugin file. If *callback* function has not been provided for this strategy, we use the filename alone to extract minimal informations. """ raise NotImplementedError("'getInfosDictFromPlugin' must be reimplemented by %s" % self) class PluginFileAnalyzerWithInfoFile(IPluginFileAnalyzer): """ Consider plugins described by a textual description file. A plugin is expected to be described by a text file ('ini' format) with a specific extension (.yapsy-plugin by default). This file must contain at least the following information:: [Core] Name = name of the module Module = relative_path/to/python_file_or_directory Optionnally the description file may also contain the following section (in addition to the above one):: [Documentation] Author = Author Name Version = Major.minor Website = url_for_plugin Description = A simple one-sentence description Ctor Arguments: *name* name of the analyzer. *extensions* the expected extensions for the plugin info file. May be a string or a tuple of strings if several extensions are expected. """ def __init__(self, name, extensions="yapsy-plugin"): IPluginFileAnalyzer.__init__(self,name) self.setPluginInfoExtension(extensions) def setPluginInfoExtension(self,extensions): """ Set the extension that will identify a plugin info file. *extensions* May be a string or a tuple of strings if several extensions are expected. """ # Make sure extension is a tuple if not isinstance(extensions, tuple): extensions = (extensions, ) self.expectedExtensions = extensions def isValidPlugin(self, filename): """ Check if it is a valid plugin based on the given plugin info file extension(s). If several extensions are provided, the first matching will cause the function to exit successfully. """ res = False for ext in self.expectedExtensions: if filename.endswith(".%s" % ext): res = True break return res def getPluginNameAndModuleFromStream(self, infoFileObject, candidate_infofile=None): """ Extract the name and module of a plugin from the content of the info file that describes it and which is stored in ``infoFileObject``. .. note:: Prefer using ``_extractCorePluginInfo`` instead, whenever possible... .. warning:: ``infoFileObject`` must be a file-like object: either an opened file for instance or a string buffer wrapped in a StringIO instance as another example. .. note:: ``candidate_infofile`` must be provided whenever possible to get better error messages. Return a 3-uple with the name of the plugin, its module and the config_parser used to gather the core data *in a tuple*, if the required info could be localised, else return ``(None,None,None)``. .. note:: This is supposed to be used internally by subclasses and decorators. """ # parse the information buffer to get info about the plugin config_parser = ConfigParser() try: if is_py2: config_parser.readfp(infoFileObject) else: config_parser.read_file(infoFileObject) except Exception as e: log.debug("Could not parse the plugin file '%s' (exception raised was '%s')" % (candidate_infofile,e)) return (None, None, None) # check if the basic info is available if not config_parser.has_section("Core"): log.debug("Plugin info file has no 'Core' section (in '%s')" % candidate_infofile) return (None, None, None) if not config_parser.has_option("Core","Name") or not config_parser.has_option("Core","Module"): log.debug("Plugin info file has no 'Name' or 'Module' section (in '%s')" % candidate_infofile) return (None, None, None) # check that the given name is valid name = config_parser.get("Core", "Name") name = name.strip() if PLUGIN_NAME_FORBIDEN_STRING in name: log.debug("Plugin name contains forbiden character: %s (in '%s')" % (PLUGIN_NAME_FORBIDEN_STRING, candidate_infofile)) return (None, None, None) return (name, config_parser.get("Core", "Module"), config_parser) def _extractCorePluginInfo(self,directory, filename): """ Gather the core information (name, and module to be loaded) about a plugin described by it's info file (found at 'directory/filename'). Return a dictionary with name and path of the plugin as well as the ConfigParser instance used to collect these info. .. note:: This is supposed to be used internally by subclasses and decorators. """ # now we can consider the file as a serious candidate if not isinstance(filename, basestring): # filename is a file object: use it name, moduleName, config_parser = self.getPluginNameAndModuleFromStream(filename) else: candidate_infofile_path = os.path.join(directory, filename) # parse the information file to get info about the plugin with open(candidate_infofile_path) as candidate_infofile: name, moduleName, config_parser = self.getPluginNameAndModuleFromStream(candidate_infofile,candidate_infofile_path) if (name, moduleName, config_parser) == (None, None, None): return (None,None) infos = {"name":name, "path":os.path.join(directory, moduleName)} return infos, config_parser def _extractBasicPluginInfo(self,directory, filename): """ Gather some basic documentation about the plugin described by it's info file (found at 'directory/filename'). Return a dictionary containing the core information (name and path) as well as as the 'documentation' info (version, author, description etc). See also: ``self._extractCorePluginInfo`` """ infos, config_parser = self._extractCorePluginInfo(directory, filename) # collect additional (but usually quite usefull) information if infos and config_parser and config_parser.has_section("Documentation"): if config_parser.has_option("Documentation","Author"): infos["author"] = config_parser.get("Documentation", "Author") if config_parser.has_option("Documentation","Version"): infos["version"] = config_parser.get("Documentation", "Version") if config_parser.has_option("Documentation","Website"): infos["website"] = config_parser.get("Documentation", "Website") if config_parser.has_option("Documentation","Copyright"): infos["copyright"] = config_parser.get("Documentation", "Copyright") if config_parser.has_option("Documentation","Description"): infos["description"] = config_parser.get("Documentation", "Description") return infos, config_parser def getInfosDictFromPlugin(self, dirpath, filename): """ Returns the extracted plugin informations as a dictionary. This function ensures that "name" and "path" are provided. If *callback* function has not been provided for this strategy, we use the filename alone to extract minimal informations. """ infos, config_parser = self._extractBasicPluginInfo(dirpath, filename) if not infos or infos.get("name", None) is None: raise ValueError("Missing *name* of the plugin in extracted infos.") if not infos or infos.get("path", None) is None: raise ValueError("Missing *path* of the plugin in extracted infos.") return infos, config_parser class PluginFileAnalyzerMathingRegex(IPluginFileAnalyzer): """ An analyzer that targets plugins decribed by files whose name match a given regex. """ def __init__(self, name, regexp): IPluginFileAnalyzer.__init__(self,name) self.regexp = regexp def isValidPlugin(self, filename): """ Checks if the given filename is a valid plugin for this Strategy """ reg = re.compile(self.regexp) if reg.match(filename) is not None: return True return False def getInfosDictFromPlugin(self, dirpath, filename): """ Returns the extracted plugin informations as a dictionary. This function ensures that "name" and "path" are provided. """ # use the filename alone to extract minimal informations. infos = {} module_name = os.path.splitext(filename)[0] plugin_filename = os.path.join(dirpath,filename) if module_name == "__init__": module_name = os.path.basename(dirpath) plugin_filename = dirpath infos["name"] = "%s" % module_name infos["path"] = plugin_filename cf_parser = ConfigParser() cf_parser.add_section("Core") cf_parser.set("Core","Name",infos["name"]) cf_parser.set("Core","Module",infos["path"]) return infos,cf_parser class PluginFileLocator(IPluginLocator): """ Locates plugins on the file system using a set of analyzers to determine what files actually corresponds to plugins. If more than one analyzer is being used, the first that will discover a new plugin will avoid other strategies to find it too. By default each directory set as a "plugin place" is scanned recursively. You can change that by a call to ``disableRecursiveScan``. """ def __init__(self, analyzers=None, plugin_info_cls=PluginInfo): IPluginLocator.__init__(self) self._discovered_plugins = {} self.setPluginPlaces(None) self._analyzers = analyzers # analyzers used to locate plugins if self._analyzers is None: self._analyzers = [PluginFileAnalyzerWithInfoFile("info_ext")] self._default_plugin_info_cls = plugin_info_cls self._plugin_info_cls_map = {} self._max_size = 1e3*1024 # in octets (by default 1 Mo) self.recursive = True def disableRecursiveScan(self): """ Disable recursive scan of the directories given as plugin places. """ self.recursive = False def setAnalyzers(self, analyzers): """ Sets a new set of analyzers. .. warning:: the new analyzers won't be aware of the plugin info class that may have been set via a previous call to ``setPluginInfoClass``. """ self._analyzers = analyzers def removeAnalyzers(self, name): """ Removes analyzers of a given name. """ analyzersListCopy = self._analyzers[:] foundAndRemoved = False for obj in analyzersListCopy: if obj.name == name: self._analyzers.remove(obj) foundAndRemoved = True if not foundAndRemoved: log.debug("'%s' is not a known strategy name: can't remove it." % name) def removeAllAnalyzer(self): """ Remove all analyzers. """ self._analyzers = [] def appendAnalyzer(self, analyzer): """ Append an analyzer to the existing list. """ self._analyzers.append(analyzer) def _getInfoForPluginFromAnalyzer(self,analyzer,dirpath, filename): """ Return an instance of plugin_info_cls filled with data extracted by the analyzer. May return None if the analyzer fails to extract any info. """ plugin_info_dict,config_parser = analyzer.getInfosDictFromPlugin(dirpath, filename) if plugin_info_dict is None: return None plugin_info_cls = self._plugin_info_cls_map.get(analyzer.name,self._default_plugin_info_cls) plugin_info = plugin_info_cls(plugin_info_dict["name"],plugin_info_dict["path"]) plugin_info.details = config_parser return plugin_info 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 gatherCorePluginInfo(self, directory, filename): """ Return a ``PluginInfo`` as well as the ``ConfigParser`` used to build it. If filename is a valid plugin discovered by any of the known strategy in use. Returns None,None otherwise. """ for analyzer in self._analyzers: # eliminate the obvious non plugin files if not analyzer.isValidPlugin(filename): continue plugin_info = self._getInfoForPluginFromAnalyzer(analyzer,directory, filename) return plugin_info,plugin_info.details return None,None # ----------------------------------------------- # Backward compatible methods # Note: their implementation must be conform to their # counterpart in yapsy<1.10 # ----------------------------------------------- def getPluginNameAndModuleFromStream(self, infoFileObject, candidate_infofile=None): for analyzer in self._analyzers: if analyzer.name == "info_ext": return analyzer.getPluginNameAndModuleFromStream(infoFileObject) else: raise RuntimeError("No current file analyzer is able to provide plugin information from stream") def setPluginInfoClass(self, picls, name=None): """ Set the class that holds PluginInfo. The class should inherit from ``PluginInfo``. If name is given, then the class will be used only by the corresponding analyzer. If name is None, the class will be set for all analyzers. """ if name is None: self._default_plugin_info_cls = picls self._plugin_info_cls_map = {} else: self._plugin_info_cls_map[name] = picls def setPluginPlaces(self, directories_list): """ Set the list of directories where to look for plugin places. """ if isinstance(directories_list, basestring): raise ValueError("'directories_list' given as a string, but expected to be a list or enumeration of strings") if directories_list is None: directories_list = [os.path.dirname(__file__)] self.plugins_places = directories_list def updatePluginPlaces(self, directories_list): """ Updates the list of directories where to look for plugin places. """ self.plugins_places = list(set.union(set(directories_list), set(self.plugins_places))) def setPluginInfoExtension(self, ext): """ DEPRECATED(>1.9): for backward compatibility. Directly configure the IPluginLocator instance instead ! This will only work if the strategy "info_ext" is active for locating plugins. """ for analyzer in self._analyzers: if analyzer.name == "info_ext": analyzer.setPluginInfoExtension(ext) Yapsy-1.12.0/yapsy/MultiprocessPluginManager.py0000664000175000017500000000631313343003466023203 0ustar thibauldthibauld00000000000000# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- """ Role ==== Defines a plugin manager that runs all plugins in separate process linked by pipes. API === """ import multiprocessing as mproc from yapsy.IMultiprocessPlugin import IMultiprocessPlugin from yapsy.IMultiprocessChildPlugin import IMultiprocessChildPlugin from yapsy.MultiprocessPluginProxy import MultiprocessPluginProxy from yapsy.PluginManager import PluginManager class MultiprocessPluginManager(PluginManager): """ Subclass of the PluginManager that runs each plugin in a different process """ def __init__(self, categories_filter=None, directories_list=None, plugin_info_ext=None, plugin_locator=None): if categories_filter is None: categories_filter = {"Default": IMultiprocessPlugin} PluginManager.__init__(self, categories_filter=categories_filter, directories_list=directories_list, plugin_info_ext=plugin_info_ext, plugin_locator=plugin_locator) self.connections = [] def instanciateElementWithImportInfo(self, element, element_name, plugin_module_name, candidate_filepath): """This method instanciates each plugin in a new process and links it to the parent with a pipe. In the parent process context, the plugin's class is replaced by the ``MultiprocessPluginProxy`` class that hold the information about the child process and the pipe to communicate with it. :warning: The plugin code should only use the pipe to communicate with the rest of the applica`tion and should not assume any kind of shared memory, not any specific functionality of the `multiprocessing.Process` parent class (its behaviour is different between platforms !) See :doc:`IMultiprocessPlugin` """ if element is IMultiprocessChildPlugin: # The following will keep retro compatibility for IMultiprocessChildPlugin raise Exception("Preventing instanciation of a bar child plugin interface.") instanciated_element = MultiprocessPluginProxy() parent_pipe, child_pipe = mproc.Pipe() instanciated_element.child_pipe = parent_pipe instanciated_element.proc = MultiprocessPluginManager._PluginProcessWrapper( element_name, plugin_module_name, candidate_filepath, child_pipe) instanciated_element.proc.start() return instanciated_element class _PluginProcessWrapper(mproc.Process): """Helper class that strictly needed to be able to spawn the plugin on Windows but kept also for Unix platform to get a more uniform behaviour. This will handle re-importing the plugin's module in the child process (again this is necessary on windows because what has been imported in the main thread/process will not be shared with the spawned process.) """ def __init__(self, element_name, plugin_module_name, candidate_filepath, child_pipe): self.element_name = element_name self.child_pipe = child_pipe self.plugin_module_name = plugin_module_name self.candidate_filepath = candidate_filepath mproc.Process.__init__(self) def run(self): module = PluginManager._importModule(self.plugin_module_name, self.candidate_filepath) element = getattr(module, self.element_name) e = element(self.child_pipe) e.run() Yapsy-1.12.0/yapsy/VersionedPluginManager.py0000664000175000017500000000763613043424237022461 0ustar thibauldthibauld00000000000000# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- """ Role ==== Defines the basic interface for a plugin manager that also keeps track of versions of plugins API === """ from distutils.version import StrictVersion from yapsy.PluginInfo import PluginInfo from yapsy.IPlugin import IPlugin from yapsy.PluginManagerDecorator import PluginManagerDecorator class VersionedPluginInfo(PluginInfo): """ Gather some info about a plugin such as its name, author, description... """ def __init__(self, plugin_name, plugin_path): PluginInfo.__init__(self, plugin_name, plugin_path) # version number is now required to be a StrictVersion object self.version = StrictVersion("0.0") def setVersion(self, vstring): self.version = StrictVersion(vstring) class VersionedPluginManager(PluginManagerDecorator): """ Handle plugin versioning by making sure that when several versions are present for a same plugin, only the latest version is manipulated via the standard methods (eg for activation and deactivation) More precisely, for operations that must be applied on a single named plugin at a time (``getPluginByName``, ``activatePluginByName``, ``deactivatePluginByName`` etc) the targetted plugin will always be the one with the latest version. .. note:: The older versions of a given plugin are still reachable via the ``getPluginsOfCategoryFromAttic`` method. """ def __init__(self, decorated_manager=None, categories_filter={"Default":IPlugin}, directories_list=None, plugin_info_ext="yapsy-plugin"): # Create the base decorator class PluginManagerDecorator.__init__(self,decorated_manager, categories_filter, directories_list, plugin_info_ext) self.setPluginInfoClass(VersionedPluginInfo) # prepare the storage for the early version of the plugins, # for which only the latest version is the one that will be # kept in the "core" plugin storage. self._prepareAttic() def _prepareAttic(self): """ Create and correctly initialize the storage where the wrong version of the plugins will be stored. """ self._attic = {} for categ in self.getCategories(): self._attic[categ] = [] def setCategoriesFilter(self, categories_filter): """ Set the categories of plugins to be looked for as well as the way to recognise them. Note: will also reset the attic toa void inconsistencies. """ self._component.setCategoriesFilter(categories_filter) self._prepareAttic() def getLatestPluginsOfCategory(self,category_name): """ DEPRECATED(>1.8): Please consider using getPluginsOfCategory instead. Return the list of all plugins belonging to a category. """ return self.getPluginsOfCategory(category_name) def loadPlugins(self, callback=None, callback_after=None): """ Load the candidate plugins that have been identified through a previous call to locatePlugins. In addition to the baseclass functionality, this subclass also needs to find the latest version of each plugin. """ self._prepareAttic() self._component.loadPlugins(callback, callback_after) for categ in self.getCategories(): latest_plugins = {} allPlugins = self.getPluginsOfCategory(categ) # identify the latest version of each plugin for plugin in allPlugins: name = plugin.name version = plugin.version if name in latest_plugins: if version > latest_plugins[name].version: older_plugin = latest_plugins[name] latest_plugins[name] = plugin self.removePluginFromCategory(older_plugin,categ) self._attic[categ].append(older_plugin) else: self.removePluginFromCategory(plugin,categ) self._attic[categ].append(plugin) else: latest_plugins[name] = plugin def getPluginsOfCategoryFromAttic(self,categ): """ Access the older version of plugins for which only the latest version is available through standard methods. """ return self._attic[categ] Yapsy-1.12.0/yapsy/ConfigurablePluginManager.py0000664000175000017500000002466213043424257023123 0ustar thibauldthibauld00000000000000# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- """ Role ==== Defines plugin managers that can handle configuration files similar to the ini files manipulated by Python's ConfigParser module. API === """ from yapsy.IPlugin import IPlugin from yapsy.PluginManagerDecorator import PluginManagerDecorator from yapsy.PluginManager import PLUGIN_NAME_FORBIDEN_STRING class ConfigurablePluginManager(PluginManagerDecorator): """ A plugin manager that also manages a configuration file. The configuration file will be accessed through a ``ConfigParser`` derivated object. The file can be used for other purpose by the application using this plugin manager as it will only add a new specific section ``[Plugin Management]`` for itself and also new sections for some plugins that will start with ``[Plugin:...]`` (only the plugins that explicitly requires to save configuration options will have this kind of section). .. warning:: when giving/building the list of plugins to activate by default, there must not be any space in the list (neither in the names nor in between) The ``config_change_trigger`` argument can be used to set a specific method to call when the configuration is altered. This will let the client application manage the way they want the configuration to be updated (e.g. write on file at each change or at precise time intervalls or whatever....) .. warning:: when no ``config_change_trigger`` is given and if the provided ``configparser_instance`` doesn't handle it implicitely, recording the changes persistently (ie writing on the config file) won't happen. """ CONFIG_SECTION_NAME = "Plugin Management" def __init__(self, configparser_instance=None, config_change_trigger= lambda :True, decorated_manager=None, # The following args will only be used if we need to # create a default PluginManager categories_filter=None, directories_list=None, plugin_info_ext="yapsy-plugin"): if categories_filter is None: categories_filter = {"Default":IPlugin} # Create the base decorator class PluginManagerDecorator.__init__(self,decorated_manager, categories_filter, directories_list, plugin_info_ext) self.setConfigParser(configparser_instance, config_change_trigger) def setConfigParser(self,configparser_instance,config_change_trigger): """ Set the ConfigParser instance. """ self.config_parser = configparser_instance # set the (optional) fucntion to be called when the # configuration is changed: self.config_has_changed = config_change_trigger def __getCategoryPluginsListFromConfig(self, plugin_list_str): """ Parse the string describing the list of plugins to activate, to discover their actual names and return them. """ return plugin_list_str.strip(" ").split("%s"%PLUGIN_NAME_FORBIDEN_STRING) def __getCategoryPluginsConfigFromList(self, plugin_list): """ Compose a string describing the list of plugins to activate """ return PLUGIN_NAME_FORBIDEN_STRING.join(plugin_list) def __getCategoryOptionsName(self,category_name): """ Return the appropirately formated version of the category's option. """ return "%s_plugins_to_load" % category_name.replace(" ","_") def __addPluginToConfig(self,category_name, plugin_name): """ Utility function to add a plugin to the list of plugin to be activated. """ # check that the section is here if not self.config_parser.has_section(self.CONFIG_SECTION_NAME): self.config_parser.add_section(self.CONFIG_SECTION_NAME) # check that the category's list of activated plugins is here too option_name = self.__getCategoryOptionsName(category_name) if not self.config_parser.has_option(self.CONFIG_SECTION_NAME, option_name): # if there is no list yet add a new one self.config_parser.set(self.CONFIG_SECTION_NAME,option_name,plugin_name) return self.config_has_changed() else: # get the already existing list and append the new # activated plugin to it. past_list_str = self.config_parser.get(self.CONFIG_SECTION_NAME,option_name) past_list = self.__getCategoryPluginsListFromConfig(past_list_str) # make sure we don't add it twice if plugin_name not in past_list: past_list.append(plugin_name) new_list_str = self.__getCategoryPluginsConfigFromList(past_list) self.config_parser.set(self.CONFIG_SECTION_NAME,option_name,new_list_str) return self.config_has_changed() def __removePluginFromConfig(self,category_name, plugin_name): """ Utility function to add a plugin to the list of plugin to be activated. """ # check that the section is here if not self.config_parser.has_section(self.CONFIG_SECTION_NAME): # then nothing to remove :) return # check that the category's list of activated plugins is here too option_name = self.__getCategoryOptionsName(category_name) if not self.config_parser.has_option(self.CONFIG_SECTION_NAME, option_name): # if there is no list still nothing to do return else: # get the already existing list past_list_str = self.config_parser.get(self.CONFIG_SECTION_NAME,option_name) past_list = self.__getCategoryPluginsListFromConfig(past_list_str) if plugin_name in past_list: past_list.remove(plugin_name) new_list_str = self.__getCategoryPluginsConfigFromList(past_list) self.config_parser.set(self.CONFIG_SECTION_NAME,option_name,new_list_str) self.config_has_changed() def registerOptionFromPlugin(self, category_name, plugin_name, option_name, option_value): """ To be called from a plugin object, register a given option in the name of a given plugin. """ section_name = "%s Plugin: %s" % (category_name,plugin_name) # if the plugin's section is not here yet, create it if not self.config_parser.has_section(section_name): self.config_parser.add_section(section_name) # set the required option self.config_parser.set(section_name,option_name,option_value) self.config_has_changed() def hasOptionFromPlugin(self, category_name, plugin_name, option_name): """ To be called from a plugin object, return True if the option has already been registered. """ section_name = "%s Plugin: %s" % (category_name,plugin_name) return self.config_parser.has_section(section_name) and self.config_parser.has_option(section_name,option_name) def readOptionFromPlugin(self, category_name, plugin_name, option_name): """ To be called from a plugin object, read a given option in the name of a given plugin. """ section_name = "%s Plugin: %s" % (category_name,plugin_name) return self.config_parser.get(section_name,option_name) def __decoratePluginObject(self, category_name, plugin_name, plugin_object): """ Add two methods to the plugin objects that will make it possible for it to benefit from this class's api concerning the management of the options. """ plugin_object.setConfigOption = lambda x,y: self.registerOptionFromPlugin(category_name, plugin_name, x,y) plugin_object.setConfigOption.__doc__ = self.registerOptionFromPlugin.__doc__ plugin_object.getConfigOption = lambda x: self.readOptionFromPlugin(category_name, plugin_name, x) plugin_object.getConfigOption.__doc__ = self.readOptionFromPlugin.__doc__ plugin_object.hasConfigOption = lambda x: self.hasOptionFromPlugin(category_name, plugin_name, x) plugin_object.hasConfigOption.__doc__ = self.hasOptionFromPlugin.__doc__ def activatePluginByName(self, plugin_name, category_name="Default", save_state=True): """ Activate a plugin, , and remember it (in the config file). If you want the plugin to benefit from the configuration utility defined by this manager, it is crucial to use this method to activate a plugin and not call the plugin object's ``activate`` method. In fact, this method will also "decorate" the plugin object so that it can use this class's methods to register its own options. By default, the plugin's activation is registered in the config file but if you d'ont want this set the 'save_state' argument to False. """ # first decorate the plugin pta = self._component.getPluginByName(plugin_name,category_name) if pta is None: return None self.__decoratePluginObject(category_name,plugin_name,pta.plugin_object) # activate the plugin plugin_object = self._component.activatePluginByName(plugin_name,category_name) # check the activation and then optionally set the config option if plugin_object.is_activated: if save_state: self.__addPluginToConfig(category_name,plugin_name) return plugin_object return None def deactivatePluginByName(self, plugin_name, category_name="Default", save_state=True): """ Deactivate a plugin, and remember it (in the config file). By default, the plugin's deactivation is registered in the config file but if you d'ont want this set the ``save_state`` argument to False. """ # activate the plugin plugin_object = self._component.deactivatePluginByName(plugin_name,category_name) if plugin_object is None: return None # check the deactivation and then optionnally set the config option if not plugin_object.is_activated: if save_state: self.__removePluginFromConfig(category_name,plugin_name) return plugin_object return None def loadPlugins(self,callback=None, callback_after=None): """ Walk through the plugins' places and look for plugins. Then for each plugin candidate look for its category, load it and stores it in the appropriate slot of the ``category_mapping``. """ self._component.loadPlugins(callback, callback_after) # now load the plugins according to the recorded configuration if self.config_parser.has_section(self.CONFIG_SECTION_NAME): # browse all the categories for category_name in list(self._component.category_mapping.keys()): # get the list of plugins to be activated for this # category option_name = "%s_plugins_to_load"%category_name if self.config_parser.has_option(self.CONFIG_SECTION_NAME, option_name): plugin_list_str = self.config_parser.get(self.CONFIG_SECTION_NAME, option_name) plugin_list = self.__getCategoryPluginsListFromConfig(plugin_list_str) # activate all the plugins that should be # activated for plugin_name in plugin_list: self.activatePluginByName(plugin_name,category_name) Yapsy-1.12.0/yapsy/IPluginLocator.py0000664000175000017500000000617313043423776020746 0ustar thibauldthibauld00000000000000# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- """ Role ==== ``IPluginLocator`` defines the basic interface expected by a ``PluginManager`` to be able to locate plugins and get basic info about each discovered plugin (name, version etc). API === """ from yapsy import log class IPluginLocator(object): """ Plugin Locator interface with some methods already implemented to manage the awkward backward compatible stuff. """ def locatePlugins(self): """ Walk through the plugins' places and look for plugins. Return the discovered plugins as a list of ``(candidate_infofile_path, candidate_file_path,plugin_info_instance)`` and their number. """ raise NotImplementedError("locatePlugins must be reimplemented by %s" % self) def gatherCorePluginInfo(self, directory, filename): """ Return a ``PluginInfo`` as well as the ``ConfigParser`` used to build it. If filename is a valid plugin discovered by any of the known strategy in use. Returns None,None otherwise. """ raise NotImplementedError("gatherPluginInfo must be reimplemented by %s" % self) # -------------------------------------------------------------------- # Below are backward compatibility methods: if you inherit from # IPluginLocator it's ok not to reimplement them, there will only # be a warning message logged if they are called and not # reimplemented. # -------------------------------------------------------------------- def getPluginNameAndModuleFromStream(self,fileobj): """ DEPRECATED(>1.9): kept for backward compatibility with existing PluginManager child classes. Return a 3-uple with the name of the plugin, its module and the config_parser used to gather the core data *in a tuple*, if the required info could be localised, else return ``(None,None,None)``. """ log.warning("setPluginInfoClass was called but '%s' doesn't implement it." % self) return None,None,None def setPluginInfoClass(self, picls, names=None): """ DEPRECATED(>1.9): kept for backward compatibility with existing PluginManager child classes. Set the class that holds PluginInfo. The class should inherit from ``PluginInfo``. """ log.warning("setPluginInfoClass was called but '%s' doesn't implement it." % self) def getPluginInfoClass(self): """ DEPRECATED(>1.9): kept for backward compatibility with existing PluginManager child classes. Get the class that holds PluginInfo. """ log.warning("getPluginInfoClass was called but '%s' doesn't implement it." % self) return None def setPluginPlaces(self, directories_list): """ DEPRECATED(>1.9): kept for backward compatibility with existing PluginManager child classes. Set the list of directories where to look for plugin places. """ log.warning("setPluginPlaces was called but '%s' doesn't implement it." % self) def updatePluginPlaces(self, directories_list): """ DEPRECATED(>1.9): kept for backward compatibility with existing PluginManager child classes. Updates the list of directories where to look for plugin places. """ log.warning("updatePluginPlaces was called but '%s' doesn't implement it." % self) Yapsy-1.12.0/yapsy/compat.py0000664000175000017500000000272712575022002017321 0ustar thibauldthibauld00000000000000# -*- coding: utf-8 -*- """ Adapted from pythoncompat https://github.com/kennethreitz/requests/blob/724a3889bcd26c318cd4063519e67581d3be5d7e/requests/compat.py License (ISC): Copyright (c) 2012 Kenneth Reitz. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. """ import sys # ------- # Pythons # ------- # Syntax sugar. _ver = sys.version_info #: Python 2.x? is_py2 = (_ver[0] == 2) #: Python 3.x? is_py3 = (_ver[0] == 3) if is_py2: from StringIO import StringIO from ConfigParser import ConfigParser builtin_str = str bytes = str str = unicode basestring = basestring numeric_types = (int, long, float) elif is_py3: from io import StringIO from configparser import ConfigParser builtin_str = str str = str bytes = bytes basestring = (str, bytes) numeric_types = (int, float) Yapsy-1.12.0/CHANGELOG.txt0000664000175000017500000001215712575022002016345 0ustar thibauldthibauld00000000000000version-1.11.123 [2015-05-08] - code: Make _extractCorePluginInfo accept Unicode filenames (bug https://sourceforge.net/p/yapsy/bugs/30/) - code: fix default change trigger for ConfigurablePluginManager (see https://sourceforge.net/p/yapsy/support-requests/9/) version-1.11.023 [2015-04-05] - code: merge python3 and default branch (contrib delijati) - code: fix exception catching to support flask use case (contrib delijati: https://github.com/tibonihoo/yapsy/pull/4) - code: fix error reporting (contrib frmdstryr: https://github.com/tibonihoo/yapsy/pull/5) - code: allow plugins to run in separate processes (contrib pylanglois: https://github.com/tibonihoo/yapsy/pull/6) - code: fix dangerous usage of mutable objects as default arguments - doc: added a few badges - doc: added an example of fetching yapsy's development version with pip version-1.10.423 [2014-06-07] - code: Speed optimisation for the regexp compiled in __init__.py (see https://sourceforge.net/p/yapsy/patches/4/) - code: fix bug "Plugin detection doesn't follow symlinks" (see https://sourceforge.net/p/yapsy/bugs/19/) - doc: add links to coveralls.io for code coverage version-1.10.323 [2014-03-23] - code: fix PluginInfo properties (see https://sourceforge.net/p/yapsy/bugs/13/) - code: fix ConfigurablePluginManager.loadplugin ignore callback bug reported at https://sourceforge.net/p/yapsy/bugs/17/ - code: small improvement to the parse error handling (related to https://sourceforge.net/p/yapsy/bugs/12/) version-1.10.223 [2013-12-06] - packaging: version name change to comply with PEP440 and resolve pip install problems. - code: fix compatibility with python2.5 version-1.10.2 [2013-05-22] - code: fix compatibility with python2.5 - doc: add links to travis-ci and readthedocs.org - code: fix AutoInstall test failures [contrib. Agustin Henze] - code: replace deprecated methods usage (for Python3) version-1.10.1 [2013-01-13] - code: switch from exec to imp.load_module for plugin loading which also solves https://sourceforge.net/p/yapsy/bugs/9/ - doc: add explanation about plugin class detection caveat https://sourceforge.net/p/yapsy/bugs/8/ - code: fix unicode bug on python2 version, see https://sourceforge.net/p/yapsy/bugs/10/ version-1.10 [2012-12-18] - code: [contrib. Mathieu Havel] "plugin locators" allow to change the strategy to describe and locate plugins - code: [contrib. Mathieu Clabaut] multiple categories per plugin (cf https://bitbucket.org/matclab/yapsy-mcl) - code: [contrib. Mark Fickett] improve logging - code: Gather detailed information on plugin load error via a callback - code: Extra info to plug-in (eg add extra section or embed the ConfigParser output to the plugin_info), see also https://github.com/tintinweb/yapsy - code: proper config of the default "plugin locator" can stop plugin detection from scanning a directory recursively - code: Enforce a same tab convention everywhere - doc: update list of project using yapsy - doc: highlight the existence of tutorial and link to these ones: - doc: be more helpful to users with an advice/troubleshooting page - doc: add a CHANGELOG.txt file version-1.9.2 [2012-07-15] - packaging fixes and strange version bumps to workaround pypi.python.org's version handling version-1.9 [2011-12-23] - ability to load zipped plugins - a separate development branch has been created where the focus is on the compatibility with python3 - no more SVN repository (as advertised last year it wasn't kept in sync with the Mercurial repository, and it is now officially dead) - better logging of errors and debug infos - small doc improvement, especially to show how simple it is to interactwith the plugins once they are loaded version-1.8 [2010-09-26] - the documentation has been refactored and should now go "straight to the point" - the source control is now performed by Mercurial - Filtering manager to filter out plugins that must not be loaded, contributed by Roger Gammans - a getAllPlugins method has been added to the PluginManager to make it easier to access plugins when only the default category is defined - code has been slightly cleaned up and should now be easy to adapt to Python3 via the 2to3 tool. version-1.7 [2008-04-09] - WARNING: API BREAK ! the arguments for [de]activatePluginByName and getPluginByName are now the other way round: category,name -> name,category="Default" - new AutoInstall manager for automatically installing plugins by copying them in proper place - small improvements to generic code for plugin loading version-1.6 [2007-11-10] - fix major bug in ConfigurablePluginManager version-1.5 [2007-11-03] - separation of plugin loading into locate and load contributed by Rob McMullen - package with "Easy install" framework - new forge (https://sourceforge.net/p/yapsy) and independent repo from mathbench version-1.1 [2007-09-21] - VersionedPlugin manager contributed by Rob McMullen version-1.0 [2007-08-26] - basic implementation of a PluginManager - ConfigurablePlugin manager that can store information in a ConfigParser compatible file - singleton versions of these plugin managers. Yapsy-1.12.0/artwork/0000775000175000017500000000000013343011254016001 5ustar thibauldthibauld00000000000000Yapsy-1.12.0/artwork/yapsy.png0000664000175000017500000000502412044762520017663 0ustar thibauldthibauld00000000000000PNG  IHDR00WsBIT|d pHYspMBtEXtSoftwarewww.inkscape.org< IDAThkpSuƟs%Mor/\( X/:W]%+eudW?8݋fݝeg@EV+В6m&i)-՜3s}X}Iܥ]YYOƓQV6nj{)$qI/߷Nm}ʫ.{ۮ+(<7Qe޻hȯ.=ӧtb^}{Zhx~ jpDۃ3k5gciYzt8aŒ@__"A14X'N x -K uUqP8cq`㗇GyHDu`'Gj 'oyzbkvu RʫKm5Jxbhg(۷;\p'ƻyiEx-3%AD7'P2 B<Pd4C5m"H"n`@yȦi4CW8r:x3u* 7\$ϖ5mD 0$'Nzu %%4evrl ʧ[9d sI۾ ǵiJFA[Gٓ'Z M;9lt0IVi,u=užW/_2o4{?mv^ɁS瓷uR'Ty7ٹq#}j߬oyŞ|L]6eYN-شnfp}{毚]-3!tT ۞{Ƒ#7 Murnmm=m_E"h͙Da1MSJ'WVTH(={_ql`k-}S|4!K`BU58c()Iޝ>ʮ :-}K=""%%=1_DG_oWx_xoԅ1FqMuG9)RںwpЅFlxg a#Bq.}mK@[fT4*/o^~PXy`;c<@)6?75=P^YiECS3yLH(?Yr[B[K1׬aYm]4@kiL4I/^:ڕ/wpi33P>LN` πܼp:0L+ 4Lsep4کs`"*oο}Sۨ௿T+ nRbh ,˂5A5h 5@ɨ0[J%N|IN,! IX|<+Dāw]<8+`y ςhP̹: IA:ȶB`BIa/셋>+dѠX4Cb(PyR Mbp&u"5`8 ¹22Y4&A$If<V'>tE?#)@;4YԐqA .z0N;pI om7x"NEڰmayx֎(r ਍q`UeGy7ec˶w V= .2/9P8s m T~Ub (ZQ^ĒhJZ!d>|w95?*{i5+[T3ăHXC3)ޟVd <ڽS-gF{M)o6,\)}&tE;*]ݱx'D:WD kn4I'x <nD$MѐH }ɾ:4|G;Ԙ(ghsd"a,|Nh`ȎIENDB`Yapsy-1.12.0/artwork/yapsy-favicon.png0000664000175000017500000000275412044762520021315 0ustar thibauldthibauld00000000000000PNG  IHDR szzsBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org<iIDATX{lSUo{cm71e b䙁(`4ڠc&D4āX5")QCSF#m6Fjn}އh1u=w~瓜_ν$ #s.X-DѳIkTUbZlTebe #Yso\}ٝrGSw$̩SWN>[.4.u6 "'@ΤeA_^ߐPfwWIiY/L `2 \ t PBe* 0.ӴQ  \mX} " E^Nn iUR@0p8흱@@ѨSx(QW*P&Iw\}~7DzDnq,[-6 Է p}%A@n~X 'laHZvvu =30ؒf6b˨t~%{|JWוrpuPj6/V6R~L]xDXץw| I`Kf9掆ͿUz}8vb#X Sm^W$I+ u%f*85D (eTJI(EmrxCtAEcl>WveD5|骚 73XjujWuw$Mޘx] +;cꚫTƳ(I]Y1brr36ef<%H0(Kuݮ}{fO:eܲL F'ϯ~7*jqsϬImCxNT^@VMeL4LE4FAgN`@) #yʠhXj)F`tc$~o?`,"*>Cg?&c݉z#`tN˥2Z= c:&4z = Nc ENP$@FK8vV)ISc蒇!IA^~Eya$]ڨUN`p H$JB$# (|A1԰@# y(/Հbh*I@I($;QvTiV(PHPԀP(=/P^O]Buݺ,󗬞ub:ڡOСd<ADu'-&x5)*" "B {G"Rck{U͙~b_i­9yYiA@$Cpz[}pѫfcg\e4%OHr,/ƎSS1@Ezg IENDB`Yapsy-1.12.0/artwork/LICENSE.txt0000664000175000017500000005171512147214332017640 0ustar thibauldthibauld00000000000000The "yapsy" icon is copyright (c) 2007-2013, Thibauld Nion It is licensed under the Creative Commons Attribution-Share Alike 3.0 License. More info about this license can be found there: http://creativecommons.org/licenses/by-sa/3.0 --- LICENSE TEXT: --- CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM ITS USE. License THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED. BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS. 1. Definitions 1. "Adaptation" means a work based upon the Work, or upon the Work and other pre-existing works, such as a translation, adaptation, derivative work, arrangement of music or other alterations of a literary or artistic work, or phonogram or performance and includes cinematographic adaptations or any other form in which the Work may be recast, transformed, or adapted including in any form recognizably derived from the original, except that a work that constitutes a Collection will not be considered an Adaptation for the purpose of this License. For the avoidance of doubt, where the Work is a musical work, performance or phonogram, the synchronization of the Work in timed-relation with a moving image ("synching") will be considered an Adaptation for the purpose of this License. 2. "Collection" means a collection of literary or artistic works, such as encyclopedias and anthologies, or performances, phonograms or broadcasts, or other works or subject matter other than works listed in Section 1(f) below, which, by reason of the selection and arrangement of their contents, constitute intellectual creations, in which the Work is included in its entirety in unmodified form along with one or more other contributions, each constituting separate and independent works in themselves, which together are assembled into a collective whole. A work that constitutes a Collection will not be considered an Adaptation (as defined below) for the purposes of this License. 3. "Creative Commons Compatible License" means a license that is listed at http://creativecommons.org/compatiblelicenses that has been approved by Creative Commons as being essentially equivalent to this License, including, at a minimum, because that license: (i) contains terms that have the same purpose, meaning and effect as the License Elements of this License; and, (ii) explicitly permits the relicensing of adaptations of works made available under that license under this License or a Creative Commons jurisdiction license with the same License Elements as this License. 4. "Distribute" means to make available to the public the original and copies of the Work or Adaptation, as appropriate, through sale or other transfer of ownership. 5. "License Elements" means the following high-level license attributes as selected by Licensor and indicated in the title of this License: Attribution, ShareAlike. 6. "Licensor" means the individual, individuals, entity or entities that offer(s) the Work under the terms of this License. 7. "Original Author" means, in the case of a literary or artistic work, the individual, individuals, entity or entities who created the Work or if no individual or entity can be identified, the publisher; and in addition (i) in the case of a performance the actors, singers, musicians, dancers, and other persons who act, sing, deliver, declaim, play in, interpret or otherwise perform literary or artistic works or expressions of folklore; (ii) in the case of a phonogram the producer being the person or legal entity who first fixes the sounds of a performance or other sounds; and, (iii) in the case of broadcasts, the organization that transmits the broadcast. 8. "Work" means the literary and/or artistic work offered under the terms of this License including without limitation any production in the literary, scientific and artistic domain, whatever may be the mode or form of its expression including digital form, such as a book, pamphlet and other writing; a lecture, address, sermon or other work of the same nature; a dramatic or dramatico-musical work; a choreographic work or entertainment in dumb show; a musical composition with or without words; a cinematographic work to which are assimilated works expressed by a process analogous to cinematography; a work of drawing, painting, architecture, sculpture, engraving or lithography; a photographic work to which are assimilated works expressed by a process analogous to photography; a work of applied art; an illustration, map, plan, sketch or three-dimensional work relative to geography, topography, architecture or science; a performance; a broadcast; a phonogram; a compilation of data to the extent it is protected as a copyrightable work; or a work performed by a variety or circus performer to the extent it is not otherwise considered a literary or artistic work. 9. "You" means an individual or entity exercising rights under this License who has not previously violated the terms of this License with respect to the Work, or who has received express permission from the Licensor to exercise rights under this License despite a previous violation. 10. "Publicly Perform" means to perform public recitations of the Work and to communicate to the public those public recitations, by any means or process, including by wire or wireless means or public digital performances; to make available to the public Works in such a way that members of the public may access these Works from a place and at a place individually chosen by them; to perform the Work to the public by any means or process and the communication to the public of the performances of the Work, including by public digital performance; to broadcast and rebroadcast the Work by any means including signs, sounds or images. 11. "Reproduce" means to make copies of the Work by any means including without limitation by sound or visual recordings and the right of fixation and reproducing fixations of the Work, including storage of a protected performance or phonogram in digital form or other electronic medium. 2. Fair Dealing Rights. Nothing in this License is intended to reduce, limit, or restrict any uses free from copyright or rights arising from limitations or exceptions that are provided for in connection with the copyright protection under copyright law or other applicable laws. 3. License Grant. Subject to the terms and conditions of this License, Licensor hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the duration of the applicable copyright) license to exercise the rights in the Work as stated below: 1. to Reproduce the Work, to incorporate the Work into one or more Collections, and to Reproduce the Work as incorporated in the Collections; 2. to create and Reproduce Adaptations provided that any such Adaptation, including any translation in any medium, takes reasonable steps to clearly label, demarcate or otherwise identify that changes were made to the original Work. For example, a translation could be marked "The original work was translated from English to Spanish," or a modification could indicate "The original work has been modified."; 3. to Distribute and Publicly Perform the Work including as incorporated in Collections; and, 4. to Distribute and Publicly Perform Adaptations. 5. For the avoidance of doubt: 1. Non-waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme cannot be waived, the Licensor reserves the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; 2. Waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme can be waived, the Licensor waives the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; and, 3. Voluntary License Schemes. The Licensor waives the right to collect royalties, whether individually or, in the event that the Licensor is a member of a collecting society that administers voluntary licensing schemes, via that society, from any exercise by You of the rights granted under this License. The above rights may be exercised in all media and formats whether now known or hereafter devised. The above rights include the right to make such modifications as are technically necessary to exercise the rights in other media and formats. Subject to Section 8(f), all rights not expressly granted by Licensor are hereby reserved. 4. Restrictions. The license granted in Section 3 above is expressly made subject to and limited by the following restrictions: 1. You may Distribute or Publicly Perform the Work only under the terms of this License. You must include a copy of, or the Uniform Resource Identifier (URI) for, this License with every copy of the Work You Distribute or Publicly Perform. You may not offer or impose any terms on the Work that restrict the terms of this License or the ability of the recipient of the Work to exercise the rights granted to that recipient under the terms of the License. You may not sublicense the Work. You must keep intact all notices that refer to this License and to the disclaimer of warranties with every copy of the Work You Distribute or Publicly Perform. When You Distribute or Publicly Perform the Work, You may not impose any effective technological measures on the Work that restrict the ability of a recipient of the Work from You to exercise the rights granted to that recipient under the terms of the License. This Section 4(a) applies to the Work as incorporated in a Collection, but this does not require the Collection apart from the Work itself to be made subject to the terms of this License. If You create a Collection, upon notice from any Licensor You must, to the extent practicable, remove from the Collection any credit as required by Section 4(c), as requested. If You create an Adaptation, upon notice from any Licensor You must, to the extent practicable, remove from the Adaptation any credit as required by Section 4(c), as requested. 2. You may Distribute or Publicly Perform an Adaptation only under the terms of: (i) this License; (ii) a later version of this License with the same License Elements as this License; (iii) a Creative Commons jurisdiction license (either this or a later license version) that contains the same License Elements as this License (e.g., Attribution-ShareAlike 3.0 US)); (iv) a Creative Commons Compatible License. If you license the Adaptation under one of the licenses mentioned in (iv), you must comply with the terms of that license. If you license the Adaptation under the terms of any of the licenses mentioned in (i), (ii) or (iii) (the "Applicable License"), you must comply with the terms of the Applicable License generally and the following provisions: (I) You must include a copy of, or the URI for, the Applicable License with every copy of each Adaptation You Distribute or Publicly Perform; (II) You may not offer or impose any terms on the Adaptation that restrict the terms of the Applicable License or the ability of the recipient of the Adaptation to exercise the rights granted to that recipient under the terms of the Applicable License; (III) You must keep intact all notices that refer to the Applicable License and to the disclaimer of warranties with every copy of the Work as included in the Adaptation You Distribute or Publicly Perform; (IV) when You Distribute or Publicly Perform the Adaptation, You may not impose any effective technological measures on the Adaptation that restrict the ability of a recipient of the Adaptation from You to exercise the rights granted to that recipient under the terms of the Applicable License. This Section 4(b) applies to the Adaptation as incorporated in a Collection, but this does not require the Collection apart from the Adaptation itself to be made subject to the terms of the Applicable License. 3. If You Distribute, or Publicly Perform the Work or any Adaptations or Collections, You must, unless a request has been made pursuant to Section 4(a), keep intact all copyright notices for the Work and provide, reasonable to the medium or means You are utilizing: (i) the name of the Original Author (or pseudonym, if applicable) if supplied, and/or if the Original Author and/or Licensor designate another party or parties (e.g., a sponsor institute, publishing entity, journal) for attribution ("Attribution Parties") in Licensor's copyright notice, terms of service or by other reasonable means, the name of such party or parties; (ii) the title of the Work if supplied; (iii) to the extent reasonably practicable, the URI, if any, that Licensor specifies to be associated with the Work, unless such URI does not refer to the copyright notice or licensing information for the Work; and (iv) , consistent with Ssection 3(b), in the case of an Adaptation, a credit identifying the use of the Work in the Adaptation (e.g., "French translation of the Work by Original Author," or "Screenplay based on original Work by Original Author"). The credit required by this Section 4(c) may be implemented in any reasonable manner; provided, however, that in the case of a Adaptation or Collection, at a minimum such credit will appear, if a credit for all contributing authors of the Adaptation or Collection appears, then as part of these credits and in a manner at least as prominent as the credits for the other contributing authors. For the avoidance of doubt, You may only use the credit required by this Section for the purpose of attribution in the manner set out above and, by exercising Your rights under this License, You may not implicitly or explicitly assert or imply any connection with, sponsorship or endorsement by the Original Author, Licensor and/or Attribution Parties, as appropriate, of You or Your use of the Work, without the separate, express prior written permission of the Original Author, Licensor and/or Attribution Parties. 4. Except as otherwise agreed in writing by the Licensor or as may be otherwise permitted by applicable law, if You Reproduce, Distribute or Publicly Perform the Work either by itself or as part of any Adaptations or Collections, You must not distort, mutilate, modify or take other derogatory action in relation to the Work which would be prejudicial to the Original Author's honor or reputation. Licensor agrees that in those jurisdictions (e.g. Japan), in which any exercise of the right granted in Section 3(b) of this License (the right to make Adaptations) would be deemed to be a distortion, mutilation, modification or other derogatory action prejudicial to the Original Author's honor and reputation, the Licensor will waive or not assert, as appropriate, this Section, to the fullest extent permitted by the applicable national law, to enable You to reasonably exercise Your right under Section 3(b) of this License (right to make Adaptations) but not otherwise. 5. Representations, Warranties and Disclaimer UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU. 6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 7. Termination 1. This License and the rights granted hereunder will terminate automatically upon any breach by You of the terms of this License. Individuals or entities who have received Adaptations or Collections from You under this License, however, will not have their licenses terminated provided such individuals or entities remain in full compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of this License. 2. Subject to the above terms and conditions, the license granted here is perpetual (for the duration of the applicable copyright in the Work). Notwithstanding the above, Licensor reserves the right to release the Work under different license terms or to stop distributing the Work at any time; provided, however that any such election will not serve to withdraw this License (or any other license that has been, or is required to be, granted under the terms of this License), and this License will continue in full force and effect unless terminated as stated above. 8. Miscellaneous 1. Each time You Distribute or Publicly Perform the Work or a Collection, the Licensor offers to the recipient a license to the Work on the same terms and conditions as the license granted to You under this License. 2. Each time You Distribute or Publicly Perform an Adaptation, Licensor offers to the recipient a license to the original Work on the same terms and conditions as the license granted to You under this License. 3. If any provision of this License is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this License, and without further action by the parties to this agreement, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. 4. No term or provision of this License shall be deemed waived and no breach consented to unless such waiver or consent shall be in writing and signed by the party to be charged with such waiver or consent. 5. This License constitutes the entire agreement between the parties with respect to the Work licensed here. There are no understandings, agreements or representations with respect to the Work not specified here. Licensor shall not be bound by any additional provisions that may appear in any communication from You. This License may not be modified without the mutual written agreement of the Licensor and You. 6. The rights granted under, and the subject matter referenced, in this License were drafted utilizing the terminology of the Berne Convention for the Protection of Literary and Artistic Works (as amended on September 28, 1979), the Rome Convention of 1961, the WIPO Copyright Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996 and the Universal Copyright Convention (as revised on July 24, 1971). These rights and subject matter take effect in the relevant jurisdiction in which the License terms are sought to be enforced according to the corresponding provisions of the implementation of those treaty provisions in the applicable national law. If the standard suite of rights granted under applicable copyright law includes additional rights not granted under this License, such additional rights are deemed to be included in the License; this License is not intended to restrict the license of any rights under applicable law. Creative Commons Notice Creative Commons is not a party to this License, and makes no warranty whatsoever in connection with the Work. Creative Commons will not be liable to You or any party on any legal theory for any damages whatsoever, including without limitation any general, special, incidental or consequential damages arising in connection to this license. Notwithstanding the foregoing two (2) sentences, if Creative Commons has expressly identified itself as the Licensor hereunder, it shall have all rights and obligations of Licensor. Except for the limited purpose of indicating to the public that the Work is licensed under the CCPL, Creative Commons does not authorize the use by either party of the trademark "Creative Commons" or any related trademark or logo of Creative Commons without the prior written consent of Creative Commons. Any permitted use will be in compliance with Creative Commons' then-current trademark usage guidelines, as may be published on its website or otherwise made available upon request from time to time. For the avoidance of doubt, this trademark restriction does not form part of the License. Creative Commons may be contacted at http://creativecommons.org/. Yapsy-1.12.0/artwork/yapsy-favicon.ico0000664000175000017500000000427612044762520021304 0ustar thibauldthibauld00000000000000 ( @:82=96C=;IEBMDHNHHQHL]PYWSQ~QyT}j]gV~\]^^`abefffgklrtmnnoprstuvx}xyyyx||}{~¥çŪūƫƬɯɰʱ˳̴ͷϹйкѻҽ"&$SN~m(Wk0bO9=Tv992#H993Q999oAF9999dGnjC>@\huVp|acB7Qi r+_6C{IvfM:E*r:5K)lL4]P^;n/E. he wP%z0 t?????????Yapsy-1.12.0/artwork/yapsy-big.png0000664000175000017500000002174012044762520020425 0ustar thibauldthibauld00000000000000PNG  IHDR>asBIT|d pHYs7]7]F]tEXtSoftwarewww.inkscape.org< IDATxw[u=ALo,EHbJbYZrI8ivb|"Y;q 9e%Z"i@;g {w@`f:`>7pp{+qZtx=>=k9&^%ǹ x=O]Rɀ7?c!/,[Tk& _MZYI㛓k"z|v!Ķb;nsfר=ײ] _GW]azwbw ۼ-ײ#X1^1- <[Hp^a.,kґSx=_MJyM)-=Th.+ȥ|W gB|kz@3ײqj^ﳹ1 ^H|XyRh;J,cӖ*R+_Rt jd BsD"u59BBn-;)BU8ZzQe {=9ꀿXr(t} fT-woP3/TrB9uƌuFmAѸMlV!r!UL&51tAFQ;ez)Kgͱ9%ut7v.1>v+NG@xEBT[-fĢ11gn$f넔 ##ȴ-=+V/Qd+ɑYj 21W9p GYmS- nTE5Yb.upG6>YXeńz2.8=A,6~t k3+yos7B]9,@EMB@g<l1?̦\S'QTQq! - !z|9gKR)eͼh#ZuGgZvgKD ?.hj:݌af ,nȅdꙕ &™M]_Ϣ\S )厳kB#TTB/Ʌ|$@uqYa['l CrXvfQl`Wks)Ӱ93c>CX , P IROGCʢL!nT֔+ @eWL TCY)hޮq bե*P IZˮU>*D#:ЗEBF"L'@HdB !D].v}#Q}??Ҕ'6_+Y(@JYiLq0!li _STUQR"K( 4T:h1 &,ki0e# "(@)3Ox!iX& 0B@?eѲGCCCa8#:BR|˛HYmZa,,!PA82 Д Ѳ hjх=3ndxۙ|;R.,,q G3%{PĐRɅ|$@S!%\΢k$~Oa1:g@r2U Q(($@,2nd6EU쌄3xW -qlB<`ud;VE #(&JEU|22ewˊ42 prt n.k>lЭlّHf̝7&vB|fY&!h,v NB#P.dv¿jhTWfhQa/ `':v-`jz3EhΑǑRr%` K)e +i~*gVp5,5U(ЫgT6:đpF1YGV #S{ > >\$ôS\D}3J~=x' noW?9@2lθ/E5}!Z@祔V^,RCYOޣȥY'@ ?|W/e Q6oe|_ay W ۅnzS*4/?oNM[)iyuۛatF)o'8b]XTc;*/w%4[1sD@Rc')]kvqmk\xz|l-kDI- %DՂZygs-oH,/ޡ[6ŤꍟUP)Wr^?u7l^E[_|Eh3 !˵E>ŴO/5*- :N''U c9/VU h;X5{_>>Rc5 >Rnj=FAQUk04c̪S]|1 }mwo53+i;ތ,]͗ȓGR{i[-}yUCnau ˫(aBWZrF1OۉV*Y\KÁ OXqț!w7rwM *c:GB >)e^myiEX6z*v N@H;<'[.xcZmU hD)UDgZ9vz̉G>phI;rQj!)%Q-Y:ϴpr ~N 6ÆPt MO5=cұp.IoW6=2>,%IYô#3ngTzC_[̞?^WŠ"jƓ?nDco),`Z #/B}곷kuIPXW"4tc;p ^y ~ xLJOGZPMaEѤ~OR NO_Ѝ^ ʇilX?>-rsVQCㅧv @g r`ZtH)g'RtFjd5S%'ך!@ 4䏞e_O?U nյD oNk ϡrl \E .}5gΝmUS(c^$@ ?>n$gSQ)1opM 7ΜhBѰ]N6 cR|1 Rܶ(pّN5%=e|; 0 409Y'۸f K[sJ0_SYx=>E E. 0 CO rLiח '+RQ7vEiBe]qXK^E @?|OG\+ y!ķuVE[riƕtHQT+ginl7gOS'~tOiOwRʅUƍW)dVcs/g9؜6:[ٻ#x/%|iiK7Gu"}Ӗq;.rQXQD :w=&!8-|,O$@|~OX--LK,R`e3IRf#AcC=kA&˲iED[mO̒wRX$teˁjV)]HdWɡ!$v90mPi-kź[W~mFS J:,v+}W_m7OGqZ ѽ{BnW̥-H# EPY_MVs԰i].<{D9l9'ʇxİx GS=[Vz|c=ߟ_;h- εX trJj m@(CU26sk2~!?z|%dܭoVh3%N*(&դTTsykŤ$H1uvn'[EQLM^o157T*[@IWyPRJO`>%Z^9!i=BQi![nP|u%1K_XͫfcO*>C)e*ǐBɰʨ%0 2)WEp_^>n%%E3.*snZmt:%o $xEA$|!x ]rR6,^$x.Y?/ R^D{OGMQĒ0g%d5Sv>oOXNY]%1UUQVm\I7D7jㅢ'A]Os(Bj-';?"8RJOB<6?zNK0}mo?wʅZ4F{ WΧHdkStj]z Qht[ʹKoEUǘn}s(YNaס=i;9=&|NcywI YeU&s^|2i2W6JIђ f$k%Gg]9^ҏEFbw\^1dF[6^YW@g8bRQմZlVBLg_=󓽀 ?;t髏¸JpB6WBܶd|EQQԤh./.`-/.SPyI)_&fB-eB\ ^-\9{nx/ % j+/Xs,>'*^_Y6cq`6ѱD}{3ʿos``l[S^lwxʋ ;&@A#l3Gú2>g$o7q1\7yӿ3*bė;qe-%n,:“IEeqT,G̺̦3r||x`=jH.IJbVW4t݈ߚtx=[W߲F F92wrw g{^Z;륯CJTEMR]̊yKt-s_aC35k#qYn >|0<_i]"^+>h6hB8JOu.b R`)C;UQIDATKWvZʷ bgO4OGJ@\ۥ"AWeow?fp4P#u$2v7p#096 ቦDi©i$:mX>N~/H)叧a h1i|c+H8rR$+]O 2?'u#McZ9N0낺\}srlxEѱx03(c1:YTxzS`5:e*pr`^~M x\ 嶔.ӆZ0/^V/>t,hQ 6.NI1[mDntFI6> ROAaZ o"ӽܡ̪D0Yqg0q_2b/Hg6#')Aifwu 6g[yeNK ?YAL% !G|bqͫR"OI@78>xo-b+,U@W{/o~*`r]WiK$KNjR.Xd_)jր !)弥;{N 6 ) uQ#Ⱄ끠\´'@b}/ !FJYpUV Ս%c:ڈH٣T+Bt]Osz8< !</CF&D{J)TE1jˍB K Z-D G n[;تh!&Ro_kO"Xx=FQR !v)yAe\8x=nO6?7f-/IENDB`Yapsy-1.12.0/artwork/yapsy.svg0000664000175000017500000002066512044762520017706 0ustar thibauldthibauld00000000000000 image/svg+xml Yapsy-1.12.0/setup.py0000664000175000017500000000355012575022002016024 0ustar thibauldthibauld00000000000000#!/usr/bin/python # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t -*- """ The setup.py script needed to build a .egg for an easier distribution and installation of yapsy. Requires 'Easy Install' to be installed :) see there: http://peak.telecommunity.com/DevCenter/EasyInstall#installation-instructions Then to create a package run: $ python setup.py bdist_egg To use the generated .egg file then: easy_install Yapsy-{yapsy version}-py{python version}.egg Automagical stuff: - test everything:: python setup.py test - build the packages (sources an egg) and upload all the stuff to pypi:: python setup.py sdist bdist_egg upload - build the documentation python setup.py build_sphinx """ import os from setuptools import setup # just in case setup.py is launched from elsewhere that the containing directory originalDir = os.getcwd() os.chdir(os.path.dirname(os.path.abspath(__file__))) try: setup( name = "Yapsy", version = __import__("yapsy").__version__, packages = ['yapsy'], package_dir = {'yapsy':'yapsy'}, # the unit tests test_suite = "test.test_All.MainTestSuite", # metadata for upload to PyPI author = "Thibauld Nion", author_email = "thibauld@tibonihoo.net", description = "Yet another plugin system", license = "BSD", keywords = "plugin manager", url = "http://yapsy.sourceforge.net", # more details long_description = open("README.txt").read(), classifiers=['Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 3', 'Topic :: Software Development :: Libraries :: Python Modules'], platforms='All', ) finally: os.chdir(originalDir) Yapsy-1.12.0/doc/0000775000175000017500000000000013343011254015055 5ustar thibauldthibauld00000000000000Yapsy-1.12.0/doc/conf.py0000664000175000017500000001526412575022002016363 0ustar thibauldthibauld00000000000000# -*- coding: utf-8 -*- # # Yapsy documentation build configuration file, created by # sphinx-quickstart on Sat Aug 21 19:38:34 2010. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys, os SRC_DIR = os.path.dirname(os.path.abspath(os.path.dirname(__file__))) sys.path = [SRC_DIR] + sys.path # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.append(os.path.abspath('.')) # -- General configuration ----------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.coverage'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8' # The master toctree document. master_doc = 'index' # General information about the project. project = 'Yapsy' copyright = '2007-2015, Thibauld Nion' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # import sys sys.path.insert(0,os.path.dirname(__file__)) import yapsy # The short X.Y version. version = yapsy.__version__ # The full version, including alpha/beta/rc tags. release = yapsy.__version__ # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of documents that shouldn't be included in the build. #unused_docs = [] # List of directories, relative to source directory, that shouldn't be searched # for source files. exclude_trees = ['_build'] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. html_theme_options = { "sidebarbgcolor" : "#777", "sidebarlinkcolor": "#e0cede", "relbarbgcolor" : "#999", "relbarlinkcolor": "#e0cede", "footerbgcolor" : "#777", "headtextcolor" : "#5c3566", "linkcolor": "#5c3566", } # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. html_logo = os.path.join(SRC_DIR,"artwork","yapsy-big.png") # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. html_favicon = os.path.join(SRC_DIR,"artwork","yapsy-favicon.ico") # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_use_modindex = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = '' # Output file base name for HTML help builder. htmlhelp_basename = 'Yapsydoc' # -- Options for LaTeX output -------------------------------------------------- # The paper size ('letter' or 'a4'). #latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). #latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'Yapsy.tex', 'Yapsy Documentation', 'Thibauld Nion', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # Additional stuff for the LaTeX preamble. #latex_preamble = '' # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_use_modindex = True Yapsy-1.12.0/doc/VersionedPluginManager.rst0000664000175000017500000000020012044762520022215 0ustar thibauldthibauld00000000000000VersionedPluginManager ====================== .. automodule:: yapsy.VersionedPluginManager :members: :undoc-members: Yapsy-1.12.0/doc/PluginFileLocator.rst0000664000175000017500000000016012063427774021206 0ustar thibauldthibauld00000000000000PluginFileLocator ================= .. automodule:: yapsy.PluginFileLocator :members: :undoc-members: Yapsy-1.12.0/doc/FilteredPluginManager.rst0000664000175000017500000000017512044762520022030 0ustar thibauldthibauld00000000000000FilteredPluginManager ===================== .. automodule:: yapsy.FilteredPluginManager :members: :undoc-members: Yapsy-1.12.0/doc/ConfigurablePluginManager.rst0000664000175000017500000000021112044762520022661 0ustar thibauldthibauld00000000000000ConfigurablePluginManager ========================= .. automodule:: yapsy.ConfigurablePluginManager :members: :undoc-members: Yapsy-1.12.0/doc/make.bat0000664000175000017500000000561512044762520016477 0ustar thibauldthibauld00000000000000@ECHO OFF REM Command file for Sphinx documentation set SPHINXBUILD=sphinx-build set ALLSPHINXOPTS=-d _build/doctrees %SPHINXOPTS% . if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. changes to make an overview over all changed/added/deprecated items echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled goto end ) if "%1" == "clean" ( for /d %%i in (_build\*) do rmdir /q /s %%i del /q /s _build\* goto end ) if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% _build/html echo. echo.Build finished. The HTML pages are in _build/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% _build/dirhtml echo. echo.Build finished. The HTML pages are in _build/dirhtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% _build/pickle echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% _build/json echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% _build/htmlhelp echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in _build/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% _build/qthelp echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in _build/qthelp, like this: echo.^> qcollectiongenerator _build\qthelp\Yapsy.qhcp echo.To view the help file: echo.^> assistant -collectionFile _build\qthelp\Yapsy.ghc goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% _build/latex echo. echo.Build finished; the LaTeX files are in _build/latex. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% _build/changes echo. echo.The overview file is in _build/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% _build/linkcheck echo. echo.Link check complete; look for any errors in the above output ^ or in _build/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% _build/doctest echo. echo.Testing of doctests in the sources finished, look at the ^ results in _build/doctest/output.txt. goto end ) :end Yapsy-1.12.0/doc/index.rst0000664000175000017500000002525012575022002016721 0ustar thibauldthibauld00000000000000.. Yapsy documentation master file, created by sphinx-quickstart on Sat Aug 21 19:38:34 2010. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. ================================ Yapsy: Yet Another Plugin SYstem ================================ *A simple plugin system for Python applications* .. |Yapsy| replace:: **Yapsy** .. |CC-BYSA| image:: http://i.creativecommons.org/l/by-sa/3.0/88x31.png :alt: Creative Commons License Quick links: .. toctree:: :maxdepth: 1 IPlugin PluginManager PluginInfo Extensions Advices .. contents:: On this page :local: .. automodule:: yapsy :members: :undoc-members: .. _extend: Make it your own ================ For applications that require the plugins and their managers to be more sophisticated, several techniques make such enhancement easy. The following sections detail the most frequent needs for extensions and what you can do about it. More sophisticated plugin classes --------------------------------- You can define a plugin class with a richer interface than ``IPlugin``, so long as it inherits from IPlugin, it should work the same. The only thing you need to know is that the plugin instance is accessible via the ``PluginInfo`` instance from its ``PluginInfo.plugin_object``. It is also possible to define a wider variety of plugins, by defining as much subclasses of IPlugin. But in such a case you have to inform the manager about that before collecting plugins:: # Build the manager simplePluginManager = PluginManager() # Tell it the default place(s) where to find plugins simplePluginManager.setPluginPlaces(["path/to/myplugins"]) # Define the various categories corresponding to the different # kinds of plugins you have defined simplePluginManager.setCategoriesFilter({ "Playback" : IPlaybackPlugin, "SongInfo" : ISongInfoPlugin, "Visualization" : IVisualisation, }) .. note:: Communicating with the plugins belonging to a given category might then be achieved with some code looking like the following:: # Trigger 'some action' from the "Visualization" plugins for pluginInfo in simplePluginManager.getPluginsOfCategory("Visualization"): pluginInfo.plugin_object.doSomething(...) Enhance the plugin manager's interface -------------------------------------- To make the plugin manager more helpful to the other components of an application, you should consider decorating it. Actually a "template" for such decoration is provided as :doc:`PluginManagerDecorator`, which must be inherited in order to implement the right decorator for your application. Such decorators can be chained, so that you can take advantage of the ready-made decorators such as: :doc:`ConfigurablePluginManager` Implements a ``PluginManager`` that uses a configuration file to save the plugins to be activated by default and also grants access to this file to the plugins. :doc:`AutoInstallPluginManager` Automatically copy the plugin files to the right plugin directory. A full list of pre-implemented decorators is available at :doc:`Extensions`. Modify plugin descriptions and detections ----------------------------------------- By default, plugins are described by a text file called the plugin "info file" expected to have a ".yapsy-plugin" extension. You may want to use another way to describe and detect your application's plugin and happily yapsy (since version 1.10) makes it possible to provide the ``PluginManager`` with a custom strategy for plugin detection. See :doc:`IPluginLocator` for the required interface of such strategies and :doc:`PluginFileLocator` for a working example of such a detection strategy. Modify the way plugins are loaded --------------------------------- To tweak the plugin loading phase it is highly advised to re-implement your own manager class. The nice thing is, if your new manager inherits ``PluginManager``, then it will naturally fit as the start point of any decoration chain. You just have to provide an instance of this new manager to the first decorators, like in the following:: # build and configure a specific manager baseManager = MyNewManager() # start decorating this manager to add some more responsibilities myFirstDecorator = AFirstPluginManagerDecorator(baseManager) # add even more stuff mySecondDecorator = ASecondPluginManagerDecorator(myFirstDecorator) .. note:: Some decorators have been implemented that modify the way plugins are loaded, this is however not the easiest way to do it and it makes it harder to build a chain of decoration that would include these decorators. Among those are :doc:`VersionedPluginManager` and :doc:`FilteredPluginManager` Showcase and tutorials ====================== |yapsy| 's development has been originally motivated by the MathBench_ project but it is now used in other (more advanced) projects like: - peppy_ : "an XEmacs-like editor in Python. Eventually. " - MysteryMachine_ : "an application for writing freeform games." - Aranduka_ : "A simple e-book manager and reader" - err_ : "a plugin based chatbot" - nikola_ : "a Static Site and Blog Generator" .. _MathBench: http://mathbench.sourceforge.net .. _peppy: http://www.flipturn.org/peppy/ .. _MysteryMachine: http://trac.backslashat.org/MysteryMachine .. _Aranduka: https://github.com/ralsina/aranduka .. _err: http://gbin.github.com/err/ .. _nikola: http://nikola.ralsina.com.ar/ Nowadays, the development is clearly motivated by such external projects and the enthusiast developpers who use the library. If you're interested in using yapsy, feel free to look into the following links: - :doc:`Advices` - `A minimal example on stackoverflow`_ - `Making your app modular: Yapsy`_ (applied to Qt apps) - `Python plugins with yapsy`_ (applied to GTK apps) .. _`Making your app modular: Yapsy`: http://ralsina.me/weblog/posts/BB923.html .. _`A minimal example on stackoverflow`: http://stackoverflow.com/questions/5333128/yapsy-minimal-example .. _`Python plugins with yapsy`: https://github.com/MicahCarrick/yapsy-gtk-example Development =========== Contributing or forking ? ------------------------- You're always welcome if you suggest any kind of enhancements, any new decorators or any new pluginmanager. Even more if there is some code coming with it though this is absolutely not compulsory. It is also really fine to *fork* the code ! In the past, some people found |yapsy| just good enough to be used as a "code base" for their own plugin system, which they evolved in a more or less incompatible way with the "original" |yapsy|, if you think about it, with such a small library this is actually a clever thing to do. In any case, please remember that just providing some feedback on where you're using |yapsy| (original or forked) and how it is useful to you, is in itself a appreciable contribution :) License ------- The work is placed under the simplified BSD_ license in order to make it as easy as possible to be reused in other projects. .. _BSD: http://www.opensource.org/licenses/bsd-license.php Please note that the icon is not under the same license but under the `Creative Common Attribution-ShareAlike`_ license. .. _`Creative Common Attribution-ShareAlike`: http://creativecommons.org/licenses/by-sa/3.0/ Forge ----- The project is hosted by `Sourceforge`_ where you can access the code, documentation and a tracker to share your feedback and ask for support. |SourceForge.net| .. _`Sourceforge`: http://sourceforge.net/projects/yapsy/ .. |SourceForge.net| image:: http://sflogo.sourceforge.net/sflogo.php?group_id=208383&type=5 :alt: SourceForge.net **Any suggestion and help are much welcome !** Yapsy is also tested on the continous integration service `TravisCI`_: |CITests| |Coverage| .. _`TravisCI`: https://travis-ci.org/tibonihoo/yapsy .. |CITests| image:: https://travis-ci.org/tibonihoo/yapsy.png?branch=master :alt: Continuous integration tests .. |Coverage| image:: https://coveralls.io/repos/tibonihoo/yapsy/badge.png?branch=master :alt: Code coverage from continuous integration tests. :target: https://coveralls.io/r/tibonihoo/yapsy?branch=master A few alternative sites are available: * Yapsy's sources are mirrored on `GitHub`_. * To use `pip for a development install`_ you can do something like:: pip install -e "git+https://github.com/tibonihoo/yapsy.git#egg=yapsy&subdirectory=package" pip install -e "hg+http://hg.code.sf.net/p/yapsy/code#egg=yapsy&subdirectory=package" * A development version of the documentation is available on `ReadTheDoc`_. .. _`GitHub`: https://github.com/tibonihoo/yapsy/ .. _`pip for a development install`: http://pip.readthedocs.org/en/latest/reference/pip_install.html#vcs-support .. _`ReadTheDoc`: https://yapsy.readthedocs.org References ---------- Other Python plugin systems already existed before |yapsy| and some have appeared after that. |yapsy|'s creation is by no mean a sign that these others plugin systems sucks :) It is just the results of me being slighlty lazy and as I had already a good idea of how a simple plugin system should look like, I wanted to implement my own [#older_systems]_. - setuptools_ seems to be designed to allow applications to have a plugin system. .. _setuptools: http://cheeseshop.python.org/pypi/setuptools - Sprinkles_ seems to be also quite lightweight and simple but just maybe too far away from the design I had in mind. .. _Sprinkles: http://termie.pbwiki.com/SprinklesPy - PlugBoard_ is certainly quite good also but too complex for me. It also depends on zope which considered what I want to do here is way too much. .. _PlugBoard: https://pypi.python.org/pypi/PlugBoard - `Marty Alchin's simple plugin framework`_ is a quite interesting description of a plugin architecture with code snippets as illustrations. .. _`Marty Alchin's simple plugin framework`: http://martyalchin.com/2008/jan/10/simple-plugin-framework/ - stevedor_ looks quite promising and actually seems to make setuptools relevant to build plugin systems. .. _stevedor: https://pypi.python.org/pypi/stevedore - You can look up more example on a `stackoverflow's discution about minimal plugin systems in Python`_ .. _`stackoverflow's discution about minimal plugin systems in Python`: http://stackoverflow.com/questions/932069/building-a-minimal-plugin-architecture-in-python .. [#older_systems] All the more because it seems that my modest design ideas slightly differ from what has been done in other libraries. Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` Yapsy-1.12.0/doc/IPluginLocator.rst0000664000175000017500000000014712063427727020522 0ustar thibauldthibauld00000000000000IPluginLocator ============== .. automodule:: yapsy.IPluginLocator :members: :undoc-members: Yapsy-1.12.0/doc/IMultiprocessChildPlugin.rst0000664000175000017500000000027112575022002022533 0ustar thibauldthibauld00000000000000======================== IMultiprocessChildPlugin ======================== .. toctree:: :maxdepth: 2 .. automodule:: yapsy.IMultiprocessChildPlugin :members: :undoc-members: Yapsy-1.12.0/doc/PluginManagerDecorator.rst0000664000175000017500000000026612044762520022215 0ustar thibauldthibauld00000000000000====================== PluginManagerDecorator ====================== .. toctree:: :maxdepth: 2 .. automodule:: yapsy.PluginManagerDecorator :members: :undoc-members: Yapsy-1.12.0/doc/IPlugin.rst0000664000175000017500000000012312044762520017160 0ustar thibauldthibauld00000000000000IPlugin ======= .. automodule:: yapsy.IPlugin :members: :undoc-members: Yapsy-1.12.0/doc/AutoInstallPluginManager.rst0000664000175000017500000000020612044762520022524 0ustar thibauldthibauld00000000000000AutoInstallPluginManager ======================== .. automodule:: yapsy.AutoInstallPluginManager :members: :undoc-members: Yapsy-1.12.0/doc/MultiprocessPluginManager.rst0000664000175000017500000000030212575022002022744 0ustar thibauldthibauld00000000000000========================= MultiprocessPluginManager ========================= .. toctree:: :maxdepth: 2 .. automodule:: yapsy.MultiprocessPluginManager :members: :undoc-members: Yapsy-1.12.0/doc/PluginInfo.rst0000664000175000017500000000014712044762520017671 0ustar thibauldthibauld00000000000000========== PluginInfo ========== .. automodule:: yapsy.PluginInfo :members: :undoc-members: Yapsy-1.12.0/doc/Makefile0000664000175000017500000000565712044762520016540 0ustar thibauldthibauld00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d _build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf _build/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) _build/html @echo @echo "Build finished. The HTML pages are in _build/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) _build/dirhtml @echo @echo "Build finished. The HTML pages are in _build/dirhtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) _build/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) _build/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) _build/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in _build/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) _build/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in _build/qthelp, like this:" @echo "# qcollectiongenerator _build/qthelp/Yapsy.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile _build/qthelp/Yapsy.qhc" latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) _build/latex @echo @echo "Build finished; the LaTeX files are in _build/latex." @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ "run these through (pdf)latex." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) _build/changes @echo @echo "The overview file is in _build/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) _build/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in _build/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) _build/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in _build/doctest/output.txt." Yapsy-1.12.0/doc/MultiprocessPluginProxy.rst0000664000175000017500000000026512575022002022523 0ustar thibauldthibauld00000000000000======================= MultiprocessPluginProxy ======================= .. toctree:: :maxdepth: 2 .. automodule:: yapsy.MultiprocessPluginProxy :members: :undoc-members: Yapsy-1.12.0/doc/Extensions.rst0000664000175000017500000000154612575022002017753 0ustar thibauldthibauld00000000000000=================== Built-in Extensions =================== The followig ready-to-use classes give you this exact extra functionality you need for your plugin manager: .. toctree:: :maxdepth: 1 VersionedPluginManager ConfigurablePluginManager AutoInstallPluginManager FilteredPluginManager MultiprocessPluginManager The following item offer customization for the way plugins are described and detected: .. toctree:: :maxdepth: 1 PluginFileLocator If you want to build your own extensions, have a look at the following interfaces: .. toctree:: :maxdepth: 1 IPluginLocator PluginManagerDecorator If you want to isolate your plugins in separate processes with the ``MultiprocessPluginManager``, you should look at the following classes too: .. toctree:: :maxdepth: 1 IMultiprocessChildPlugin MultiprocessPluginProxy Yapsy-1.12.0/doc/Advices.rst0000664000175000017500000001146712313623056017203 0ustar thibauldthibauld00000000000000=================================== General advices and troubleshooting =================================== .. contents:: :local: Getting code samples -------------------- Yapsy is used enough for your favorite search provider to have good chances of finding some examples of yapsy being used in the wild. However if you wonder how a specific functionality can be used, you can also look at the corresponding unit test (in the test folder packaged with yapsy's sources). Use the logging system ---------------------- Yapsy uses Python's standard ``logging`` module to record most important events and especially plugin loading failures. When developping an application based on yapsy, you'll benefit from looking at the 'debug' level logs, which can easily be done from your application code with the following snippet:: import logging logging.basicConfig(level=logging.DEBUG) Also, please note that yapsy uses a named logger for all its logs, so that you can selectively activage debug logs for yapsy with the following snippet:: import logging logging.getLogger('yapsy').setLevel(logging.DEBUG) Categorization by inheritance caveat ------------------------------------ If your application defines various categories of plugins with the yapsy's built-in mechanism for that, please keep in mind the following facts: - a plugin instance is attributed to a given category by looking if it is an instance, *even via a subclass*, of the class associated to this category; - a plugin may be attributed to several categories. Considering this, and if you consider using several categories, you should consider the following tips: - **don't associate any category to ``IPlugin``** (unless you want all plugins to be attributed to the corresponding category) - **design a specific subclass** of ``IPlugin`` for each category - if you want to regroup plugins of some categories into a common category: do this by attributing a subclass of ``IPlugin`` to the common category and attribute to the other categories specific subclasses to this intermediate mother class so that **the plugin class inheritance hierarchy reflects the hierarchy between categories** (and if you want something more complex that a hierarchy, you can consider using mixins). Plugin class detection caveat ----------------------------- There must be **only one plugin defined per module**. This means that you can't have two plugin description files pointing at the same module for instance. Because of the "categorization by inheritance" system, you **musn't directly import the subclass** of ``IPlugin`` in the main plugin file, instead import its containing module and make your plugin class inherit from ``ContainingModule.SpecificPluginClass`` as in the following example. The following code won't work (the class ``MyBasePluginClass`` will be detected as the plugin's implementation instead of ``MyPlugin``):: from myapp.plugintypes import MyBasePluginClass class MyPlugin(MyBasePluginClass): pass Instead you should do the following:: import myapp.plugintypes as plugintypes class MyPlugin(plugintypes.MyBasePluginClass): pass Plugin packaging ---------------- When packaging plugins in a distutils installer or as parts of an application (like for instance with `py2exe`), you may want to take care about the following points: - when you set specific directories where to look for plugins with a hardcoded path, be very carefully about the way you write these paths because depending on the cases **using ``__file__`` or relative paths may be unreliable**. For instance with py2exe, you may want to follow the tips from the `Where Am I FAQ`_. - you'd should either **package the plugins as plain Python modules or data files** (if you want to consider you application as the only module), either using the dedicated `setup` argument for `py2exe` or using distutils' `MANIFEST.in` - if you do package the plugins as data files, **make sure that their dependencies are correctly indicated as dependencies of your package** (or packaged with you application if you use `py2exe`). See also a more detailed example for py2exe on `Simon on Tech's Using python plugin scripts with py2exe`_. .. _`Where Am I FAQ`: http://www.py2exe.org/index.cgi/WhereAmI .. _`Simon on Tech's Using python plugin scripts with py2exe`: http://notinthestars.blogspot.com.es/2011/04/using-python-plugin-scripts-with-py2exe.html Code conventions ---------------- If you intend to modify yapsy's sources and to contribute patches back, please respect the following conventions: - CamelCase (upper camel case) for class names and functions - camelCase (lower camel case) for methods - UPPERCASE for global variables (with a few exceptions) - tabulations are used for indentation (and not spaces !) - unit-test each new functionality Yapsy-1.12.0/doc/PluginManager.rst0000664000175000017500000000022212044762520020342 0ustar thibauldthibauld00000000000000============= PluginManager ============= .. toctree:: :maxdepth: 2 .. automodule:: yapsy.PluginManager :members: :undoc-members: Yapsy-1.12.0/Yapsy.egg-info/0000775000175000017500000000000013343011254017107 5ustar thibauldthibauld00000000000000Yapsy-1.12.0/Yapsy.egg-info/SOURCES.txt0000664000175000017500000000544313343011254021001 0ustar thibauldthibauld00000000000000CHANGELOG.txt LICENSE.txt MANIFEST.in README.txt runtests.py setup.py Yapsy.egg-info/PKG-INFO Yapsy.egg-info/SOURCES.txt Yapsy.egg-info/dependency_links.txt Yapsy.egg-info/top_level.txt artwork/LICENSE.txt artwork/yapsy-big.png artwork/yapsy-favicon.ico artwork/yapsy-favicon.png artwork/yapsy.png artwork/yapsy.svg doc/Advices.rst doc/AutoInstallPluginManager.rst doc/ConfigurablePluginManager.rst doc/Extensions.rst doc/FilteredPluginManager.rst doc/IMultiprocessChildPlugin.rst doc/IPlugin.rst doc/IPluginLocator.rst doc/Makefile doc/MultiprocessPluginManager.rst doc/MultiprocessPluginProxy.rst doc/PluginFileLocator.rst doc/PluginInfo.rst doc/PluginManager.rst doc/PluginManagerDecorator.rst doc/VersionedPluginManager.rst doc/conf.py doc/index.rst doc/make.bat test/__init__.py test/test_All.py test/test_AutoInstallPlugin.py test/test_ConfigPlugin.py test/test_ErrorInPlugin.py test/test_FilterPlugin.py test/test_PluginFileLocator.py test/test_PluginInfo.py test/test_SimpleMultiprocessPlugin.py test/test_SimplePlugin.py test/test_Singleton.py test/test_VersionedPlugin.py test/test_settings.py test/plugins/ConfigPlugin.py test/plugins/ErroneousPlugin.py test/plugins/LegacyMultiprocessPlugin.py test/plugins/SimpleMultiprocessPlugin.py test/plugins/SimplePlugin.py test/plugins/VersionedPlugin10.py test/plugins/VersionedPlugin11.py test/plugins/VersionedPlugin111.py test/plugins/VersionedPlugin12.py test/plugins/VersionedPlugin12a1.py test/plugins/configplugin.yapsy-config-plugin test/plugins/configplugin.yapsy-filter-plugin test/plugins/erroneousplugin.yapsy-error-plugin test/plugins/legacymultiprocessplugin.multiprocess-plugin test/plugins/simplemultiprocessplugin.multiprocess-plugin test/plugins/simpleplugin.yapsy-filter-plugin test/plugins/simpleplugin.yapsy-plugin test/plugins/versioned10.version-plugin test/plugins/versioned11.version-plugin test/plugins/versioned111.version-plugin test/plugins/versioned12.version-plugin test/plugins/versioned12a1.version-plugin test/pluginsasdirs/simpleplugin.yapsy-plugin test/pluginsasdirs/SimplePlugin/__init__.py test/pluginstoinstall/AutoInstallPlugin.py test/pluginstoinstall/autoinstallWRONGzipplugin.zip test/pluginstoinstall/autoinstallZIPplugin.zip test/pluginstoinstall/autoinstalldirplugin.yapsy-autoinstall-plugin test/pluginstoinstall/autoinstallplugin.yapsy-autoinstall-plugin test/pluginstoinstall/autoinstalldirplugin/__init__.py yapsy/AutoInstallPluginManager.py yapsy/ConfigurablePluginManager.py yapsy/FilteredPluginManager.py yapsy/IMultiprocessChildPlugin.py yapsy/IMultiprocessPlugin.py yapsy/IPlugin.py yapsy/IPluginLocator.py yapsy/MultiprocessPluginManager.py yapsy/MultiprocessPluginProxy.py yapsy/PluginFileLocator.py yapsy/PluginInfo.py yapsy/PluginManager.py yapsy/PluginManagerDecorator.py yapsy/VersionedPluginManager.py yapsy/__init__.py yapsy/compat.pyYapsy-1.12.0/Yapsy.egg-info/top_level.txt0000664000175000017500000000000613343011254021635 0ustar thibauldthibauld00000000000000yapsy Yapsy-1.12.0/Yapsy.egg-info/PKG-INFO0000664000175000017500000000440313343011254020205 0ustar thibauldthibauld00000000000000Metadata-Version: 1.1 Name: Yapsy Version: 1.12.0 Summary: Yet another plugin system Home-page: http://yapsy.sourceforge.net Author: Thibauld Nion Author-email: thibauld@tibonihoo.net License: BSD Description: Yapsy is a small library implementing the core mechanisms needed to build a plugin system into a wider application. The main purpose is to depend only on Python's standard libraries and to implement only the basic functionalities needed to detect, load and keep track of several plugins. It supports both Python 2 and 3. To use yapsy, make sure that the "yapsy" directory is in your Python loading path and just import the needed class from yapsy (e.g. "from yapsy.PluginManager import PluginManager"). To see more examples, you can have a look at the unit tests inside the "test" directory or at the "Showcase and tutorials" section of the documentation (http://yapsy.sourceforge.net/#showcase-and-tutorials). Please let me know if you find this useful. Site of the project: http://yapsy.sourceforge.net/ List of Contributors: - Thibauld Nion - Rob McMullen - Roger Gammans - Mathieu Havel - Mathieu Clabaut - Mark Fickett - Agustin Henze - qitta - Roberto Alsina - Josip Delic (delijati) - frmdstryr - Pierre-Yves Langlois - Guillaume Binet (gbin) - Blake Oliver (Oliver2213) - Xuecheng Zhang (csuzhangxc) Contributions are welcome as pull requests, patches or tickets on the forge (https://sourceforge.net/projects/yapsy/) or on github (https://github.com/tibonihoo/yapsy). Keywords: plugin manager Platform: All Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 3 Classifier: Topic :: Software Development :: Libraries :: Python Modules Yapsy-1.12.0/Yapsy.egg-info/dependency_links.txt0000664000175000017500000000000113343011254023155 0ustar thibauldthibauld00000000000000 Yapsy-1.12.0/setup.cfg0000664000175000017500000000004613343011254016131 0ustar thibauldthibauld00000000000000[egg_info] tag_build = tag_date = 0 Yapsy-1.12.0/PKG-INFO0000664000175000017500000000440313343011254015406 0ustar thibauldthibauld00000000000000Metadata-Version: 1.1 Name: Yapsy Version: 1.12.0 Summary: Yet another plugin system Home-page: http://yapsy.sourceforge.net Author: Thibauld Nion Author-email: thibauld@tibonihoo.net License: BSD Description: Yapsy is a small library implementing the core mechanisms needed to build a plugin system into a wider application. The main purpose is to depend only on Python's standard libraries and to implement only the basic functionalities needed to detect, load and keep track of several plugins. It supports both Python 2 and 3. To use yapsy, make sure that the "yapsy" directory is in your Python loading path and just import the needed class from yapsy (e.g. "from yapsy.PluginManager import PluginManager"). To see more examples, you can have a look at the unit tests inside the "test" directory or at the "Showcase and tutorials" section of the documentation (http://yapsy.sourceforge.net/#showcase-and-tutorials). Please let me know if you find this useful. Site of the project: http://yapsy.sourceforge.net/ List of Contributors: - Thibauld Nion - Rob McMullen - Roger Gammans - Mathieu Havel - Mathieu Clabaut - Mark Fickett - Agustin Henze - qitta - Roberto Alsina - Josip Delic (delijati) - frmdstryr - Pierre-Yves Langlois - Guillaume Binet (gbin) - Blake Oliver (Oliver2213) - Xuecheng Zhang (csuzhangxc) Contributions are welcome as pull requests, patches or tickets on the forge (https://sourceforge.net/projects/yapsy/) or on github (https://github.com/tibonihoo/yapsy). Keywords: plugin manager Platform: All Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 3 Classifier: Topic :: Software Development :: Libraries :: Python Modules Yapsy-1.12.0/MANIFEST.in0000664000175000017500000000034012575022002016042 0ustar thibauldthibauld00000000000000include README.txt include LICENSE.txt include CHANGELOG.txt include runtests.py recursive-include test *.py *-plugin *.zip recursive-include yapsy *.py recursive-include artwork * recursive-include doc * prune doc/_build Yapsy-1.12.0/README.txt0000664000175000017500000000244713343006070016015 0ustar thibauldthibauld00000000000000Yapsy is a small library implementing the core mechanisms needed to build a plugin system into a wider application. The main purpose is to depend only on Python's standard libraries and to implement only the basic functionalities needed to detect, load and keep track of several plugins. It supports both Python 2 and 3. To use yapsy, make sure that the "yapsy" directory is in your Python loading path and just import the needed class from yapsy (e.g. "from yapsy.PluginManager import PluginManager"). To see more examples, you can have a look at the unit tests inside the "test" directory or at the "Showcase and tutorials" section of the documentation (http://yapsy.sourceforge.net/#showcase-and-tutorials). Please let me know if you find this useful. Site of the project: http://yapsy.sourceforge.net/ List of Contributors: - Thibauld Nion - Rob McMullen - Roger Gammans - Mathieu Havel - Mathieu Clabaut - Mark Fickett - Agustin Henze - qitta - Roberto Alsina - Josip Delic (delijati) - frmdstryr - Pierre-Yves Langlois - Guillaume Binet (gbin) - Blake Oliver (Oliver2213) - Xuecheng Zhang (csuzhangxc) Contributions are welcome as pull requests, patches or tickets on the forge (https://sourceforge.net/projects/yapsy/) or on github (https://github.com/tibonihoo/yapsy). Yapsy-1.12.0/runtests.py0000664000175000017500000000206412425477475016600 0ustar thibauldthibauld00000000000000#!/usr/bin/python # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- """ Main file to launch the tests. """ import sys import getopt import unittest import logging from test.test_All import MainTestSuite def usage(): """ Show/explain the options. """ return """python main.py [OPTIONS] Options: -h or --help Print this help text -d Switch the logger to DEBUG mode. -v Switch the test to verbose mode. """ def main(argv): """ Launch all the test. """ try: opts, args = getopt.getopt(argv[1:], "vdh", ["help"]) except getopt.GetoptError: print(usage()) sys.exit(2) loglevel = logging.ERROR test_verbosity = 1 for o,a in opts: if o in ("-h","--help"): print(usage()) sys.exit(0) elif o == "-d": loglevel = logging.DEBUG elif o == "-v": test_verbosity = 2 logging.basicConfig(level= loglevel, format='%(asctime)s %(levelname)s %(message)s') # launch the testing process unittest.TextTestRunner(verbosity=test_verbosity).run(MainTestSuite) if __name__=="__main__": main(sys.argv) Yapsy-1.12.0/test/0000775000175000017500000000000013343011254015267 5ustar thibauldthibauld00000000000000Yapsy-1.12.0/test/__init__.py0000664000175000017500000000016412575022002017400 0ustar thibauldthibauld00000000000000# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- """ Gathers the unittests of yapsy """ Yapsy-1.12.0/test/test_AutoInstallPlugin.py0000664000175000017500000002336013343003466022330 0ustar thibauldthibauld00000000000000# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- from . import test_settings import unittest import sys import os import shutil from yapsy.AutoInstallPluginManager import AutoInstallPluginManager class AutoInstallTestsCase(unittest.TestCase): """ Test the correct installation and loading of a simple plugin. """ def setUp(self): """ init """ # create the plugin manager self.storing_dir = os.path.join( os.path.dirname(os.path.abspath(__file__)),"plugins") self.pluginManager = AutoInstallPluginManager( self.storing_dir, directories_list=[self.storing_dir], plugin_info_ext="yapsy-autoinstall-plugin") # load the plugins that may be found self.pluginManager.collectPlugins() # Will be used later self.plugin_info = None self.new_plugins_waiting_dir = os.path.join( os.path.dirname(os.path.abspath(__file__)),"pluginstoinstall") def tearDown(self): """ Clean the plugin installation directory. """ try: os.remove(os.path.join(self.pluginManager.plugins_places[0], "autoinstallplugin.yapsy-autoinstall-plugin")) except OSError: pass try: os.remove(os.path.join(self.pluginManager.plugins_places[0], "AutoInstallPlugin.py")) except OSError: pass try: os.remove(os.path.join(self.pluginManager.plugins_places[0], "autoinstalldirplugin.yapsy-autoinstall-plugin")) except OSError: pass try: shutil.rmtree(os.path.join(self.pluginManager.plugins_places[0], "autoinstalldirplugin")) except OSError: pass def plugin_loading_check_none(self): """ Test that no plugin has been loaded. """ # check nb of categories self.assertEqual(len(self.pluginManager.getCategories()),1) sole_category = self.pluginManager.getCategories()[0] # check the number of plugins self.assertEqual(len(self.pluginManager.getPluginsOfCategory(sole_category)),0) def plugin_loading_check(self,new_plugin_name): """ Test if the correct plugin has been loaded. """ if self.plugin_info is None: # check nb of categories self.assertEqual(len(self.pluginManager.getCategories()),1) sole_category = self.pluginManager.getCategories()[0] # check the number of plugins self.assertEqual(len(self.pluginManager.getPluginsOfCategory(sole_category)),1) self.plugin_info = self.pluginManager.getPluginsOfCategory(sole_category)[0] # test that the name of the plugin has been correctly defined self.assertEqual(self.plugin_info.name,new_plugin_name) self.assertEqual(sole_category,self.plugin_info.category) else: self.assertTrue(True) def testGetSetInstallDir(self): """ Test getting and setting install dir. """ self.assertEqual(self.storing_dir,self.pluginManager.getInstallDir()) custom_install_dir = os.path.join("mouf", "bla") self.pluginManager.setInstallDir(custom_install_dir) self.assertEqual(custom_install_dir, self.pluginManager.getInstallDir()) def testNoneLoaded(self): """ Test if the correct plugin has been loaded. """ self.plugin_loading_check_none() def testInstallFile(self): """ Test if the correct plugin (defined by a file) can be installed and loaded. """ install_success = self.pluginManager.install(self.new_plugins_waiting_dir, "autoinstallplugin.yapsy-autoinstall-plugin") self.assertTrue(install_success) self.pluginManager.collectPlugins() self.plugin_loading_check("Auto Install Plugin") def testInstallDir(self): """ Test if the correct plugin (define by a directory) can be installed and loaded. """ install_success = self.pluginManager.install(self.new_plugins_waiting_dir, "autoinstalldirplugin.yapsy-autoinstall-plugin") self.assertTrue(install_success) self.pluginManager.collectPlugins() self.plugin_loading_check("Auto Install Dir Plugin") def testActivationAndDeactivation(self): """ Test if the activation procedure works. """ install_success = self.pluginManager.install(self.new_plugins_waiting_dir, "autoinstallplugin.yapsy-autoinstall-plugin") self.assertTrue(install_success) self.pluginManager.collectPlugins() self.plugin_loading_check("Auto Install Plugin") self.assertTrue(not self.plugin_info.plugin_object.is_activated) self.pluginManager.activatePluginByName(self.plugin_info.name, self.plugin_info.category) self.assertTrue(self.plugin_info.plugin_object.is_activated) self.pluginManager.deactivatePluginByName(self.plugin_info.name, self.plugin_info.category) self.assertTrue(not self.plugin_info.plugin_object.is_activated) class AutoInstallZIPTestsCase(unittest.TestCase): """ Test the correct installation and loading of a zipped plugin. """ def setUp(self): """ init """ # create the plugin manager storing_dir = os.path.join( os.path.dirname(os.path.abspath(__file__)),"plugins") self.pluginManager = AutoInstallPluginManager( storing_dir, directories_list=[storing_dir], plugin_info_ext="yapsy-autoinstall-plugin") # load the plugins that may be found self.pluginManager.collectPlugins() # Will be used later self.plugin_info = None self.new_plugins_waiting_dir = os.path.join( os.path.dirname(os.path.abspath(__file__)),"pluginstoinstall") def tearDown(self): """ Clean the plugin installation directory. """ try: os.remove(os.path.join(self.pluginManager.plugins_places[0], "autoinstallzipplugin.yapsy-autoinstall-plugin")) except OSError: pass try: shutil.rmtree(os.path.join(self.pluginManager.plugins_places[0], "autoinstallzipplugin")) except OSError: pass def plugin_loading_check_none(self): """ Test that no plugin has been loaded. """ # check nb of categories self.assertEqual(len(self.pluginManager.getCategories()),1) sole_category = self.pluginManager.getCategories()[0] # check the number of plugins self.assertEqual(len(self.pluginManager.getPluginsOfCategory(sole_category)),0) def plugin_loading_check(self,new_plugin_name): """ Test if the correct plugin has been loaded. """ if self.plugin_info is None: # check nb of categories self.assertEqual(len(self.pluginManager.getCategories()),1) sole_category = self.pluginManager.getCategories()[0] # check the number of plugins self.assertEqual(len(self.pluginManager.getPluginsOfCategory(sole_category)),1) self.plugin_info = self.pluginManager.getPluginsOfCategory(sole_category)[0] # test that the name of the plugin has been correctly defined self.assertEqual(self.plugin_info.name,new_plugin_name) self.assertEqual(sole_category,self.plugin_info.category) else: self.assertTrue(True) def testNoneLoaded(self): """ Test if the correct plugin has been loaded. """ self.plugin_loading_check_none() def testInstallZIP(self): """ Test if the correct plugin (define by a zip file) can be installed and loaded. """ test_file = os.path.join(self.new_plugins_waiting_dir,"autoinstallZIPplugin.zip") if sys.version_info < (2, 6): self.assertRaises(NotImplementedError,self.pluginManager.installFromZIP,test_file) return install_success = self.pluginManager.installFromZIP(test_file) self.assertTrue(install_success) self.pluginManager.collectPlugins() self.plugin_loading_check("Auto Install ZIP Plugin") def testInstallZIPFailOnWrongZip(self): """ Test if, when the zip file does not contain what is required the installation fails. """ test_file = os.path.join(self.new_plugins_waiting_dir,"autoinstallWRONGzipplugin.zip") if sys.version_info < (2, 6): self.assertRaises(NotImplementedError,self.pluginManager.installFromZIP,test_file) return install_success = self.pluginManager.installFromZIP(test_file) self.assertFalse(install_success) self.pluginManager.collectPlugins() self.plugin_loading_check_none() def testInstallZIPFailOnUnexistingFile(self): """ Test if, when the zip file is not a file. """ test_file = os.path.join(self.new_plugins_waiting_dir,"doesNotExists.zip") if sys.version_info < (2, 6): self.assertRaises(NotImplementedError,self.pluginManager.installFromZIP,test_file) return install_success = self.pluginManager.installFromZIP(test_file) self.assertFalse(install_success) self.pluginManager.collectPlugins() self.plugin_loading_check_none() def testInstallZIPFailOnNotAZipFile(self): """ Test if, when the zip file is not a valid zip. """ test_file = os.path.join(self.new_plugins_waiting_dir,"AutoInstallPlugin.py") if sys.version_info < (2, 6): self.assertRaises(NotImplementedError,self.pluginManager.installFromZIP,test_file) return install_success = self.pluginManager.installFromZIP(test_file) self.assertFalse(install_success) self.pluginManager.collectPlugins() self.plugin_loading_check_none() def testActivationAndDeactivation(self): """ Test if the activation procedure works. """ test_file = os.path.join(self.new_plugins_waiting_dir,"autoinstallZIPplugin.zip") if sys.version_info < (2, 6): self.assertRaises(NotImplementedError,self.pluginManager.installFromZIP,test_file) return install_success = self.pluginManager.installFromZIP(test_file) self.assertTrue(install_success) self.pluginManager.collectPlugins() self.plugin_loading_check("Auto Install ZIP Plugin") self.assertTrue(not self.plugin_info.plugin_object.is_activated) self.pluginManager.activatePluginByName(self.plugin_info.name, self.plugin_info.category) self.assertTrue(self.plugin_info.plugin_object.is_activated) self.pluginManager.deactivatePluginByName(self.plugin_info.name, self.plugin_info.category) self.assertTrue(not self.plugin_info.plugin_object.is_activated) suite = unittest.TestSuite([ unittest.TestLoader().loadTestsFromTestCase(AutoInstallTestsCase), unittest.TestLoader().loadTestsFromTestCase(AutoInstallZIPTestsCase), ]) Yapsy-1.12.0/test/test_All.py0000664000175000017500000000201012575022002017400 0ustar thibauldthibauld00000000000000# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- """ The test suite that binds them all... """ import sys import os import unittest # set correct loading path for test files sys.path.append( os.path.dirname( os.path.abspath(__file__))) # load the tests from . import test_SimplePlugin from . import test_Singleton from . import test_ConfigPlugin from . import test_VersionedPlugin from . import test_AutoInstallPlugin from . import test_FilterPlugin from . import test_ErrorInPlugin from . import test_PluginFileLocator from . import test_PluginInfo from . import test_SimpleMultiprocessPlugin # add them to a common test suite MainTestSuite = unittest.TestSuite( [ # add the tests suites below test_SimplePlugin.suite, test_Singleton.suite, test_ConfigPlugin.suite, test_VersionedPlugin.suite, test_AutoInstallPlugin.suite, test_FilterPlugin.suite, test_ErrorInPlugin.suite, test_PluginFileLocator.suite, test_PluginInfo.suite, test_SimpleMultiprocessPlugin.suite, ]) Yapsy-1.12.0/test/test_PluginInfo.py0000664000175000017500000000314512575022002020754 0ustar thibauldthibauld00000000000000# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- import test_settings from yapsy.compat import ConfigParser import unittest from yapsy.PluginInfo import PluginInfo class PluginInfoTest(unittest.TestCase): """ Test basic manipulations of PluginInfo. """ def testDefaultValuesAndAccessors(self): pi = PluginInfo("mouf","/bla/mouf") self.assertEqual("mouf",pi.name) self.assertEqual("/bla/mouf",pi.path) self.assertEqual(None,pi.plugin_object) self.assertEqual([],pi.categories) self.assertEqual(None,pi.error) self.assertEqual("0.0",pi.version) self.assertEqual("Unknown",pi.author) self.assertEqual("Unknown",pi.copyright) self.assertEqual("None",pi.website) self.assertEqual("",pi.description) self.assertEqual("UnknownCategory",pi.category) def testDetailsAccessors(self): pi = PluginInfo("mouf","/bla/mouf") details = ConfigParser() details.add_section("Core") details.set("Core","Name","hop") details.set("Core","Module","/greuh") details.add_section("Documentation") details.set("Documentation","Author","me") pi.details = details # Beware this is not so obvious: the plugin info still points # (and possibly modifies) the same instance of ConfigParser self.assertEqual(details,pi.details) # also the name and path are kept to their original value when # the details is set in one go. self.assertEqual("mouf",pi.name) self.assertEqual("/bla/mouf",pi.path) # check that some other info do change... self.assertEqual("me",pi.author) suite = unittest.TestSuite([ unittest.TestLoader().loadTestsFromTestCase(PluginInfoTest), ]) Yapsy-1.12.0/test/test_settings.py0000664000175000017500000000075412575022002020545 0ustar thibauldthibauld00000000000000# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- import os import sys import logging TEST_MESSAGE = logging.debug TEMP_CONFIG_FILE_NAME=os.path.join( os.path.dirname( os.path.abspath(__file__)), "tempconfig") # set correct loading path for yapsy's files sys.path.insert(0, os.path.dirname( os.path.dirname( os.path.abspath(__file__)))) sys.path.insert(0, os.path.dirname( os.path.dirname( os.path.dirname( os.path.abspath(__file__))))) Yapsy-1.12.0/test/plugins/0000775000175000017500000000000013343011254016750 5ustar thibauldthibauld00000000000000Yapsy-1.12.0/test/plugins/versioned111.version-plugin0000664000175000017500000000031712044762520024103 0ustar thibauldthibauld00000000000000[Core] Name = Versioned Plugin Module = VersionedPlugin111 [Documentation] Author = Rob McMullen Version = 1.1.1 Website = http://mathbench.sourceforge.net Description = A simple plugin for version testing Yapsy-1.12.0/test/plugins/erroneousplugin.yapsy-error-plugin0000664000175000017500000000033412045505157025732 0ustar thibauldthibauld00000000000000[Core] Name = Erroreous Plugin Module = ErroneousPlugin [Documentation] Author = Thibauld Nion Version = 0.1 Website = http://yapsy.sourceforge.net Description = A simple plugin trigger a syntax error for basic testing Yapsy-1.12.0/test/plugins/ConfigPlugin.py0000664000175000017500000000164312575022002021711 0ustar thibauldthibauld00000000000000# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- """ This is certainly the second simplest plugin ever. """ from yapsy.IPlugin import IPlugin class ConfigPlugin(IPlugin): """ Try to use the methods with which it has been decorated. """ def __init__(self): """ init """ # initialise parent class IPlugin.__init__(self) def activate(self): """ Call the parent class's acivation method """ IPlugin.activate(self) return def deactivate(self): """ Just call the parent class's method """ IPlugin.deactivate(self) def choseTestOption(self, value): """ Set an option to a given value. """ self.setConfigOption("Test",value) def checkTestOption(self): """ Test if the test option is here. """ return self.hasConfigOption("Test") def getTestOption(self): """ Return the value of the test option. """ return self.getConfigOption("Test") Yapsy-1.12.0/test/plugins/simplemultiprocessplugin.multiprocess-plugin0000664000175000017500000000032012575022002030073 0ustar thibauldthibauld00000000000000[Core] Name = Simple Multiprocess Plugin Module = SimpleMultiprocessPlugin [Documentation] Author = Pierre-Yves Langlois Version = 0.1 Description = A minimal plugin to test multiprocessing Copyright = 2015 Yapsy-1.12.0/test/plugins/legacymultiprocessplugin.multiprocess-plugin0000664000175000017500000000032013343003466030055 0ustar thibauldthibauld00000000000000[Core] Name = Legacy Multiprocess Plugin Module = LegacyMultiprocessPlugin [Documentation] Author = Pierre-Yves Langlois Version = 0.1 Description = A minimal plugin to test multiprocessing Copyright = 2015 Yapsy-1.12.0/test/plugins/SimpleMultiprocessPlugin.py0000664000175000017500000000107013343003466024350 0ustar thibauldthibauld00000000000000# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- """ A simple multiprocessed plugin that echoes the content received to the parent """ from yapsy.IMultiprocessPlugin import IMultiprocessPlugin class SimpleMultiprocessPlugin(IMultiprocessPlugin): """ Only trigger the expected test results. """ def __init__(self, parent_pipe): IMultiprocessPlugin.__init__(self, parent_pipe=parent_pipe) def run(self): content_from_parent = self.parent_pipe.recv() self.parent_pipe.send("{0}|echo_from_child".format(content_from_parent)) Yapsy-1.12.0/test/plugins/VersionedPlugin111.py0000664000175000017500000000137612575022002022670 0ustar thibauldthibauld00000000000000# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- """ This is certainly the second simplest plugin ever. """ from test_settings import TEST_MESSAGE from yapsy.IPlugin import IPlugin class VersionedPlugin111(IPlugin): """ Only trigger the expected test results. """ def __init__(self): """ init """ # initialise parent class IPlugin.__init__(self) TEST_MESSAGE("Version 1.1.1") def activate(self): """ On activation tell that this has been successfull. """ # get the automatic procedure from IPlugin IPlugin.activate(self) return def deactivate(self): """ On deactivation check that the 'activated' flag was on then tell everything's ok to the test procedure. """ IPlugin.deactivate(self) Yapsy-1.12.0/test/plugins/VersionedPlugin10.py0000664000175000017500000000151612575022002022602 0ustar thibauldthibauld00000000000000# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- """ This is certainly the second simplest plugin ever. """ from test_settings import TEST_MESSAGE from yapsy.IPlugin import IPlugin class VersionedPlugin10(IPlugin): """ Only trigger the expected test results. """ def __init__(self): """ init """ # initialise parent class IPlugin.__init__(self) TEST_MESSAGE("Version 1.0") def activate(self): """ On activation tell that this has been successfull. """ # get the automatic procedure from IPlugin IPlugin.activate(self) TEST_MESSAGE("Activated Version 1.0!") return def deactivate(self): """ On deactivation check that the 'activated' flag was on then tell everything's ok to the test procedure. """ IPlugin.deactivate(self) TEST_MESSAGE("Deactivated Version 1.0!") Yapsy-1.12.0/test/plugins/versioned10.version-plugin0000664000175000017500000000031412044762520024016 0ustar thibauldthibauld00000000000000[Core] Name = Versioned Plugin Module = VersionedPlugin10 [Documentation] Author = Rob McMullen Version = 1.0 Website = http://mathbench.sourceforge.net Description = A simple plugin for version testing Yapsy-1.12.0/test/plugins/versioned11.version-plugin0000664000175000017500000000031412044762520024017 0ustar thibauldthibauld00000000000000[Core] Name = Versioned Plugin Module = VersionedPlugin11 [Documentation] Author = Rob McMullen Version = 1.1 Website = http://mathbench.sourceforge.net Description = A simple plugin for version testing Yapsy-1.12.0/test/plugins/configplugin.yapsy-filter-plugin0000664000175000017500000000031512044762520025307 0ustar thibauldthibauld00000000000000[Core] Name = Config Plugin Module = ConfigPlugin [Documentation] Author = Thibauld Nion Version = 0.1 Website = http://mathbench.sourceforge.net Description = A simple plugin with configuration handling Yapsy-1.12.0/test/plugins/simpleplugin.yapsy-plugin0000664000175000017500000000033412342441472024052 0ustar thibauldthibauld00000000000000[Core] Name = Simple Plugin Module = SimplePlugin [Documentation] Author = Thibauld Nion Version = 0.1 Website = http://mathbench.sourceforge.net Description = A simple plugin usefull for basic testing Copyright = 2014 Yapsy-1.12.0/test/plugins/VersionedPlugin12a1.py0000664000175000017500000000152412575022002023025 0ustar thibauldthibauld00000000000000# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- """ This is certainly the second simplest plugin ever. """ from test_settings import TEST_MESSAGE from yapsy.IPlugin import IPlugin class VersionedPlugin12a1(IPlugin): """ Only trigger the expected test results. """ def __init__(self): """ init """ # initialise parent class IPlugin.__init__(self) TEST_MESSAGE("Version 1.2a1") def activate(self): """ On activation tell that this has been successfull. """ # get the automatic procedure from IPlugin IPlugin.activate(self) TEST_MESSAGE("Activated Version 1.2a1!") return def deactivate(self): """ On deactivation check that the 'activated' flag was on then tell everything's ok to the test procedure. """ IPlugin.deactivate(self) TEST_MESSAGE("Deactivated Version 1.2a1!") Yapsy-1.12.0/test/plugins/SimplePlugin.py0000664000175000017500000000126212575022002021732 0ustar thibauldthibauld00000000000000# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- """ This is certainly the second simplest plugin ever. """ from yapsy.IPlugin import IPlugin class SimplePlugin(IPlugin): """ Only trigger the expected test results. """ def __init__(self): """ init """ # initialise parent class IPlugin.__init__(self) def activate(self): """ On activation tell that this has been successfull. """ # get the automatic procedure from IPlugin IPlugin.activate(self) return def deactivate(self): """ On deactivation check that the 'activated' flag was on then tell everything's ok to the test procedure. """ IPlugin.deactivate(self) Yapsy-1.12.0/test/plugins/versioned12.version-plugin0000664000175000017500000000031412044762520024020 0ustar thibauldthibauld00000000000000[Core] Name = Versioned Plugin Module = VersionedPlugin12 [Documentation] Author = Rob McMullen Version = 1.2 Website = http://mathbench.sourceforge.net Description = A simple plugin for version testing Yapsy-1.12.0/test/plugins/configplugin.yapsy-config-plugin0000664000175000017500000000031512044762520025267 0ustar thibauldthibauld00000000000000[Core] Name = Config Plugin Module = ConfigPlugin [Documentation] Author = Thibauld Nion Version = 0.1 Website = http://mathbench.sourceforge.net Description = A simple plugin with configuration handling Yapsy-1.12.0/test/plugins/ErroneousPlugin.py0000664000175000017500000000134212575022002022461 0ustar thibauldthibauld00000000000000# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- """ This is certainly the second simplest plugin ever. """ from yapsy.IPlugin import IPlugin from import_error import the_error_is_here class ErrorenousPlugin(IPlugin): """ Only trigger the expected test results. """ def __init__(self): """ init """ # initialise parent class IPlugin.__init__(self) def activate(self): """ On activation tell that this has been successfull. """ # get the automatic procedure from IPlugin IPlugin.activate(self) return def deactivate(self): """ On deactivation check that the 'activated' flag was on then tell everything's ok to the test procedure. """ IPlugin.deactivate(self) Yapsy-1.12.0/test/plugins/versioned12a1.version-plugin0000664000175000017500000000032012044762520024237 0ustar thibauldthibauld00000000000000[Core] Name = Versioned Plugin Module = VersionedPlugin12a1 [Documentation] Author = Rob McMullen Version = 1.2a1 Website = http://mathbench.sourceforge.net Description = A simple plugin for version testing Yapsy-1.12.0/test/plugins/VersionedPlugin12.py0000664000175000017500000000151412575022002022602 0ustar thibauldthibauld00000000000000# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- """ This is certainly the second simplest plugin ever. """ from test_settings import TEST_MESSAGE from yapsy.IPlugin import IPlugin class VersionedPlugin12(IPlugin): """ Only trigger the expected test results. """ def __init__(self): """ init """ # initialise parent class IPlugin.__init__(self) TEST_MESSAGE("Version 1.2") def activate(self): """ On activation tell that this has been successfull. """ # get the automatic procedure from IPlugin IPlugin.activate(self) TEST_MESSAGE("Activated Version 1.2!") return def deactivate(self): """ On deactivation check that the 'activated' flag was on then tell everything's ok to the test procedure. """ IPlugin.deactivate(self) TEST_MESSAGE("Deactivated Version 1.2!") Yapsy-1.12.0/test/plugins/simpleplugin.yapsy-filter-plugin0000664000175000017500000000031312044762520025331 0ustar thibauldthibauld00000000000000[Core] Name = Simple Plugin Module = SimplePlugin [Documentation] Author = Thibauld Nion Version = 0.1 Website = http://mathbench.sourceforge.net Description = A simple plugin usefull for basic testing Yapsy-1.12.0/test/plugins/VersionedPlugin11.py0000664000175000017500000000137312575022002022604 0ustar thibauldthibauld00000000000000# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- """ This is certainly the second simplest plugin ever. """ from test_settings import TEST_MESSAGE from yapsy.IPlugin import IPlugin class VersionedPlugin11(IPlugin): """ Only trigger the expected test results. """ def __init__(self): """ init """ # initialise parent class IPlugin.__init__(self) TEST_MESSAGE("Version 1.1") def activate(self): """ On activation tell that this has been successfull. """ # get the automatic procedure from IPlugin IPlugin.activate(self) return def deactivate(self): """ On deactivation check that the 'activated' flag was on then tell everything's ok to the test procedure. """ IPlugin.deactivate(self) Yapsy-1.12.0/test/plugins/LegacyMultiprocessPlugin.py0000664000175000017500000000111113343003466024317 0ustar thibauldthibauld00000000000000# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- """ A simple multiprocessed plugin that echoes the content received to the parent """ from yapsy.IMultiprocessChildPlugin import IMultiprocessChildPlugin class LegacyMultiprocessPlugin(IMultiprocessChildPlugin): """ Only trigger the expected test results. """ def __init__(self, parent_pipe): IMultiprocessChildPlugin.__init__(self, parent_pipe=parent_pipe) def run(self): content_from_parent = self.parent_pipe.recv() self.parent_pipe.send("{0}|echo_from_child".format(content_from_parent)) Yapsy-1.12.0/test/pluginsasdirs/0000775000175000017500000000000013343011254020156 5ustar thibauldthibauld00000000000000Yapsy-1.12.0/test/pluginsasdirs/SimplePlugin/0000775000175000017500000000000013343011254022566 5ustar thibauldthibauld00000000000000Yapsy-1.12.0/test/pluginsasdirs/SimplePlugin/__init__.py0000664000175000017500000000126112575022002024676 0ustar thibauldthibauld00000000000000# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- """ This is certainly the second simplest plugin ever. """ from yapsy.IPlugin import IPlugin class SimplePlugin(IPlugin): """ Only trigger the expected test results. """ def __init__(self): """ init """ # initialise parent class IPlugin.__init__(self) def activate(self): """ On activation tell that this has been successfull. """ # get the automatic procedure from IPlugin IPlugin.activate(self) return def deactivate(self): """ On deactivation check that the 'activated' flag was on then tell everything's ok to the test procedure. """ IPlugin.deactivate(self) Yapsy-1.12.0/test/pluginsasdirs/simpleplugin.yapsy-plugin0000664000175000017500000000031312044762520025254 0ustar thibauldthibauld00000000000000[Core] Name = Simple Plugin Module = SimplePlugin [Documentation] Author = Thibauld Nion Version = 0.1 Website = http://mathbench.sourceforge.net Description = A simple plugin usefull for basic testing Yapsy-1.12.0/test/test_PluginFileLocator.py0000664000175000017500000004655613343003466022310 0ustar thibauldthibauld00000000000000# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- from . import test_settings import unittest import sys import os from yapsy.compat import ConfigParser, StringIO, str, builtin_str import tempfile import shutil import yapsy from yapsy import PLUGIN_NAME_FORBIDEN_STRING from yapsy.PluginManager import PluginManager from yapsy.PluginManager import IPlugin from yapsy.PluginInfo import PluginInfo from yapsy.IPluginLocator import IPluginLocator from yapsy.PluginFileLocator import PluginFileLocator from yapsy.PluginFileLocator import PluginFileAnalyzerWithInfoFile from yapsy.PluginFileLocator import PluginFileAnalyzerMathingRegex class IPluginLocatorTest(unittest.TestCase): def test_deprecated_method_dont_raise_notimplemetederror(self): class DummyPluginLocator(IPluginLocator): pass dpl = DummyPluginLocator() self.assertEqual((None,None,None),dpl.getPluginNameAndModuleFromStream(None)) dpl.setPluginInfoClass(PluginInfo) self.assertEqual(None,dpl.getPluginInfoClass()) dpl.setPluginPlaces([]) dpl.updatePluginPlaces([]) class PluginFileAnalyzerWithInfoFileTest(unittest.TestCase): """ Test that the "info file" analyzer enforces the correct policy. """ def setUp(self): """ init """ self.plugin_directory = os.path.join( os.path.dirname(os.path.abspath(__file__)), "plugins") self.yapsy_plugin_path = os.path.join(self.plugin_directory,"simpleplugin.yapsy-plugin") self.version_plugin_path = os.path.join(self.plugin_directory,"versioned11.version-plugin") self.yapsy_filter_plugin_path = os.path.join(self.plugin_directory,"simpleplugin.yapsy-filter-plugin") def test_Contruction(self): analyzer = PluginFileAnalyzerWithInfoFile("mouf") self.assertEqual(analyzer.name,"mouf") def test_isValid(self): analyzer = PluginFileAnalyzerWithInfoFile("mouf") self.assertTrue(analyzer.isValidPlugin(self.yapsy_plugin_path)) self.assertFalse(analyzer.isValidPlugin(self.version_plugin_path)) def test_getInfosDictFromPlugin(self): analyzer = PluginFileAnalyzerWithInfoFile("mouf") info_dict,cf_parser = analyzer.getInfosDictFromPlugin(self.plugin_directory, os.path.basename(self.yapsy_plugin_path)) self.assertEqual(info_dict, {'website': 'http://mathbench.sourceforge.net', 'description': 'A simple plugin usefull for basic testing', 'author': 'Thibauld Nion', 'version': '0.1', 'path': '%s' % os.path.join(self.plugin_directory,"SimplePlugin"), 'name': 'Simple Plugin', 'copyright': '2014'}) self.assertTrue(isinstance(cf_parser,ConfigParser)) def test_isValid_WithMultiExtensions(self): analyzer = PluginFileAnalyzerWithInfoFile("mouf",("yapsy-plugin","yapsy-filter-plugin")) self.assertTrue(analyzer.isValidPlugin(self.yapsy_plugin_path)) self.assertFalse(analyzer.isValidPlugin(self.version_plugin_path)) self.assertTrue(analyzer.isValidPlugin(self.yapsy_filter_plugin_path)) def test__extractCorePluginInfo_with_builtin_str_filename(self): plugin_desc_content = builtin_str("simpleplugin.yapsy-plugin") analyzer = PluginFileAnalyzerWithInfoFile("mouf", ("yapsy-plugin")) infos, parser = analyzer._extractCorePluginInfo(self.plugin_directory, plugin_desc_content) self.assertEqual("Simple Plugin", infos["name"]) self.assertEqual(os.path.join(self.plugin_directory, "SimplePlugin"), infos["path"]) def test__extractCorePluginInfo_with_unicode_filename(self): """Note: this test is redundant with its 'builtin_str' counterpart on Python3 but not on Python2""" # Note: compat.py redefines str as unicode for Python2 plugin_desc_content = str("simpleplugin.yapsy-plugin") analyzer = PluginFileAnalyzerWithInfoFile("mouf", ("yapsy-plugin")) infos, parser = analyzer._extractCorePluginInfo(self.plugin_directory, plugin_desc_content) self.assertEqual("Simple Plugin", infos["name"]) self.assertEqual(os.path.join(self.plugin_directory, "SimplePlugin"), infos["path"]) def test__extractCorePluginInfo_with_minimal_description(self): plugin_desc_content = StringIO("""\ [Core] Name = Simple Plugin Module = SimplePlugin """) analyzer = PluginFileAnalyzerWithInfoFile("mouf", ("yapsy-plugin")) infos, parser = analyzer._extractCorePluginInfo("bla",plugin_desc_content) self.assertEqual("Simple Plugin", infos["name"]) self.assertEqual(os.path.join("bla","SimplePlugin"), infos["path"]) self.assertTrue(isinstance(parser,ConfigParser)) def test_getPluginNameAndModuleFromStream_with_invalid_descriptions(self): plugin_desc_content = StringIO("""\ [Core] Name = Bla{0}Bli Module = SimplePlugin """.format(PLUGIN_NAME_FORBIDEN_STRING)) analyzer = PluginFileAnalyzerWithInfoFile("mouf", ("yapsy-plugin")) res = analyzer._extractCorePluginInfo("bla",plugin_desc_content) self.assertEqual((None, None), res) plugin_desc_content = StringIO("""\ [Core] Name = Simple Plugin """) analyzer = PluginFileAnalyzerWithInfoFile("mouf", ("yapsy-plugin")) res = analyzer._extractCorePluginInfo("bla",plugin_desc_content) self.assertEqual((None, None), res) plugin_desc_content = StringIO("""\ [Core] Module = Simple Plugin """) res = analyzer._extractCorePluginInfo("bla",plugin_desc_content) self.assertEqual((None, None), res) plugin_desc_content = StringIO("""\ [Mouf] Bla = Simple Plugin """) res = analyzer._extractCorePluginInfo("bla",plugin_desc_content) self.assertEqual((None, None), res) class PluginFileAnalyzerMathingRegexTest(unittest.TestCase): """ Test that the "regex" analyzer enforces the correct policy. """ def setUp(self): """ init """ self.plugin_directory = os.path.join( os.path.dirname(os.path.abspath(__file__)), "plugins") self.yapsy_plugin_path = os.path.join(self.plugin_directory,"SimplePlugin.py") self.version_plugin_10_path = os.path.join(self.plugin_directory,"VersionedPlugin10.py") self.version_plugin_12_path = os.path.join(self.plugin_directory,"VersionedPlugin12.py") def test_Contruction(self): analyzer = PluginFileAnalyzerMathingRegex("mouf",".*") self.assertEqual(analyzer.name,"mouf") def test_isValid(self): analyzer = PluginFileAnalyzerMathingRegex("mouf",r".*VersionedPlugin\d+\.py$") self.assertFalse(analyzer.isValidPlugin(self.yapsy_plugin_path)) self.assertTrue(analyzer.isValidPlugin(self.version_plugin_10_path)) self.assertTrue(analyzer.isValidPlugin(self.version_plugin_12_path)) def test_getInfosDictFromPlugin(self): analyzer = PluginFileAnalyzerMathingRegex("mouf",r".*VersionedPlugin\d+\.py$") info_dict,cf_parser = analyzer.getInfosDictFromPlugin(self.plugin_directory, os.path.basename(self.version_plugin_10_path)) self.assertEqual(info_dict,{'path': self.version_plugin_10_path, 'name': 'VersionedPlugin10'}) self.assertTrue(isinstance(cf_parser,ConfigParser)) class PluginFileLocatorTest(unittest.TestCase): """ Test that the "file" locator. NB: backward compatible methods are not directly tested here. We rely only on the 'indirect' tests made for the classes that still depend on them. """ def setUp(self): """ init """ self.plugin_directory = os.path.join( os.path.dirname(os.path.abspath(__file__)), "plugins") self.plugin_as_dir_directory = os.path.join( os.path.dirname(os.path.abspath(__file__)), "pluginsasdirs") self.plugin_info_file = "simpleplugin.yapsy-plugin" self.plugin_name = "SimplePlugin" self.plugin_impl_file = self.plugin_name+".py" def test_default_plugins_place_is_parent_dir(self): """Test a non-trivial default behaviour introduced some time ago :S""" pl = PluginFileLocator() expected_yapsy_module_path = os.path.dirname(yapsy.__file__) first_plugin_place = pl.plugins_places[0] self.assertEqual(expected_yapsy_module_path, first_plugin_place) def test_given_string_as_plugin_places_raises_error(self): pl = PluginFileLocator() self.assertRaises(ValueError, pl.setPluginPlaces, "/mouf") def test_locatePlugins(self): pl = PluginFileLocator() pl.setPluginPlaces([self.plugin_directory]) candidates, num = pl.locatePlugins() self.assertEqual(num,1) self.assertEqual(len(candidates),num) self.assertEqual(os.path.join(self.plugin_directory,self.plugin_info_file), candidates[0][0]) self.assertEqual(os.path.join(self.plugin_directory,self.plugin_name), candidates[0][1]) self.assertTrue(isinstance(candidates[0][2],PluginInfo)) def test_locatePlugins_when_plugin_is_symlinked(self): if sys.platform.startswith("win"): return temp_dir = tempfile.mkdtemp() try: plugin_info_file = "simpleplugin.yapsy-plugin" plugin_impl_file = "SimplePlugin.py" os.symlink(os.path.join(self.plugin_directory,plugin_info_file), os.path.join(temp_dir,plugin_info_file)) os.symlink(os.path.join(self.plugin_directory,plugin_impl_file), os.path.join(temp_dir,plugin_impl_file)) pl = PluginFileLocator() pl.setPluginPlaces([temp_dir]) candidates, num = pl.locatePlugins() self.assertEqual(num,1) self.assertEqual(len(candidates),num) self.assertEqual(os.path.join(temp_dir,self.plugin_info_file), candidates[0][0]) self.assertEqual(os.path.join(temp_dir,self.plugin_name), candidates[0][1]) self.assertTrue(isinstance(candidates[0][2],PluginInfo)) finally: shutil.rmtree(temp_dir) def test_locatePlugins_when_plugin_is_a_directory(self): pl = PluginFileLocator() pl.setPluginPlaces([self.plugin_as_dir_directory]) candidates, num = pl.locatePlugins() self.assertEqual(num,1) self.assertEqual(len(candidates),num) self.assertEqual(os.path.join(self.plugin_as_dir_directory,self.plugin_info_file), candidates[0][0]) self.assertEqual(os.path.join(self.plugin_as_dir_directory,self.plugin_name, "__init__"), candidates[0][1]) self.assertTrue(isinstance(candidates[0][2],PluginInfo)) def test_locatePlugins_when_plugin_is_a_symlinked_directory(self): if sys.platform.startswith("win"): return temp_dir = tempfile.mkdtemp() try: plugin_info_file = "simpleplugin.yapsy-plugin" plugin_impl_dir = "SimplePlugin" os.symlink(os.path.join(self.plugin_as_dir_directory,plugin_info_file), os.path.join(temp_dir,plugin_info_file)) os.symlink(os.path.join(self.plugin_as_dir_directory,plugin_impl_dir), os.path.join(temp_dir,plugin_impl_dir)) pl = PluginFileLocator() pl.setPluginPlaces([temp_dir]) candidates, num = pl.locatePlugins() self.assertEqual(num,1) self.assertEqual(len(candidates),num) self.assertEqual(os.path.join(temp_dir,self.plugin_info_file), candidates[0][0]) self.assertEqual(os.path.join(temp_dir,self.plugin_name,"__init__"), candidates[0][1]) self.assertTrue(isinstance(candidates[0][2],PluginInfo)) finally: shutil.rmtree(temp_dir) def test_locatePlugins_recursively_when_plugin_is_a_directory(self): temp_dir = tempfile.mkdtemp() try: temp_sub_dir = os.path.join(temp_dir,"plugins") shutil.copytree(self.plugin_as_dir_directory,temp_sub_dir) pl = PluginFileLocator() pl.setPluginPlaces([temp_dir]) candidates, num = pl.locatePlugins() self.assertEqual(num,1) self.assertEqual(len(candidates),num) self.assertEqual(os.path.join(temp_sub_dir,self.plugin_info_file), candidates[0][0]) self.assertEqual(os.path.join(temp_sub_dir,self.plugin_name, "__init__"), candidates[0][1]) self.assertTrue(isinstance(candidates[0][2],PluginInfo)) finally: shutil.rmtree(temp_dir) def test_locatePlugins_recursively_fails_when_recursion_is_disabled(self): temp_dir = tempfile.mkdtemp() try: temp_sub_dir = os.path.join(temp_dir,"plugins") shutil.copytree(self.plugin_as_dir_directory,temp_sub_dir) pl = PluginFileLocator() pl.disableRecursiveScan() pl.setPluginPlaces([temp_dir]) candidates, num = pl.locatePlugins() self.assertEqual(num,0) self.assertEqual(len(candidates),num) finally: shutil.rmtree(temp_dir) def test_locatePlugins_recursively_when_plugin_is_a_symlinked_directory(self): if sys.platform.startswith("win"): return temp_dir = tempfile.mkdtemp() try: temp_sub_dir = os.path.join(temp_dir,"plugins") os.mkdir(temp_sub_dir) plugin_info_file = "simpleplugin.yapsy-plugin" plugin_impl_dir = "SimplePlugin" os.symlink(os.path.join(self.plugin_as_dir_directory,plugin_info_file), os.path.join(temp_sub_dir,plugin_info_file)) os.symlink(os.path.join(self.plugin_as_dir_directory,plugin_impl_dir), os.path.join(temp_sub_dir,plugin_impl_dir)) pl = PluginFileLocator() pl.setPluginPlaces([temp_dir]) candidates, num = pl.locatePlugins() self.assertEqual(num,1) self.assertEqual(len(candidates),num) self.assertEqual(os.path.join(temp_sub_dir,self.plugin_info_file), candidates[0][0]) self.assertEqual(os.path.join(temp_sub_dir,self.plugin_name, "__init__"), candidates[0][1]) self.assertTrue(isinstance(candidates[0][2],PluginInfo)) finally: shutil.rmtree(temp_dir) def test_locatePlugins_recursively_when_plugin_parent_dir_is_a_symlinked_directory(self): if sys.platform.startswith("win"): return # This actually reproduced the "Plugin detection doesn't follow symlinks" bug # at http://sourceforge.net/p/yapsy/bugs/19/ temp_dir = tempfile.mkdtemp() try: temp_sub_dir = os.path.join(temp_dir,"plugins") os.symlink(self.plugin_as_dir_directory,temp_sub_dir) pl = PluginFileLocator() pl.setPluginPlaces([temp_dir]) candidates, num = pl.locatePlugins() self.assertEqual(num,1) self.assertEqual(len(candidates),num) self.assertEqual(os.path.join(temp_sub_dir,self.plugin_info_file), candidates[0][0]) self.assertEqual(os.path.join(temp_sub_dir,self.plugin_name, "__init__"), candidates[0][1]) self.assertTrue(isinstance(candidates[0][2],PluginInfo)) finally: shutil.rmtree(temp_dir) def test_gatherCorePluginInfo(self): pl = PluginFileLocator() plugin_info,cf_parser = pl.gatherCorePluginInfo(self.plugin_directory,"simpleplugin.yapsy-plugin") self.assertTrue(plugin_info.name,"Simple Plugin") self.assertTrue(isinstance(cf_parser,ConfigParser)) plugin_info,cf_parser = pl.gatherCorePluginInfo(self.plugin_directory,"notaplugin.atall") self.assertEqual(plugin_info,None) self.assertEqual(cf_parser,None) def test_setAnalyzer(self): pl = PluginFileLocator() pl.setPluginPlaces([self.plugin_directory]) newAnalyzer = PluginFileAnalyzerMathingRegex("mouf",r".*VersionedPlugin\d+\.py$") pl.setAnalyzers([newAnalyzer]) candidates, num = pl.locatePlugins() self.assertEqual(num,4) self.assertEqual(len(candidates),num) def test_appendAnalyzer(self): pl = PluginFileLocator() pl.setPluginPlaces([self.plugin_directory]) newAnalyzer = PluginFileAnalyzerMathingRegex("mouf",r".*VersionedPlugin\d+\.py$") pl.appendAnalyzer(newAnalyzer) candidates, num = pl.locatePlugins() self.assertEqual(num,5) self.assertEqual(len(candidates),num) def test_removeAnalyzers_when_analyzer_is_unknown(self): pl = PluginFileLocator() pl.setPluginPlaces([self.plugin_directory]) pl.removeAnalyzers("nogo") def test_removeAnalyzers(self): pl = PluginFileLocator() pl.setPluginPlaces([self.plugin_directory]) newAnalyzer = PluginFileAnalyzerMathingRegex("mouf",r".*VersionedPlugin\d+\.py$") pl.appendAnalyzer(newAnalyzer) pl.removeAnalyzers("info_ext") candidates, num = pl.locatePlugins() self.assertEqual(num,4) self.assertEqual(len(candidates),num) def test_removeAllAnalyzers(self): pl = PluginFileLocator() pl.setPluginPlaces([self.plugin_directory]) pl.removeAllAnalyzer() candidates, num = pl.locatePlugins() self.assertEqual(num,0) self.assertEqual(len(candidates),num) def test_setPluginInfoClass_for_named_analyzer(self): class SpecificPluginInfo(PluginInfo): pass pl = PluginFileLocator() pl.setPluginPlaces([self.plugin_directory]) newAnalyzer = PluginFileAnalyzerMathingRegex("mouf",r".*VersionedPlugin\d+\.py$") pl.appendAnalyzer(newAnalyzer) pl.setPluginInfoClass(SpecificPluginInfo,"info_ext") candidates, num = pl.locatePlugins() self.assertEqual(num,5) self.assertEqual(len(candidates),num) versioned_plugins = [c for c in candidates if "VersionedPlugin" in c[0]] self.assertEqual(4,len(versioned_plugins)) for p in versioned_plugins: self.assertTrue(isinstance(p[2],PluginInfo)) simple_plugins = [c for c in candidates if "VersionedPlugin" not in c[0]] self.assertEqual(1,len(simple_plugins)) for p in simple_plugins: self.assertTrue(isinstance(p[2],SpecificPluginInfo)) class PluginManagerSetUpTest(unittest.TestCase): def test_default_init(self): pm = PluginManager() self.assertEqual(["Default"],pm.getCategories()) self.assertTrue(isinstance(pm.getPluginLocator(),PluginFileLocator)) def test_init_with_category_filter(self): pm = PluginManager(categories_filter={"Mouf": IPlugin}) self.assertEqual(["Mouf"],pm.getCategories()) self.assertTrue(isinstance(pm.getPluginLocator(),PluginFileLocator)) def test_init_with_plugin_info_ext(self): pm = PluginManager(plugin_info_ext="bla") self.assertEqual(["Default"],pm.getCategories()) self.assertTrue(isinstance(pm.getPluginLocator(),PluginFileLocator)) def test_init_with_plugin_locator(self): class SpecificLocator(IPluginLocator): pass pm = PluginManager(plugin_locator=SpecificLocator()) self.assertEqual(["Default"],pm.getCategories()) self.assertTrue(isinstance(pm.getPluginLocator(),SpecificLocator)) def test_init_with_plugin_info_ext_and_locator(self): class SpecificLocator(IPluginLocator): pass self.assertRaises(ValueError, PluginManager,plugin_info_ext="bla", plugin_locator=SpecificLocator()) def test_updatePluginPlaces(self): class SpecificLocator(IPluginLocator): pass pm = PluginManager() pm.setPluginPlaces(["bla/bli"]) pm.updatePluginPlaces(["mif/maf"]) self.assertEqual(set(["bla/bli","mif/maf"]),set(pm.getPluginLocator().plugins_places)) def test_getPluginCandidates_too_early(self): pm = PluginManager() self.assertRaises(RuntimeError,pm.getPluginCandidates) def test_setPluginLocator_with_plugin_info_class(self): class SpecificLocator(IPluginLocator): def getPluginInfoClass(self): return self.picls def setPluginInfoClass(self,picls): self.picls = picls class SpecificPluginInfo(PluginInfo): pass pm = PluginManager() pm.setPluginLocator(SpecificLocator(),picls=SpecificPluginInfo) self.assertEqual(SpecificPluginInfo,pm.getPluginInfoClass()) def test_setPluginLocator_with_invalid_locator(self): class SpecificLocator: pass pm = PluginManager() self.assertRaises(TypeError, pm.setPluginLocator,SpecificLocator()) def test_setPluginInfoClass_with_strategies(self): class SpecificPluginInfo(PluginInfo): pass class SpecificLocator(IPluginLocator): def setPluginInfoClass(self,cls,name): if not hasattr(self,"icls"): self.icls = {} self.icls[name] = cls loc = SpecificLocator() pm = PluginManager(plugin_locator=loc) pm.setPluginInfoClass(SpecificPluginInfo,["mouf","hop"]) self.assertEqual({"mouf":SpecificPluginInfo,"hop":SpecificPluginInfo},loc.icls) suite = unittest.TestSuite([ unittest.TestLoader().loadTestsFromTestCase(IPluginLocatorTest), unittest.TestLoader().loadTestsFromTestCase(PluginFileAnalyzerWithInfoFileTest), unittest.TestLoader().loadTestsFromTestCase(PluginFileAnalyzerMathingRegexTest), unittest.TestLoader().loadTestsFromTestCase(PluginFileLocatorTest), unittest.TestLoader().loadTestsFromTestCase(PluginManagerSetUpTest), ]) Yapsy-1.12.0/test/test_VersionedPlugin.py0000664000175000017500000001173112575022002022017 0ustar thibauldthibauld00000000000000# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- from . import test_settings from .test_settings import TEST_MESSAGE import unittest import os from yapsy.IPlugin import IPlugin from yapsy.VersionedPluginManager import VersionedPluginManager class VersionedTestsCase(unittest.TestCase): """ Test the correct loading of a simple plugin as well as basic commands. """ def setUp(self): """ init """ # create the plugin manager self.versionedPluginManager = VersionedPluginManager( directories_list=[os.path.join( os.path.dirname(os.path.abspath(__file__)),"plugins")], plugin_info_ext="version-plugin", ) # load the plugins that may be found self.versionedPluginManager.collectPlugins() # Will be used later self.plugin_info = None def plugin_loading_check(self): """ Test if the correct plugin has been loaded. """ if self.plugin_info is None: # check nb of categories self.assertEqual(len(self.versionedPluginManager.getCategories()),1) sole_category = self.versionedPluginManager.getCategories()[0] # check the number of plugins (the older versions of the # plugins should not be there) self.assertEqual(len(self.versionedPluginManager.getPluginsOfCategory(sole_category)),1) # older versions of the plugin should be found in the attic self.assertEqual(len(self.versionedPluginManager.getPluginsOfCategoryFromAttic(sole_category)),4) plugins = self.versionedPluginManager.getPluginsOfCategory(sole_category) self.plugin_info = None for plugin_info in plugins: TEST_MESSAGE("plugin info: %s" % plugin_info) if plugin_info.name == "Versioned Plugin": self.plugin_info = plugin_info break self.assertTrue(self.plugin_info) # test that the name of the plugin has been correctly defined self.assertEqual(self.plugin_info.name,"Versioned Plugin") self.assertEqual(sole_category,self.plugin_info.category) else: self.assertTrue(True) def testLoaded(self): """ Test if the correct plugin has been loaded. """ self.plugin_loading_check() sole_category = self.versionedPluginManager.getCategories()[0] self.assertEqual(len(self.versionedPluginManager.getLatestPluginsOfCategory(sole_category)),1) self.plugin_info = self.versionedPluginManager.getLatestPluginsOfCategory(sole_category)[0] TEST_MESSAGE("plugin info: %s" % self.plugin_info) # test that the name of the plugin has been correctly defined self.assertEqual(self.plugin_info.name,"Versioned Plugin") self.assertEqual(sole_category,self.plugin_info.category) self.assertEqual("1.2",str(self.plugin_info.version)) def testLatestPluginOfCategory(self): self.plugin_loading_check() def testActivationAndDeactivation(self): """ Test if the activation procedure works. """ self.plugin_loading_check() self.assertTrue(not self.plugin_info.plugin_object.is_activated) self.versionedPluginManager.activatePluginByName(self.plugin_info.name, self.plugin_info.category) self.assertTrue(self.plugin_info.plugin_object.is_activated) self.versionedPluginManager.deactivatePluginByName(self.plugin_info.name, self.plugin_info.category) self.assertTrue(not self.plugin_info.plugin_object.is_activated) # also check that this is the plugin of the latest version # that has been activated (ok the following test is already # ensured by the plugin_loading_check method, but this is to # make the things clear: the plugin chosen for activation is # the one with the latest version) self.assertEqual("1.2",str(self.plugin_info.version)) def testDirectActivationAndDeactivation(self): """ Test if the activation procedure works when directly activating a plugin. """ self.plugin_loading_check() self.assertTrue(not self.plugin_info.plugin_object.is_activated) TEST_MESSAGE("plugin object = %s" % self.plugin_info.plugin_object) self.plugin_info.plugin_object.activate() self.assertTrue(self.plugin_info.plugin_object.is_activated) self.plugin_info.plugin_object.deactivate() self.assertTrue(not self.plugin_info.plugin_object.is_activated) def testAtticConsistencyAfterCategoryFilterUpdate(self): """ Test that changing the category filer doesn't make the attic inconsistent. """ self.plugin_loading_check() newCategory = "Mouf" # Pre-requisite for the test previousCategories = self.versionedPluginManager.getCategories() self.assertTrue(len(previousCategories) >= 1) self.assertTrue(newCategory not in previousCategories) # change the category and see what's happening self.versionedPluginManager.setCategoriesFilter({newCategory: IPlugin}) self.versionedPluginManager.collectPlugins() for categoryName in previousCategories: self.assertRaises(KeyError, self.versionedPluginManager\ .getPluginsOfCategory, categoryName) self.assertEqual(len(self.versionedPluginManager\ .getPluginsOfCategoryFromAttic(newCategory)),4) suite = unittest.TestSuite([ unittest.TestLoader().loadTestsFromTestCase(VersionedTestsCase), ]) Yapsy-1.12.0/test/test_SimpleMultiprocessPlugin.py0000664000175000017500000000267013343003466023735 0ustar thibauldthibauld00000000000000# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- import unittest import os from yapsy.MultiprocessPluginManager import MultiprocessPluginManager class SimpleMultiprocessTestCase(unittest.TestCase): """ Test the correct loading of a multiprocessed plugin as well as basic communication. """ def setUp(self): """ init """ # create the plugin manager self.mpPluginManager = MultiprocessPluginManager(directories_list=[ os.path.join( os.path.dirname(os.path.abspath(__file__)),"plugins")], plugin_info_ext="multiprocess-plugin") # load the plugins that may be found self.mpPluginManager.collectPlugins() # Will be used later self.plugin_info = None def testUpAndRunning(self): """ Test if the plugin is loaded and if the communication pipe is properly setuped. """ for plugin_index, plugin in enumerate(self.mpPluginManager.getAllPlugins()): child_pipe = plugin.plugin_object.child_pipe content_from_parent = "hello-{0}-from-parent".format(plugin_index) child_pipe.send(content_from_parent) content_from_child = False if child_pipe.poll(5): content_from_child = child_pipe.recv() self.assertEqual("{0}|echo_from_child".format(content_from_parent), content_from_child) num_tested_plugin = plugin_index+1 self.assertEqual(2, num_tested_plugin) suite = unittest.TestSuite([ unittest.TestLoader().loadTestsFromTestCase(SimpleMultiprocessTestCase), ]) Yapsy-1.12.0/test/test_SimplePlugin.py0000664000175000017500000003414213343003466021322 0ustar thibauldthibauld00000000000000# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- from . import test_settings import unittest import os from yapsy.PluginManager import PluginManager from yapsy.IPlugin import IPlugin from yapsy.PluginFileLocator import PluginFileLocator from yapsy.PluginFileLocator import IPluginFileAnalyzer from yapsy import NormalizePluginNameForModuleName from yapsy.compat import ConfigParser class YapsyUtils(unittest.TestCase): def test_NormalizePluginNameForModuleName_on_ok_name(self): self.assertEqual("moufGlop2",NormalizePluginNameForModuleName("moufGlop2")) def test_NormalizePluginNameForModuleName_on_empty_name(self): self.assertEqual("_",NormalizePluginNameForModuleName("")) def test_NormalizePluginNameForModuleName_on_name_with_space(self): self.assertEqual("mouf_glop",NormalizePluginNameForModuleName("mouf glop")) def test_NormalizePluginNameForModuleName_on_name_with_nonalphanum(self): self.assertEqual("mouf__glop_a_é",NormalizePluginNameForModuleName("mouf+?glop:a/é")) class SimpleTestCase(unittest.TestCase): """ Test the correct loading of a simple plugin as well as basic commands. """ def setUp(self): """ init """ # create the plugin manager self.simplePluginManager = PluginManager(directories_list=[ os.path.join( os.path.dirname(os.path.abspath(__file__)),"plugins")]) # load the plugins that may be found self.simplePluginManager.collectPlugins() # Will be used later self.plugin_info = None def plugin_loading_check(self): """ Test if the correct plugin has been loaded. """ if self.plugin_info is None: # check nb of categories self.assertEqual(len(self.simplePluginManager.getCategories()),1) sole_category = self.simplePluginManager.getCategories()[0] # check the number of plugins self.assertEqual(len(self.simplePluginManager.getPluginsOfCategory(sole_category)),1) self.plugin_info = self.simplePluginManager.getPluginsOfCategory(sole_category)[0] # test that the name of the plugin has been correctly defined self.assertEqual(self.plugin_info.name,"Simple Plugin") self.assertEqual(sole_category,self.plugin_info.category) else: self.assertTrue(True) def testLoaded(self): """ Test if the correct plugin has been loaded. """ self.plugin_loading_check() def testGetAll(self): """ Test if the correct plugin has been loaded. """ self.plugin_loading_check() self.assertEqual(len(self.simplePluginManager.getAllPlugins()),1) self.assertEqual(self.simplePluginManager.getAllPlugins()[0],self.plugin_info) def testActivationAndDeactivation(self): """ Test if the activation procedure works. """ self.plugin_loading_check() self.assertTrue(not self.plugin_info.plugin_object.is_activated) self.simplePluginManager.activatePluginByName(self.plugin_info.name, self.plugin_info.category) self.assertTrue(self.plugin_info.plugin_object.is_activated) self.simplePluginManager.deactivatePluginByName(self.plugin_info.name, self.plugin_info.category) self.assertTrue(not self.plugin_info.plugin_object.is_activated) class SimplePluginAdvancedManipulationTestsCase(unittest.TestCase): """ Test some advanced manipulation on the core data of a PluginManager. """ def testCategoryManipulation(self): """ Test querying, removing and adding plugins from/to a category. """ spm = PluginManager(directories_list=[ os.path.join( os.path.dirname(os.path.abspath(__file__)),"plugins")]) # load the plugins that may be found spm.collectPlugins() # check that the getCategories works self.assertEqual(len(spm.getCategories()),1) sole_category = spm.getCategories()[0] # check the getPluginsOfCategory self.assertEqual(len(spm.getPluginsOfCategory(sole_category)),1) plugin_info = spm.getPluginsOfCategory(sole_category)[0] # try to remove it and check that is worked spm.removePluginFromCategory(plugin_info,sole_category) self.assertEqual(len(spm.getPluginsOfCategory(sole_category)),0) # now re-add this plugin the to same category spm.appendPluginToCategory(plugin_info,sole_category) self.assertEqual(len(spm.getPluginsOfCategory(sole_category)),1) def testChangingCategoriesFilter(self): """ Test the effect of setting a new category filer. """ spm = PluginManager(directories_list=[ os.path.join( os.path.dirname(os.path.abspath(__file__)),"plugins")]) # load the plugins that may be found spm.collectPlugins() newCategory = "Mouf" # Pre-requisite for the test previousCategories = spm.getCategories() self.assertTrue(len(previousCategories) >= 1) self.assertTrue(newCategory not in previousCategories) # change the category and see what's happening spm.setCategoriesFilter({newCategory: IPlugin}) spm.collectPlugins() for categoryName in previousCategories: self.assertRaises(KeyError, spm.getPluginsOfCategory, categoryName) self.assertTrue(len(spm.getPluginsOfCategory(newCategory)) >= 1) def testCandidatesManipulation(self): """ Test querying, removing and adding plugins from/to the lkist of plugins to load. """ spm = PluginManager(directories_list=[ os.path.join( os.path.dirname(os.path.abspath(__file__)),"plugins")]) # locate the plugins that should be loaded spm.locatePlugins() # check nb of candidatesx self.assertEqual(len(spm.getPluginCandidates()),1) # get the description of the plugin candidate candidate = spm.getPluginCandidates()[0] self.assertTrue(isinstance(candidate,tuple)) # try removing the candidate spm.removePluginCandidate(candidate) self.assertEqual(len(spm.getPluginCandidates()),0) # try re-adding it spm.appendPluginCandidate(candidate) self.assertEqual(len(spm.getPluginCandidates()),1) def testTwoStepsLoad(self): """ Test loading the plugins in two steps in order to collect more deltailed informations. """ spm = PluginManager(directories_list=[ os.path.join( os.path.dirname(os.path.abspath(__file__)),"plugins")]) # trigger the first step to look up for plugins spm.locatePlugins() # make full use of the "feedback" the loadPlugins can give # - set-up the callback function that will be called *before* # loading each plugin callback_infos = [] def preload_cbk(plugin_info): callback_infos.append(plugin_info) callback_after_infos = [] def postload_cbk(plugin_info): callback_after_infos.append(plugin_info) # - gather infos about the processed plugins (loaded or not) loadedPlugins = spm.loadPlugins(callback=preload_cbk, callback_after=postload_cbk) self.assertEqual(len(loadedPlugins),1) self.assertEqual(len(callback_infos),1) self.assertEqual(loadedPlugins[0].error,None) self.assertEqual(loadedPlugins[0],callback_infos[0]) self.assertEqual(len(callback_after_infos),1) self.assertEqual(loadedPlugins[0],callback_infos[0]) # check that the getCategories works self.assertEqual(len(spm.getCategories()),1) sole_category = spm.getCategories()[0] # check the getPluginsOfCategory self.assertEqual(len(spm.getPluginsOfCategory(sole_category)),1) plugin_info = spm.getPluginsOfCategory(sole_category)[0] # try to remove it and check that is worked spm.removePluginFromCategory(plugin_info,sole_category) self.assertEqual(len(spm.getPluginsOfCategory(sole_category)),0) # now re-add this plugin the to same category spm.appendPluginToCategory(plugin_info,sole_category) self.assertEqual(len(spm.getPluginsOfCategory(sole_category)),1) def testMultipleCategoriesForASamePlugin(self): """ Test that associating a plugin to multiple categories works as expected. """ class AnotherPluginIfce(object): def __init__(self): pass def activate(self): pass def deactivate(self): pass spm = PluginManager( categories_filter = { "Default": IPlugin, "IP": IPlugin, "Other": AnotherPluginIfce, }, directories_list=[ os.path.join( os.path.dirname(os.path.abspath(__file__)),"plugins")]) # load the plugins that may be found spm.collectPlugins() # check that the getCategories works self.assertEqual(len(spm.getCategories()),3) categories = spm.getCategories() self.assertTrue("Default" in categories) # check the getPluginsOfCategory self.assertEqual(len(spm.getPluginsOfCategory("Default")), 1) plugin_info = spm.getPluginsOfCategory("Default")[0] self.assertTrue("Default" in plugin_info.categories) self.assertTrue("IP" in plugin_info.categories) self.assertTrue("IP" in categories) # check the getPluginsOfCategory self.assertEqual(len(spm.getPluginsOfCategory("IP")),1) self.assertTrue("Other" in categories) # check the getPluginsOfCategory self.assertEqual(len(spm.getPluginsOfCategory("Other")),0) # try to remove the plugin from one category and check the # other category spm.removePluginFromCategory(plugin_info, "Default") self.assertEqual(len(spm.getPluginsOfCategory("Default")), 0) self.assertEqual(len(spm.getPluginsOfCategory("IP")), 1) # now re-add this plugin the to same category spm.appendPluginToCategory(plugin_info, "Default") self.assertEqual(len(spm.getPluginsOfCategory("Default")),1) self.assertEqual(len(spm.getPluginsOfCategory("IP")),1) def testGetPluginOf(self): """ Test the plugin query function. """ spm = PluginManager( categories_filter = { "Default": IPlugin, "IP": IPlugin, }, directories_list=[ os.path.join( os.path.dirname(os.path.abspath(__file__)),"plugins")]) # load the plugins that may be found spm.collectPlugins() # check the getPluginsOfCategory self.assertEqual(len(spm.getPluginsOf(categories="IP")), 1) self.assertEqual(len(spm.getPluginsOf(categories="Default")), 1) self.assertEqual(len(spm.getPluginsOf(name="Simple Plugin")), 1) self.assertEqual(len(spm.getPluginsOf(is_activated=False)), 1) self.assertEqual(len(spm.getPluginsOf(categories="IP", is_activated=True)), 0) self.assertEqual(len(spm.getPluginsOf(categories="IP", is_activated=False)), 1) self.assertEqual(len(spm.getPluginsOf(categories="IP", pouet=False)), 0) self.assertEqual(len(spm.getPluginsOf(categories=["IP"])), 0) # The order in the categories are added to plugin info is random in this setup, hence the strange formula below self.assertEqual(len(spm.getPluginsOf(categories=["IP", "Default"]) | spm.getPluginsOf(categories=["Default", "IP"])), 1) self.assertEqual(len(spm.getPluginsOf(category="Default") | spm.getPluginsOf(category="IP")), 1) class SimplePluginDetectionTestsCase(unittest.TestCase): """ Test particular aspects of plugin detection """ def testRecursivePluginlocation(self): """ Test detection of plugins which by default must be recursive. Here we give the test directory as a plugin place whereas we expect the plugins to be in test/plugins. """ spm = PluginManager(directories_list=[ os.path.dirname(os.path.abspath(__file__))]) # load the plugins that may be found spm.collectPlugins() # check that the getCategories works self.assertEqual(len(spm.getCategories()),1) sole_category = spm.getCategories()[0] # check the getPluginsOfCategory self.assertEqual(len(spm.getPluginsOfCategory(sole_category)),2) def testDisablingRecursivePluginLocationIsEnforced(self): """ Test detection of plugins when the detection is non recursive. Here we test that it cannot look into subdirectories of the test directory. """ pluginLocator = PluginFileLocator() pluginLocator.setPluginPlaces([ os.path.dirname(os.path.abspath(__file__))]) pluginLocator.disableRecursiveScan() spm = PluginManager() spm.setPluginLocator(pluginLocator) # load the plugins that may be found spm.collectPlugins() # check that the getCategories works self.assertEqual(len(spm.getCategories()),1) sole_category = spm.getCategories()[0] # check the getPluginsOfCategory self.assertEqual(len(spm.getPluginsOfCategory(sole_category)),0) def testDisablingRecursivePluginLocationAllowsFindingTopLevelPlugins(self): """ Test detection of plugins when the detection is non recursive. Here we test that if we give test/plugin as the directory to scan it can find the plugin. """ pluginLocator = PluginFileLocator() pluginLocator.setPluginPlaces([ os.path.join( os.path.dirname(os.path.abspath(__file__)),"plugins")]) pluginLocator.disableRecursiveScan() spm = PluginManager() spm.setPluginLocator(pluginLocator) # load the plugins that may be found spm.collectPlugins() # check that the getCategories works self.assertEqual(len(spm.getCategories()),1) sole_category = spm.getCategories()[0] # check the getPluginsOfCategory self.assertEqual(len(spm.getPluginsOfCategory(sole_category)),1) def testEnforcingPluginDirsDoesNotKeepDefaultDir(self): """ Test that providing the directories list override the default search directory instead of extending the default list. """ class AcceptAllPluginFileAnalyzer(IPluginFileAnalyzer): def __init__(self): IPluginFileAnalyzer.__init__(self, "AcceptAll") def isValidPlugin(self, filename): return True def getInfosDictFromPlugin(self, dirpath, filename): return { "name": filename, "path": dirpath}, ConfigParser() pluginLocator = PluginFileLocator() pluginLocator.setAnalyzers([AcceptAllPluginFileAnalyzer()]) spm_default_dirs = PluginManager(plugin_locator= pluginLocator) spm_default_dirs.locatePlugins() candidates_in_default_dir = spm_default_dirs.getPluginCandidates() candidates_files_in_default_dir = set([c[0] for c in candidates_in_default_dir]) pluginLocator = PluginFileLocator() pluginLocator.setAnalyzers([AcceptAllPluginFileAnalyzer()]) spm = PluginManager(plugin_locator= pluginLocator, directories_list=[os.path.dirname(os.path.abspath(__file__)),"does-not-exists"]) spm.locatePlugins() candidates = spm.getPluginCandidates() candidates_files = set([c[0] for c in candidates]) self.assertFalse(set(candidates_files_in_default_dir).issubset(set(candidates_files))) suite = unittest.TestSuite([ unittest.TestLoader().loadTestsFromTestCase(YapsyUtils), unittest.TestLoader().loadTestsFromTestCase(SimpleTestCase), unittest.TestLoader().loadTestsFromTestCase(SimplePluginAdvancedManipulationTestsCase), unittest.TestLoader().loadTestsFromTestCase(SimplePluginDetectionTestsCase), ]) Yapsy-1.12.0/test/test_ErrorInPlugin.py0000664000175000017500000000435013043424771021452 0ustar thibauldthibauld00000000000000# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- from . import test_settings import os import unittest import logging from yapsy.PluginManager import PluginManager from yapsy import log class ErrorTestCase(unittest.TestCase): """ Test the handling of errors during plugin load. """ def testTwoStepsLoadWithError(self): """ Test loading the plugins in two steps in order to collect more deltailed informations and take care of an erroneous plugin. """ spm = PluginManager(directories_list=[ os.path.join( os.path.dirname(os.path.abspath(__file__)),"plugins") ], plugin_info_ext="yapsy-error-plugin") # trigger the first step to look up for plugins spm.locatePlugins() # make full use of the "feedback" the loadPlugins can give # - set-up the callback function that will be called *before* # loading each plugin callback_infos = [] def preload_cbk(i_plugin_info): callback_infos.append(i_plugin_info) callback_after_infos = [] def postload_cbk(i_plugin_info): callback_after_infos.append(i_plugin_info) # - gather infos about the processed plugins (loaded or not) # and for the test, monkey patch the logger originalLogLevel = log.getEffectiveLevel() log.setLevel(logging.ERROR) errorLogCallFlag = [False] def errorMock(*args,**kwargs): errorLogCallFlag[0]=True originalErrorMethod = log.error log.error = errorMock try: loadedPlugins = spm.loadPlugins(callback=preload_cbk, callback_after=postload_cbk) finally: log.setLevel(originalLogLevel) log.error = originalErrorMethod self.assertTrue(errorLogCallFlag[0]) self.assertEqual(len(loadedPlugins),1) self.assertEqual(len(callback_infos),1) self.assertTrue(isinstance(callback_infos[0].error,tuple)) self.assertEqual(loadedPlugins[0],callback_infos[0]) self.assertTrue(issubclass(callback_infos[0].error[0],ImportError)) self.assertEqual(len(callback_after_infos),0) # check that the getCategories works self.assertEqual(len(spm.getCategories()),1) sole_category = spm.getCategories()[0] # check the getPluginsOfCategory self.assertEqual(len(spm.getPluginsOfCategory(sole_category)),0) suite = unittest.TestSuite([ unittest.TestLoader().loadTestsFromTestCase(ErrorTestCase), ]) Yapsy-1.12.0/test/test_Singleton.py0000664000175000017500000000773512575022002020655 0ustar thibauldthibauld00000000000000# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- from . import test_settings import os import unittest from yapsy.ConfigurablePluginManager import ConfigurablePluginManager from yapsy.VersionedPluginManager import VersionedPluginManager from yapsy.PluginManager import PluginManagerSingleton from yapsy.compat import ConfigParser """ There can be only one series of tests for the singleton, guess why ... """ class ConfigSingletonTestsCase(unittest.TestCase): """ Test the correct loading of a simple plugin as well as basic commands, use the Singleton version of the ConfigurablePluginManager. """ CONFIG_FILE = test_settings.TEMP_CONFIG_FILE_NAME def setUp(self): """ init """ # create a config file self.config_file = self.CONFIG_FILE self.config_parser = ConfigParser() self.plugin_info = None # create the plugin manager PluginManagerSingleton.setBehaviour([ConfigurablePluginManager,VersionedPluginManager]) pluginManager = PluginManagerSingleton.get() pluginManager.setPluginPlaces(directories_list=[os.path.dirname(os.path.abspath(__file__))]) pluginManager.setPluginInfoExtension("yapsy-config-plugin") pluginManager.setConfigParser(self.config_parser,self.update_config) # load the plugins that may be found pluginManager.collectPlugins() def tearDown(self): """ When the test has been performed erase the temp file. """ if os.path.isfile(self.config_file): os.remove(self.config_file) def testConfigurationFileExistence(self): """ Test if the configuration file has been properly written. """ # activate the only loaded plugin self.plugin_activate() # get rid of the plugin manager and create a new one self.config_parser.read(self.config_file) self.assertTrue(self.config_parser.has_section("Plugin Management")) self.assertTrue(self.config_parser.has_option("Plugin Management", "default_plugins_to_load")) def testLoaded(self): """ Test if the correct plugin has been loaded. """ self.plugin_loading_check() def testActivationAndDeactivation(self): """ Test if the activation/deactivaion procedures work. """ self.plugin_activate() PluginManagerSingleton.get().deactivatePluginByName(self.plugin_info.name, self.plugin_info.category) self.assertTrue(not self.plugin_info.plugin_object.is_activated) def testPluginOptions(self): """ Test is the plugin can register and access options from the ConfigParser. """ self.plugin_activate() plugin = self.plugin_info.plugin_object plugin.choseTestOption("voila") self.assertTrue(plugin.checkTestOption()) self.assertEqual(plugin.getTestOption(),"voila") #--- UTILITIES def plugin_loading_check(self): """ Test if the correct plugin has been loaded. """ if self.plugin_info is None: pluginManager = PluginManagerSingleton.get() # check nb of categories self.assertEqual(len(pluginManager.getCategories()),1) sole_category = pluginManager.getCategories()[0] # check the number of plugins self.assertEqual(len(pluginManager.getPluginsOfCategory(sole_category)),1) self.plugin_info = pluginManager.getPluginsOfCategory(sole_category)[0] # test that the name of the plugin has been correctly defined self.assertEqual(self.plugin_info.name,"Config Plugin") self.assertEqual(sole_category,self.plugin_info.category) else: self.assertTrue(True) def plugin_activate(self): """ Activate the plugin with basic checking """ self.plugin_loading_check() if not self.plugin_info.plugin_object.is_activated: PluginManagerSingleton.get().activatePluginByName(self.plugin_info.name, self.plugin_info.category) self.assertTrue(self.plugin_info.plugin_object.is_activated) def update_config(self): """ Write the content of the ConfigParser in a file. """ cf = open(self.config_file,"a") self.config_parser.write(cf) cf.close() suite = unittest.TestSuite([ unittest.TestLoader().loadTestsFromTestCase(ConfigSingletonTestsCase), ]) Yapsy-1.12.0/test/test_ConfigPlugin.py0000664000175000017500000001302112575022002021260 0ustar thibauldthibauld00000000000000# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- from . import test_settings import os import unittest from yapsy.compat import ConfigParser from yapsy.ConfigurablePluginManager import ConfigurablePluginManager class ConfigTestMixin: def plugin_loading_check(self): """ Test if the correct plugin has been loaded. """ if self.plugin_info is None: # check nb of categories self.assertEqual(len(self.pluginManager.getCategories()),1) sole_category = self.pluginManager.getCategories()[0] # check the number of plugins self.assertEqual(len(self.pluginManager.getPluginsOfCategory(sole_category)),1) self.plugin_info = self.pluginManager.getPluginsOfCategory(sole_category)[0] # test that the name of the plugin has been correctly defined self.assertEqual(self.plugin_info.name,"Config Plugin") self.assertEqual(sole_category,self.plugin_info.category) else: self.assertTrue(True) def plugin_activate(self): """ Activate the plugin with basic checking """ self.plugin_loading_check() self.assertTrue(not self.plugin_info.plugin_object.is_activated) self.pluginManager.activatePluginByName(self.plugin_info.name, self.plugin_info.category) self.assertTrue(self.plugin_info.plugin_object.is_activated) class ConfigTestCase(unittest.TestCase, ConfigTestMixin): """ Test the correct loading of a plugin that uses a configuration file through a ConfigurablePluginManager as well as basic commands. """ CONFIG_FILE = test_settings.TEMP_CONFIG_FILE_NAME def setUp(self): """ init """ # create a config file self.config_file = self.CONFIG_FILE self.config_parser = ConfigParser() self.plugin_info = None # create the plugin manager self.pluginManager = ConfigurablePluginManager( directories_list=[os.path.join( os.path.dirname(os.path.abspath(__file__)),"plugins")], plugin_info_ext="yapsy-config-plugin", configparser_instance=self.config_parser, config_change_trigger=self.update_config) # load the plugins that may be found self.pluginManager.collectPlugins() def tearDown(self): """ When the test has been performed erase the temp file. """ if os.path.isfile(self.config_file): os.remove(self.config_file) def testConfigurationFileExistence(self): """ Test if the configuration file has been properly written. """ # activate the only loaded plugin self.plugin_activate() # get rid of the plugin manager and create a new one del self.pluginManager del self.config_parser self.config_parser = ConfigParser() self.config_parser.read(self.config_file) self.assertTrue(self.config_parser.has_section("Plugin Management")) self.assertTrue(self.config_parser.has_option("Plugin Management", "default_plugins_to_load")) self.pluginManager = ConfigurablePluginManager( directories_list=[os.path.join( os.path.dirname(os.path.abspath(__file__)),"plugins")], plugin_info_ext="yapsy-config-plugin", configparser_instance=self.config_parser, config_change_trigger=self.update_config) self.pluginManager.collectPlugins() self.plugin_loading_check() self.assertTrue(self.plugin_info.plugin_object.is_activated) self.pluginManager.deactivatePluginByName(self.plugin_info.name, self.plugin_info.category) # check that activating the plugin once again, won't cause an error self.pluginManager.activatePluginByName(self.plugin_info.name, self.plugin_info.category) # Will be used later self.plugin_info = None def testLoaded(self): """ Test if the correct plugin has been loaded. """ self.plugin_loading_check() def testActivationAndDeactivation(self): """ Test if the activation/deactivaion procedures work. """ self.plugin_activate() self.pluginManager.deactivatePluginByName(self.plugin_info.name, self.plugin_info.category) self.assertTrue(not self.plugin_info.plugin_object.is_activated) def testPluginOptions(self): """ Test is the plugin can register and access options from the ConfigParser. """ self.plugin_activate() plugin = self.plugin_info.plugin_object plugin.choseTestOption("voila") self.assertTrue(plugin.checkTestOption()) self.assertEqual(plugin.getTestOption(),"voila") def update_config(self): """ Write the content of the ConfigParser in a file. """ cf = open(self.config_file,"a") self.config_parser.write(cf) cf.close() class ConfigurablePMWithDefaultChangeTriggerTestCase(unittest.TestCase, ConfigTestMixin): """Test the correctness of default values of args specific to the ConfigurablePM in its construtor. """ def setUp(self): """ init """ # create a config file self.config_parser = ConfigParser() self.plugin_info = None # create the plugin manager self.pluginManager = ConfigurablePluginManager( directories_list=[os.path.join( os.path.dirname(os.path.abspath(__file__)),"plugins")], plugin_info_ext="yapsy-config-plugin", configparser_instance=self.config_parser) # load the plugins that may be found self.pluginManager.collectPlugins() def testPluginOptions(self): """ Test is the plugin can register and access options from the ConfigParser. """ self.plugin_activate() plugin = self.plugin_info.plugin_object plugin.choseTestOption("voila") self.assertTrue(plugin.checkTestOption()) self.assertEqual(plugin.getTestOption(),"voila") suite = unittest.TestSuite([ unittest.TestLoader().loadTestsFromTestCase(ConfigTestCase), unittest.TestLoader().loadTestsFromTestCase(ConfigurablePMWithDefaultChangeTriggerTestCase), ]) Yapsy-1.12.0/test/test_FilterPlugin.py0000664000175000017500000002070712575022002021311 0ustar thibauldthibauld00000000000000# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- from . import test_settings from .test_settings import TEST_MESSAGE import unittest import os import re from yapsy.FilteredPluginManager import FilteredPluginManager class testFilter(FilteredPluginManager): """ Test filter class. Refused to load plugins whose Name starts with 'C'. """ _bannednames = re.compile("^C") def isPluginOk(self,info): return not self._bannednames.match(info.name) class FilteredTestsCase(unittest.TestCase): """ Test the correct loading of a simple plugin as well as basic commands. """ def setUp(self): """ init """ # create the plugin manager # print os.path.join(os.path.dirname(os.path.abspath(__file__)),"plugins") self.filteredPluginManager = testFilter( directories_list=[os.path.join( os.path.dirname(os.path.abspath(__file__)),"plugins")], plugin_info_ext="yapsy-filter-plugin", ) # load the plugins that may be found self.filteredPluginManager.collectPlugins() # Will be used later self.plugin_info = None def plugin_loading_check(self): """ Test if the correct plugins have been loaded. """ # check nb of categories self.assertEqual(len(self.filteredPluginManager.getCategories()),1) sole_category = self.filteredPluginManager.getCategories()[0] # check the number of plugins self.assertEqual(len(self.filteredPluginManager.getPluginsOfCategory(sole_category)),1) plugins = self.filteredPluginManager.getPluginsOfCategory(sole_category) for plugin_info in plugins: TEST_MESSAGE("plugin info: %s" % plugin_info) self.plugin_info = plugin_info self.assertTrue(self.plugin_info) self.assertEqual(self.plugin_info.name,"Simple Plugin") self.assertEqual(sole_category,self.plugin_info.category) def testLoaded(self): """ Test if the correct plugin has been loaded. """ self.plugin_loading_check() def testActivationAndDeactivation(self): """ Test if the activation procedure works. """ self.plugin_loading_check() self.assertTrue(not self.plugin_info.plugin_object.is_activated) TEST_MESSAGE("plugin object = %s" % self.plugin_info.plugin_object) self.plugin_info.plugin_object.activate() self.assertTrue(self.plugin_info.plugin_object.is_activated) self.plugin_info.plugin_object.deactivate() self.assertTrue(not self.plugin_info.plugin_object.is_activated) def testRejectedList(self): """ Test if the list of rejected plugins is correct. """ for plugin in self.filteredPluginManager.getRejectedPlugins(): TEST_MESSAGE("plugin info: %s" % plugin[2]) self.assertEqual(plugin[2].name,"Config Plugin") def testRejectedStable(self): reject1 = list(self.filteredPluginManager.getRejectedPlugins()) self.filteredPluginManager.collectPlugins() reject2 = list(self.filteredPluginManager.getRejectedPlugins()) self.assertEqual(len(reject1),len(reject2)) def testRejectPlugin(self): self.filteredPluginManager.locatePlugins() rejected = self.filteredPluginManager.rejectedPlugins #If this fails the test in not meaningful.. self.assertTrue(len(rejected) > 0) nrRejected = len(rejected) for plugin in rejected: self.filteredPluginManager.rejectPluginCandidate(plugin) self.assertEqual(nrRejected,len(self.filteredPluginManager.rejectedPlugins)) def testRemovePlugin(self): self.filteredPluginManager.locatePlugins() rejected = self.filteredPluginManager.rejectedPlugins nrCandidates = len(self.filteredPluginManager.getPluginCandidates()) #If this fails the test in not meaningful.. self.assertTrue(len(rejected) > 0) for plugin in rejected: self.filteredPluginManager.removePluginCandidate(plugin) self.assertEqual(0,len(self.filteredPluginManager.rejectedPlugins)) self.assertEqual( nrCandidates , len(self.filteredPluginManager.getPluginCandidates())) def testAppendRejectedPlugin(self): self.filteredPluginManager.locatePlugins() rejected = self.filteredPluginManager.getRejectedPlugins() nrRejected = len(rejected) nrCandidates = len(self.filteredPluginManager.getPluginCandidates()) #If this fails the test in not meaningful.. self.assertTrue(len(rejected) > 0) #Remove the rejected plugins into out own list. for plugin in rejected: self.filteredPluginManager.removePluginCandidate(plugin) self.assertEqual(len(self.filteredPluginManager.getRejectedPlugins()),0) ##Now Actually test Append. for plugin in rejected: self.filteredPluginManager.appendPluginCandidate(plugin) self.assertEqual(nrRejected ,len(self.filteredPluginManager.rejectedPlugins)) self.assertEqual(nrCandidates , len(self.filteredPluginManager.getPluginCandidates())) def testAppendOkPlugins(self): self.filteredPluginManager.locatePlugins() rejected = self.filteredPluginManager.getRejectedPlugins() nrRejected = len(rejected) nrCandidates = len(self.filteredPluginManager.getPluginCandidates()) #If this fails the test in not meaningful.. self.assertTrue(len(rejected) > 0) #Remove the rejected plugins again. for plugin in rejected: self.filteredPluginManager.removePluginCandidate(plugin) self.assertEqual(len(self.filteredPluginManager.getRejectedPlugins()),0) for plugin in rejected: #change the name so it is acceptable. plugin[2].name = "X" + plugin[2].name[1:] self.filteredPluginManager.appendPluginCandidate(plugin) self.assertEqual(0,len(self.filteredPluginManager.rejectedPlugins)) self.assertEqual(nrRejected + nrCandidates , len(self.filteredPluginManager.getPluginCandidates())) def testUnrejectPlugin(self): self.filteredPluginManager.locatePlugins() rejected = self.filteredPluginManager.rejectedPlugins nrRejected = len(rejected) nrCandidates = len(self.filteredPluginManager.getPluginCandidates()) #If this fails the test in not meaningful.. self.assertTrue(len(rejected) > 0) for plugin in rejected: self.filteredPluginManager.unrejectPluginCandidate(plugin) self.assertEqual(0,len(self.filteredPluginManager.rejectedPlugins)) self.assertEqual( nrRejected + nrCandidates , len(self.filteredPluginManager.getPluginCandidates())) class FilteredWithMonkeyPathTestsCase(unittest.TestCase): """ Test the correct loading oand filtering of plugins when the FilteredPluginManager is just monkey-patched """ def setUp(self): """ init """ # create the plugin manager # print os.path.join(os.path.dirname(os.path.abspath(__file__)),"plugins") self.filteredPluginManager = FilteredPluginManager( directories_list=[os.path.join( os.path.dirname(os.path.abspath(__file__)),"plugins")], plugin_info_ext="yapsy-filter-plugin", ) self.filteredPluginManager.isPluginOk = lambda info:not re.match("^C",info.name) # load the plugins that may be found self.filteredPluginManager.collectPlugins() # Will be used later self.plugin_info = None def plugin_loading_check(self): """ Test if the correct plugins have been loaded. """ # check nb of categories self.assertEqual(len(self.filteredPluginManager.getCategories()),1) sole_category = self.filteredPluginManager.getCategories()[0] # check the number of plugins self.assertEqual(len(self.filteredPluginManager.getPluginsOfCategory(sole_category)),1) plugins = self.filteredPluginManager.getPluginsOfCategory(sole_category) for plugin_info in plugins: TEST_MESSAGE("plugin info: %s" % plugin_info) self.plugin_info = plugin_info self.assertTrue(self.plugin_info) self.assertEqual(self.plugin_info.name,"Simple Plugin") self.assertEqual(sole_category,self.plugin_info.category) def testLoaded(self): """ Test if the correct plugin has been loaded. """ self.plugin_loading_check() def testActivationAndDeactivation(self): """ Test if the activation procedure works. """ self.plugin_loading_check() self.assertTrue(not self.plugin_info.plugin_object.is_activated) TEST_MESSAGE("plugin object = %s" % self.plugin_info.plugin_object) self.plugin_info.plugin_object.activate() self.assertTrue(self.plugin_info.plugin_object.is_activated) self.plugin_info.plugin_object.deactivate() self.assertTrue(not self.plugin_info.plugin_object.is_activated) def testRejectedList(self): """ Test if the list of rejected plugins is correct. """ for plugin in self.filteredPluginManager.getRejectedPlugins(): TEST_MESSAGE("plugin info: %s" % plugin[2]) self.assertEqual(plugin[2].name,"Config Plugin") suite = unittest.TestSuite([ unittest.TestLoader().loadTestsFromTestCase(FilteredTestsCase), unittest.TestLoader().loadTestsFromTestCase(FilteredWithMonkeyPathTestsCase), ]) Yapsy-1.12.0/test/pluginstoinstall/0000775000175000017500000000000013343011254020702 5ustar thibauldthibauld00000000000000Yapsy-1.12.0/test/pluginstoinstall/autoinstallZIPplugin.zip0000664000175000017500000000213112425477475025611 0ustar thibauldthibauld00000000000000PK?ṉ-autoinstallzipplugin.yapsy-autoinstall-pluginUT NNux %0 D|Ā`@D! nc)MYz\ut|-MgW \$h# >m} EH6*@w QPOO 慅~lNl^$Պ0:pʂMD1wdW(Ӛu*\u0Y&,PK)?ײV autoinstallzipplugin/__init__.pyUT NNux }n0 )<8&Qn;rNmTI[VEi(no w_`Bm}k>QsYY#kC 4@;$ޥz3ƪ&\tOjT a*e&a뉵sƘ75n]8ڶXGX#RrLf56p8XopX& N|z, ovz3,FHڰ=kG$9''M4%cIU`ZR@:EЩw7{"r~¬ЫQMkbEXNy[\QRX~n"hJjy+ )/PK @K?autoinstallzipplugin/UT N:Nux PK?ṉ-autoinstallzipplugin.yapsy-autoinstall-pluginUTNux PK)?ײV autoinstallzipplugin/__init__.pyUTNux PK @K?Aautoinstallzipplugin/UTNux PK4Yapsy-1.12.0/test/pluginstoinstall/autoinstalldirplugin/0000775000175000017500000000000013343011254025157 5ustar thibauldthibauld00000000000000Yapsy-1.12.0/test/pluginstoinstall/autoinstalldirplugin/__init__.py0000664000175000017500000000127212575022002027271 0ustar thibauldthibauld00000000000000# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- """ This is certainly the second simplest plugin ever. """ from yapsy.IPlugin import IPlugin class AutoInstallDirPlugin(IPlugin): """ Only trigger the expected test results. """ def __init__(self): """ init """ # initialise parent class IPlugin.__init__(self) def activate(self): """ On activation tell that this has been successfull. """ # get the automatic procedure from IPlugin IPlugin.activate(self) return def deactivate(self): """ On deactivation check that the 'activated' flag was on then tell everything's ok to the test procedure. """ IPlugin.deactivate(self) Yapsy-1.12.0/test/pluginstoinstall/AutoInstallPlugin.py0000664000175000017500000000126712575022002024677 0ustar thibauldthibauld00000000000000# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- """ This is certainly the second simplest plugin ever. """ from yapsy.IPlugin import IPlugin class AutoInstallPlugin(IPlugin): """ Only trigger the expected test results. """ def __init__(self): """ init """ # initialise parent class IPlugin.__init__(self) def activate(self): """ On activation tell that this has been successfull. """ # get the automatic procedure from IPlugin IPlugin.activate(self) return def deactivate(self): """ On deactivation check that the 'activated' flag was on then tell everything's ok to the test procedure. """ IPlugin.deactivate(self) Yapsy-1.12.0/test/pluginstoinstall/autoinstallplugin.yapsy-autoinstall-plugin0000664000175000017500000000032612044762520031407 0ustar thibauldthibauld00000000000000[Core] Name = Auto Install Plugin Module = AutoInstallPlugin [Documentation] Author = Thibauld Nion Version = 0.1 Website = http://mathbench.sourceforge.net Description = A simple plugin usefull for basic testing Yapsy-1.12.0/test/pluginstoinstall/autoinstalldirplugin.yapsy-autoinstall-plugin0000664000175000017500000000033512044762520032106 0ustar thibauldthibauld00000000000000[Core] Name = Auto Install Dir Plugin Module = autoinstalldirplugin [Documentation] Author = Thibauld Nion Version = 0.1 Website = http://mathbench.sourceforge.net Description = A simple plugin usefull for basic testing Yapsy-1.12.0/test/pluginstoinstall/autoinstallWRONGzipplugin.zip0000664000175000017500000000214212425477475026570 0ustar thibauldthibauld00000000000000PKs?U y-autoinstallzipplugin.yapsy-autoinstall-pluginUT 9N9Nux %1 D{`="$M )R؎x-cM>Z h{np'"®+ñڸ,p;B ѠBTX+qy8kM,wI|O8_ӷbҭ=֟y^%I/n NPb#ڮ|!xTPK$?ײV autoinstallzipplugin/__init__.pyUT NNux }n0 )<8&Qn;rNmTI[VEi(no w_`Bm}k>QsYY#kC 4@;$ޥz3ƪ&\tOjT a*e&a뉵sƘ75n]8ڶXGX#RrLf56p8XopX& N|z, ovz3,FHڰ=kG$9''M4%cIU`ZR@:EЩw7{"r~¬ЫQMkbEXNy[\QRX~n"hJjy+ )/PK @K?autoinstallzipplugin/UT N:Nux PKs?U y-autoinstallzipplugin.yapsy-autoinstall-pluginUT9Nux PK$?ײV autoinstallzipplugin/__init__.pyUTNux PK @K?Aautoinstallzipplugin/UTNux PK4