1
0
mirror of https://github.com/gsi-upm/senpy synced 2025-09-16 19:42:21 +00:00

Compare commits

...

6 Commits

Author SHA1 Message Date
J. Fernando Sánchez
5493070d40 Filter conversion plugins
Closes #12

* Shows only analysis plugins by default on /api/plugins
* Adds a plugin_type parameter to get other types of plugins
* default_plugin chosen from analysis plugins
2017-03-06 11:27:49 +01:00
J. Fernando Sánchez
cbeb3adbdb Added fallback version '0.0'
Installing depends on the VERSION file, so it raies an error if it is
installed in some other way.

ReadTheDocs installs the package so it can generate code docs.
This commit adds a default version 0.0
2017-03-01 18:53:54 +01:00
J. Fernando Sánchez
efb305173e Removed future from __init__
Since __init__ is imported by setup.py, future may not be installed yet.

Other options would be:

* Read VERSION -> and that code has to be duplicated in setup.py and
  senpy (to avoid the import, once again)
* Eval version.py
* Do without versioning :)
2017-03-01 18:28:20 +01:00
J. Fernando Sánchez
2288b04c92 Remove iteritems for py2/3 compatibility 2017-03-01 18:14:44 +01:00
J. Fernando Sánchez
7899cb4d33 Fixed docker upload
Doing docker push without a tag makes the client upload **ALL** the
images it has for that repo.
2017-03-01 17:59:35 +01:00
J. Fernando Sánchez
62ddca79ac Fixed conversion docs 2017-03-01 17:56:17 +01:00
11 changed files with 73 additions and 40 deletions

View File

@@ -85,8 +85,6 @@ git_push:
pip_upload: pip_upload:
python setup.py sdist upload ; python setup.py sdist upload ;
pip_test: $(addprefix pip_test-,$(PYVERSIONS))
run-%: build-% run-%: build-%
docker run --rm -p 5000:5000 -ti '$(IMAGEWTAG)-python$(PYMAIN)' --default-plugins docker run --rm -p 5000:5000 -ti '$(IMAGEWTAG)-python$(PYMAIN)' --default-plugins
@@ -95,12 +93,16 @@ run: run-$(PYMAIN)
push-latest: build-$(PYMAIN) push-latest: build-$(PYMAIN)
docker tag '$(IMAGEWTAG)-python$(PYMAIN)' '$(IMAGEWTAG)' docker tag '$(IMAGEWTAG)-python$(PYMAIN)' '$(IMAGEWTAG)'
docker tag '$(IMAGEWTAG)-python$(PYMAIN)' '$(IMAGENAME)' docker tag '$(IMAGEWTAG)-python$(PYMAIN)' '$(IMAGENAME)'
docker push '$(IMAGENAME)' docker push '$(IMAGENAME):latest'
docker push '$(IMAGEWTAG)' docker push '$(IMAGEWTAG)'
push-%: build-% push-%: build-%
docker push $(IMAGENAME):$(VERSION)-python$* docker push $(IMAGENAME):$(VERSION)-python$*
push: $(addprefix push-,$(PYVERSIONS))
docker tag '$(IMAGEWTAG)-python$(PYMAIN)' '$(IMAGEWTAG)'
docker push $(IMAGENAME):$(VERSION)
ci: ci:
gitlab-runner exec docker --docker-volumes /var/run/docker.sock:/var/run/docker.sock --env CI_PROJECT_NAME=$(NAME) ${action} gitlab-runner exec docker --docker-volumes /var/run/docker.sock:/var/run/docker.sock --env CI_PROJECT_NAME=$(NAME) ${action}

View File

