Merge remote-tracking branch 'is_derek/bug405_email_notifications_for_comments' into...
[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
82 def clear(self):
83 """This is only useful for testing."""
84 del self.plugin_classes[:]
85 del self.plugin_objects[:]
86
87 def __init__(self):
88 self.__dict__ = self.__state
89
90 def register_plugin_class(self, plugin_class):
91 """Registers a plugin class"""
92 self.plugin_classes.append(plugin_class)
93
94 def register_plugin_object(self, plugin_obj):
95 """Registers a plugin object"""
96 self.plugin_objects.append(plugin_obj)
97
98
99 class MetaPluginClass(type):
100 """Metaclass for PluginBase derivatives"""
101 def __new__(cls, name, bases, attrs):
102 new_class = super(MetaPluginClass, cls).__new__(cls, name, bases, attrs)
103 parents = [b for b in bases if isinstance(b, MetaPluginClass)]
104 if not parents:
105 return new_class
106 PluginCache().register_plugin_class(new_class)
107 return new_class
108
109
110 class Plugin(object):
111 """Exttend this class for plugins.
112
113 Example::
114
115 from mediagoblin.tools.pluginapi import Plugin
116
117 class MyPlugin(Plugin):
118 ...
119
120 def setup_plugin(self):
121 ....
122
123 """
124
125 __metaclass__ = MetaPluginClass
126
127 def setup_plugin(self):
128 pass
129
130
131 def get_config(key):
132 """Retrieves the configuration for a specified plugin by key
133
134 Example:
135
136 >>> get_config('mediagoblin.plugins.sampleplugin')
137 {'foo': 'bar'}
138 >>> get_config('myplugin')
139 {}
140 >>> get_config('flatpages')
141 {'directory': '/srv/mediagoblin/pages', 'nesting': 1}}
142
143 """
144
145 global_config = mg_globals.global_config
146 plugin_section = global_config.get('plugins', {})
147 return plugin_section.get(key, {})