# 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 . """ This module implements the plugin api bits. Two things about things in this module: 1. they should be excessively well documented because we should pull from this file for the docs 2. they should be well tested 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`` 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 file structure could look like this:: myplugin/ |- setup.py # plugin project packaging metadata |- README # holds plugin project information |- LICENSE # holds license information |- myplugin/ # plugin package directory |- __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. MediaGoblin registers any hooks in the ``hooks`` dict of those modules. 2. After all plugin modules are imported, the ``setup`` hook is called allowing plugins to do any set up they need to do. """ import logging from functools import wraps from mediagoblin import mg_globals _log = logging.getLogger(__name__) 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 "plugins": [], # map of hook names -> list of callables for that hook "hooks": {}, # list of registered template paths "template_paths": set(), # list of registered routes "routes": [], } def clear(self): """This is only useful for testing.""" # 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(self, plugin): """Registers a 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 get_hook_callables(self, hook_name): return self.hooks.get(hook_name, []) def register_template_path(self, path): """Registers a template path""" self.template_paths.add(path) def get_template_paths(self): """Returns a tuple of registered template paths""" return tuple(self.template_paths) def register_route(self, route): """Registers a single route""" self.routes.append(route) def get_routes(self): return tuple(self.routes) def register_routes(routes): """Registers one or more routes If your plugin handles requests, then you need to call this with the routes your plugin handles. A "route" is a `routes.Route` object. See `the routes.Route documentation `_ for more details. Example passing in a single route: >>> from routes import Route >>> register_routes(Route('about-view', '/about', ... controller=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) ... ]) .. Note:: Be careful when designing your route urls. If they clash with core urls, then it could result in DISASTER! """ if isinstance(routes, list): for route in routes: PluginManager().register_route(route) else: PluginManager().register_route(routes) def register_template_path(path): """Registers a path for template loading If your plugin has templates, then you need to call this with the absolute path of the root of templates directory. Example: >>> my_plugin_dir = os.path.dirname(__file__) >>> template_dir = os.path.join(my_plugin_dir, 'templates') >>> register_template_path(template_dir) .. Note:: You can only do this in `setup_plugins()`. Doing this after that will have no effect on template loading. """ PluginManager().register_template_path(path) def get_config(key): """Retrieves the configuration for a specified plugin by key Example: >>> get_config('mediagoblin.plugins.sampleplugin') {'foo': 'bar'} >>> get_config('myplugin') {} >>> get_config('flatpages') {'directory': '/srv/mediagoblin/pages', 'nesting': 1}} """ global_config = mg_globals.global_config plugin_section = global_config.get('plugins', {}) return plugin_section.get(key, {})