@@ -1,13 +1,13 @@
Introduction Conversion
------------ ----------
Senpy includes experimental support for emotion/sentiment conversion plugins. Senpy includes experimental support for emotion/sentiment conversion plugins.
Use Use
--- ===
Consider the original query: `http://127.0.0.1:5000/api/?i=hello&algo=emoRand`_ Consider the original query: http://127.0.0.1:5000/api/?i=hello&algo=emoRand
The requested plugin (emoRand) returns emotions using Ekman's model (or big6 in EmotionML): The requested plugin (emoRand) returns emotions using Ekman's model (or big6 in EmotionML):
@@ -28,7 +28,7 @@ The requested plugin (emoRand) returns emotions using Ekman's model (or big6 in
To get these emotions in VAD space (FSRE dimensions in EmotionML), we'd do this: To get these emotions in VAD space (FSRE dimensions in EmotionML), we'd do this:
`http://127.0.0.1:5000/api/?i=hello&algo=emoRand&emotionModel=emoml:fsre-dimensions`_ http://127.0.0.1:5000/api/?i=hello&algo=emoRand&emotionModel=emoml:fsre-dimensions
This call, provided there is a valid conversion plugin from Ekman's to VAD, would return something like this: This call, provided there is a valid conversion plugin from Ekman's to VAD, would return something like this:
@@ -87,7 +87,7 @@ It is also possible to get the original emotion nested within the new converted
Lastly, `conversion=filtered` would only return the converted emotions. Lastly, `conversion=filtered` would only return the converted emotions.
Developing a conversion plugin Developing a conversion plugin
------------------------------ ================================
Conversion plugins are discovered by the server just like any other plugin. Conversion plugins are discovered by the server just like any other plugin.
The difference is the slightly different API, and the need to specify the `source` and `target` of the conversion. The difference is the slightly different API, and the need to specify the `source` and `target` of the conversion.

View File

@@ -1,8 +1,3 @@
.. Senpy documentation master file, created by
sphinx-quickstart on Tue Feb 24 08:57:32 2015.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Welcome to Senpy's documentation! Welcome to Senpy's documentation!
================================= =================================
@@ -15,5 +10,6 @@ Contents:
api api
schema schema
plugins plugins
conversion
demo demo
:maxdepth: 2 :maxdepth: 2

View File

@@ -74,3 +74,7 @@ This example shows how to make a request to the default plugin:
.. _section: http://senpy.readthedocs.org/en/latest/api.html .. _section: http://senpy.readthedocs.org/en/latest/api.html
Conversion
==========
See :doc:`conversion`

View File

@@ -17,7 +17,6 @@
""" """
Sentiment analysis server in Python Sentiment analysis server in Python
""" """
from __future__ import print_function
from .version import __version__ from .version import __version__
import logging import logging

View File

@@ -26,6 +26,13 @@ API_PARAMS = {
"aliases": ["emotionModel", "emoModel"], "aliases": ["emotionModel", "emoModel"],
"required": False "required": False
}, },
"plugin_type": {
"@id": "pluginType",
"description": 'What kind of plugins to list',
"aliases": ["pluginType", "plugin_type"],
"required": True,
"default": "analysisPlugin"
},
"conversion": { "conversion": {
"@id": "conversion", "@id": "conversion",
"description": "How to show the elements that have (not) been converted", "description": "How to show the elements that have (not) been converted",

View File

@@ -121,7 +121,9 @@ def api():
@basic_api @basic_api
def plugins(): def plugins():
sp = current_app.senpy sp = current_app.senpy
dic = Plugins(plugins=list(sp.plugins.values())) ptype = request.params.get('plugin_type')
plugins = sp.filter_plugins(plugin_type=ptype)
dic = Plugins(plugins=list(plugins.values()))
return dic return dic

View File

@@ -5,7 +5,8 @@ It orchestrates plugin (de)activation and analysis.
from future import standard_library from future import standard_library
standard_library.install_aliases() standard_library.install_aliases()
from .plugins import SentimentPlugin, SenpyPlugin from . import plugins
from .plugins import SenpyPlugin
from .models import Error, Entry, Results from .models import Error, Entry, Results
from .blueprints import api_blueprint, demo_blueprint, ns_blueprint from .blueprints import api_blueprint, demo_blueprint, ns_blueprint
from .api import API_PARAMS, NIF_PARAMS, parse_params from .api import API_PARAMS, NIF_PARAMS, parse_params
@@ -367,6 +368,22 @@ class Senpy(object):
def filter_plugins(self, **kwargs): def filter_plugins(self, **kwargs):
""" Filter plugins by different criteria """ """ Filter plugins by different criteria """
ptype = kwargs.pop('plugin_type', None)
logger.debug('#' * 100)
logger.debug('ptype {}'.format(ptype))
if ptype:
try:
ptype = ptype[0].upper() + ptype[1:]
pclass = getattr(plugins, ptype)
logger.debug('Class: {}'.format(pclass))
candidates = filter(lambda x: isinstance(x, pclass),
self.plugins.values())
except AttributeError:
raise Error('{} is not a valid type'.format(ptype))
else:
candidates = self.plugins.values()
logger.debug(candidates)
def matches(plug): def matches(plug):
res = all(getattr(plug, k, None) == v for (k, v) in kwargs.items()) res = all(getattr(plug, k, None) == v for (k, v) in kwargs.items())
@@ -374,15 +391,11 @@ class Senpy(object):
"matching {} with {}: {}".format(plug.name, kwargs, res)) "matching {} with {}: {}".format(plug.name, kwargs, res))
return res return res
if not kwargs: if kwargs:
return self.plugins candidates = filter(matches, candidates)
else: return {p.name: p for p in candidates}
return {n: p for n, p in self.plugins.items() if matches(p)}
def sentiment_plugins(self): @property
""" Return only the sentiment plugins """ def analysis_plugins(self):
return { """ Return only the analysis plugins """
p: plugin return self.filter_plugins(plugin_type='analysisPlugin')
for p, plugin in self.plugins.items()
if isinstance(plugin, SentimentPlugin)
}

