grim/pyscovery

Parents adeb09288d28
Children 8574ebad18fb
Fixed a bug where all objects were being "found". Added support to instantiate the classes that are found as well. Made recurse and create required arguments due to the new create stuff
--- a/pyscovery.py Sun Apr 14 01:41:01 2013 -0500
+++ b/pyscovery.py Sun Apr 14 02:12:14 2013 -0500
@@ -102,9 +102,16 @@
MODULES[:] = []
-def find(cls, recurse=False):
+def find(cls, recurse, create, *args, **kwargs):
"""
Find all plugins that are subclasses of cls in the current search paths
+
+ If recurse is set to true, all files under a package will be inspected as
+ well.
+
+ If create is set to true, instances of the classes will be returned instead
+ of the class itself. Note that *args and **kwargs will passed directly to
+ the classes __init__ method.
"""
if not inspect.isclass(cls):
@@ -129,8 +136,14 @@
if inspect.isabstract(symbol):
continue
+
+ if not issubclass(symbol, cls):
+ continue
- yield symbol
+ if create:
+ yield symbol(*args, **kwargs)
+ else:
+ yield symbol
def _is_package(module):
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/modules/create.py Sun Apr 14 02:12:14 2013 -0500
@@ -0,0 +1,69 @@
+# pyscovery - A python plugin finder
+# Copyright (C) 2013 Gary Kramlich <grim@reaperworld.com>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+"""
+Unit tests for pyscovery
+"""
+
+class CreatePlugin(object): # pylint:disable-msg=R0903
+ """ A base class for plugins that take variables to their constructors """
+
+ def __init__(self, *args, **kwargs):
+ self.args = args
+ self.kwargs = kwargs
+
+
+class ArgsPlugin(CreatePlugin): # pylint:disable-msg=R0903
+ """ A plugin that stores *args to it's constructor """
+
+ def __init__(self, *args):
+ CreatePlugin.__init__(self, *args)
+
+
+class ArgsSubClass(ArgsPlugin): # pylint:disable-msg=R0903
+ """ A subclass of ArgsPlugin so we can find it """
+
+ def __init__(self, *args):
+ ArgsPlugin.__init__(self, *args)
+
+
+class KwArgsPlugin(CreatePlugin): # pylint:disable-msg=R0903
+ """ A plugin that stores *kwargs to it's constructor """
+
+ def __init__(self, **kwargs):
+ CreatePlugin.__init__(self, **kwargs)
+
+
+class KwArgsSubClass(KwArgsPlugin): # pylint:disable-msg=R0903
+ """ A subclass of KwArgsPlugin so we can find it """
+
+ def __init__(self, **kwargs):
+ KwArgsPlugin.__init__(self, **kwargs)
+
+
+class BothArgsPlugin(CreatePlugin): # pylint:disable-msg=R0903
+ """ A plugin that stores *args and **kwargs to it's constructor """
+
+ def __init__(self, *args, **kwargs):
+ CreatePlugin.__init__(self, *args, **kwargs)
+
+
+class BothArgsSubClass(BothArgsPlugin): # pylint:disable-msg=R0903
+ """ A subclass of BothArgsPlugins so we can find it """
+
+ def __init__(self, *args, **kwargs):
+ BothArgsPlugin.__init__(self, *args, **kwargs)
+
--- a/tests/modules/mixed.py Sun Apr 14 01:41:01 2013 -0500
+++ b/tests/modules/mixed.py Sun Apr 14 02:12:14 2013 -0500
@@ -60,3 +60,10 @@
def abstract(self): # pylint:disable-msg=R0201
return
+class Other(object): # pylint:disable-msg=R0903
+ """
+ Another class to make sure we only match what we're looking for
+ """
+
+ pass
+
--- a/tests/pyscovery_test.py Sun Apr 14 01:41:01 2013 -0500
+++ b/tests/pyscovery_test.py Sun Apr 14 02:12:14 2013 -0500
@@ -23,20 +23,39 @@
import pyscovery
+from tests.modules import create
-def _test_module(test, module, count, recurse=False):
+
+# pylint:disable-msg=R0913
+
+
+class Plugin(object): # pylint:disable-msg=R0903
+ """
+ The class all the test plugins descend from
+ """
+
+ pass
+
+
+def _test_module(test, module, count, recurse=False, instantiate=False):
"""
Given a module, test that we find count plugins
"""
pyscovery.add_module(module)
- gen = pyscovery.find(Plugin, recurse=recurse)
+ gen = pyscovery.find(Plugin, recurse, instantiate)
test.assertTrue(inspect.isgenerator(gen))
found = list(gen)
- test.assertEqual(len(found), count, 'plugins found: {}'.format(found))
+ test.assertEqual(len(found), count, 'plugins found: {}'.format(len(found)))
+ if instantiate:
+ for plugin in found:
+ test.assertIsInstance(plugin, Plugin)
+
+ return found
+
def _test_module_count(test, count):
"""
@@ -47,14 +66,6 @@
test.assertEqual(len(modules), count, 'search paths {}'.format(modules))
-class Plugin(object): # pylint:disable-msg=R0903
- """
- The class all the test plugins descend from
- """
-
- pass
-
-
class TestModules(unittest.TestCase): # pylint:disable-msg=R0904
"""
Unit tests for path manipulation
@@ -129,7 +140,7 @@
Test that a TypeError is raised when find is called with None
"""
- self.assertRaises(TypeError, pyscovery.find(None))
+ self.assertRaises(TypeError, pyscovery.find(None, False, False))
def test_string(self):
@@ -137,7 +148,7 @@
Test that a TypeError is raised when find is called with a string
"""
- self.assertRaises(TypeError, pyscovery.find(''))
+ self.assertRaises(TypeError, pyscovery.find('', False, False))
def test_int(self):
@@ -145,7 +156,7 @@
Test that a TypeError is raised when find is called with an int
"""
- self.assertRaises(TypeError, pyscovery.find(0))
+ self.assertRaises(TypeError, pyscovery.find(0, False, False))
def test_old_style_class(self): # pylint:disable-msg=R0201
@@ -159,7 +170,7 @@
pass
- pyscovery.find(Test)
+ pyscovery.find(Test, False, False)
def test_new_style_class(self): # pylint:disable-msg=R0201
@@ -173,7 +184,7 @@
pass
- pyscovery.find(Test)
+ pyscovery.find(Test, False, False)
class TestModule(unittest.TestCase): # pylint:disable-msg=R0904
@@ -229,3 +240,58 @@
""" Test a package with recursion """
_test_module(self, 'tests.modules', 7, recurse=True)
+
+class TestCreate(unittest.TestCase): # pylint:disable-msg=R0904
+ """ Tests the find method with the create option on """
+
+ def setUp(self): # pylint:disable-msg=C0103
+ pyscovery.clear_modules()
+
+
+ def test_create_without_recurse(self):
+ """ Test a package with create but without recursion """
+ _test_module(self, 'tests.modules.deep', 1, instantiate=True)
+
+
+ def test_create_with_recursion(self):
+ """ Test a package with create and recursion """
+ _test_module(self, 'tests.modules.deep', 2, recurse=True,
+ instantiate=True)
+
+
+ def test_create_with_deep_recursion(self):
+ """ Test a packge with create and deep recursion """
+ _test_module(self, 'tests.modules', 7, recurse=True, instantiate=True)
+
+
+class TestCreateWithParameters(unittest.TestCase): # pylint:disable-msg=R0904
+ """ Tests for finding and creating plugins that take arguments """
+
+ def setUp(self): # pylint:disable-msg=C0103
+ pyscovery.clear_modules()
+ pyscovery.add_module('tests.modules.create')
+
+ def test_create_args(self):
+ """ Tests that create works with args """
+
+ for plugin in pyscovery.find(create.ArgsPlugin, False, True,
+ 'one', 'two'):
+ self.assertTupleEqual(plugin.args, ('one', 'two'))
+
+
+ def test_create_kwargs(self):
+ """ Tests that create works with kwargs """
+
+ for plugin in pyscovery.find(create.KwArgsPlugin, False, True,
+ one=1, two=2):
+ self.assertDictEqual(plugin.kwargs, {'one': 1, 'two': 2})
+
+
+ def test_both_args(self):
+ """ Tests the create works with args and kwargs """
+
+ for plugin in pyscovery.find(create.BothArgsPlugin, False, True,
+ 'one', 'two', one=1, two=2):
+ self.assertTupleEqual(plugin.args, ('one', 'two'))
+ self.assertDictEqual(plugin.kwargs, {'one': 1, 'two': 2})
+