aboutsummaryrefslogtreecommitdiffstats
path: root/mediagoblin/tools/pluginapi.py
diff options
context:
space:
mode:
authorWill Kahn-Greene <willg@bluesock.org>2012-07-17 21:02:12 -0400
committerWill Kahn-Greene <willg@bluesock.org>2012-07-17 21:02:12 -0400
commit05e007c1dbe7b5b8a092f1a99ed361c4e6b71f26 (patch)
tree36a0d1ba4174baff5a003871a28e08a512b57a84 /mediagoblin/tools/pluginapi.py
parent8464bcc3e86e223db0739101c0b5d914eea225af (diff)
downloadmediagoblin-05e007c1dbe7b5b8a092f1a99ed361c4e6b71f26.tar.lz
mediagoblin-05e007c1dbe7b5b8a092f1a99ed361c4e6b71f26.tar.xz
mediagoblin-05e007c1dbe7b5b8a092f1a99ed361c4e6b71f26.zip
Rework plugin infrastructure to nix side-effects
This reworks the plugin infrastructure so as to remove module-loading side-effects which were making things a pain in the ass to test. With the new system, there's no auto-registering meta class. Instead plugins do whatever they want and then specify a hooks dict that maps hook names to callables for the things they're tying into. The most common one (and the only one we've implemented so far) is "setup". This also simplifies the sampleplugin a little by moving the code to __init__.py.
Diffstat (limited to 'mediagoblin/tools/pluginapi.py')
-rw-r--r--mediagoblin/tools/pluginapi.py100
1 files changed, 40 insertions, 60 deletions
diff --git a/mediagoblin/tools/pluginapi.py b/mediagoblin/tools/pluginapi.py
index 0c338540..bf3775d5 100644
--- a/mediagoblin/tools/pluginapi.py
+++ b/mediagoblin/tools/pluginapi.py
@@ -15,8 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
-This module implements the plugin api bits and provides the plugin
-base.
+This module implements the plugin api bits.
Two things about things in this module:
@@ -30,8 +29,8 @@ How do plugins work?
====================
Plugins are structured like any Python project. You create a Python package.
-In that package, you define a high-level ``__init__.py`` that either defines
-or imports modules that define classes that inherit from the ``Plugin`` class.
+In that package, you define a high-level ``__init__.py`` module that has a
+``hooks`` dict that maps hooks to callables that implement those hooks.
Additionally, you want a LICENSE file that specifies the license and a
``setup.py`` that specifies the metadata for packaging your plugin. A rough
@@ -42,23 +41,19 @@ file structure could look like this::
|- README # holds plugin project information
|- LICENSE # holds license information
|- myplugin/ # plugin package directory
- |- __init__.py # imports myplugin.main
- |- main.py # code for plugin
+ |- __init__.py # has hooks dict and code
Lifecycle
=========
1. All the modules listed as subsections of the ``plugins`` section in
- the config file are imported. This causes any ``Plugin`` subclasses in
- those modules to be defined and when the classes are defined they get
- automatically registered with the ``PluginCache``.
+ the config file are imported. MediaGoblin registers any hooks in
+ the ``hooks`` dict of those modules.
-2. After all plugin modules are imported, registered plugin classes are
- instantiated and ``setup_plugin`` is called for each plugin object.
+2. After all plugin modules are imported, the ``setup`` hook is called
+ allowing plugins to do any set up they need to do.
- Plugins can do any setup they need to do in their ``setup_plugin``
- method.
"""
import logging
@@ -69,14 +64,19 @@ from mediagoblin import mg_globals
_log = logging.getLogger(__name__)
-class PluginCache(object):
- """Cache of plugin things"""
+class PluginManager(object):
+ """Manager for plugin things
+
+ .. Note::
+
+ This is a Borg class--there is one and only one of this class.
+ """
__state = {
# list of plugin classes
- "plugin_classes": [],
+ "plugins": [],
- # list of plugin objects
- "plugin_objects": [],
+ # map of hook names -> list of callables for that hook
+ "hooks": {},
# list of registered template paths
"template_paths": set(),
@@ -87,19 +87,31 @@ class PluginCache(object):
def clear(self):
"""This is only useful for testing."""
- del self.plugin_classes[:]
- del self.plugin_objects[:]
+ # Why lists don't have a clear is not clear.
+ del self.plugins[:]
+ del self.routes[:]
+ self.hooks.clear()
+ self.template_paths.clear()
def __init__(self):
self.__dict__ = self.__state
- def register_plugin_class(self, plugin_class):
+ def register_plugin(self, plugin):
"""Registers a plugin class"""
- self.plugin_classes.append(plugin_class)
+ self.plugins.append(plugin)
+
+ def register_hooks(self, hook_mapping):
+ """Takes a hook_mapping and registers all the hooks"""
+ for hook, callables in hook_mapping.items():
+ if isinstance(callables, (list, tuple)):
+ self.hooks.setdefault(hook, []).extend(list(callables))
+ else:
+ # In this case, it's actually a single callable---not a
+ # list of callables.
+ self.hooks.setdefault(hook, []).append(callables)
- def register_plugin_object(self, plugin_obj):
- """Registers a plugin object"""
- self.plugin_objects.append(plugin_obj)
+ def get_hook_callables(self, hook_name):
+ return self.hooks.get(hook_name, [])
def register_template_path(self, path):
"""Registers a template path"""
@@ -117,38 +129,6 @@ class PluginCache(object):
return tuple(self.routes)
-class MetaPluginClass(type):
- """Metaclass for PluginBase derivatives"""
- def __new__(cls, name, bases, attrs):
- new_class = super(MetaPluginClass, cls).__new__(cls, name, bases, attrs)
- parents = [b for b in bases if isinstance(b, MetaPluginClass)]
- if not parents:
- return new_class
- PluginCache().register_plugin_class(new_class)
- return new_class
-
-
-class Plugin(object):
- """Extend this class for plugins.
-
- Example::
-
- from mediagoblin.tools.pluginapi import Plugin
-
- class MyPlugin(Plugin):
- ...
-
- def setup_plugin(self):
- ....
-
- """
-
- __metaclass__ = MetaPluginClass
-
- def setup_plugin(self):
- pass
-
-
def register_routes(routes):
"""Registers one or more routes
@@ -182,9 +162,9 @@ def register_routes(routes):
"""
if isinstance(routes, (tuple, list)):
for route in routes:
- PluginCache().register_route(route)
+ PluginManager().register_route(route)
else:
- PluginCache().register_route(routes)
+ PluginManager().register_route(routes)
def register_template_path(path):
@@ -205,7 +185,7 @@ def register_template_path(path):
that will have no effect on template loading.
"""
- PluginCache().register_template_path(path)
+ PluginManager().register_template_path(path)
def get_config(key):