extensions.py 5.43 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

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
12
from .blueprints import nif_blueprint
13
14
from git import Repo, InvalidGitRepositoryError

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

16
class Senpy(object):
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
17
    """ Default Senpy extension for Flask """
18
19
20
21
22

    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
23
24
25
26
27
        self._search_folders = set()
        self._outdated = True

        for folder in (base_folder, plugin_folder):
            self.add_folder(folder)
28
29
30
31

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

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
32
33
34
    def init_app(self, app):
        """ Initialise a flask app to add plugins to its context """
        """
35
36
        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
37
        """
38
39
40
41
42
43
44
45
46
        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
47
48
49
50
51
52
53
54
    def add_folder(self, folder):
        if os.path.isdir(folder):
            self._search_folders.add(folder)
            self._outdated = True
            return True
        else:
            return False

55
    def analyse(self, **params):
56
        algo = None
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
57
        logger.debug("analysing with params: {}".format(params))
58
59
        if "algorithm" in params:
            algo = params["algorithm"]
60
61
62
63
64
65
66
67
        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
68
            return {"status": 400, "message": "The algorithm '{}' is not valid".format(algo)}
69
70
71

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

80
    def parameters(self, algo):
81
82
83
        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
84
        self.plugins[plugin].enable()
85
86
87
88
89

    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
90
        logger.debug("Reloading {}".format(plugin))
91
92
93
94
        plug = self.plugins[plugin]
        nplug = self._load_plugin(plug.module, plug.path)
        del self.plugins[plugin]
        self.plugins[nplug.name] = nplug
95

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
96
97
    @staticmethod
    def _load_plugin(plugin, search_folder, enabled=True):
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
98
        logger.debug("Loading plugins")
99
        sys.path.append(search_folder)
100
        (fp, pathname, desc) = imp.find_module(plugin)
101
        try:
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
102
103
104
105
106
107
108
109
110
111
112
113
114
115
            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))
116
117
118
119
        return tmp

    def _load_plugins(self):
        plugins = {}
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
120
        for search_folder in self._search_folders:
121
122
            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
123
124
125
                        and os.path.exists(os.path.join(search_folder,
                                                        item,
                                                        "__init__.py")):
126
                    plugin = self._load_plugin(item, search_folder)
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
127
128
                    if plugin:
                        plugins[plugin.name] = plugin
129

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
130
        self._outdated = False
131
132
133
134
135
136
137
138
139
140
141
        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
142
        """ Return the plugins registered for a given application.  """
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
143
144
145
        if not hasattr(self, 'senpy_plugins') or self._outdated:
            self.senpy_plugins = self._load_plugins()
        return self.senpy_plugins
146

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

150
        def matches(plug):
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
151
152
153
154
            res = all(getattr(plug, k, None) == v for (k, v) in kwargs.items())
            logger.debug("matching {} with {}: {}".format(plug.name,
                                                          kwargs,
                                                          res))
155
            return res
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
156

157
158
159
        if not kwargs:
            return self.plugins
        else:
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
160
            return {n: p for n, p in self.plugins.items() if matches(p)}
161
162

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