Hello community, here is the log from the commit of package python-Yapsy for openSUSE:Factory checked in at 2019-07-26 12:42:08 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-Yapsy (Old) and /work/SRC/openSUSE:Factory/.python-Yapsy.new.4126 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-Yapsy" Fri Jul 26 12:42:08 2019 rev:3 rq:718566 version:1.12.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-Yapsy/python-Yapsy.changes 2018-12-13 19:45:57.856893920 +0100 +++ /work/SRC/openSUSE:Factory/.python-Yapsy.new.4126/python-Yapsy.changes 2019-07-26 12:42:12.237859736 +0200 @@ -1,0 +2,9 @@ +Thu Jul 25 13:31:48 UTC 2019 - [email protected] + +- version update to 1.12.0 + * no upstream change log +- deleted patches + - fix-error-plugin-test.patch (unneeded) + - fix-file-location-test.patch (unneeded) + +------------------------------------------------------------------- Old: ---- Yapsy-1.11.223.tar.gz fix-error-plugin-test.patch fix-file-location-test.patch New: ---- Yapsy-1.12.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-Yapsy.spec ++++++ --- /var/tmp/diff_new_pack.vqJ4RH/_old 2019-07-26 12:42:14.029858984 +0200 +++ /var/tmp/diff_new_pack.vqJ4RH/_new 2019-07-26 12:42:14.029858984 +0200 @@ -1,7 +1,7 @@ # # spec file for package python-Yapsy # -# Copyright (c) 2018 SUSE LINUX GmbH, Nuernberg, Germany. +# Copyright (c) 2019 SUSE LINUX GmbH, Nuernberg, Germany. # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -17,19 +17,14 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} -%bcond_without test Name: python-Yapsy -Version: 1.11.223 +Version: 1.12.0 Release: 0 Summary: Yet another plugin system License: BSD-2-Clause Group: Development/Languages/Python Url: http://yapsy.sourceforge.net -Source: https://files.pythonhosted.org/packages/source/Y/Yapsy/Yapsy-1.11.223.tar.gz -# PATCH-FIX-OPENSUSE fix-file-location-test.patch [email protected] -- Fix file locations for tests -Patch0: fix-file-location-test.patch -# PATCH-FIX-OPENSUSE fix-error-plugin-test.patch [email protected] -- Fix exception class for tests -Patch1: fix-error-plugin-test.patch +Source: https://files.pythonhosted.org/packages/source/Y/Yapsy/Yapsy-%{version}.tar.gz BuildRequires: %{python_module setuptools} BuildRequires: fdupes BuildRequires: python-rpm-macros @@ -59,8 +54,6 @@ %prep %setup -q -n Yapsy-%{version} -%patch0 -%patch1 %build %{python_build} @@ -74,10 +67,8 @@ %{python_install} %{python_expand %fdupes %{buildroot}%{$python_sitelib}} -%if %{with test} %check %{python_exec setup.py test} -%endif %files %{python_files} %{python_sitelib}/* ++++++ Yapsy-1.11.223.tar.gz -> Yapsy-1.12.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Yapsy-1.11.223/PKG-INFO new/Yapsy-1.12.0/PKG-INFO --- old/Yapsy-1.11.223/PKG-INFO 2015-06-25 20:35:47.000000000 +0200 +++ new/Yapsy-1.12.0/PKG-INFO 2018-09-02 18:41:16.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: Yapsy -Version: 1.11.223 +Version: 1.12.0 Summary: Yet another plugin system Home-page: http://yapsy.sourceforge.net Author: Thibauld Nion @@ -38,6 +38,10 @@ - Josip Delic (delijati) - frmdstryr - Pierre-Yves Langlois + - Guillaume Binet (gbin) + - Blake Oliver (Oliver2213) + - Xuecheng Zhang (csuzhangxc) + Contributions are welcome as pull requests, patches or tickets on the diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Yapsy-1.11.223/README.txt new/Yapsy-1.12.0/README.txt --- old/Yapsy-1.11.223/README.txt 2015-04-19 14:39:05.000000000 +0200 +++ new/Yapsy-1.12.0/README.txt 2018-09-02 18:13:44.000000000 +0200 @@ -30,6 +30,10 @@ - Josip Delic (delijati) - frmdstryr - Pierre-Yves Langlois + - Guillaume Binet (gbin) + - Blake Oliver (Oliver2213) + - Xuecheng Zhang (csuzhangxc) + Contributions are welcome as pull requests, patches or tickets on the diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Yapsy-1.11.223/Yapsy.egg-info/PKG-INFO new/Yapsy-1.12.0/Yapsy.egg-info/PKG-INFO --- old/Yapsy-1.11.223/Yapsy.egg-info/PKG-INFO 2015-06-25 20:35:47.000000000 +0200 +++ new/Yapsy-1.12.0/Yapsy.egg-info/PKG-INFO 2018-09-02 18:41:16.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: Yapsy -Version: 1.11.223 +Version: 1.12.0 Summary: Yet another plugin system Home-page: http://yapsy.sourceforge.net Author: Thibauld Nion @@ -38,6 +38,10 @@ - Josip Delic (delijati) - frmdstryr - Pierre-Yves Langlois + - Guillaume Binet (gbin) + - Blake Oliver (Oliver2213) + - Xuecheng Zhang (csuzhangxc) + Contributions are welcome as pull requests, patches or tickets on the diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Yapsy-1.11.223/Yapsy.egg-info/SOURCES.txt new/Yapsy-1.12.0/Yapsy.egg-info/SOURCES.txt --- old/Yapsy-1.11.223/Yapsy.egg-info/SOURCES.txt 2015-06-25 20:35:47.000000000 +0200 +++ new/Yapsy-1.12.0/Yapsy.egg-info/SOURCES.txt 2018-09-02 18:41:16.000000000 +0200 @@ -48,6 +48,7 @@ test/test_settings.py test/plugins/ConfigPlugin.py test/plugins/ErroneousPlugin.py +test/plugins/LegacyMultiprocessPlugin.py test/plugins/SimpleMultiprocessPlugin.py test/plugins/SimplePlugin.py test/plugins/VersionedPlugin10.py @@ -58,6 +59,7 @@ test/plugins/configplugin.yapsy-config-plugin test/plugins/configplugin.yapsy-filter-plugin test/plugins/erroneousplugin.yapsy-error-plugin +test/plugins/legacymultiprocessplugin.multiprocess-plugin test/plugins/simplemultiprocessplugin.multiprocess-plugin test/plugins/simpleplugin.yapsy-filter-plugin test/plugins/simpleplugin.yapsy-plugin @@ -78,6 +80,7 @@ yapsy/ConfigurablePluginManager.py yapsy/FilteredPluginManager.py yapsy/IMultiprocessChildPlugin.py +yapsy/IMultiprocessPlugin.py yapsy/IPlugin.py yapsy/IPluginLocator.py yapsy/MultiprocessPluginManager.py diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Yapsy-1.11.223/setup.cfg new/Yapsy-1.12.0/setup.cfg --- old/Yapsy-1.11.223/setup.cfg 2015-06-25 20:35:47.000000000 +0200 +++ new/Yapsy-1.12.0/setup.cfg 2018-09-02 18:41:16.000000000 +0200 @@ -1,5 +1,4 @@ [egg_info] tag_build = tag_date = 0 -tag_svn_revision = 0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Yapsy-1.11.223/test/plugins/LegacyMultiprocessPlugin.py new/Yapsy-1.12.0/test/plugins/LegacyMultiprocessPlugin.py --- old/Yapsy-1.11.223/test/plugins/LegacyMultiprocessPlugin.py 1970-01-01 01:00:00.000000000 +0100 +++ new/Yapsy-1.12.0/test/plugins/LegacyMultiprocessPlugin.py 2018-09-02 17:52:22.000000000 +0200 @@ -0,0 +1,19 @@ +# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- + +""" +A simple multiprocessed plugin that echoes the content received to the parent +""" + +from yapsy.IMultiprocessChildPlugin import IMultiprocessChildPlugin + +class LegacyMultiprocessPlugin(IMultiprocessChildPlugin): + """ + Only trigger the expected test results. + """ + + def __init__(self, parent_pipe): + IMultiprocessChildPlugin.__init__(self, parent_pipe=parent_pipe) + + def run(self): + content_from_parent = self.parent_pipe.recv() + self.parent_pipe.send("{0}|echo_from_child".format(content_from_parent)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Yapsy-1.11.223/test/plugins/SimpleMultiprocessPlugin.py new/Yapsy-1.12.0/test/plugins/SimpleMultiprocessPlugin.py --- old/Yapsy-1.11.223/test/plugins/SimpleMultiprocessPlugin.py 2015-03-28 22:03:30.000000000 +0100 +++ new/Yapsy-1.12.0/test/plugins/SimpleMultiprocessPlugin.py 2018-09-02 17:52:22.000000000 +0200 @@ -4,16 +4,17 @@ A simple multiprocessed plugin that echoes the content received to the parent """ -from yapsy.IMultiprocessChildPlugin import IMultiprocessChildPlugin +from yapsy.IMultiprocessPlugin import IMultiprocessPlugin -class SimpleMultiprocessPlugin(IMultiprocessChildPlugin): +class SimpleMultiprocessPlugin(IMultiprocessPlugin): """ Only trigger the expected test results. """ def __init__(self, parent_pipe): - IMultiprocessChildPlugin.__init__(self, parent_pipe=parent_pipe) + IMultiprocessPlugin.__init__(self, parent_pipe=parent_pipe) def run(self): content_from_parent = self.parent_pipe.recv() self.parent_pipe.send("{0}|echo_from_child".format(content_from_parent)) + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Yapsy-1.11.223/test/plugins/legacymultiprocessplugin.multiprocess-plugin new/Yapsy-1.12.0/test/plugins/legacymultiprocessplugin.multiprocess-plugin --- old/Yapsy-1.11.223/test/plugins/legacymultiprocessplugin.multiprocess-plugin 1970-01-01 01:00:00.000000000 +0100 +++ new/Yapsy-1.12.0/test/plugins/legacymultiprocessplugin.multiprocess-plugin 2018-09-02 17:52:22.000000000 +0200 @@ -0,0 +1,9 @@ +[Core] +Name = Legacy Multiprocess Plugin +Module = LegacyMultiprocessPlugin + +[Documentation] +Author = Pierre-Yves Langlois +Version = 0.1 +Description = A minimal plugin to test multiprocessing +Copyright = 2015 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Yapsy-1.11.223/test/test_AutoInstallPlugin.py new/Yapsy-1.12.0/test/test_AutoInstallPlugin.py --- old/Yapsy-1.11.223/test/test_AutoInstallPlugin.py 2015-03-28 22:00:00.000000000 +0100 +++ new/Yapsy-1.12.0/test/test_AutoInstallPlugin.py 2018-09-02 17:52:22.000000000 +0200 @@ -8,7 +8,6 @@ from yapsy.AutoInstallPluginManager import AutoInstallPluginManager - class AutoInstallTestsCase(unittest.TestCase): """ Test the correct installation and loading of a simple plugin. @@ -92,8 +91,9 @@ 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()) + custom_install_dir = os.path.join("mouf", "bla") + self.pluginManager.setInstallDir(custom_install_dir) + self.assertEqual(custom_install_dir, self.pluginManager.getInstallDir()) def testNoneLoaded(self): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Yapsy-1.11.223/test/test_ErrorInPlugin.py new/Yapsy-1.12.0/test/test_ErrorInPlugin.py --- old/Yapsy-1.11.223/test/test_ErrorInPlugin.py 2015-03-28 22:00:14.000000000 +0100 +++ new/Yapsy-1.12.0/test/test_ErrorInPlugin.py 2017-01-29 18:44:25.000000000 +0100 @@ -31,6 +31,9 @@ callback_infos = [] def preload_cbk(i_plugin_info): callback_infos.append(i_plugin_info) + callback_after_infos = [] + def postload_cbk(i_plugin_info): + callback_after_infos.append(i_plugin_info) # - gather infos about the processed plugins (loaded or not) # and for the test, monkey patch the logger originalLogLevel = log.getEffectiveLevel() @@ -41,7 +44,7 @@ originalErrorMethod = log.error log.error = errorMock try: - loadedPlugins = spm.loadPlugins(callback=preload_cbk) + loadedPlugins = spm.loadPlugins(callback=preload_cbk, callback_after=postload_cbk) finally: log.setLevel(originalLogLevel) log.error = originalErrorMethod @@ -50,7 +53,8 @@ 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) + self.assertTrue(issubclass(callback_infos[0].error[0],ImportError)) + self.assertEqual(len(callback_after_infos),0) # check that the getCategories works self.assertEqual(len(spm.getCategories()),1) sole_category = spm.getCategories()[0] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Yapsy-1.11.223/test/test_PluginFileLocator.py new/Yapsy-1.12.0/test/test_PluginFileLocator.py --- old/Yapsy-1.11.223/test/test_PluginFileLocator.py 2015-04-18 22:48:35.000000000 +0200 +++ new/Yapsy-1.12.0/test/test_PluginFileLocator.py 2018-09-02 17:52:22.000000000 +0200 @@ -8,6 +8,7 @@ import tempfile import shutil +import yapsy from yapsy import PLUGIN_NAME_FORBIDEN_STRING from yapsy.PluginManager import PluginManager from yapsy.PluginManager import IPlugin @@ -60,7 +61,14 @@ 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.assertEqual(info_dict, + {'website': 'http://mathbench.sourceforge.net', + 'description': 'A simple plugin usefull for basic testing', + 'author': 'Thibauld Nion', + 'version': '0.1', + 'path': '%s' % os.path.join(self.plugin_directory,"SimplePlugin"), + 'name': 'Simple Plugin', + 'copyright': '2014'}) self.assertTrue(isinstance(cf_parser,ConfigParser)) def test_isValid_WithMultiExtensions(self): @@ -192,8 +200,14 @@ 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]) + expected_yapsy_module_path = os.path.dirname(yapsy.__file__) + first_plugin_place = pl.plugins_places[0] + self.assertEqual(expected_yapsy_module_path, first_plugin_place) + def test_given_string_as_plugin_places_raises_error(self): + pl = PluginFileLocator() + self.assertRaises(ValueError, pl.setPluginPlaces, "/mouf") + def test_locatePlugins(self): pl = PluginFileLocator() pl.setPluginPlaces([self.plugin_directory]) @@ -207,7 +221,7 @@ self.assertTrue(isinstance(candidates[0][2],PluginInfo)) def test_locatePlugins_when_plugin_is_symlinked(self): - if "win" in sys.platform: + if sys.platform.startswith("win"): return temp_dir = tempfile.mkdtemp() try: @@ -244,7 +258,7 @@ self.assertTrue(isinstance(candidates[0][2],PluginInfo)) def test_locatePlugins_when_plugin_is_a_symlinked_directory(self): - if "win" in sys.platform: + if sys.platform.startswith("win"): return temp_dir = tempfile.mkdtemp() try: @@ -301,6 +315,8 @@ shutil.rmtree(temp_dir) def test_locatePlugins_recursively_when_plugin_is_a_symlinked_directory(self): + if sys.platform.startswith("win"): + return temp_dir = tempfile.mkdtemp() try: temp_sub_dir = os.path.join(temp_dir,"plugins") @@ -326,6 +342,8 @@ shutil.rmtree(temp_dir) def test_locatePlugins_recursively_when_plugin_parent_dir_is_a_symlinked_directory(self): + if sys.platform.startswith("win"): + return # This actually reproduced the "Plugin detection doesn't follow symlinks" bug # at http://sourceforge.net/p/yapsy/bugs/19/ temp_dir = tempfile.mkdtemp() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Yapsy-1.11.223/test/test_SimpleMultiprocessPlugin.py new/Yapsy-1.12.0/test/test_SimpleMultiprocessPlugin.py --- old/Yapsy-1.11.223/test/test_SimpleMultiprocessPlugin.py 2015-03-29 00:26:07.000000000 +0100 +++ new/Yapsy-1.12.0/test/test_SimpleMultiprocessPlugin.py 2018-09-02 17:52:22.000000000 +0200 @@ -29,17 +29,18 @@ """ Test if the plugin is loaded and if the communication pipe is properly setuped. """ - numTestedPlugins = 0 - for plugin in self.mpPluginManager.getAllPlugins(): - content_from_parent = "hello-from-parent" + for plugin_index, plugin in enumerate(self.mpPluginManager.getAllPlugins()): + child_pipe = plugin.plugin_object.child_pipe + content_from_parent = "hello-{0}-from-parent".format(plugin_index) + child_pipe.send(content_from_parent) content_from_child = False - plugin.plugin_object.child_pipe.send(content_from_parent) - if plugin.plugin_object.child_pipe.poll(5): - content_from_child = plugin.plugin_object.child_pipe.recv() - self.assertEqual(content_from_child, "{0}|echo_from_child".format(content_from_parent)) - numTestedPlugins += 1 - self.assertTrue(numTestedPlugins >= 1) - + if child_pipe.poll(5): + content_from_child = child_pipe.recv() + self.assertEqual("{0}|echo_from_child".format(content_from_parent), + content_from_child) + num_tested_plugin = plugin_index+1 + self.assertEqual(2, num_tested_plugin) + suite = unittest.TestSuite([ unittest.TestLoader().loadTestsFromTestCase(SimpleMultiprocessTestCase), ]) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Yapsy-1.11.223/test/test_SimplePlugin.py new/Yapsy-1.12.0/test/test_SimplePlugin.py --- old/Yapsy-1.11.223/test/test_SimplePlugin.py 2015-03-29 00:26:34.000000000 +0100 +++ new/Yapsy-1.12.0/test/test_SimplePlugin.py 2018-09-02 17:52:22.000000000 +0200 @@ -7,7 +7,9 @@ from yapsy.PluginManager import PluginManager from yapsy.IPlugin import IPlugin from yapsy.PluginFileLocator import PluginFileLocator +from yapsy.PluginFileLocator import IPluginFileAnalyzer from yapsy import NormalizePluginNameForModuleName +from yapsy.compat import ConfigParser class YapsyUtils(unittest.TestCase): @@ -179,12 +181,17 @@ callback_infos = [] def preload_cbk(plugin_info): callback_infos.append(plugin_info) + callback_after_infos = [] + def postload_cbk(plugin_info): + callback_after_infos.append(plugin_info) # - gather infos about the processed plugins (loaded or not) - loadedPlugins = spm.loadPlugins(callback=preload_cbk) + loadedPlugins = spm.loadPlugins(callback=preload_cbk, callback_after=postload_cbk) self.assertEqual(len(loadedPlugins),1) self.assertEqual(len(callback_infos),1) self.assertEqual(loadedPlugins[0].error,None) self.assertEqual(loadedPlugins[0],callback_infos[0]) + self.assertEqual(len(callback_after_infos),1) + self.assertEqual(loadedPlugins[0],callback_infos[0]) # check that the getCategories works self.assertEqual(len(spm.getCategories()),1) sole_category = spm.getCategories()[0] @@ -245,7 +252,34 @@ spm.appendPluginToCategory(plugin_info, "Default") self.assertEqual(len(spm.getPluginsOfCategory("Default")),1) self.assertEqual(len(spm.getPluginsOfCategory("IP")),1) - + + def testGetPluginOf(self): + """ + Test the plugin query function. + """ + spm = PluginManager( + categories_filter = { + "Default": IPlugin, + "IP": IPlugin, + }, + directories_list=[ + os.path.join( + os.path.dirname(os.path.abspath(__file__)),"plugins")]) + # load the plugins that may be found + spm.collectPlugins() + # check the getPluginsOfCategory + self.assertEqual(len(spm.getPluginsOf(categories="IP")), 1) + self.assertEqual(len(spm.getPluginsOf(categories="Default")), 1) + self.assertEqual(len(spm.getPluginsOf(name="Simple Plugin")), 1) + self.assertEqual(len(spm.getPluginsOf(is_activated=False)), 1) + self.assertEqual(len(spm.getPluginsOf(categories="IP", is_activated=True)), 0) + self.assertEqual(len(spm.getPluginsOf(categories="IP", is_activated=False)), 1) + self.assertEqual(len(spm.getPluginsOf(categories="IP", pouet=False)), 0) + self.assertEqual(len(spm.getPluginsOf(categories=["IP"])), 0) + # The order in the categories are added to plugin info is random in this setup, hence the strange formula below + self.assertEqual(len(spm.getPluginsOf(categories=["IP", "Default"]) | spm.getPluginsOf(categories=["Default", "IP"])), 1) + self.assertEqual(len(spm.getPluginsOf(category="Default") | spm.getPluginsOf(category="IP")), 1) + class SimplePluginDetectionTestsCase(unittest.TestCase): """ Test particular aspects of plugin detection @@ -254,7 +288,7 @@ def testRecursivePluginlocation(self): """ Test detection of plugins which by default must be - recusrive. Here we give the test directory as a plugin place + recursive. Here we give the test directory as a plugin place whereas we expect the plugins to be in test/plugins. """ spm = PluginManager(directories_list=[ @@ -309,6 +343,40 @@ # check the getPluginsOfCategory self.assertEqual(len(spm.getPluginsOfCategory(sole_category)),1) + def testEnforcingPluginDirsDoesNotKeepDefaultDir(self): + """ + Test that providing the directories list override the default search directory + instead of extending the default list. + """ + + class AcceptAllPluginFileAnalyzer(IPluginFileAnalyzer): + + def __init__(self): + IPluginFileAnalyzer.__init__(self, "AcceptAll") + + def isValidPlugin(self, filename): + return True + + def getInfosDictFromPlugin(self, dirpath, filename): + return { "name": filename, "path": dirpath}, ConfigParser() + + pluginLocator = PluginFileLocator() + pluginLocator.setAnalyzers([AcceptAllPluginFileAnalyzer()]) + + spm_default_dirs = PluginManager(plugin_locator= pluginLocator) + spm_default_dirs.locatePlugins() + candidates_in_default_dir = spm_default_dirs.getPluginCandidates() + candidates_files_in_default_dir = set([c[0] for c in candidates_in_default_dir]) + + pluginLocator = PluginFileLocator() + pluginLocator.setAnalyzers([AcceptAllPluginFileAnalyzer()]) + spm = PluginManager(plugin_locator= pluginLocator, + directories_list=[os.path.dirname(os.path.abspath(__file__)),"does-not-exists"]) + spm.locatePlugins() + candidates = spm.getPluginCandidates() + candidates_files = set([c[0] for c in candidates]) + + self.assertFalse(set(candidates_files_in_default_dir).issubset(set(candidates_files))) suite = unittest.TestSuite([ unittest.TestLoader().loadTestsFromTestCase(YapsyUtils), diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Yapsy-1.11.223/yapsy/AutoInstallPluginManager.py new/Yapsy-1.12.0/yapsy/AutoInstallPluginManager.py --- old/Yapsy-1.11.223/yapsy/AutoInstallPluginManager.py 2015-05-08 22:35:28.000000000 +0200 +++ new/Yapsy-1.12.0/yapsy/AutoInstallPluginManager.py 2018-09-02 17:52:22.000000000 +0200 @@ -179,8 +179,19 @@ 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 + candidate_module_paths = [ + moduleName, + # Try path consistent with the platform specific one + os.path.join(moduleName,"__init__.py"), + # Try typical paths (unix and windows) + "%s/__init__.py" % moduleName, + "%s\\__init__.py" % moduleName + ] + for candidate in candidate_module_paths: + if candidate in zipContent: + isValid = True + break + if isValid: break if not isValid: log.warning("Zip file structure seems wrong in '%s', " diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Yapsy-1.11.223/yapsy/ConfigurablePluginManager.py new/Yapsy-1.12.0/yapsy/ConfigurablePluginManager.py --- old/Yapsy-1.11.223/yapsy/ConfigurablePluginManager.py 2015-05-08 22:50:56.000000000 +0200 +++ new/Yapsy-1.12.0/yapsy/ConfigurablePluginManager.py 2017-01-29 18:38:55.000000000 +0100 @@ -250,13 +250,13 @@ return plugin_object return None - def loadPlugins(self,callback=None): + def loadPlugins(self,callback=None, callback_after=None): """ Walk through the plugins' places and look for plugins. Then for each plugin candidate look for its category, load it and stores it in the appropriate slot of the ``category_mapping``. """ - self._component.loadPlugins(callback) + self._component.loadPlugins(callback, callback_after) # now load the plugins according to the recorded configuration if self.config_parser.has_section(self.CONFIG_SECTION_NAME): # browse all the categories diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Yapsy-1.11.223/yapsy/IMultiprocessChildPlugin.py new/Yapsy-1.12.0/yapsy/IMultiprocessChildPlugin.py --- old/Yapsy-1.11.223/yapsy/IMultiprocessChildPlugin.py 2015-03-28 21:57:17.000000000 +0100 +++ new/Yapsy-1.12.0/yapsy/IMultiprocessChildPlugin.py 2018-09-02 17:52:22.000000000 +0200 @@ -5,41 +5,38 @@ Role ==== -Defines the basic interfaces for multiprocessed plugins. +Originally defined the basic interfaces for multiprocessed plugins. -Extensibility -============= +Deprecation Note +================ -In your own software, you'll probably want to build derived classes of -the ``IMultiprocessChildPlugin`` class as it is a mere interface with no specific -functionality. +This class is deprecated and replaced by :doc:`IMultiprocessChildPlugin`. -Your software's plugins should then inherit your very own plugin class -(itself derived from ``IMultiprocessChildPlugin``). +Child classes of `IMultiprocessChildPlugin` used to be an `IPlugin` as well as +a `multiprocessing.Process`, possibly playing with the functionalities of both, +which make maintenance harder than necessary. -Override the run method to include your code. Use the self.parent_pipe to send -and receive data with the parent process or create your own communication -mecanism. - -Where and how to code these plugins is explained in the section about -the :doc:`PluginManager`. +And indeed following a bug fix to make multiprocess plugins work on Windows, +instances of IMultiprocessChildPlugin inherit Process but are not exactly the +running process (there is a new wrapper process). API === """ from multiprocessing import Process -from yapsy.IPlugin import IPlugin +from yapsy.IMultiprocessPlugin import IMultiprocessPlugin -class IMultiprocessChildPlugin(IPlugin, Process): +class IMultiprocessChildPlugin(IMultiprocessPlugin, Process): """ Base class for multiprocessed plugin. + + DEPRECATED(>1.11): Please use IMultiProcessPluginBase instead ! """ def __init__(self, parent_pipe): - self.parent_pipe = parent_pipe - IPlugin.__init__(self) + IMultiprocessPlugin.__init__(self, parent_pipe) Process.__init__(self) def run(self): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Yapsy-1.11.223/yapsy/IMultiprocessPlugin.py new/Yapsy-1.12.0/yapsy/IMultiprocessPlugin.py --- old/Yapsy-1.11.223/yapsy/IMultiprocessPlugin.py 1970-01-01 01:00:00.000000000 +0100 +++ new/Yapsy-1.12.0/yapsy/IMultiprocessPlugin.py 2018-09-02 17:52:22.000000000 +0200 @@ -0,0 +1,47 @@ +# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- + + +""" +Role +==== + +Defines the basic interfaces for multiprocessed plugins. + +Extensibility +============= + +In your own software, you'll probably want to build derived classes of +the ``IMultiprocessPlugin`` class as it is a mere interface with no specific +functionality. + +Your software's plugins should then inherit your very own plugin class +(itself derived from ``IMultiprocessPlugin``). + +Override the `run` method to include your code. Use the `self.parent_pipe` to send +and receive data with the parent process or create your own communication +mecanism. + +Where and how to code these plugins is explained in the section about +the :doc:`PluginManager`. + +API +=== +""" + +from yapsy.IPlugin import IPlugin + + +class IMultiprocessPlugin(IPlugin): + """ + Base class for multiprocessed plugin. + """ + + def __init__(self, parent_pipe): + IPlugin.__init__(self) + self.parent_pipe = parent_pipe + + def run(self): + """ + Override this method in your implementation + """ + return diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Yapsy-1.11.223/yapsy/IPluginLocator.py new/Yapsy-1.12.0/yapsy/IPluginLocator.py --- old/Yapsy-1.11.223/yapsy/IPluginLocator.py 2015-03-28 21:57:28.000000000 +0100 +++ new/Yapsy-1.12.0/yapsy/IPluginLocator.py 2017-01-29 18:35:58.000000000 +0100 @@ -60,7 +60,7 @@ 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) + log.warning("setPluginInfoClass was called but '%s' doesn't implement it." % self) return None,None,None @@ -72,7 +72,7 @@ Set the class that holds PluginInfo. The class should inherit from ``PluginInfo``. """ - log.warn("setPluginInfoClass was called but '%s' doesn't implement it." % self) + log.warning("setPluginInfoClass was called but '%s' doesn't implement it." % self) def getPluginInfoClass(self): """ @@ -81,7 +81,7 @@ Get the class that holds PluginInfo. """ - log.warn("getPluginInfoClass was called but '%s' doesn't implement it." % self) + log.warning("getPluginInfoClass was called but '%s' doesn't implement it." % self) return None def setPluginPlaces(self, directories_list): @@ -91,7 +91,7 @@ Set the list of directories where to look for plugin places. """ - log.warn("setPluginPlaces was called but '%s' doesn't implement it." % self) + log.warning("setPluginPlaces was called but '%s' doesn't implement it." % self) def updatePluginPlaces(self, directories_list): """ @@ -100,5 +100,5 @@ Updates the list of directories where to look for plugin places. """ - log.warn("updatePluginPlaces was called but '%s' doesn't implement it." % self) + log.warning("updatePluginPlaces was called but '%s' doesn't implement it." % self) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Yapsy-1.11.223/yapsy/MultiprocessPluginManager.py new/Yapsy-1.12.0/yapsy/MultiprocessPluginManager.py --- old/Yapsy-1.11.223/yapsy/MultiprocessPluginManager.py 2015-05-08 22:37:29.000000000 +0200 +++ new/Yapsy-1.12.0/yapsy/MultiprocessPluginManager.py 2018-09-02 17:52:22.000000000 +0200 @@ -14,6 +14,7 @@ import multiprocessing as mproc +from yapsy.IMultiprocessPlugin import IMultiprocessPlugin from yapsy.IMultiprocessChildPlugin import IMultiprocessChildPlugin from yapsy.MultiprocessPluginProxy import MultiprocessPluginProxy from yapsy.PluginManager import PluginManager @@ -22,7 +23,7 @@ class MultiprocessPluginManager(PluginManager): """ Subclass of the PluginManager that runs each plugin in a different process - """ + """ def __init__(self, categories_filter=None, @@ -30,26 +31,65 @@ plugin_info_ext=None, plugin_locator=None): if categories_filter is None: - categories_filter = {"Default": IMultiprocessChildPlugin} + categories_filter = {"Default": IMultiprocessPlugin} PluginManager.__init__(self, categories_filter=categories_filter, directories_list=directories_list, plugin_info_ext=plugin_info_ext, plugin_locator=plugin_locator) + self.connections = [] - def instanciateElement(self, element): - """ - This method instanciate each plugin in a new process and link it to + + def instanciateElementWithImportInfo(self, element, element_name, + plugin_module_name, candidate_filepath): + """This method instanciates each plugin in a new process and links it to the parent with a pipe. - In the parent process context, the plugin's class is replaced by the ``MultiprocessPluginProxy`` - class that hold the information about the child process and the pipe to communicate with it. See - :doc:`IMultiprocessChildPlugin` + In the parent process context, the plugin's class is replaced by + the ``MultiprocessPluginProxy`` class that hold the information + about the child process and the pipe to communicate with it. + + :warning: The plugin code should only use the pipe to + communicate with the rest of the applica`tion and should not + assume any kind of shared memory, not any specific functionality + of the `multiprocessing.Process` parent class (its behaviour is + different between platforms !) + + See :doc:`IMultiprocessPlugin` """ + if element is IMultiprocessChildPlugin: + # The following will keep retro compatibility for IMultiprocessChildPlugin + raise Exception("Preventing instanciation of a bar child plugin interface.") instanciated_element = MultiprocessPluginProxy() parent_pipe, child_pipe = mproc.Pipe() - instanciated_element.proc = element(child_pipe) instanciated_element.child_pipe = parent_pipe + instanciated_element.proc = MultiprocessPluginManager._PluginProcessWrapper( + element_name, plugin_module_name, candidate_filepath, + child_pipe) instanciated_element.proc.start() return instanciated_element + + class _PluginProcessWrapper(mproc.Process): + """Helper class that strictly needed to be able to spawn the + plugin on Windows but kept also for Unix platform to get a more + uniform behaviour. + + This will handle re-importing the plugin's module in the child + process (again this is necessary on windows because what has + been imported in the main thread/process will not be shared with + the spawned process.) + """ + def __init__(self, element_name, plugin_module_name, candidate_filepath, child_pipe): + self.element_name = element_name + self.child_pipe = child_pipe + self.plugin_module_name = plugin_module_name + self.candidate_filepath = candidate_filepath + mproc.Process.__init__(self) + + def run(self): + module = PluginManager._importModule(self.plugin_module_name, + self.candidate_filepath) + element = getattr(module, self.element_name) + e = element(self.child_pipe) + e.run() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Yapsy-1.11.223/yapsy/PluginFileLocator.py new/Yapsy-1.12.0/yapsy/PluginFileLocator.py --- old/Yapsy-1.11.223/yapsy/PluginFileLocator.py 2015-05-08 22:55:16.000000000 +0200 +++ new/Yapsy-1.12.0/yapsy/PluginFileLocator.py 2018-09-02 17:52:22.000000000 +0200 @@ -332,7 +332,7 @@ 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._default_plugin_info_cls = plugin_info_cls self._plugin_info_cls_map = {} self._max_size = 1e3*1024 # in octets (by default 1 Mo) self.recursive = True @@ -433,7 +433,7 @@ # 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)) + log.debug("Plugin candidate '%s' rejected by strategy '%s'" % (candidate_infofile, analyzer.name)) break # we consider this was the good strategy to use for: it failed -> not a plugin -> don't try another strategy # now determine the path of the file to execute, # depending on wether the path indicated is a @@ -514,6 +514,8 @@ """ Set the list of directories where to look for plugin places. """ + if isinstance(directories_list, basestring): + raise ValueError("'directories_list' given as a string, but expected to be a list or enumeration of strings") if directories_list is None: directories_list = [os.path.dirname(__file__)] self.plugins_places = directories_list diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Yapsy-1.11.223/yapsy/PluginInfo.py new/Yapsy-1.12.0/yapsy/PluginInfo.py --- old/Yapsy-1.11.223/yapsy/PluginInfo.py 2015-05-08 22:40:29.000000000 +0200 +++ new/Yapsy-1.12.0/yapsy/PluginInfo.py 2017-01-29 20:58:27.000000000 +0100 @@ -167,7 +167,7 @@ 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 + that if a category is set as if it were the only category to which the plugin belongs, then a __getCategory will return this newly set category. """ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Yapsy-1.11.223/yapsy/PluginManager.py new/Yapsy-1.12.0/yapsy/PluginManager.py --- old/Yapsy-1.11.223/yapsy/PluginManager.py 2015-05-08 22:43:22.000000000 +0200 +++ new/Yapsy-1.12.0/yapsy/PluginManager.py 2018-09-02 17:52:22.000000000 +0200 @@ -296,12 +296,15 @@ """ Sets the strategy used to locate the basic information. + .. note: If a dir_list is provided it overrides the directory list + that may have been previously set in the locator. + See ``IPluginLocator`` for the policy that plugin_locator must enforce. """ if isinstance(plugin_locator, IPluginLocator): self._plugin_locator = plugin_locator if dir_list is not None: - self._plugin_locator.updatePluginPlaces(dir_list) + self._plugin_locator.setPluginPlaces(dir_list) if picls is not None: self.setPluginInfoClass(picls) else: @@ -381,7 +384,7 @@ 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. @@ -397,6 +400,30 @@ allPlugins.update(pluginsOfOneCategory) return list(allPlugins) + def getPluginsOf(self, **kwargs): + """ + Returns a set of plugins whose properties match the named arguments provided here along with their correspoding values. + """ + selectedPLugins = set() + for plugin in self.getAllPlugins(): + for attrName in kwargs: + if not hasattr(plugin, attrName): + break + attrValue = kwargs[attrName] + pluginValue = getattr(plugin, attrName) + if pluginValue == attrValue: + continue + if type(pluginValue) == type(attrValue): + break + try: + if attrValue in pluginValue: + continue + except: + break + else: + selectedPLugins.add(plugin) + return selectedPLugins + def getPluginCandidates(self): """ Return the list of possible plugins. @@ -442,16 +469,16 @@ """ self._candidates, npc = self.getPluginLocator().locatePlugins() - def loadPlugins(self, callback=None): + def loadPlugins(self, callback=None, callback_after=None): """ Load the candidate plugins that have been identified through a previous call to locatePlugins. For each plugin candidate look for its category, load it and store it in the appropriate slot of the ``category_mapping``. - If a callback function is specified, call it before every load + You can specify 2 callbacks: callback, and callback_after. If either of these are passed a function, (in the case of callback), it will get called before each plugin load attempt and (for callback_after), after each attempt. The ``plugin_info`` instance is passed as an argument to - the callback. + each callback. This is meant to facilitate code that needs to run for each plugin, such as adding the directory it resides in to sys.path (so imports of other files in the plugin's directory work correctly). You can use callback_after to remove anything you added to the path. """ # print "%s.loadPlugins" % self.__class__ if not hasattr(self, '_candidates'): @@ -480,28 +507,27 @@ 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)) + candidate_module = PluginManager._importModule(plugin_module_name, candidate_filepath) except Exception: exc_info = sys.exc_info() log.error("Unable to import plugin: %s" % candidate_filepath, exc_info=exc_info) plugin_info.error = exc_info processed_plugins.append(plugin_info) continue + processed_plugins.append(plugin_info) if "__init__" in os.path.basename(candidate_filepath): sys.path.remove(plugin_info.path) # now try to find and initialise the first subclass of the correct plugin interface - for element in (getattr(candidate_module,name) for name in dir(candidate_module)): + last_failed_attempt_message = None + for element, element_name in ((getattr(candidate_module,name),name) for name in dir(candidate_module)): plugin_info_reference = None for category_name in self.categories_interfaces: try: is_correct_subclass = issubclass(element, self.categories_interfaces[category_name]) except Exception: + exc_info = sys.exc_info() + log.debug("correct subclass tests failed for: %s in %s" % (element_name, candidate_filepath), exc_info=exc_info) continue if is_correct_subclass and element is not self.categories_interfaces[category_name]: current_category = category_name @@ -509,27 +535,68 @@ # we found a new plugin: initialise it and search for the next one if not plugin_info_reference: try: - plugin_info.plugin_object = self.instanciateElement(element) + plugin_info.plugin_object = self.instanciateElementWithImportInfo(element, element_name, plugin_module_name, candidate_filepath) plugin_info_reference = plugin_info except Exception: exc_info = sys.exc_info() - log.error("Unable to create plugin object: %s" % candidate_filepath, exc_info=exc_info) + last_failed_attempt_message = "Unable to create plugin object: %s" % candidate_filepath + log.debug(last_failed_attempt_message, exc_info=exc_info) plugin_info.error = exc_info break # If it didn't work once it wont again + else: + last_failed_attempt_message = None plugin_info.categories.append(current_category) self.category_mapping[current_category].append(plugin_info_reference) self._category_file_mapping[current_category].append(candidate_infofile) + #Everything is loaded and instantiated for this plugin now + if callback_after is not None: + callback_after(plugin_info) + else: + if last_failed_attempt_message: + log.error(last_failed_attempt_message, exc_info=plugin_info.error) + # Remove candidates list since we don't need them any more and # don't need to take up the space delattr(self, '_candidates') return processed_plugins + + @staticmethod + def _importModule(plugin_module_name, candidate_filepath): + """ + Import a module, trying either to find it as a single file or as a directory. + + :note: Isolated and provided to be reused, but not to be reimplemented ! + """ + # use imp to correctly load the plugin as a module + if os.path.isdir(candidate_filepath): + candidate_module = imp.load_module(plugin_module_name,None,candidate_filepath,("py","r",imp.PKG_DIRECTORY)) + else: + with open(candidate_filepath+".py","r") as plugin_file: + candidate_module = imp.load_module(plugin_module_name,plugin_file,candidate_filepath+".py",("py","r",imp.PY_SOURCE)) + return candidate_module + + def instanciateElementWithImportInfo(self, element, element_name, + plugin_module_name, candidate_filepath): + """Override this method to customize how plugins are instanciated. + + :note: This methods recieves the 'element' that is a candidate + as the plugin's main file, but also enough information to reload + its containing module and this element. + """ + return self.instanciateElement(element) + def instanciateElement(self, element): """ - Override this method to customize how plugins are instanciated + DEPRECATED(>1.11): reimplement instead `instanciateElementWithImportInfo` ! + + Override this method to customize how plugins are instanciated. + + :warning: This method is called only if + `instanciateElementWithImportInfo` has not been reimplemented ! """ return element() - + def collectPlugins(self): """ Walk through the plugins' places and look for plugins. Then diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Yapsy-1.11.223/yapsy/VersionedPluginManager.py new/Yapsy-1.12.0/yapsy/VersionedPluginManager.py --- old/Yapsy-1.11.223/yapsy/VersionedPluginManager.py 2015-05-08 22:43:55.000000000 +0200 +++ new/Yapsy-1.12.0/yapsy/VersionedPluginManager.py 2017-01-29 18:38:39.000000000 +0100 @@ -94,7 +94,7 @@ """ return self.getPluginsOfCategory(category_name) - def loadPlugins(self, callback=None): + def loadPlugins(self, callback=None, callback_after=None): """ Load the candidate plugins that have been identified through a previous call to locatePlugins. @@ -103,7 +103,7 @@ needs to find the latest version of each plugin. """ self._prepareAttic() - self._component.loadPlugins(callback) + self._component.loadPlugins(callback, callback_after) for categ in self.getCategories(): latest_plugins = {} allPlugins = self.getPluginsOfCategory(categ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Yapsy-1.11.223/yapsy/__init__.py new/Yapsy-1.12.0/yapsy/__init__.py --- old/Yapsy-1.11.223/yapsy/__init__.py 2015-06-25 20:29:24.000000000 +0200 +++ new/Yapsy-1.12.0/yapsy/__init__.py 2018-09-02 18:23:56.000000000 +0200 @@ -52,7 +52,7 @@ """ -__version__="1.11.223" +__version__="1.12.0" # tell epydoc that the documentation is in the reStructuredText format __docformat__ = "restructuredtext en"
