Completed
Branch dev-4.1 (fc3790)
by Felipe A.
01:10
created

PluginManagerBase.load_plugin()   B

Complexity

Conditions 7

Size

Total Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 7
c 1
b 1
f 0
dl 0
loc 20
rs 7.3333
1
#!/usr/bin/env python
2
# -*- coding: UTF-8 -*-
3
4
import sys
5
import collections
6
7
from . import mimetype
8
from . import widget
9
from .compat import isnonstriterable
10
11
12
class PluginNotFoundError(ImportError):
13
    pass
14
15
16
class PluginManagerBase(object):
17
18
    @property
19
    def namespaces(self):
20
        return self.app.config['plugin_namespaces']
21
22
    def __init__(self, app=None):
23
        if app is not None:
24
            self.init_app(app)
25
26
    def init_app(self, app):
27
        self.app = app
28
        if not hasattr(app, 'extensions'):
29
            app.extensions = {}
30
        app.extensions['plugin_manager'] = self
31
        self.reload()
32
33
    def reload(self):
34
        for plugin in self.app.config.get('plugin_modules', ()):
35
            self.load_plugin(plugin)
36
37
    def load_plugin(self, plugin):
38
        names = [
39
            '%s.%s' % (namespace, plugin) if namespace else plugin
40
            for namespace in self.namespaces
41
            ]
42
43
        for name in names:
44
            if name in sys.modules:
45
                return sys.modules[name]
46
47
        for name in names:
48
            try:
49
                __import__(name)
50
                return sys.modules[name]
51
            except (ImportError, IndexError):
52
                pass
53
54
        raise PluginNotFoundError(
55
            'No plugin module %r found, tried %r' % (plugin, names),
56
            plugin, names)
57
58
59
class BlueprintPluginManager(PluginManagerBase):
60
    def register_blueprint(self, blueprint):
61
        self.app.register_blueprint(blueprint)
62
63
    def load_plugin(self, plugin):
64
        module = super(BlueprintPluginManager, self).load_plugin(plugin)
65
        if hasattr(module, 'register_plugin'):
66
            module.register_plugin(self)
67
        return module
68
69
70
class ActionPluginManager(PluginManagerBase):
71
    action_class = collections.namedtuple(
72
        'CallbackAction', ('endpoint', 'widget'))
73
    button_class = widget.ButtonWidget
74
    style_class = widget.StyleWidget
75
    javascript_class = widget.JavascriptWidget
76
    link_class = widget.LinkWidget
77
78
    def __init__(self, app=None):
79
        self._widgets = {}
80
        self._callback_actions = []
81
        super(ActionPluginManager, self).__init__(app=app)
82
83
    def get_actions(self, file):
84
        return list(self.iter_actions(file))
85
86
    def iter_actions(self, file):
87
        for callback, endpoint, widget in self._callback_actions:
88
            if callback(file):
89
                yield self.action_class(endpoint, widget.for_file(file))
90
91
    def get_widgets(self, place):
92
        return self._widgets.get(place, [])
93
94
    def register_widget(self, widget):
95
        self._widgets.setdefault(widget.place, []).append(widget)
96
97
    def register_action(self, endpoint, widget, callback=None, **kwargs):
98
        if callable(callback):
99
            self._callback_actions.append((callback, endpoint, widget))
100
101
102
class MimetypeActionPluginManager(ActionPluginManager):
103
    action_class = collections.namedtuple(
104
        'MimetypeAction', ('endpoint', 'widget'))
105
106
    _default_mimetype_functions = [
107
        mimetype.by_python,
108
        mimetype.by_file,
109
        mimetype.by_default,
110
    ]
111
112
    def __init__(self, app=None):
113
        self._root = {}
114
        self._mimetype_functions = list(self._default_mimetype_functions)
115
        super(MimetypeActionPluginManager, self).__init__(app=app)
116
117
    def get_mimetype(self, path):
118
        for fnc in self._mimetype_functions:
119
            mime = fnc(path)
120
            if mime:
121
                return mime
122
        return mimetype.by_default(path)
123
124
    def iter_actions(self, file):
125
        for action in super(MimetypeActionPluginManager, self)\
126
                        .iter_actions(file):
127
            yield action
128
129
        category, variant = file.mimetype.split('/')
130
        for tree_category in (category, '*'):
131
            for tree_variant in (variant, '*'):
132
                acts = self._root.get(tree_category, {}).get(tree_variant, ())
133
                for endpoint, widget in acts:
134
                    yield self.action_class(endpoint, widget.for_file(file))
135
136
    def register_mimetype_function(self, fnc):
137
        self._mimetype_functions.insert(0, fnc)
138
139
    def register_action(self, endpoint, widget, mimetypes=(), **kwargs):
140
        if not mimetypes:
141
            super(MimetypeActionPluginManager, self)\
142
                    .register_action(endpoint, widget, **kwargs)
143
            return
144
        mimetypes = mimetypes if isnonstriterable(mimetypes) else (mimetypes,)
145
        action = (endpoint, widget)
146
        for mimetype in mimetypes:
147
            category, variant = mimetype.split('/')
148
            self._root.setdefault(
149
                category, {}
150
                ).setdefault(variant, []).append(action)
151
152
153
class PluginManager(BlueprintPluginManager, MimetypeActionPluginManager):
154
    pass
155