diff --git a/src/wuttjamaican/app.py b/src/wuttjamaican/app.py index f284f40..b28b63b 100644 --- a/src/wuttjamaican/app.py +++ b/src/wuttjamaican/app.py @@ -842,6 +842,87 @@ class AppHandler: self.handlers['auth'] = factory(self.config, **kwargs) return self.handlers['auth'] + def get_batch_handler(self, key, default=None, **kwargs): + """ + Get the configured :term:`batch handler` for the given type. + + :param key: Unique key designating the :term:`batch type`. + + :param default: Spec string to use as the default, if none is + configured. + + :returns: :class:`~wuttjamaican.batch.BatchHandler` instance + for the requested type. If no spec can be determined, a + ``KeyError`` is raised. + """ + spec = self.config.get(f'{self.appname}.batch.{key}.handler.spec', + default=default) + if not spec: + spec = self.config.get(f'{self.appname}.batch.{key}.handler.default_spec') + if not spec: + raise KeyError(f"handler spec not found for batch key: {key}") + factory = self.load_object(spec) + return factory(self.config, **kwargs) + + def get_batch_handler_specs(self, key, default=None): + """ + Get the :term:`spec` strings for all available handlers of the + given batch type. + + :param key: Unique key designating the :term:`batch type`. + + :param default: Default spec string(s) to include, even if not + registered. Can be a string or list of strings. + + :returns: List of batch handler spec strings. + + This will gather available spec strings from the following: + + First, the ``default`` as provided by caller. + + Second, the default spec from config, if set; for example: + + .. code-block:: ini + + [wutta.batch] + inventory.handler.default_spec = poser.batch.inventory:InventoryBatchHandler + + Third, each spec registered via entry points. For instance in + ``pyproject.toml``: + + .. code-block:: toml + + [project.entry-points."wutta.batch.inventory"] + poser = "poser.batch.inventory:InventoryBatchHandler" + + The final list will be "sorted" according to the above, with + the latter registered handlers being sorted alphabetically. + """ + handlers = [] + + # defaults from caller + if isinstance(default, str): + handlers.append(default) + elif default: + handlers.extend(default) + + # configured default, if applicable + default = self.config.get(f'{self.config.appname}.batch.{key}.handler.default_spec') + if default and default not in handlers: + handlers.append(default) + + # registered via entry points + registered = [] + for Handler in load_entry_points(f'{self.appname}.batch.{key}').values(): + spec = Handler.get_spec() + if spec not in handlers: + registered.append(spec) + if registered: + registered.sort() + handlers.extend(registered) + + return handlers + def get_db_handler(self, **kwargs): """ Get the configured :term:`db handler`. @@ -1021,3 +1102,10 @@ class GenericHandler: See also :attr:`AppHandler.appname`. """ return self.app.appname + + @classmethod + def get_spec(cls): + """ + Returns the class :term:`spec` string for the handler. + """ + return f'{cls.__module__}:{cls.__name__}' diff --git a/tests/test_app.py b/tests/test_app.py index 0176cfb..ebede2e 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -19,7 +19,15 @@ from wuttjamaican import app as mod from wuttjamaican.progress import ProgressBase from wuttjamaican.conf import WuttaConfig from wuttjamaican.util import UNSPECIFIED -from wuttjamaican.testing import FileTestCase +from wuttjamaican.testing import FileTestCase, ConfigTestCase +from wuttjamaican.batch import BatchHandler + + +class MockBatchHandler(BatchHandler): + pass + +class AnotherBatchHandler(BatchHandler): + pass class TestAppHandler(FileTestCase): @@ -546,6 +554,59 @@ app_title = WuttaTest auth = self.app.get_auth_handler() self.assertIsInstance(auth, AuthHandler) + def test_get_batch_handler(self): + + # error if handler not found + self.assertRaises(KeyError, self.app.get_batch_handler, 'CannotFindMe!') + + # caller can specify default + handler = self.app.get_batch_handler('foo', default='wuttjamaican.batch:BatchHandler') + self.assertIsInstance(handler, BatchHandler) + + # default can be configured + self.config.setdefault('wuttatest.batch.foo.handler.default_spec', + 'wuttjamaican.batch:BatchHandler') + handler = self.app.get_batch_handler('foo') + self.assertIsInstance(handler, BatchHandler) + + # preference can be configured + self.config.setdefault('wuttatest.batch.foo.handler.spec', + 'tests.test_app:MockBatchHandler') + handler = self.app.get_batch_handler('foo') + self.assertIsInstance(handler, MockBatchHandler) + + def test_get_batch_handler_specs(self): + + # empty by default + specs = self.app.get_batch_handler_specs('foo') + self.assertEqual(specs, []) + + # caller can specify default as string + specs = self.app.get_batch_handler_specs('foo', default='wuttjamaican.batch:BatchHandler') + self.assertEqual(specs, ['wuttjamaican.batch:BatchHandler']) + + # caller can specify default as list + specs = self.app.get_batch_handler_specs('foo', default=['wuttjamaican.batch:BatchHandler', + 'tests.test_app:MockBatchHandler']) + self.assertEqual(specs, ['wuttjamaican.batch:BatchHandler', + 'tests.test_app:MockBatchHandler']) + + # default can be configured + self.config.setdefault('wuttatest.batch.foo.handler.default_spec', + 'wuttjamaican.batch:BatchHandler') + specs = self.app.get_batch_handler_specs('foo') + self.assertEqual(specs, ['wuttjamaican.batch:BatchHandler']) + + # the rest come from entry points + with patch.object(mod, 'load_entry_points', return_value={ + 'mock': MockBatchHandler, + 'another': AnotherBatchHandler, + }): + specs = self.app.get_batch_handler_specs('foo') + self.assertEqual(specs, ['wuttjamaican.batch:BatchHandler', + 'tests.test_app:AnotherBatchHandler', + 'tests.test_app:MockBatchHandler']) + def test_get_db_handler(self): try: from wuttjamaican.db.handler import DatabaseHandler @@ -684,15 +745,17 @@ class TestAppProvider(TestCase): self.assertEqual(self.app.foo_value, 'bar') -class TestGenericHandler(TestCase): +class TestGenericHandler(ConfigTestCase): - def setUp(self): - self.config = WuttaConfig(appname='wuttatest') - self.app = mod.AppHandler(self.config) - self.config._app = self.app + def make_config(self, **kw): + kw.setdefault('appname', 'wuttatest') + return super().make_config(**kw) def test_constructor(self): handler = mod.GenericHandler(self.config) self.assertIs(handler.config, self.config) self.assertIs(handler.app, self.app) self.assertEqual(handler.appname, 'wuttatest') + + def test_get_spec(self): + self.assertEqual(mod.GenericHandler.get_spec(), 'wuttjamaican.app:GenericHandler')