Merge branch '637_friendlier_hooks'
authorChristopher Allan Webber <cwebber@dustycloud.org>
Wed, 10 Apr 2013 22:53:05 +0000 (17:53 -0500)
committerChristopher Allan Webber <cwebber@dustycloud.org>
Wed, 10 Apr 2013 22:53:05 +0000 (17:53 -0500)
mediagoblin/init/celery/__init__.py
mediagoblin/init/celery/from_celery.py
mediagoblin/init/plugins/__init__.py
mediagoblin/tests/test_pluginapi.py
mediagoblin/tests/testplugins/__init__.py [new file with mode: 0644]
mediagoblin/tests/testplugins/callables1/__init__.py [new file with mode: 0644]
mediagoblin/tests/testplugins/callables2/__init__.py [new file with mode: 0644]
mediagoblin/tests/testplugins/callables3/__init__.py [new file with mode: 0644]
mediagoblin/tools/pluginapi.py

index 8d7a41bdaf7a617ebcbe17d38b9eced48cffeadd..bb0d5989496a48df2872935726019e22154459ab 100644 (file)
@@ -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,
index 8a794abbe1e4a62649c90dd6e68ba8db748167ac..e2899c0b3522aa29cc08f3e74167a8be540366c4 100644 (file)
@@ -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)
index cdf9b5adc7db3a10f62d5862e8b2f7ed42426657..72bd5c7d857b56023442c64d77f6f3af105f7932 100644 (file)
@@ -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')
index 245c396dee15a2be9f7a42939dcfdc041dbed8f6..d40a5081729c3231e50c586f46b40dfe23864669 100644 (file)
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 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 (file)
index 0000000..621845b
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
diff --git a/mediagoblin/tests/testplugins/callables1/__init__.py b/mediagoblin/tests/testplugins/callables1/__init__.py
new file mode 100644 (file)
index 0000000..9c278b4
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+
+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 (file)
index 0000000..aaab5b2
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+
+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 (file)
index 0000000..8d0c9c2
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+
+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,
+    }
index 784bede948a59c8a35ab84165d848de399caccc6..283350a8eb0289207363ba9a419e152ddaf9b3d7 100644 (file)
@@ -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