From: Christopher Allan Webber Date: Wed, 10 Apr 2013 22:53:05 +0000 (-0500) Subject: Merge branch '637_friendlier_hooks' X-Git-Url: https://vcs.fsf.org/?a=commitdiff_plain;h=761e26bb29623f58ebf2496b7fd7e945fad4e6ed;hp=0d656f44a1252cc8cdc8ec05be33187db3470e5d;p=mediagoblin.git Merge branch '637_friendlier_hooks' --- diff --git a/mediagoblin/init/celery/__init__.py b/mediagoblin/init/celery/__init__.py index 8d7a41bd..bb0d5989 100644 --- a/mediagoblin/init/celery/__init__.py +++ b/mediagoblin/init/celery/__init__.py @@ -18,7 +18,7 @@ import os import sys from celery import Celery -from mediagoblin.tools.pluginapi import PluginManager +from mediagoblin.tools.pluginapi import callable_runall MANDATORY_CELERY_IMPORTS = ['mediagoblin.processing.task'] @@ -66,8 +66,7 @@ def setup_celery_app(app_config, global_config, celery_app = Celery() celery_app.config_from_object(celery_settings) - for callable_hook in PluginManager().get_hook_callables('celery_setup'): - callable_hook(celery_app) + callable_runall('celery_setup', celery_app) def setup_celery_from_config(app_config, global_config, diff --git a/mediagoblin/init/celery/from_celery.py b/mediagoblin/init/celery/from_celery.py index 8a794abb..e2899c0b 100644 --- a/mediagoblin/init/celery/from_celery.py +++ b/mediagoblin/init/celery/from_celery.py @@ -22,7 +22,7 @@ from celery.signals import setup_logging from mediagoblin import app, mg_globals from mediagoblin.init.celery import setup_celery_from_config -from mediagoblin.tools.pluginapi import PluginManager +from mediagoblin.tools.pluginapi import callable_runall OUR_MODULENAME = __name__ @@ -47,9 +47,7 @@ def setup_logging_from_paste_ini(loglevel, **kw): logging.config.fileConfig(logging_conf_file) - for callable_hook in \ - PluginManager().get_hook_callables('celery_logging_setup'): - callable_hook() + callable_runall('celery_logging_setup') setup_logging.connect(setup_logging_from_paste_ini) diff --git a/mediagoblin/init/plugins/__init__.py b/mediagoblin/init/plugins/__init__.py index cdf9b5ad..72bd5c7d 100644 --- a/mediagoblin/init/plugins/__init__.py +++ b/mediagoblin/init/plugins/__init__.py @@ -59,6 +59,4 @@ def setup_plugins(): pman.register_hooks(plugin.hooks) # Execute anything registered to the setup hook. - setup_list = pman.get_hook_callables('setup') - for fun in setup_list: - fun() + pluginapi.callable_runall('setup') diff --git a/mediagoblin/tests/test_pluginapi.py b/mediagoblin/tests/test_pluginapi.py index 245c396d..d40a5081 100644 --- a/mediagoblin/tests/test_pluginapi.py +++ b/mediagoblin/tests/test_pluginapi.py @@ -15,7 +15,10 @@ # along with this program. If not, see . import sys + from configobj import ConfigObj +import pytest + from mediagoblin import mg_globals from mediagoblin.init.plugins import setup_plugins from mediagoblin.tools import pluginapi @@ -172,3 +175,111 @@ def test_disabled_plugin(): # Make sure we didn't load the plugin assert len(pman.plugins) == 0 + + +@with_cleanup() +def test_callable_runone(): + """ + Test the callable_runone method + """ + cfg = build_config([ + ('mediagoblin', {}, []), + ('plugins', {}, [ + ('mediagoblin.tests.testplugins.callables1', {}, []), + ('mediagoblin.tests.testplugins.callables2', {}, []), + ('mediagoblin.tests.testplugins.callables3', {}, []), + ]) + ]) + + mg_globals.app_config = cfg['mediagoblin'] + mg_globals.global_config = cfg + + setup_plugins() + + # Just one hook provided + call_log = [] + assert pluginapi.callable_runone( + "just_one", call_log) == "Called just once" + assert call_log == ["expect this one call"] + + # Nothing provided and unhandled not okay + call_log = [] + with pytest.raises(pluginapi.UnhandledCallable): + pluginapi.callable_runone( + "nothing_handling", call_log) + assert call_log == [] + + # Nothing provided and unhandled okay + call_log = [] + assert pluginapi.callable_runone( + "nothing_handling", call_log, unhandled_okay=True) is None + assert call_log == [] + + # Multiple provided, go with the first! + call_log = [] + assert pluginapi.callable_runone( + "multi_handle", call_log) == "the first returns" + assert call_log == ["Hi, I'm the first"] + + # Multiple provided, one has CantHandleIt + call_log = [] + assert pluginapi.callable_runone( + "multi_handle_with_canthandle", + call_log) == "the second returns" + assert call_log == ["Hi, I'm the second"] + + +@with_cleanup() +def test_callable_runall(): + """ + Test the callable_runall method + """ + cfg = build_config([ + ('mediagoblin', {}, []), + ('plugins', {}, [ + ('mediagoblin.tests.testplugins.callables1', {}, []), + ('mediagoblin.tests.testplugins.callables2', {}, []), + ('mediagoblin.tests.testplugins.callables3', {}, []), + ]) + ]) + + mg_globals.app_config = cfg['mediagoblin'] + mg_globals.global_config = cfg + + setup_plugins() + + # Just one hook, check results + call_log = [] + assert pluginapi.callable_runall( + "just_one", call_log) == ["Called just once", None, None] + assert call_log == ["expect this one call"] + + # None provided, check results + call_log = [] + assert pluginapi.callable_runall( + "nothing_handling", call_log) == [] + assert call_log == [] + + # Multiple provided, check results + call_log = [] + assert pluginapi.callable_runall( + "multi_handle", call_log) == [ + "the first returns", + "the second returns", + "the third returns", + ] + assert call_log == [ + "Hi, I'm the first", + "Hi, I'm the second", + "Hi, I'm the third"] + + # Multiple provided, one has CantHandleIt, check results + call_log = [] + assert pluginapi.callable_runall( + "multi_handle_with_canthandle", call_log) == [ + "the second returns", + "the third returns", + ] + assert call_log == [ + "Hi, I'm the second", + "Hi, I'm the third"] diff --git a/mediagoblin/tests/testplugins/__init__.py b/mediagoblin/tests/testplugins/__init__.py new file mode 100644 index 00000000..621845ba --- /dev/null +++ b/mediagoblin/tests/testplugins/__init__.py @@ -0,0 +1,15 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . diff --git a/mediagoblin/tests/testplugins/callables1/__init__.py b/mediagoblin/tests/testplugins/callables1/__init__.py new file mode 100644 index 00000000..9c278b49 --- /dev/null +++ b/mediagoblin/tests/testplugins/callables1/__init__.py @@ -0,0 +1,41 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from mediagoblin.tools.pluginapi import CantHandleIt + +def setup_plugin(): + pass + + +def just_one(call_log): + call_log.append("expect this one call") + return "Called just once" + + +def multi_handle(call_log): + call_log.append("Hi, I'm the first") + return "the first returns" + +def multi_handle_with_canthandle(call_log): + raise CantHandleIt("I just can't accept this stupid method") + + +hooks = { + 'setup': setup_plugin, + 'just_one': just_one, + 'multi_handle': multi_handle, + 'multi_handle_with_canthandle': multi_handle_with_canthandle, + } diff --git a/mediagoblin/tests/testplugins/callables2/__init__.py b/mediagoblin/tests/testplugins/callables2/__init__.py new file mode 100644 index 00000000..aaab5b21 --- /dev/null +++ b/mediagoblin/tests/testplugins/callables2/__init__.py @@ -0,0 +1,38 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +def setup_plugin(): + pass + + +def just_one(call_log): + assert "SHOULD NOT HAPPEN" + +def multi_handle(call_log): + call_log.append("Hi, I'm the second") + return "the second returns" + +def multi_handle_with_canthandle(call_log): + call_log.append("Hi, I'm the second") + return "the second returns" + + +hooks = { + 'setup': setup_plugin, + 'just_one': just_one, + 'multi_handle': multi_handle, + 'multi_handle_with_canthandle': multi_handle_with_canthandle, + } diff --git a/mediagoblin/tests/testplugins/callables3/__init__.py b/mediagoblin/tests/testplugins/callables3/__init__.py new file mode 100644 index 00000000..8d0c9c25 --- /dev/null +++ b/mediagoblin/tests/testplugins/callables3/__init__.py @@ -0,0 +1,38 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +def setup_plugin(): + pass + + +def just_one(call_log): + assert "SHOULD NOT HAPPEN" + +def multi_handle(call_log): + call_log.append("Hi, I'm the third") + return "the third returns" + +def multi_handle_with_canthandle(call_log): + call_log.append("Hi, I'm the third") + return "the third returns" + + +hooks = { + 'setup': setup_plugin, + 'just_one': just_one, + 'multi_handle': multi_handle, + 'multi_handle_with_canthandle': multi_handle_with_canthandle, + } diff --git a/mediagoblin/tools/pluginapi.py b/mediagoblin/tools/pluginapi.py index 784bede9..283350a8 100644 --- a/mediagoblin/tools/pluginapi.py +++ b/mediagoblin/tools/pluginapi.py @@ -272,3 +272,70 @@ def get_hook_templates(hook_name): A list of strings representing template paths. """ return PluginManager().get_template_hooks(hook_name) + + +########################### +# Callable convenience code +########################### + +class CantHandleIt(Exception): + """ + A callable may call this method if they look at the relevant + arguments passed and decide it's not possible for them to handle + things. + """ + pass + +class UnhandledCallable(Exception): + """ + Raise this method if no callables were available to handle the + specified hook. Only used by callable_runone. + """ + pass + + +def callable_runone(hookname, *args, **kwargs): + """ + Run the callable hook HOOKNAME... run until the first response, + then return. + + This function will run stop at the first hook that handles the + result. Hooks raising CantHandleIt will be skipped. + + Unless unhandled_okay is True, this will error out if no hooks + have been registered to handle this function. + """ + callables = PluginManager().get_hook_callables(hookname) + + unhandled_okay = kwargs.pop("unhandled_okay", False) + + for callable in callables: + try: + return callable(*args, **kwargs) + except CantHandleIt: + continue + + if unhandled_okay is False: + raise UnhandledCallable( + "No hooks registered capable of handling '%s'" % hookname) + + +def callable_runall(hookname, *args, **kwargs): + """ + Run all callables for HOOKNAME. + + This method will run *all* hooks that handle this method (skipping + those that raise CantHandleIt), and will return a list of all + results. + """ + callables = PluginManager().get_hook_callables(hookname) + + results = [] + + for callable in callables: + try: + results.append(callable(*args, **kwargs)) + except CantHandleIt: + continue + + return results