Flatpages first pass
[mediagoblin.git] / mediagoblin / tools / pluginapi.py
1 # GNU MediaGoblin -- federated, autonomous media hosting
2 # Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
3 #
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.
8 #
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.
13 #
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/>.
16
17 """
18 This module implements the plugin api bits and provides the plugin
19 base.
20
21 Two things about things in this module:
22
23 1. they should be excessively well documented because we should pull
24 from this file for the docs
25
26 2. they should be well tested
27
28
29 How do plugins work?
30 ====================
31
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.
35
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::
39
40 myplugin/
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
47
48
49 Lifecycle
50 =========
51
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``.
56
57 2. After all plugin modules are imported, registered plugin classes are
58 instantiated and ``setup_plugin`` is called for each plugin object.
59
60 Plugins can do any setup they need to do in their ``setup_plugin``
61 method.
62 """
63
64 import logging
65
66 from mediagoblin import mg_globals
67
68
69 _log = logging.getLogger(__name__)
70
71
72 class PluginCache(object):
73 """Cache of plugin things"""
74 __state = {
75 # list of plugin classes
76 "plugin_classes": [],
77
78 # list of plugin objects
79 "plugin_objects": [],
80
81 # list of registered template paths
82 "template_paths": set(),
83 }
84
85 def clear(self):
86 """This is only useful for testing."""
87 del self.plugin_classes[:]
88 del self.plugin_objects[:]
89
90 def __init__(self):
91 self.__dict__ = self.__state
92
93 def register_plugin_class(self, plugin_class):
94 """Registers a plugin class"""
95 self.plugin_classes.append(plugin_class)
96
97 def register_plugin_object(self, plugin_obj):
98 """Registers a plugin object"""
99 self.plugin_objects.append(plugin_obj)
100
101 def register_template_path(self, path):
102 """Registers a template path"""
103 self.template_paths.add(path)
104
105 def get_template_paths(self):
106 """Returns a tuple of registered template paths"""
107 return tuple(self.template_paths)
108
109
110 class MetaPluginClass(type):
111 """Metaclass for PluginBase derivatives"""
112 def __new__(cls, name, bases, attrs):
113 new_class = super(MetaPluginClass, cls).__new__(cls, name, bases, attrs)
114 parents = [b for b in bases if isinstance(b, MetaPluginClass)]
115 if not parents:
116 return new_class
117 PluginCache().register_plugin_class(new_class)
118 return new_class
119
120
121 class Plugin(object):
122 """Exttend this class for plugins.
123
124 Example::
125
126 from mediagoblin.tools.pluginapi import Plugin
127
128 class MyPlugin(Plugin):
129 ...
130
131 def setup_plugin(self):
132 ....
133
134 """
135
136 __metaclass__ = MetaPluginClass
137
138 def setup_plugin(self):
139 pass
140
141
142 def register_template_path(path):
143 """Registers a path for template loading
144
145 If your plugin has templates, then you need to call this with
146 the absolute path of the root of templates directory.
147
148 Example:
149
150 >>> my_plugin_dir = os.path.dirname(__file__)
151 >>> template_dir = os.path.join(my_plugin_dir, 'templates')
152 >>> register_template_path(template_dir)
153
154 .. Note::
155
156 You can only do this in `setup_plugins()`. Doing this after
157 that will have no effect on template loading.
158
159 """
160 PluginCache().register_template_path(path)
161
162
163 def get_config(key):
164 """Retrieves the configuration for a specified plugin by key
165
166 Example:
167
168 >>> get_config('mediagoblin.plugins.sampleplugin')
169 {'foo': 'bar'}
170 >>> get_config('myplugin')
171 {}
172 >>> get_config('flatpages')
173 {'directory': '/srv/mediagoblin/pages', 'nesting': 1}}
174
175 """
176
177 global_config = mg_globals.global_config
178 plugin_section = global_config.get('plugins', {})
179 return plugin_section.get(key, {})