__init__.py 5.06 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
class Plugin(models.Plugin):
18
    def __init__(self, info=None):
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
19
20
21
22
        """
        Provides a canonical name for plugins and serves as base for other
        kinds of plugins.
        """
23
        if not info:
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
24
25
            raise models.Error(message=("You need to provide configuration"
                                        "information for the plugin."))
26
        logger.debug("Initialising {}".format(info))
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
27
        id = 'plugins/{}_{}'.format(info['name'], info['version'])
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
28
        super(Plugin, self).__init__(id=id, **info)
29
        self.is_activated = False
30

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
31
32
33
    def get_folder(self):
        return os.path.dirname(inspect.getfile(self.__class__))

34
35
36
37
38
39
40
    def activate(self):
        pass

    def deactivate(self):
        pass


J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
41
42
43
44
SenpyPlugin = Plugin


class AnalysisPlugin(Plugin):
45

46
    def analyse(self, *args, **kwargs):
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
        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
63

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
64
65
66
67
68
69
    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

70

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
71
class ConversionPlugin(Plugin):
72
    pass
73

74

75
class SentimentPlugin(models.SentimentPlugin, AnalysisPlugin):
76
77
78
79
    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
80

81

82
class EmotionPlugin(models.EmotionPlugin, AnalysisPlugin):
83
    def __init__(self, info, *args, **kwargs):
84
        super(EmotionPlugin, self).__init__(info, *args, **kwargs)
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
85
86
87
88
        self.minEmotionValue = float(info.get("minEmotionValue", -1))
        self.maxEmotionValue = float(info.get("maxEmotionValue", 1))


89
90
class EmotionConversionPlugin(models.EmotionConversionPlugin, ConversionPlugin):
    pass
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
91
92
93
94
95


class ShelfMixin(object):
    @property
    def sh(self):
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
96
        if not hasattr(self, '_sh') or self._sh is None:
97
98
            self.__dict__['_sh'] = {}
            if os.path.isfile(self.shelf_file):
99
100
                try:
                    self.__dict__['_sh'] = pickle.load(open(self.shelf_file, 'rb'))
101
                except (IndexError, EOFError, pickle.UnpicklingError):
102
103
104
                    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
105
106
107
108
        return self._sh

    @sh.deleter
    def sh(self):
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
109
110
        if os.path.isfile(self.shelf_file):
            os.remove(self.shelf_file)
111
112
            del self.__dict__['_sh']
        self.save()
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
113

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
114
115
    @property
    def shelf_file(self):
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
116
        if 'shelf_file' not in self or not self['shelf_file']:
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
117
118
            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
119
        return self['shelf_file']
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
120

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


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}