1 # GNU MediaGoblin -- federated, autonomous media hosting
2 # Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License as published by
6 # the Free Software Foundation, either version 3 of the License, or
7 # (at your option) any later version.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU Affero General Public License for more details.
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18 This module implements the plugin api bits and provides the plugin
21 Two things about things in this module:
23 1. they should be excessively well documented because we should pull
24 from this file for the docs
26 2. they should be well tested
32 Plugins are structured like any Python project. You create a Python package.
33 In that package, you define a high-level ``__init__.py`` that either defines
34 or imports modules that define classes that inherit from the ``Plugin`` class.
36 Additionally, you want a LICENSE file that specifies the license and a
37 ``setup.py`` that specifies the metadata for packaging your plugin. A rough
38 file structure could look like this::
41 |- setup.py # plugin project packaging metadata
42 |- README # holds plugin project information
43 |- LICENSE # holds license information
44 |- myplugin/ # plugin package directory
45 |- __init__.py # imports myplugin.main
46 |- main.py # code for plugin
52 1. All the modules listed as subsections of the ``plugins`` section in
53 the config file are imported. This causes any ``Plugin`` subclasses in
54 those modules to be defined and when the classes are defined they get
55 automatically registered with the ``PluginCache``.
57 2. After all plugin modules are imported, registered plugin classes are
58 instantiated and ``setup_plugin`` is called for each plugin object.
60 Plugins can do any setup they need to do in their ``setup_plugin``
66 from mediagoblin
import mg_globals
69 _log
= logging
.getLogger(__name__
)
72 class PluginCache(object):
73 """Cache of plugin things"""
75 # list of plugin classes
78 # list of plugin objects
81 # list of registered template paths
82 "template_paths": set(),
84 # list of registered routes
89 """This is only useful for testing."""
90 del self
.plugin_classes
[:]
91 del self
.plugin_objects
[:]
94 self
.__dict
__ = self
.__state
96 def register_plugin_class(self
, plugin_class
):
97 """Registers a plugin class"""
98 self
.plugin_classes
.append(plugin_class
)
100 def register_plugin_object(self
, plugin_obj
):
101 """Registers a plugin object"""
102 self
.plugin_objects
.append(plugin_obj
)
104 def register_template_path(self
, path
):
105 """Registers a template path"""
106 self
.template_paths
.add(path
)
108 def get_template_paths(self
):
109 """Returns a tuple of registered template paths"""
110 return tuple(self
.template_paths
)
112 def register_route(self
, route
):
113 """Registers a single route"""
114 self
.routes
.append(route
)
116 def get_routes(self
):
117 return tuple(self
.routes
)
120 class MetaPluginClass(type):
121 """Metaclass for PluginBase derivatives"""
122 def __new__(cls
, name
, bases
, attrs
):
123 new_class
= super(MetaPluginClass
, cls
).__new
__(cls
, name
, bases
, attrs
)
124 parents
= [b
for b
in bases
if isinstance(b
, MetaPluginClass
)]
127 PluginCache().register_plugin_class(new_class
)
131 class Plugin(object):
132 """Extend this class for plugins.
136 from mediagoblin.tools.pluginapi import Plugin
138 class MyPlugin(Plugin):
141 def setup_plugin(self):
146 __metaclass__
= MetaPluginClass
148 def setup_plugin(self
):
152 def register_routes(routes
):
153 """Registers one or more routes
155 If your plugin handles requests, then you need to call this with
156 the routes your plugin handles.
158 A "route" is a `routes.Route` object. See `the routes.Route
160 <http://routes.readthedocs.org/en/latest/modules/route.html>`_ for
163 Example passing in a single route:
165 >>> from routes import Route
166 >>> register_routes(Route('about-view', '/about',
167 ... controller=about_view_handler))
169 Example passing in a list of routes:
171 >>> from routes import Route
172 >>> register_routes([
173 ... Route('contact-view', '/contact', controller=contact_handler),
174 ... Route('about-view', '/about', controller=about_handler)
180 Be careful when designing your route urls. If they clash with
181 core urls, then it could result in DISASTER!
183 if isinstance(routes
, (tuple, list)):
185 PluginCache().register_route(route
)
187 PluginCache().register_route(routes
)
190 def register_template_path(path
):
191 """Registers a path for template loading
193 If your plugin has templates, then you need to call this with
194 the absolute path of the root of templates directory.
198 >>> my_plugin_dir = os.path.dirname(__file__)
199 >>> template_dir = os.path.join(my_plugin_dir, 'templates')
200 >>> register_template_path(template_dir)
204 You can only do this in `setup_plugins()`. Doing this after
205 that will have no effect on template loading.
208 PluginCache().register_template_path(path
)
212 """Retrieves the configuration for a specified plugin by key
216 >>> get_config('mediagoblin.plugins.sampleplugin')
218 >>> get_config('myplugin')
220 >>> get_config('flatpages')
221 {'directory': '/srv/mediagoblin/pages', 'nesting': 1}}
225 global_config
= mg_globals
.global_config
226 plugin_section
= global_config
.get('plugins', {})
227 return plugin_section
.get(key
, {})