View File

@@ -30,6 +30,15 @@ class SenpyPlugin(models.Plugin):
def get_folder(self): def get_folder(self):
return os.path.dirname(inspect.getfile(self.__class__)) return os.path.dirname(inspect.getfile(self.__class__))
def activate(self):
pass
def deactivate(self):
pass
class AnalysisPlugin(SenpyPlugin):
def analyse(self, *args, **kwargs): def analyse(self, *args, **kwargs):
raise NotImplemented( raise NotImplemented(
'Your method should implement either analyse or analyse_entry') 'Your method should implement either analyse or analyse_entry')
@@ -48,30 +57,27 @@ class SenpyPlugin(models.Plugin):
for i in results.entries: for i in results.entries:
yield i yield i
def activate(self):
pass
def deactivate(self): class ConversionPlugin(SenpyPlugin):
pass pass
class SentimentPlugin(models.SentimentPlugin, SenpyPlugin): class SentimentPlugin(models.SentimentPlugin, AnalysisPlugin):
def __init__(self, info, *args, **kwargs): def __init__(self, info, *args, **kwargs):
super(SentimentPlugin, self).__init__(info, *args, **kwargs) super(SentimentPlugin, self).__init__(info, *args, **kwargs)
self.minPolarityValue = float(info.get("minPolarityValue", 0)) self.minPolarityValue = float(info.get("minPolarityValue", 0))
self.maxPolarityValue = float(info.get("maxPolarityValue", 1)) self.maxPolarityValue = float(info.get("maxPolarityValue", 1))
class EmotionPlugin(models.EmotionPlugin, SenpyPlugin): class EmotionPlugin(models.EmotionPlugin, AnalysisPlugin):
def __init__(self, info, *args, **kwargs): def __init__(self, info, *args, **kwargs):
super(EmotionPlugin, self).__init__(info, *args, **kwargs) super(EmotionPlugin, self).__init__(info, *args, **kwargs)
self.minEmotionValue = float(info.get("minEmotionValue", -1)) self.minEmotionValue = float(info.get("minEmotionValue", -1))
self.maxEmotionValue = float(info.get("maxEmotionValue", 1)) self.maxEmotionValue = float(info.get("maxEmotionValue", 1))
class EmotionConversionPlugin(models.EmotionConversionPlugin, SenpyPlugin): class EmotionConversionPlugin(models.EmotionConversionPlugin, ConversionPlugin):
def __init__(self, info, *args, **kwargs): pass
super(EmotionConversionPlugin, self).__init__(info, *args, **kwargs)
class ShelfMixin(object): class ShelfMixin(object):

View File

@@ -13,7 +13,7 @@ class CentroidConversion(EmotionConversionPlugin):
for e in original.onyx__hasEmotion: for e in original.onyx__hasEmotion:
category = e.onyx__hasEmotionCategory category = e.onyx__hasEmotionCategory
if category in self.centroids: if category in self.centroids:
for dim, value in self.centroids[category].iteritems(): for dim, value in self.centroids[category].items():
try: try:
res[dim] += value res[dim] += value
except Exception: except Exception:

View File

@@ -8,8 +8,12 @@ DEFAULT_FILE = os.path.join(ROOT, 'VERSION')
def read_version(versionfile=DEFAULT_FILE): def read_version(versionfile=DEFAULT_FILE):
with open(versionfile) as f: try:
return f.read().strip() with open(versionfile) as f:
return f.read().strip()
except IOError:
logger.error('Running an unknown version of senpy. Be careful!.')
return '0.0'
__version__ = read_version() __version__ = read_version()