__init__.py 6.3 KB
Newer Older
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
1
2
from future import standard_library
standard_library.install_aliases()
3

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
4
5
import inspect
import os.path
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
6
import os
7
import pickle
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
8
import logging
9
import tempfile
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
10
import copy
11
from .. import models
12
from ..api import API_PARAMS
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
13
14
15

logger = logging.getLogger(__name__)

16

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
def check_template(indict, template):
    if isinstance(template, dict) and isinstance(indict, dict):
        for k, v in template.items():
            if k not in indict:
                return '{} not in {}'.format(k, indict)
            check_template(indict[k], v)
    elif isinstance(template, list) and isinstance(indict, list):
        if len(indict) != len(template):
            raise models.Error('Different size for {} and {}'.format(indict, template))
        for e in template:
            found = False
            for i in indict:
                try:
                    check_template(i, e)
                    found = True
                except models.Error as ex:
                    continue
            if not found:
                raise models.Error('{} not found in {}'.format(e, indict))
    else:
        if indict != template:
            raise models.Error('{} and {} are different'.format(indict, template))


J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
41
class Plugin(models.Plugin):
42
    def __init__(self, info=None):
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
43
44
45
46
        """
        Provides a canonical name for plugins and serves as base for other
        kinds of plugins.
        """
47
        if not info:
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
48
49
            raise models.Error(message=("You need to provide configuration"
                                        "information for the plugin."))
50
        logger.debug("Initialising {}".format(info))
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
51
        id = 'plugins/{}_{}'.format(info['name'], info['version'])
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
52
        super(Plugin, self).__init__(id=id, **info)
53
        self.is_activated = False
54

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
55
56
57
    def get_folder(self):
        return os.path.dirname(inspect.getfile(self.__class__))

58
59
60
61
62
63
    def activate(self):
        pass

    def deactivate(self):
        pass

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
64
65
66
67
68
69
70
71
72
    def test(self):
        for case in self.test_cases:
            res = list(self.analyse_entry(models.Entry(case['entry']),
                                          case['params']))
            exp = case['expected']
            if not isinstance(exp, list):
                exp = [exp]
            check_template(res, exp)

73

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
74
75
76
77
SenpyPlugin = Plugin


class AnalysisPlugin(Plugin):
78

79
    def analyse(self, *args, **kwargs):
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
        raise NotImplemented(
            'Your method should implement either analyse or analyse_entry')

    def analyse_entry(self, entry, parameters):
        """ An implemented plugin should override this method.
        This base method is here to adapt old style plugins which only
        implement the *analyse* function.
        Note that this method may yield an annotated entry or a list of
        entries (e.g. in a tokenizer)
        """
        text = entry['text']
        params = copy.copy(parameters)
        params['input'] = text
        results = self.analyse(**params)
        for i in results.entries:
            yield i
96

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
97
98
99
100
101
102
    def analyse_entries(self, entries, parameters):
        for entry in entries:
            logger.debug('Analysing entry with plugin {}: {}'.format(self, entry))
            for result in self.analyse_entry(entry, parameters):
                yield result

103

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
104
class ConversionPlugin(Plugin):
105
    pass
106

107

108
class SentimentPlugin(models.SentimentPlugin, AnalysisPlugin):
109
110
111
112
    def __init__(self, info, *args, **kwargs):
        super(SentimentPlugin, self).__init__(info, *args, **kwargs)
        self.minPolarityValue = float(info.get("minPolarityValue", 0))
        self.maxPolarityValue = float(info.get("maxPolarityValue", 1))
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
113

114

115
class EmotionPlugin(models.EmotionPlugin, AnalysisPlugin):
116
    def __init__(self, info, *args, **kwargs):
117
        super(EmotionPlugin, self).__init__(info, *args, **kwargs)
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
118
119
120
121
        self.minEmotionValue = float(info.get("minEmotionValue", -1))
        self.maxEmotionValue = float(info.get("maxEmotionValue", 1))


122
123
class EmotionConversionPlugin(models.EmotionConversionPlugin, ConversionPlugin):
    pass
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
124
125
126
127
128


class ShelfMixin(object):
    @property
    def sh(self):
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
129
        if not hasattr(self, '_sh') or self._sh is None:
130
131
            self.__dict__['_sh'] = {}
            if os.path.isfile(self.shelf_file):
132
133
                try:
                    self.__dict__['_sh'] = pickle.load(open(self.shelf_file, 'rb'))
134
                except (IndexError, EOFError, pickle.UnpicklingError):
135
136
137
                    logger.warning('{} has a corrupted shelf file!'.format(self.id))
                    if not self.get('force_shelf', False):
                        raise
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
138
139
140
141
        return self._sh

    @sh.deleter
    def sh(self):
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
142
143
        if os.path.isfile(self.shelf_file):
            os.remove(self.shelf_file)
144
145
            del self.__dict__['_sh']
        self.save()
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
146

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
147
148
    @property
    def shelf_file(self):
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
149
        if 'shelf_file' not in self or not self['shelf_file']:
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
150
151
            sd = os.environ.get('SENPY_DATA', tempfile.gettempdir())
            self.shelf_file = os.path.join(sd, self.name + '.p')
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
152
        return self['shelf_file']
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
153

154
    def save(self):
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
155
        logger.debug('saving pickle')
156
157
158
        if hasattr(self, '_sh') and self._sh is not None:
            with open(self.shelf_file, 'wb') as f:
                pickle.dump(self._sh, f)
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


default_plugin_type = API_PARAMS['plugin_type']['default']


def pfilter(plugins, **kwargs):
    """ Filter plugins by different criteria """
    if isinstance(plugins, models.Plugins):
        plugins = plugins.plugins
    elif isinstance(plugins, dict):
        plugins = plugins.values()
    ptype = kwargs.pop('plugin_type', default_plugin_type)
    logger.debug('#' * 100)
    logger.debug('ptype {}'.format(ptype))
    if ptype:
        try:
            ptype = ptype[0].upper() + ptype[1:]
            pclass = globals()[ptype]
            logger.debug('Class: {}'.format(pclass))
            candidates = filter(lambda x: isinstance(x, pclass),
                                plugins)
        except KeyError:
            raise models.Error('{} is not a valid type'.format(ptype))
    else:
        candidates = plugins

    logger.debug(candidates)

    def matches(plug):
        res = all(getattr(plug, k, None) == v for (k, v) in kwargs.items())
        logger.debug(
            "matching {} with {}: {}".format(plug.name, kwargs, res))
        return res

    if kwargs:
        candidates = filter(matches, candidates)
    return {p.name: p for p in candidates}