extensions.py 5.61 KB
Newer Older
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
1
2
"""
"""
3
4
import os
import sys
5
import imp
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
6
7
8
import logging

logger = logging.getLogger(__name__)
9

10
from .plugins import SentimentPlugin, EmotionPlugin
11
12
13
14
15
16

try:
    from flask import _app_ctx_stack as stack
except ImportError:
    from flask import _request_ctx_stack as stack

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
17
from .blueprints import nif_blueprint
18
19
from git import Repo, InvalidGitRepositoryError

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
20

21
class Senpy(object):
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
22
    """ Default Senpy extension for Flask """
23
24
25
26
27

    def __init__(self, app=None, plugin_folder="plugins"):
        self.app = app
        base_folder = os.path.join(os.path.dirname(__file__), "plugins")

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
28
29
30
31
32
        self._search_folders = set()
        self._outdated = True

        for folder in (base_folder, plugin_folder):
            self.add_folder(folder)
33
34
35
36

        if app is not None:
            self.init_app(app)

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
37
38
39
    def init_app(self, app):
        """ Initialise a flask app to add plugins to its context """
        """
40
41
        Note: I'm not particularly fond of adding self.app and app.senpy, but
        I can't think of a better way to do it.
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
42
        """
43
44
45
46
47
48
49
50
51
        app.senpy = self
        # Use the newstyle teardown_appcontext if it's available,
        # otherwise fall back to the request context
        if hasattr(app, 'teardown_appcontext'):
            app.teardown_appcontext(self.teardown)
        else:
            app.teardown_request(self.teardown)
        app.register_blueprint(nif_blueprint)

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
52
53
54
55
56
57
58
59
    def add_folder(self, folder):
        if os.path.isdir(folder):
            self._search_folders.add(folder)
            self._outdated = True
            return True
        else:
            return False

60
    def analyse(self, **params):
61
        algo = None
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
62
        logger.debug("analysing with params: {}".format(params))
63
64
        if "algorithm" in params:
            algo = params["algorithm"]
65
66
67
68
69
70
71
72
        elif self.plugins:
            algo = self.default_plugin
        if algo in self.plugins and self.plugins[algo].enabled:
            plug = self.plugins[algo]
            resp = plug.analyse(**params)
            resp.analysis.append(plug.jsonable())
            return resp
        else:
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
73
            return {"status": 400, "message": "The algorithm '{}' is not valid".format(algo)}
74
75
76

    @property
    def default_plugin(self):
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
77
        candidates = self.filter_plugins(enabled=True)
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
78
        if len(candidates) > 1:
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
79
80
            candidate = candidates.keys()[0]
            logger.debug("Default: {}".format(candidate))
81
82
            return candidate
        else:
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
83
            return None
84

85
    def parameters(self, algo):
86
87
88
        return getattr(self.plugins.get(algo or self.default_plugin), "params", {})

    def enable_plugin(self, plugin):
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
89
        self.plugins[plugin].enable()
90
91
92
93
94

    def disable_plugin(self, plugin):
        self.plugins[plugin].disable()

    def reload_plugin(self, plugin):
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
95
        logger.debug("Reloading {}".format(plugin))
96
97
98
99
        plug = self.plugins[plugin]
        nplug = self._load_plugin(plug.module, plug.path)
        del self.plugins[plugin]
        self.plugins[nplug.name] = nplug
100

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
101
102
    @staticmethod
    def _load_plugin(plugin, search_folder, enabled=True):
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
103
        logger.debug("Loading plugins")
104
        sys.path.append(search_folder)
105
        (fp, pathname, desc) = imp.find_module(plugin)
106
        try:
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
107
108
109
110
111
112
113
114
115
116
117
118
119
120
            tmp = imp.load_module(plugin, fp, pathname, desc).plugin
            sys.path.remove(search_folder)
            tmp.path = search_folder
            try:
                repo_path = os.path.join(search_folder, plugin)
                tmp.repo = Repo(repo_path)
            except InvalidGitRepositoryError:
                tmp.repo = None
            if not hasattr(tmp, "enabled"):
                tmp.enabled = enabled
            tmp.module = plugin
        except Exception as ex:
            tmp = None
            logger.debug("Exception importing {}: {}".format(plugin, ex))
121
122
123
124
        return tmp

    def _load_plugins(self):
        plugins = {}
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
125
        for search_folder in self._search_folders:
126
127
            for item in os.listdir(search_folder):
                if os.path.isdir(os.path.join(search_folder, item)) \
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
128
129
130
                        and os.path.exists(os.path.join(search_folder,
                                                        item,
                                                        "__init__.py")):
131
                    plugin = self._load_plugin(item, search_folder)
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
132
133
                    if plugin:
                        plugins[plugin.name] = plugin
134

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
135
        self._outdated = False
136
137
138
139
140
141
142
143
144
145
146
        return plugins

    def teardown(self, exception):
        pass

    def enable_all(self):
        for plugin in self.plugins:
            self.enable_plugin(plugin)

    @property
    def plugins(self):
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
147
        """ Return the plugins registered for a given application.  """
148
149
        ctx = stack.top
        if ctx is not None:
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
150
151
152
            if not hasattr(ctx, 'senpy_plugins') or self._outdated:
                ctx.senpy_plugins = self._load_plugins()
            return ctx.senpy_plugins
153

154
    def filter_plugins(self, **kwargs):
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
155
        """ Filter plugins by different criteria """
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
156

157
        def matches(plug):
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
158
159
160
161
            res = all(getattr(plug, k, None) == v for (k, v) in kwargs.items())
            logger.debug("matching {} with {}: {}".format(plug.name,
                                                          kwargs,
                                                          res))
162
            return res
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
163

164
165
166
        if not kwargs:
            return self.plugins
        else:
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
167
            return {n: p for n, p in self.plugins.items() if matches(p)}
168
169

    def sentiment_plugins(self):
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
170
        """ Return only the sentiment plugins """
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
171
172
        return {p: plugin for p, plugin in self.plugins.items() if
                isinstance(plugin, SentimentPlugin)}