# 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:
====================
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
|- 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
+from functools import wraps
+
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(),
+ # list of template hooks
+ "template_hooks": {},
+
# list of registered routes
"routes": [],
}
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"""
def register_route(self, route):
"""Registers a single route"""
+ _log.debug('registering route: {0}'.format(route))
self.routes.append(route)
def get_routes(self):
return tuple(self.routes)
+ def register_template_hooks(self, template_hooks):
+ for hook, templates in template_hooks.items():
+ if isinstance(templates, (list, tuple)):
+ self.template_hooks.setdefault(hook, []).extend(list(templates))
+ else:
+ # In this case, it's actually a single callable---not a
+ # list of callables.
+ self.template_hooks.setdefault(hook, []).append(templates)
-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 get_template_hooks(self, hook_name):
+ return self.template_hooks.get(hook_name, [])
def register_routes(routes):
Example passing in a single route:
- >>> from routes import Route
- >>> register_routes(Route('about-view', '/about',
- ... controller=about_view_handler))
+ >>> register_routes(('about-view', '/about',
+ ... 'mediagoblin.views:about_view_handler'))
Example passing in a list of routes:
- >>> from routes import Route
>>> register_routes([
- ... Route('contact-view', '/contact', controller=contact_handler),
- ... Route('about-view', '/about', controller=about_handler)
+ ... ('contact-view', '/contact', 'mediagoblin.views:contact_handler'),
+ ... ('about-view', '/about', 'mediagoblin.views:about_handler')
... ])
Be careful when designing your route urls. If they clash with
core urls, then it could result in DISASTER!
"""
- if isinstance(routes, (tuple, list)):
+ if isinstance(routes, 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):
that will have no effect on template loading.
"""
- PluginCache().register_template_path(path)
+ PluginManager().register_template_path(path)
def get_config(key):
global_config = mg_globals.global_config
plugin_section = global_config.get('plugins', {})
return plugin_section.get(key, {})
+
+
+def register_template_hooks(template_hooks):
+ """
+ Register a dict of template hooks.
+
+ Takes template_hooks as an argument, which is a dictionary of
+ template hook names/keys to the templates they should provide.
+ (The value can either be a single template path or an iterable
+ of paths.)
+
+ Example:
+ {"media_sidebar": "/plugin/sidemess/mess_up_the_side.html",
+ "media_descriptionbox": ["/plugin/sidemess/even_more_mess.html",
+ "/plugin/sidemess/so_much_mess.html"]}
+ """
+ PluginManager().register_template_hooks(template_hooks)
+
+
+def get_hook_templates(hook_name):
+ """
+ Get a list of hook templates for this hook_name.
+
+ Note: for the most part, you access this via a template tag, not
+ this method directly, like so:
+
+ {% template_hook "media_sidebar" %}
+
+ ... which will include all templates for you, partly using this
+ method.
+
+ Returns:
+ A list of strings representing template paths.
+
+ """
+ return PluginManager().get_template_hooks(hook_name)