diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d691f93 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +*.pyc +*.idea +*.swp +.coverage +env*/ +dist/ +build/ +*.egg-info/ +.idea/ diff --git a/.hgtags b/.hgtags index db1532e..a893ab0 100644 --- a/.hgtags +++ b/.hgtags @@ -4,11 +4,18 @@ 27a222ccc739de48d114ae282f848aad27775904 SubversionImport 87ace7ce5663b6c662bcfa54818e099ffdcceb53 release_Yapsy-1.8 43a85ec50934b636dce6647eb5342b70b809ca20 release_Yapsy-1.9 +ee9987b833d65887487af38cecf0eb9ed6b871eb release_Yapsy-1.9-python3 991a7a83265127a463c845fa56ada2183cdaafe3 release_Yapsy-1.10 991a7a83265127a463c845fa56ada2183cdaafe3 release_Yapsy-1.10 0000000000000000000000000000000000000000 release_Yapsy-1.10 0000000000000000000000000000000000000000 release_Yapsy-1.10 b934a474c2e8fa765bfda7bce134060184284872 release_Yapsy-1.10 a5c62d9f560fa44bbf41fdd0d2c9ddc85144f637 release_Yapsy-1.10.1 +7000b8072f00e42d9c448092bbd2149cc76e8d21 release_Yapsy-1.10-python3 +0000000000000000000000000000000000000000 release_Yapsy-1.10-python3 +0000000000000000000000000000000000000000 release_Yapsy-1.10-python3 +87fbef4ba66ba9f98692e9b657479fe5daae12a2 release_Yapsy-1.10-python3 +f59fd5772939d6779725d0bb55b612ba5b254534 release_Yapsy-1.10.1-python3 5c0ff8646c2e1b6e0a4676b57676ed5adbe6b479 release_Yapsy-1.10.2 -95b58ee3f7f4cd225caf8ff84bf5808fbf52279a release_Yapsy-1.10.323 +c1f8228a9fd08bbffbfd8b6b8ecd1de5c2d9236f release_Yapsy-1.10.2-python3 +777d3daf4648d8395be90a2059c749bca191cbcd release_Yapsy-1.10.323-python3 diff --git a/.travis.yml b/.travis.yml index 15a246c..c1e5da3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,9 @@ language: python python: - "2.6" - "2.7" + - "3.2" + - "3.3" + - "3.4" # command to install dependencies install: diff --git a/README.md b/README.md index 936f108..566338c 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ To get more details about the sources please look: Yapsy's development is [hosted at Sourceforge](http://sourceforge.net/projects/yapsy/) -Yapsy is also continuously tested on [travis-ci](https://travis-ci.org): +Yapsy is also continuously tested on [travis-ci](https://travis-ci.org): [![Build Status](https://travis-ci.org/tibonihoo/yapsy.png?branch=master)](https://travis-ci.org/tibonihoo/yapsy) [![Coverage Status](https://coveralls.io/repos/tibonihoo/yapsy/badge.png?branch=master)](https://coveralls.io/r/tibonihoo/yapsy?branch=master) diff --git a/package/CHANGELOG.txt b/package/CHANGELOG.txt index ee905ed..f6aaf73 100644 --- a/package/CHANGELOG.txt +++ b/package/CHANGELOG.txt @@ -1,3 +1,9 @@ +version-X.XX.XXX [unreleased] + + - code: one branch for python3 and python2 + - code: pep8 with 'autopep8 -aaa -i -r .' + - code: PluginManger can also throw other exceptions in issubclass + 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/) @@ -18,6 +24,8 @@ 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] diff --git a/package/MANIFEST.in b/package/MANIFEST.in index a84cd90..8a1b74f 100644 --- a/package/MANIFEST.in +++ b/package/MANIFEST.in @@ -1,6 +1,7 @@ include README.txt include LICENSE.txt include CHANGELOG.txt +include runtests.py recursive-include test *.py *-plugin *.zip diff --git a/package/README.txt b/package/README.txt index 6c4ce9d..f2d6ffb 100644 --- a/package/README.txt +++ b/package/README.txt @@ -3,7 +3,7 @@ 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. +keep track of several plugins. 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 diff --git a/package/TODO.txt b/package/TODO.txt index 3d2d316..45ba2f0 100644 --- a/package/TODO.txt +++ b/package/TODO.txt @@ -4,6 +4,7 @@ Next Release +- follow evolutions from default branch Later diff --git a/package/doc/conf.py b/package/doc/conf.py index 07c1778..82db1df 100644 --- a/package/doc/conf.py +++ b/package/doc/conf.py @@ -11,7 +11,8 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import sys, os +import sys +import os SRC_DIR = os.path.dirname(os.path.abspath(os.path.dirname(__file__))) sys.path = [SRC_DIR] + sys.path @@ -19,9 +20,9 @@ # 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('.')) +# sys.path.append(os.path.abspath('.')) -# -- General configuration ----------------------------------------------------- +# -- 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. @@ -40,8 +41,8 @@ master_doc = 'index' # General information about the project. -project = u'Yapsy' -copyright = u'2010-2014, Thibauld Nion' +project = 'Yapsy' +copyright = '2010-2014, 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 @@ -49,7 +50,7 @@ # import sys -sys.path.insert(0,os.path.dirname(__file__)) +sys.path.insert(0, os.path.dirname(__file__)) import yapsy # The short X.Y version. version = yapsy.__version__ @@ -94,7 +95,7 @@ #modindex_common_prefix = [] -# -- Options for HTML output --------------------------------------------------- +# -- 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'. @@ -104,14 +105,14 @@ # further. For a list of options available for each theme, see the # documentation. html_theme_options = { - "sidebarbgcolor" : "#777", + "sidebarbgcolor": "#777", "sidebarlinkcolor": "#e0cede", - "relbarbgcolor" : "#999", + "relbarbgcolor": "#999", "relbarlinkcolor": "#e0cede", - "footerbgcolor" : "#777", - "headtextcolor" : "#5c3566", + "footerbgcolor": "#777", + "headtextcolor": "#5c3566", "linkcolor": "#5c3566", - } +} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] @@ -125,12 +126,12 @@ # 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") +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") +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, @@ -176,7 +177,7 @@ htmlhelp_basename = 'Yapsydoc' -# -- Options for LaTeX output -------------------------------------------------- +# -- Options for LaTeX output -------------------------------------------- # The paper size ('letter' or 'a4'). #latex_paper_size = 'letter' @@ -187,8 +188,8 @@ # 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', u'Yapsy Documentation', - u'Thibauld Nion', 'manual'), + ('index', 'Yapsy.tex', 'Yapsy Documentation', + 'Thibauld Nion', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of diff --git a/package/runtests.py b/package/runtests.py new file mode 100644 index 0000000..a2c6676 --- /dev/null +++ b/package/runtests.py @@ -0,0 +1,61 @@ +#!/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) diff --git a/package/setup.py b/package/setup.py index 3aeea5d..0c75dd1 100644 --- a/package/setup.py +++ b/package/setup.py @@ -25,43 +25,45 @@ 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 +# 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', - 'Topic :: Software Development :: Libraries :: Python Modules'], - platforms='All', - ) - + 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 :: 3', + 'Topic :: Software Development :: Libraries :: Python Modules'], + platforms='All', + ) + finally: - os.chdir(originalDir) + os.chdir(originalDir) diff --git a/package/test/main.py b/package/test/main.py deleted file mode 100644 index 66cb679..0000000 --- a/package/test/main.py +++ /dev/null @@ -1,64 +0,0 @@ -#!/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_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) - - - diff --git a/package/test/plugins/ConfigPlugin.py b/package/test/plugins/ConfigPlugin.py index 642fe0a..1ebf22c 100644 --- a/package/test/plugins/ConfigPlugin.py +++ b/package/test/plugins/ConfigPlugin.py @@ -2,59 +2,53 @@ # -*- coding: utf-8 -*- - """ This is certainly the second simplest plugin ever. """ -import test_settings -import main 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") +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") diff --git a/package/test/plugins/ErroneousPlugin.py b/package/test/plugins/ErroneousPlugin.py index 444ac8a..924106c 100644 --- a/package/test/plugins/ErroneousPlugin.py +++ b/package/test/plugins/ErroneousPlugin.py @@ -2,45 +2,39 @@ # -*- coding: utf-8 -*- - """ This is certainly the second simplest plugin ever. """ -import test_settings -import main 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) - +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) diff --git a/package/test/plugins/SimplePlugin.py b/package/test/plugins/SimplePlugin.py index fb6355e..5ff5d09 100644 --- a/package/test/plugins/SimplePlugin.py +++ b/package/test/plugins/SimplePlugin.py @@ -2,43 +2,37 @@ # -*- coding: utf-8 -*- - """ This is certainly the second simplest plugin ever. """ -import test_settings -import main 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) - +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) diff --git a/package/test/plugins/VersionedPlugin10.py b/package/test/plugins/VersionedPlugin10.py index d1ca29b..795f7d5 100644 --- a/package/test/plugins/VersionedPlugin10.py +++ b/package/test/plugins/VersionedPlugin10.py @@ -2,44 +2,41 @@ # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t -*- - """ This is certainly the second simplest plugin ever. """ -from test_settings import * -import main +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!") +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!") diff --git a/package/test/plugins/VersionedPlugin11.py b/package/test/plugins/VersionedPlugin11.py index 35f1893..8133bc6 100644 --- a/package/test/plugins/VersionedPlugin11.py +++ b/package/test/plugins/VersionedPlugin11.py @@ -2,43 +2,39 @@ # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t -*- - """ This is certainly the second simplest plugin ever. """ -from test_settings import * -import main +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) - +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) diff --git a/package/test/plugins/VersionedPlugin111.py b/package/test/plugins/VersionedPlugin111.py index 1f44fce..ca8ed77 100644 --- a/package/test/plugins/VersionedPlugin111.py +++ b/package/test/plugins/VersionedPlugin111.py @@ -2,43 +2,39 @@ # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t -*- - """ This is certainly the second simplest plugin ever. """ -from test_settings import * -import main +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) - +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) diff --git a/package/test/plugins/VersionedPlugin12.py b/package/test/plugins/VersionedPlugin12.py index e5ff740..88c2a70 100644 --- a/package/test/plugins/VersionedPlugin12.py +++ b/package/test/plugins/VersionedPlugin12.py @@ -2,42 +2,41 @@ # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t -*- - """ This is certainly the second simplest plugin ever. """ -from test_settings import * -import main +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!") + + """ + 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!") diff --git a/package/test/plugins/VersionedPlugin12a1.py b/package/test/plugins/VersionedPlugin12a1.py index bca7262..b9e72da 100644 --- a/package/test/plugins/VersionedPlugin12a1.py +++ b/package/test/plugins/VersionedPlugin12a1.py @@ -2,42 +2,41 @@ # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t -*- - """ This is certainly the second simplest plugin ever. """ -from test_settings import * -import main +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!") + + """ + 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!") diff --git a/package/test/pluginsasdirs/SimplePlugin/__init__.py b/package/test/pluginsasdirs/SimplePlugin/__init__.py index fb6355e..5ff5d09 100644 --- a/package/test/pluginsasdirs/SimplePlugin/__init__.py +++ b/package/test/pluginsasdirs/SimplePlugin/__init__.py @@ -2,43 +2,37 @@ # -*- coding: utf-8 -*- - """ This is certainly the second simplest plugin ever. """ -import test_settings -import main 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) - +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) diff --git a/package/test/pluginstoinstall/AutoInstallPlugin.py b/package/test/pluginstoinstall/AutoInstallPlugin.py index 2ca11b9..fe95a81 100644 --- a/package/test/pluginstoinstall/AutoInstallPlugin.py +++ b/package/test/pluginstoinstall/AutoInstallPlugin.py @@ -2,43 +2,37 @@ # -*- coding: utf-8 -*- - """ This is certainly the second simplest plugin ever. """ -import test_settings -import main 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) - +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) diff --git a/package/test/pluginstoinstall/autoinstallWRONGzipplugin.zip b/package/test/pluginstoinstall/autoinstallWRONGzipplugin.zip index 008c1ef..ee8b586 100644 Binary files a/package/test/pluginstoinstall/autoinstallWRONGzipplugin.zip and b/package/test/pluginstoinstall/autoinstallWRONGzipplugin.zip differ diff --git a/package/test/pluginstoinstall/autoinstallZIPplugin.zip b/package/test/pluginstoinstall/autoinstallZIPplugin.zip index 3c63d43..5ef06b5 100644 Binary files a/package/test/pluginstoinstall/autoinstallZIPplugin.zip and b/package/test/pluginstoinstall/autoinstallZIPplugin.zip differ diff --git a/package/test/pluginstoinstall/autoinstalldirplugin/__init__.py b/package/test/pluginstoinstall/autoinstalldirplugin/__init__.py index b0df239..4c38f69 100644 --- a/package/test/pluginstoinstall/autoinstalldirplugin/__init__.py +++ b/package/test/pluginstoinstall/autoinstalldirplugin/__init__.py @@ -2,43 +2,37 @@ # -*- coding: utf-8 -*- - """ This is certainly the second simplest plugin ever. """ -import test_settings -import main 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) - +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) diff --git a/package/test/test_All.py b/package/test/test_All.py index 0365dcf..560f961 100644 --- a/package/test/test_All.py +++ b/package/test/test_All.py @@ -11,33 +11,32 @@ # set correct loading path for test files sys.path.append( - os.path.dirname( - os.path.abspath(__file__))) + os.path.dirname( + os.path.abspath(__file__))) # load the tests -import test_SimplePlugin -import test_Singleton -import test_ConfigPlugin -import test_VersionedPlugin -import test_AutoInstallPlugin -import test_FilterPlugin -import test_ErrorInPlugin -import test_PluginFileLocator -import test_PluginInfo +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 # 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, - ]) - + [ # 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, + ]) diff --git a/package/test/test_AutoInstallPlugin.py b/package/test/test_AutoInstallPlugin.py index 59a4b26..fba6da7 100644 --- a/package/test/test_AutoInstallPlugin.py +++ b/package/test/test_AutoInstallPlugin.py @@ -1,296 +1,311 @@ #!/usr/bin/python # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- -import test_settings +from . import test_settings import unittest import sys -import os +import os import shutil from yapsy.AutoInstallPluginManager import AutoInstallPluginManager class AutoInstallTestsCase(unittest.TestCase): - """ - Test the correct installation and loading of a simple plugin. - """ + """ + 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 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 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_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 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.assert_(True) + def testGetSetInstallDir(self): + """ + Test getting and setting install dir. + """ + self.assertEqual(self.storing_dir, self.pluginManager.getInstallDir()) + self.pluginManager.setInstallDir("mouf/bla") + self.assertEqual("mouf/bla", self.pluginManager.getInstallDir()) - def testGetSetInstallDir(self): - """ - Test getting and setting install dir. - """ - self.assertEqual(self.storing_dir,self.pluginManager.getInstallDir()) - self.pluginManager.setInstallDir("mouf/bla") - self.assertEqual("mouf/bla",self.pluginManager.getInstallDir()) - - - def testNoneLoaded(self): - """ - Test if the correct plugin has been loaded. - """ - self.plugin_loading_check_none() + 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.assert_(install_success) - self.pluginManager.collectPlugins() - self.plugin_loading_check("Auto Install Plugin") + 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 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.assert_(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) - def testActivationAndDeactivation(self): - """ - Test if the activation procedure works. - """ - install_success = self.pluginManager.install(self.new_plugins_waiting_dir, - "autoinstallplugin.yapsy-autoinstall-plugin") - self.assert_(install_success) - self.pluginManager.collectPlugins() - self.plugin_loading_check("Auto Install Plugin") - self.assert_(not self.plugin_info.plugin_object.is_activated) - self.pluginManager.activatePluginByName(self.plugin_info.name, - self.plugin_info.category) - self.assert_(self.plugin_info.plugin_object.is_activated) - self.pluginManager.deactivatePluginByName(self.plugin_info.name, - self.plugin_info.category) - self.assert_(not self.plugin_info.plugin_object.is_activated) class AutoInstallZIPTestsCase(unittest.TestCase): - """ - Test the correct installation and loading of a zipped plugin. - """ + """ + 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 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 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_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 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.assert_(True) + def testNoneLoaded(self): + """ + Test if the correct plugin has been loaded. + """ + self.plugin_loading_check_none() - 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") + install_success = self.pluginManager.installFromZIP(test_file) + self.assertTrue(install_success) + self.pluginManager.collectPlugins() + self.plugin_loading_check("Auto Install ZIP Plugin") - 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.assert_(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 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") + 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 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.assert_(install_success) - self.pluginManager.collectPlugins() - self.plugin_loading_check("Auto Install ZIP Plugin") - self.assert_(not self.plugin_info.plugin_object.is_activated) - self.pluginManager.activatePluginByName(self.plugin_info.name, - self.plugin_info.category) - self.assert_(self.plugin_info.plugin_object.is_activated) - self.pluginManager.deactivatePluginByName(self.plugin_info.name, - self.plugin_info.category) - self.assert_(not self.plugin_info.plugin_object.is_activated) + 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") + 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), - ]) + unittest.TestLoader().loadTestsFromTestCase(AutoInstallTestsCase), + unittest.TestLoader().loadTestsFromTestCase(AutoInstallZIPTestsCase), +]) diff --git a/package/test/test_ConfigPlugin.py b/package/test/test_ConfigPlugin.py index 6f33fad..cfd9085 100644 --- a/package/test/test_ConfigPlugin.py +++ b/package/test/test_ConfigPlugin.py @@ -1,147 +1,149 @@ #!/usr/bin/python # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- -import test_settings +from . import test_settings -import os +import os import unittest -import ConfigParser +from yapsy.compat import ConfigParser from yapsy.ConfigurablePluginManager import ConfigurablePluginManager + class ConfigTestCase(unittest.TestCase): - """ - 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.SafeConfigParser() - 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.SafeConfigParser() - self.config_parser.read(self.config_file) - self.assert_(self.config_parser.has_section("Plugin Management")) - self.assert_(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.assert_(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.assert_(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: - # 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.assert_(True) - - def plugin_activate(self): - """ - Activate the plugin with basic checking - """ - self.plugin_loading_check() - self.assert_(not self.plugin_info.plugin_object.is_activated) - self.pluginManager.activatePluginByName(self.plugin_info.name, - self.plugin_info.category) - self.assert_(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() + """ + 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") + + #--- UTILITIES + + 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) + + 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(ConfigTestCase), - ]) + unittest.TestLoader().loadTestsFromTestCase(ConfigTestCase), +]) diff --git a/package/test/test_ErrorInPlugin.py b/package/test/test_ErrorInPlugin.py index af6015b..f696b48 100644 --- a/package/test/test_ErrorInPlugin.py +++ b/package/test/test_ErrorInPlugin.py @@ -1,65 +1,71 @@ #!/usr/bin/python # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- -import test_settings +from . import test_settings -import os +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) - # - 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) - 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.assertEqual(callback_infos[0].error[0],ImportError) - # 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) + """ + 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) + # - 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) + 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.assertEqual(callback_infos[0].error[0], ImportError) + # 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), - ]) + unittest.TestLoader().loadTestsFromTestCase(ErrorTestCase), +]) diff --git a/package/test/test_FilterPlugin.py b/package/test/test_FilterPlugin.py index bed473c..adada56 100644 --- a/package/test/test_FilterPlugin.py +++ b/package/test/test_FilterPlugin.py @@ -1,245 +1,257 @@ #!/usr/bin/python # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- -import test_settings -from test_settings import TEST_MESSAGE +from . import test_settings +from .test_settings import TEST_MESSAGE import unittest -import os +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) + """ + 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 + + """ + 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.assert_(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.assert_(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.assert_(self.plugin_info.plugin_object.is_activated) - self.plugin_info.plugin_object.deactivate() - self.assert_(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.assertEquals(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.assertEquals(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())) + 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 + + """ + 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.assert_(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.assert_(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.assert_(self.plugin_info.plugin_object.is_activated) - self.plugin_info.plugin_object.deactivate() - self.assert_(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") + 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), - ]) + unittest.TestLoader().loadTestsFromTestCase(FilteredTestsCase), + unittest.TestLoader().loadTestsFromTestCase(FilteredWithMonkeyPathTestsCase), +]) diff --git a/package/test/test_PluginFileLocator.py b/package/test/test_PluginFileLocator.py index 0d413a4..d75bf5b 100644 --- a/package/test/test_PluginFileLocator.py +++ b/package/test/test_PluginFileLocator.py @@ -1,14 +1,13 @@ #!/usr/bin/python # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- -import test_settings +from . import test_settings import unittest import sys import os +from yapsy.compat import ConfigParser, StringIO import tempfile import shutil -from ConfigParser import ConfigParser -from StringIO import StringIO from yapsy import PLUGIN_NAME_FORBIDEN_STRING from yapsy.PluginManager import PluginManager @@ -22,467 +21,555 @@ 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([]) + - 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/SimplePlugin' % self.plugin_directory, '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_minimal_description(self): - plugin_desc_content = StringIO("""\ + + """ + 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/SimplePlugin' % self.plugin_directory, + '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_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("""\ + 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("""\ + 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("""\ + 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("""\ + 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) + 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)) + + """ + 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() - self.assertTrue("package/yapsy" in pl.plugins_places[0]) - - 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 "win" in sys.platform: - 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 "win" in sys.platform: - 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): - 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): - # 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)) - + + """ + 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() + self.assertTrue("package/yapsy" in pl.plugins_places[0]) + + 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 "win" in sys.platform: + 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 "win" in sys.platform: + 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): + 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): + # 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) + 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), - ]) + unittest.TestLoader().loadTestsFromTestCase(IPluginLocatorTest), + unittest.TestLoader().loadTestsFromTestCase(PluginFileAnalyzerWithInfoFileTest), + unittest.TestLoader().loadTestsFromTestCase(PluginFileAnalyzerMathingRegexTest), + unittest.TestLoader().loadTestsFromTestCase(PluginFileLocatorTest), + unittest.TestLoader().loadTestsFromTestCase(PluginManagerSetUpTest), +]) diff --git a/package/test/test_PluginInfo.py b/package/test/test_PluginInfo.py index 342bd69..8a1ec7f 100644 --- a/package/test/test_PluginInfo.py +++ b/package/test/test_PluginInfo.py @@ -1,52 +1,54 @@ #!/usr/bin/python # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- -import test_settings -from ConfigParser import ConfigParser +from . 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) - - + + """ + 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), - ]) + unittest.TestLoader().loadTestsFromTestCase(PluginInfoTest), +]) diff --git a/package/test/test_SimplePlugin.py b/package/test/test_SimplePlugin.py index a70aa86..2304503 100644 --- a/package/test/test_SimplePlugin.py +++ b/package/test/test_SimplePlugin.py @@ -1,296 +1,322 @@ #!/usr/bin/python # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- -import test_settings +from . import test_settings import unittest -import os +import os from yapsy.PluginManager import PluginManager from yapsy.IPlugin import IPlugin from yapsy.PluginFileLocator import PluginFileLocator from yapsy import NormalizePluginNameForModuleName + class YapsyUtils(unittest.TestCase): - def test_NormalizePluginNameForModuleName_on_ok_name(self): - self.assertEqual("moufGlop2",NormalizePluginNameForModuleName("moufGlop2")) + 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_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_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/é")) + 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.assert_(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.assert_(not self.plugin_info.plugin_object.is_activated) - self.simplePluginManager.activatePluginByName(self.plugin_info.name, - self.plugin_info.category) - self.assert_(self.plugin_info.plugin_object.is_activated) - self.simplePluginManager.deactivatePluginByName(self.plugin_info.name, - self.plugin_info.category) - self.assert_(not self.plugin_info.plugin_object.is_activated) + + """ + 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 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) - # - gather infos about the processed plugins (loaded or not) - loadedPlugins = spm.loadPlugins(callback=preload_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]) - # 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) - + + """ + 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 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) + # - gather infos about the processed plugins (loaded or not) + loadedPlugins = spm.loadPlugins(callback=preload_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]) + # 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) + + class SimplePluginDetectionTestsCase(unittest.TestCase): - """ - Test particular aspects of plugin detection - """ - - def testRecursivePluginlocation(self): - """ - Test detection of plugins which by default must be - recusrive. 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) - - + + """ + Test particular aspects of plugin detection + """ + + def testRecursivePluginlocation(self): + """ + Test detection of plugins which by default must be + recusrive. 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) + + suite = unittest.TestSuite([ - unittest.TestLoader().loadTestsFromTestCase(YapsyUtils), - unittest.TestLoader().loadTestsFromTestCase(SimpleTestCase), - unittest.TestLoader().loadTestsFromTestCase(SimplePluginAdvancedManipulationTestsCase), - unittest.TestLoader().loadTestsFromTestCase(SimplePluginDetectionTestsCase), - ]) + unittest.TestLoader().loadTestsFromTestCase(YapsyUtils), + unittest.TestLoader().loadTestsFromTestCase(SimpleTestCase), + unittest.TestLoader().loadTestsFromTestCase(SimplePluginAdvancedManipulationTestsCase), + unittest.TestLoader().loadTestsFromTestCase(SimplePluginDetectionTestsCase), +]) diff --git a/package/test/test_Singleton.py b/package/test/test_Singleton.py index 39dd58c..22f776c 100644 --- a/package/test/test_Singleton.py +++ b/package/test/test_Singleton.py @@ -1,136 +1,143 @@ #!/usr/bin/python # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- -import test_settings +from . import test_settings -import os +import os import unittest -import ConfigParser 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.SafeConfigParser() - 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.assert_(self.config_parser.has_section("Plugin Management")) - self.assert_(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.assert_(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.assert_(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.assert_(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.assert_(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() + """ + 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), - ]) + unittest.TestLoader().loadTestsFromTestCase(ConfigSingletonTestsCase), +]) diff --git a/package/test/test_VersionedPlugin.py b/package/test/test_VersionedPlugin.py index 010d230..711722e 100644 --- a/package/test/test_VersionedPlugin.py +++ b/package/test/test_VersionedPlugin.py @@ -1,116 +1,120 @@ #!/usr/bin/python # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- -import test_settings -from test_settings import TEST_MESSAGE +from . import test_settings +from .test_settings import TEST_MESSAGE import unittest -import os +import os 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.assert_(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.assert_(True) + """ + Test the correct loading of a simple plugin as well as basic + commands. + """ - 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 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 testLatestPluginOfCategory(self): - self.plugin_loading_check() - - def testActivationAndDeactivation(self): - """ - Test if the activation procedure works. - """ - self.plugin_loading_check() - self.assert_(not self.plugin_info.plugin_object.is_activated) - self.versionedPluginManager.activatePluginByName(self.plugin_info.name, - self.plugin_info.category) - self.assert_(self.plugin_info.plugin_object.is_activated) - self.versionedPluginManager.deactivatePluginByName(self.plugin_info.name, - self.plugin_info.category) - self.assert_(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.assert_(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.assert_(self.plugin_info.plugin_object.is_activated) - # self.plugin_info.plugin_object.deactivate() - # self.assert_(not self.plugin_info.plugin_object.is_activated) + 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) - - suite = unittest.TestSuite([ - unittest.TestLoader().loadTestsFromTestCase(VersionedTestsCase), - ]) + unittest.TestLoader().loadTestsFromTestCase(VersionedTestsCase), +]) diff --git a/package/test/test_settings.py b/package/test/test_settings.py index d4cab00..94f0ccb 100644 --- a/package/test/test_settings.py +++ b/package/test/test_settings.py @@ -7,22 +7,19 @@ import logging TEST_MESSAGE = logging.debug -TEMP_CONFIG_FILE_NAME=os.path.join( - os.path.dirname( - os.path.abspath(__file__)), - "tempconfig") +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__)))) + 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__))))) - - - + os.path.dirname( + os.path.dirname( + os.path.dirname( + os.path.abspath(__file__))))) diff --git a/package/yapsy/AutoInstallPluginManager.py b/package/yapsy/AutoInstallPluginManager.py index ae44a07..2c1d721 100644 --- a/package/yapsy/AutoInstallPluginManager.py +++ b/package/yapsy/AutoInstallPluginManager.py @@ -13,191 +13,235 @@ === """ -import sys import os import shutil import zipfile -import StringIO 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. - """ - - - 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={"Default":IPlugin}, - directories_list=None, - plugin_info_ext="yapsy-plugin"): - """ - Create the plugin manager and set up the directory where to - install new plugins. - - 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. - - """ - # 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 sys.version_info < (2, 6): - raise NotImplementedError("Installing fom a ZIP file is only supported for Python2.6 and later.") - 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,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.StringIO(infoFile)) - if moduleName is None: - continue - log.info("Checking existence of the expected module '%s' in the zip file" % moduleName) - if moduleName in zipContent or os.path.join(moduleName,"__init__.py") in zipContent: - isValid = True - 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,e: - log.error("Could not install plugin '%s' from zip file '%s' (exception: '%s')." % (pluginName,plugin_ZIP_filename,e)) - return False - + + """ + A plugin manager that also manages the installation of the plugin + files into the appropriate directory. + """ + + 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={"Default": IPlugin}, + directories_list=None, + plugin_info_ext="yapsy-plugin"): + """ + Create the plugin manager and set up the directory where to + install new plugins. + + 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. + + """ + # 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) + if moduleName in zipContent or os.path.join( + moduleName, + "__init__.py") in zipContent: + isValid = True + 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 diff --git a/package/yapsy/ConfigurablePluginManager.py b/package/yapsy/ConfigurablePluginManager.py index 61f2835..0a4b65c 100644 --- a/package/yapsy/ConfigurablePluginManager.py +++ b/package/yapsy/ConfigurablePluginManager.py @@ -19,260 +19,295 @@ 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). + """ + A plugin manager that also manages a configuration file. - .. 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) - """ - - CONFIG_SECTION_NAME = "Plugin Management" + 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) + """ - def __init__(self, - configparser_instance=None, - config_change_trigger= lambda x:True, - decorated_manager=None, - # The following args will only be used if we need to - # create a default PluginManager - categories_filter={"Default":IPlugin}, - directories_list=None, - plugin_info_ext="yapsy-plugin"): - """ - Create the plugin manager and record the ConfigParser instance - that will be used afterwards. - - 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....) - """ - # Create the base decorator class - PluginManagerDecorator.__init__(self,decorated_manager, - categories_filter, - directories_list, - plugin_info_ext) - self.setConfigParser(configparser_instance, config_change_trigger) + CONFIG_SECTION_NAME = "Plugin Management" + def __init__(self, + configparser_instance=None, + config_change_trigger=lambda x: True, + decorated_manager=None, + # The following args will only be used if we need to + # create a default PluginManager + categories_filter={"Default": IPlugin}, + directories_list=None, + plugin_info_ext="yapsy-plugin"): + """ + Create the plugin manager and record the ConfigParser instance + that will be used afterwards. - 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) + 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....) + """ + # 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 __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 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 __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 __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 __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 __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 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 __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 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 __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 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 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 __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 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 activatePluginByName(self, plugin_name, category_name="Default", save_state=True): - """ - Activate a plugin, , and remember it (in the config file). + 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__ - 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 activatePluginByName( + self, + plugin_name, + category_name="Default", + save_state=True): + """ + Activate a plugin, , and remember it (in the config file). - def deactivatePluginByName(self, plugin_name, category_name="Default", save_state=True): - """ - Deactivate 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 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 + 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 loadPlugins(self,callback=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) - # 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 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) + 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): + """ + 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) + # 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) diff --git a/package/yapsy/FilteredPluginManager.py b/package/yapsy/FilteredPluginManager.py index 05b03ef..a7618e5 100644 --- a/package/yapsy/FilteredPluginManager.py +++ b/package/yapsy/FilteredPluginManager.py @@ -25,114 +25,114 @@ API === """ - + from yapsy.IPlugin import IPlugin -from yapsy.PluginManagerDecorator import PluginManagerDecorator +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={"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) - # 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[:] + + """ + Base class for decorators which filter the plugins list + before they are loaded. + """ + + 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) + # 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[:] diff --git a/package/yapsy/IPlugin.py b/package/yapsy/IPlugin.py index cbe2d32..8bfc31f 100644 --- a/package/yapsy/IPlugin.py +++ b/package/yapsy/IPlugin.py @@ -36,25 +36,25 @@ class IPlugin(object): - """ - The most simple interface to be inherited when creating a plugin. - """ - - def __init__(self): - """ - Set the basic variables. - """ - 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 + """ + The most simple interface to be inherited when creating a plugin. + """ + + def __init__(self): + """ + Set the basic variables. + """ + 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 diff --git a/package/yapsy/IPluginLocator.py b/package/yapsy/IPluginLocator.py index fb12892..4a01231 100644 --- a/package/yapsy/IPluginLocator.py +++ b/package/yapsy/IPluginLocator.py @@ -2,7 +2,6 @@ # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- - """ Role ==== @@ -19,87 +18,101 @@ 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.warn("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.warn("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.warn("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.warn("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.warn("updatePluginPlaces was called but '%s' doesn't implement it." % self) + """ + 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.warn( + "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.warn( + "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.warn( + "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.warn( + "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.warn( + "updatePluginPlaces was called but '%s' doesn't implement it." % + self) diff --git a/package/yapsy/PluginFileLocator.py b/package/yapsy/PluginFileLocator.py index dc6b5ab..74914c4 100644 --- a/package/yapsy/PluginFileLocator.py +++ b/package/yapsy/PluginFileLocator.py @@ -31,7 +31,7 @@ look for files matching a regex and considers them as being the plugin itself. -All analyzers must enforce the +All analyzers must enforce the It enforces the ``plugin locator`` policy as defined by ``IPluginLocator`` and used by ``PluginManager``. @@ -52,481 +52,571 @@ import os import re from yapsy import log -import ConfigParser +from yapsy.compat import ConfigParser, is_py2 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'. + """ -class IPluginFileAnalyzer(object): - """ - Define the methods expected by PluginFileLocator for its 'analyzer'. - """ + def __init__(self, name): + self.name = name - 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 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. - 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 - *dirpath* is the full path to the directory where the plugin file is + *filename* is the name (ie the basename) of the plugin file. - *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) + 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 - - """ - def __init__(self, name, extensions="yapsy-plugin"): - """ - Creates a new analyzer named *name* and dedicated to check and analyze plugins described by a textual "info file". - - *name* name of the plugin. - - *extensions* the expected extensions for the plugin info file. May be a string or a tuple of strings if several extensions are expected. - """ - 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.SafeConfigParser() - try: - config_parser.readfp(infoFileObject) - except Exception,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, str) or isinstance(filename,unicode)): - # filename is a file object: use it - name, moduleName, config_parser = self.getPluginNameAndModuleFromStream(filename) - else: - candidate_infofile = os.path.join(directory, filename) - # parse the information file to get info about the plugin - name, moduleName, config_parser = self.getPluginNameAndModuleFromStream(open(candidate_infofile),candidate_infofile) - 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 - - + + """ + 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 + + """ + + def __init__(self, name, extensions="yapsy-plugin"): + """ + Creates a new analyzer named *name* and dedicated to check and analyze plugins described by a textual "info file". + + *name* name of the plugin. + + *extensions* the expected extensions for the plugin info file. May be a string or a tuple of strings if several extensions are expected. + """ + 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, str): + # 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.ConfigParser() - cf_parser.add_section("Core") - cf_parser.set("Core","Name",infos["name"]) - cf_parser.set("Core","Module",infos["path"]) - return infos,cf_parser + """ + 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): - """ - Defines the strategies, and the places for plugins to look into. - """ - 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 = PluginInfo - 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. - """ + + """ + 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): + """ + Defines the strategies, and the places for plugins to look into. + """ + 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 = PluginInfo + 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)) + _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.warning("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 + plugin_info = self._getInfoForPluginFromAnalyzer( + analyzer, + dirpath, + filename) + if plugin_info is None: + log.warning( + "Plugin candidate '%s' rejected by strategy '%s'" % + (candidate_infofile, analyzer.name)) + # we consider this was the good strategy to use + # for: it failed -> not a plugin -> don't try + # another strategy + break + # 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 + # 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 + _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 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) + 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 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) diff --git a/package/yapsy/PluginInfo.py b/package/yapsy/PluginInfo.py index fc78eee..6f69b63 100644 --- a/package/yapsy/PluginInfo.py +++ b/package/yapsy/PluginInfo.py @@ -12,203 +12,197 @@ === """ -from ConfigParser import ConfigParser +from yapsy.compat import ConfigParser, str 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. - """ - - def __init__(self, plugin_name, plugin_path): - """ - Set the basic information (at least name and path) about the - plugin as well as the default values for other usefull - variables. - - *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'``. - """ - 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 it it was 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 = "" + + """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. + """ + + def __init__(self, plugin_name, plugin_path): + """ + Set the basic information (at least name and path) about the + plugin as well as the default values for other usefull + variables. + + *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'``. + """ + 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 it it was 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 = "" diff --git a/package/yapsy/PluginManager.py b/package/yapsy/PluginManager.py index 5c2baa5..525599d 100644 --- a/package/yapsy/PluginManager.py +++ b/package/yapsy/PluginManager.py @@ -27,12 +27,12 @@ For a *Standard* plugin: - ``myplugin.yapsy-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 @@ -42,18 +42,18 @@ For a *Single file* plugin: - ``myplugin.yapsy-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 @@ -63,8 +63,9 @@ Plugin Info File Format ----------------------- -The plugin info file gathers, as its name suggests, some basic -information about the plugin. +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 @@ -76,15 +77,15 @@ [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. @@ -123,7 +124,7 @@ API === - + """ import sys @@ -147,519 +148,557 @@ 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`_ - """ - - def __init__(self, - categories_filter=None, - directories_list=None, - plugin_info_ext=None, - plugin_locator=None): - """ - 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. - """ - # 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. - - 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.updatePluginPlaces(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 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.itervalues(): - allPlugins.update(pluginsOfOneCategory) - return list(allPlugins) - - 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): - """ - 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``. - - If a callback function is specified, call it before every load - attempt. The ``plugin_info`` instance is passed as an argument to - the callback. - """ + + """ + 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`_ + """ + + def __init__(self, + categories_filter=None, + directories_list=None, + plugin_info_ext=None, + plugin_locator=None): + """ + 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. + """ + # 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. + + 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.updatePluginPlaces(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 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): + """ + 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``. + + If a callback function is specified, call it before every load + attempt. The ``plugin_info`` instance is passed as an argument to + the callback. + """ # 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: - # 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: - plugin_file = open(candidate_filepath+".py","r") - try: - candidate_module = imp.load_module(plugin_module_name,plugin_file,candidate_filepath+".py",("py","r",imp.PY_SOURCE)) - finally: - plugin_file.close() - 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 - for element in [getattr(candidate_module,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 TypeError: - 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: - plugin_info.plugin_object = element() - plugin_info_reference = plugin_info - plugin_info.categories.append(current_category) - self.category_mapping[current_category].append(plugin_info_reference) - self._category_file_mapping[current_category].append(candidate_infofile) - # 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 - - 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 + 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: + # 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)) + 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 + for element in ( + getattr( + candidate_module, + 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]) + # XXX here we also get RunTimeError if running in a Flask + # environment + except Exception: + 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: + plugin_info.plugin_object = element() + plugin_info_reference = plugin_info + plugin_info.categories.append(current_category) + self.category_mapping[current_category].append( + plugin_info_reference) + self._category_file_mapping[ + current_category].append(candidate_infofile) + # 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 + + 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): - """ - Initialisation: this class should not be initialised - explicitly and the ``get`` classmethod must be called instead. - - To set up the various configurables 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`` - """ - 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) + + """ + 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): + """ + Initialisation: this class should not be initialised + explicitly and the ``get`` classmethod must be called instead. + + To set up the various configurables 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`` + """ + 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 - diff --git a/package/yapsy/PluginManagerDecorator.py b/package/yapsy/PluginManagerDecorator.py index 893ba5a..b32f79a 100644 --- a/package/yapsy/PluginManagerDecorator.py +++ b/package/yapsy/PluginManagerDecorator.py @@ -33,70 +33,71 @@ 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. - """ - - def __init__(self, decorated_object=None, - # The following args will only be used if we need to - # create a default PluginManager - categories_filter={"Default":IPlugin}, - directories_list=[os.path.dirname(__file__)], - plugin_info_ext="yapsy-plugin"): - """ - 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``. - """ - - 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 - """ + + """ + 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. + """ + + def __init__(self, decorated_object=None, + # The following args will only be used if we need to + # create a default PluginManager + categories_filter={"Default": IPlugin}, + directories_list=[os.path.dirname(__file__)], + plugin_info_ext="yapsy-plugin"): + """ + 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``. + """ + + 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() + 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() diff --git a/package/yapsy/VersionedPluginManager.py b/package/yapsy/VersionedPluginManager.py index abee6d7..342fcf6 100644 --- a/package/yapsy/VersionedPluginManager.py +++ b/package/yapsy/VersionedPluginManager.py @@ -21,117 +21,117 @@ class VersionedPluginInfo(PluginInfo): - """ - Gather some info about a plugin such as its name, author, - description... - """ - - def __init__(self, plugin_name, plugin_path): - """ - Set the name and path of the plugin as well as the default - values for other usefull variables. - """ - 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) + + """ + Gather some info about a plugin such as its name, author, + description... + """ + + def __init__(self, plugin_name, plugin_path): + """ + Set the name and path of the plugin as well as the default + values for other usefull variables. + """ + 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 plugin manager and record the ConfigParser instance - that will be used afterwards. - - 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....) - """ - # 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 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): - """ - 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._component.loadPlugins(callback) - 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] - + + """ + 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 plugin manager and record the ConfigParser instance + that will be used afterwards. + + 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....) + """ + # 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 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): + """ + 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._component.loadPlugins(callback) + 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] diff --git a/package/yapsy/__init__.py b/package/yapsy/__init__.py index 693fad8..1be5ab4 100644 --- a/package/yapsy/__init__.py +++ b/package/yapsy/__init__.py @@ -27,7 +27,7 @@ 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 @@ -53,7 +53,7 @@ """ -__version__="1.10.423" +__version__ = "1.10.423" # tell epydoc that the documentation is in the reStructuredText format __docformat__ = "restructuredtext en" @@ -63,7 +63,7 @@ log = logging.getLogger('yapsy') # Some constants concerning the plugins -PLUGIN_NAME_FORBIDEN_STRING=";;" +PLUGIN_NAME_FORBIDEN_STRING = ";;" """ .. warning:: This string (';;' by default) is forbidden in plugin names, and will be usable to describe lists of plugins @@ -71,18 +71,28 @@ """ 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") -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 len(pluginName)==0: - return "_" - if pluginName[0].isdigit(): - pluginName = "_" + pluginName - return RE_NON_ALPHANUM.sub("_",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: + return ret.encode('utf-8') + return ret diff --git a/package/yapsy/compat.py b/package/yapsy/compat.py new file mode 100644 index 0000000..082da94 --- /dev/null +++ b/package/yapsy/compat.py @@ -0,0 +1,93 @@ +# -*- coding: utf-8 -*- + +""" +pythoncompat +""" + +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) + +#: Python 3.0.x +is_py30 = (is_py3 and _ver[1] == 0) + +#: Python 3.1.x +is_py31 = (is_py3 and _ver[1] == 1) + +#: Python 3.2.x +is_py32 = (is_py3 and _ver[1] == 2) + +#: Python 3.3.x +is_py33 = (is_py3 and _ver[1] == 3) + +#: Python 3.4.x +is_py34 = (is_py3 and _ver[1] == 4) + +#: Python 2.7.x +is_py27 = (is_py2 and _ver[1] == 7) + +#: Python 2.6.x +is_py26 = (is_py2 and _ver[1] == 6) + +#: Python 2.5.x +is_py25 = (is_py2 and _ver[1] == 5) + +#: Python 2.4.x +is_py24 = (is_py2 and _ver[1] == 4) # I'm assuming this is not by choice. + + +# --------- +# Platforms +# --------- + + +# Syntax sugar. +_ver = sys.version.lower() + +is_pypy = ('pypy' in _ver) +is_jython = ('jython' in _ver) +is_ironpython = ('iron' in _ver) + +# Assume CPython, if nothing else. +is_cpython = not any((is_pypy, is_jython, is_ironpython)) + +# Windows-based system. +is_windows = 'win32' in str(sys.platform).lower() + +# Standard Linux 2+ system. +is_linux = ('linux' in str(sys.platform).lower()) +is_osx = ('darwin' in str(sys.platform).lower()) +is_hpux = ('hpux' in str(sys.platform).lower()) # Complete guess. +is_solaris = ('solar==' in str(sys.platform).lower()) # Complete guess. + +if is_py2: + from ConfigParser import ConfigParser + from StringIO import StringIO + + builtin_str = str + bytes = str + str = unicode + basestring = basestring + numeric_types = (int, long, float) + + +elif is_py3: + from configparser import ConfigParser + from io import StringIO + + builtin_str = str + str = str + bytes = bytes + basestring = (str, bytes) + numeric_types = (int, float) diff --git a/utils/2n3_package.sh b/utils/2n3_package.sh new file mode 100755 index 0000000..3c339ee --- /dev/null +++ b/utils/2n3_package.sh @@ -0,0 +1,44 @@ +#!/bin/bash +# -*- coding: utf-8 -*- + + +# Uses the content of 2n3bundle to build a source package that holds +# both sources and also requires that the default and +# python3_transition branches are checkout'd at paths corresponding to +# the variables below. +# +# Usage example: +# ./2n3_package.sh sdist upload + + +PY2_YAPSY_DIR="../../default" +PY3_YAPSY_DIR="../../python3-transition" +BUNDLE_DIR="./2n3bundle" + +if [ -d $PY2_YAPSY_DIR ]; then + cp -r $PY2_YAPSY_DIR $BUNDLE_DIR/src2 + echo "Copied Python2-compatible sources of yapsy." +else + echo "Unable to find Python2-compatible sources of yapsy, aborting !" + exit 1 +fi + +if [ -d $PY3_YAPSY_DIR ]; then + mkdir "./2n3bundle/src3" + cp -r $PY3_YAPSY_DIR/package $BUNDLE_DIR/src3/package + echo "Copied Python3-compatible sources of yapsy." +else + echo "Unable to find Python3-compatible sources of yapsy, aborting !" + exit 1 +fi + +pushd $BUNDLE_DIR + +python setup.py $@ + +popd + +rm -r $BUNDLE_DIR/src2 +rm -r $BUNDLE_DIR/src3 +echo "Temporary source copies cleaned up." + diff --git a/utils/2n3bundle/MANIFEST.in b/utils/2n3bundle/MANIFEST.in new file mode 100644 index 0000000..25e5dfd --- /dev/null +++ b/utils/2n3bundle/MANIFEST.in @@ -0,0 +1,28 @@ +include test_switch.py + +include src2/package/README.txt +include src2/package/LICENSE.txt +include src2/package/CHANGELOG.txt + +recursive-include src2/package/test *.py *-plugin *.zip + +recursive-include src2/package/yapsy *.py + +recursive-include src2/package/artwork * +recursive-include src2/package/doc * +prune src2/package/doc/_build + + + +include src3/package/README.txt +include src3/package/LICENSE.txt +include src3/package/CHANGELOG.txt +include src3/package/runtests.py + +recursive-include src3/package/test *.py *-plugin *.zip + +recursive-include src3/package/yapsy *.py + +recursive-include src3/package/artwork * +recursive-include src3/package/doc * +prune src3/package/doc/_build diff --git a/utils/2n3bundle/setup.py b/utils/2n3bundle/setup.py new file mode 100644 index 0000000..72100e8 --- /dev/null +++ b/utils/2n3bundle/setup.py @@ -0,0 +1,74 @@ +#!/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 +""" + +from setuptools import setup + + +import sys + +# Trick from http://python3porting.com/2to3.html#distribution-section +if sys.version < '3': + package_par_dir = 'src2/package' +else: + package_par_dir = 'src3/package' + +sys.path.insert(0,package_par_dir) +import yapsy + +setup( + name = "Yapsy", + version = yapsy.__version__, + packages = ['yapsy'], + package_dir = {'yapsy':package_par_dir+"/yapsy"}, + + # the unit tests + test_suite = "test_switch.MainTestSuite", + + # metadata for upload to PyPI + author = "Thibauld Nion", + author_email = "tibonihoo@users.sourceforge.net", + description = "Yet another plugin system", + license = "BSD", + keywords = "plugin manager", + url = "http://yapsy.sourceforge.net", + # more details + long_description = open(package_par_dir+"/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 :: 3', + 'Programming Language :: Python :: 2', + 'Topic :: Software Development :: Libraries :: Python Modules'], + platforms='All', + ) + diff --git a/utils/2n3bundle/test_switch.py b/utils/2n3bundle/test_switch.py new file mode 100644 index 0000000..50f05b5 --- /dev/null +++ b/utils/2n3bundle/test_switch.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import sys + +if sys.version < '3': + sys.path.insert(0,"src2/package") +else: + sys.path.insert(0,"src3/package") + + +from test.test_All import MainTestSuite + +