1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
|
# 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 <http://www.gnu.org/licenses/>.
"""
This module implements the plugin api bits and provides the plugin
base.
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`` that either defines
or imports modules that define classes that inherit from the ``Plugin`` class.
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 # imports myplugin.main
|- main.py # code for plugin
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``.
2. After all plugin modules are imported, registered plugin classes are
instantiated and ``setup_plugin`` is called for each plugin object.
Plugins can do any setup they need to do in their ``setup_plugin``
method.
"""
import logging
from mediagoblin import mg_globals
_log = logging.getLogger(__name__)
class PluginCache(object):
"""Cache of plugin things"""
__state = {
# list of plugin classes
"plugin_classes": [],
# list of plugin objects
"plugin_objects": [],
# list of registered template paths
"template_paths": set(),
# list of registered routes
"routes": [],
}
def clear(self):
"""This is only useful for testing."""
del self.plugin_classes[:]
del self.plugin_objects[:]
def __init__(self):
self.__dict__ = self.__state
def register_plugin_class(self, plugin_class):
"""Registers a plugin class"""
self.plugin_classes.append(plugin_class)
def register_plugin_object(self, plugin_obj):
"""Registers a plugin object"""
self.plugin_objects.append(plugin_obj)
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)
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 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
<http://routes.readthedocs.org/en/latest/modules/route.html>`_ 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, (tuple, list)):
for route in routes:
PluginCache().register_route(route)
else:
PluginCache().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.
"""
PluginCache().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, {})
|