mirror of
https://github.com/gsi-upm/senpy
synced 2025-09-18 04:22:21 +00:00
Compare commits
13 Commits
0.8.x-asyn
...
0.8.5
Author | SHA1 | Date | |
---|---|---|---|
|
cc298742ec | ||
|
250052fb99 | ||
|
603e086606 | ||
|
a8614bab0c | ||
|
70ca74b03c | ||
|
c9e6d78183 | ||
|
1a582c0843 | ||
|
0394bcd69c | ||
|
cbeb3adbdb | ||
|
efb305173e | ||
|
2288b04c92 | ||
|
7899cb4d33 | ||
|
62ddca79ac |
8
Makefile
8
Makefile
@@ -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}
|
||||||
|
|
||||||
|
51
README.rst
51
README.rst
@@ -23,7 +23,7 @@ Through PIP
|
|||||||
|
|
||||||
.. code:: bash
|
.. code:: bash
|
||||||
|
|
||||||
pip install --user senpy
|
pip install -U --user senpy
|
||||||
|
|
||||||
|
|
||||||
Alternatively, you can use the development version:
|
Alternatively, you can use the development version:
|
||||||
@@ -42,6 +42,53 @@ Build the image or use the pre-built one: ``docker run -ti -p 5000:5000 gsiupm/s
|
|||||||
|
|
||||||
To add custom plugins, add a volume and tell senpy where to find the plugins: ``docker run -ti -p 5000:5000 -v <PATH OF PLUGINS>:/plugins gsiupm/senpy --default-plugins -f /plugins``
|
To add custom plugins, add a volume and tell senpy where to find the plugins: ``docker run -ti -p 5000:5000 -v <PATH OF PLUGINS>:/plugins gsiupm/senpy --default-plugins -f /plugins``
|
||||||
|
|
||||||
|
|
||||||
|
Developing
|
||||||
|
----------
|
||||||
|
|
||||||
|
Developing/debugging
|
||||||
|
********************
|
||||||
|
This command will run the senpy container using the latest image available, mounting your current folder so you get your latest code:
|
||||||
|
|
||||||
|
.. code:: bash
|
||||||
|
|
||||||
|
|
||||||
|
# Python 3.5
|
||||||
|
make dev
|
||||||
|
# Python 2.7
|
||||||
|
make dev-2.7
|
||||||
|
|
||||||
|
Building a docker image
|
||||||
|
***********************
|
||||||
|
|
||||||
|
.. code:: bash
|
||||||
|
|
||||||
|
|
||||||
|
# Python 3.5
|
||||||
|
make build-3.5
|
||||||
|
# Python 2.7
|
||||||
|
make build-2.7
|
||||||
|
|
||||||
|
Testing
|
||||||
|
*******
|
||||||
|
|
||||||
|
.. code:: bash
|
||||||
|
|
||||||
|
|
||||||
|
make test
|
||||||
|
|
||||||
|
Running
|
||||||
|
*******
|
||||||
|
This command will run the senpy server listening on localhost:5000
|
||||||
|
|
||||||
|
.. code:: bash
|
||||||
|
|
||||||
|
|
||||||
|
# Python 3.5
|
||||||
|
make run-3.5
|
||||||
|
# Python 2.7
|
||||||
|
make run-2.7
|
||||||
|
|
||||||
Usage
|
Usage
|
||||||
-----
|
-----
|
||||||
|
|
||||||
@@ -49,12 +96,14 @@ However, the easiest and recommended way is to just use the command-line tool to
|
|||||||
|
|
||||||
.. code:: bash
|
.. code:: bash
|
||||||
|
|
||||||
|
|
||||||
senpy
|
senpy
|
||||||
|
|
||||||
or, alternatively:
|
or, alternatively:
|
||||||
|
|
||||||
.. code:: bash
|
.. code:: bash
|
||||||
|
|
||||||
|
|
||||||
python -m senpy
|
python -m senpy
|
||||||
|
|
||||||
|
|
||||||
|
@@ -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.
|
||||||
|
@@ -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
|
||||||
|
@@ -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`
|
||||||
|
@@ -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
|
||||||
|
@@ -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",
|
||||||
|
@@ -113,15 +113,20 @@ def basic_api(f):
|
|||||||
@api_blueprint.route('/', methods=['POST', 'GET'])
|
@api_blueprint.route('/', methods=['POST', 'GET'])
|
||||||
@basic_api
|
@basic_api
|
||||||
def api():
|
def api():
|
||||||
response = current_app.senpy.analyse(**request.params)
|
try:
|
||||||
return response
|
response = current_app.senpy.analyse(**request.params)
|
||||||
|
return response
|
||||||
|
except Error as ex:
|
||||||
|
return ex
|
||||||
|
|
||||||
|
|
||||||
@api_blueprint.route('/plugins/', methods=['POST', 'GET'])
|
@api_blueprint.route('/plugins/', methods=['POST', 'GET'])
|
||||||
@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
|
||||||
|
|
||||||
|
|
||||||
|
@@ -5,8 +5,9 @@ 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 .models import Error, Entry, Results
|
from .plugins import SenpyPlugin
|
||||||
|
from .models import Error, Entry, Results, from_dict
|
||||||
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
|
||||||
|
|
||||||
@@ -77,70 +78,101 @@ class Senpy(object):
|
|||||||
else:
|
else:
|
||||||
logger.debug("Not a folder: %s", folder)
|
logger.debug("Not a folder: %s", folder)
|
||||||
|
|
||||||
def _find_plugin(self, params):
|
def _find_plugins(self, params):
|
||||||
api_params = parse_params(params, spec=API_PARAMS)
|
if not self.analysis_plugins:
|
||||||
algo = None
|
|
||||||
if "algorithm" in api_params and api_params["algorithm"]:
|
|
||||||
algo = api_params["algorithm"]
|
|
||||||
elif self.plugins:
|
|
||||||
algo = self.default_plugin and self.default_plugin.name
|
|
||||||
if not algo:
|
|
||||||
raise Error(
|
raise Error(
|
||||||
status=404,
|
status=404,
|
||||||
message=("No plugins found."
|
message=("No plugins found."
|
||||||
" Please install one.").format(algo))
|
" Please install one."))
|
||||||
if algo not in self.plugins:
|
api_params = parse_params(params, spec=API_PARAMS)
|
||||||
logger.debug(("The algorithm '{}' is not valid\n"
|
algos = None
|
||||||
"Valid algorithms: {}").format(algo,
|
if "algorithm" in api_params and api_params["algorithm"]:
|
||||||
self.plugins.keys()))
|
algos = api_params["algorithm"].split(',')
|
||||||
|
elif self.default_plugin:
|
||||||
|
algos = [self.default_plugin.name, ]
|
||||||
|
else:
|
||||||
raise Error(
|
raise Error(
|
||||||
status=404,
|
status=404,
|
||||||
message="The algorithm '{}' is not valid".format(algo))
|
message="No default plugin found, and None provided")
|
||||||
|
|
||||||
if not self.plugins[algo].is_activated:
|
plugins = list()
|
||||||
logger.debug("Plugin not activated: {}".format(algo))
|
for algo in algos:
|
||||||
raise Error(
|
if algo not in self.plugins:
|
||||||
status=400,
|
logger.debug(("The algorithm '{}' is not valid\n"
|
||||||
message=("The algorithm '{}'"
|
"Valid algorithms: {}").format(algo,
|
||||||
" is not activated yet").format(algo))
|
self.plugins.keys()))
|
||||||
return self.plugins[algo]
|
raise Error(
|
||||||
|
status=404,
|
||||||
|
message="The algorithm '{}' is not valid".format(algo))
|
||||||
|
|
||||||
def _get_params(self, params, plugin):
|
if not self.plugins[algo].is_activated:
|
||||||
|
logger.debug("Plugin not activated: {}".format(algo))
|
||||||
|
raise Error(
|
||||||
|
status=400,
|
||||||
|
message=("The algorithm '{}'"
|
||||||
|
" is not activated yet").format(algo))
|
||||||
|
plugins.append(self.plugins[algo])
|
||||||
|
return plugins
|
||||||
|
|
||||||
|
def _get_params(self, params, plugin=None):
|
||||||
nif_params = parse_params(params, spec=NIF_PARAMS)
|
nif_params = parse_params(params, spec=NIF_PARAMS)
|
||||||
extra_params = plugin.get('extra_params', {})
|
if plugin:
|
||||||
specific_params = parse_params(params, spec=extra_params)
|
extra_params = plugin.get('extra_params', {})
|
||||||
nif_params.update(specific_params)
|
specific_params = parse_params(params, spec=extra_params)
|
||||||
|
nif_params.update(specific_params)
|
||||||
return nif_params
|
return nif_params
|
||||||
|
|
||||||
def _get_entries(self, params):
|
def _get_entries(self, params):
|
||||||
entry = None
|
|
||||||
if params['informat'] == 'text':
|
if params['informat'] == 'text':
|
||||||
|
results = Results()
|
||||||
entry = Entry(text=params['input'])
|
entry = Entry(text=params['input'])
|
||||||
|
results.entries.append(entry)
|
||||||
|
elif params['informat'] == 'json-ld':
|
||||||
|
results = from_dict(params['input'])
|
||||||
else:
|
else:
|
||||||
raise NotImplemented('Only text input format implemented')
|
raise NotImplemented('Informat {} is not implemented'.format(params['informat']))
|
||||||
yield entry
|
return results
|
||||||
|
|
||||||
|
def _process_entries(self, entries, plugins, nif_params):
|
||||||
|
if not plugins:
|
||||||
|
for i in entries:
|
||||||
|
yield i
|
||||||
|
return
|
||||||
|
plugin = plugins[0]
|
||||||
|
specific_params = self._get_params(nif_params, plugin)
|
||||||
|
results = plugin.analyse_entries(entries, specific_params)
|
||||||
|
for i in self._process_entries(results, plugins[1:], nif_params):
|
||||||
|
yield i
|
||||||
|
|
||||||
|
def _process_response(self, resp, plugins, nif_params):
|
||||||
|
entries = resp.entries
|
||||||
|
resp.entries = []
|
||||||
|
for plug in plugins:
|
||||||
|
resp.analysis.append(plug.id)
|
||||||
|
for i in self._process_entries(entries, plugins, nif_params):
|
||||||
|
resp.entries.append(i)
|
||||||
|
return resp
|
||||||
|
|
||||||
def analyse(self, **api_params):
|
def analyse(self, **api_params):
|
||||||
|
"""
|
||||||
|
Main method that analyses a request, either from CLI or HTTP.
|
||||||
|
It uses a dictionary of parameters, provided by the user.
|
||||||
|
"""
|
||||||
logger.debug("analysing with params: {}".format(api_params))
|
logger.debug("analysing with params: {}".format(api_params))
|
||||||
plugin = self._find_plugin(api_params)
|
plugins = self._find_plugins(api_params)
|
||||||
nif_params = self._get_params(api_params, plugin)
|
nif_params = self._get_params(api_params)
|
||||||
resp = Results()
|
resp = self._get_entries(nif_params)
|
||||||
if 'with_parameters' in api_params:
|
if 'with_parameters' in api_params:
|
||||||
resp.parameters = nif_params
|
resp.parameters = nif_params
|
||||||
try:
|
try:
|
||||||
entries = []
|
resp = self._process_response(resp, plugins, nif_params)
|
||||||
for i in self._get_entries(nif_params):
|
self.convert_emotions(resp, plugins, nif_params)
|
||||||
entries += list(plugin.analyse_entry(i, nif_params))
|
|
||||||
resp.entries = entries
|
|
||||||
self.convert_emotions(resp, plugin, nif_params)
|
|
||||||
resp.analysis.append(plugin.id)
|
|
||||||
logger.debug("Returning analysis result: {}".format(resp))
|
logger.debug("Returning analysis result: {}".format(resp))
|
||||||
except Error as ex:
|
except (Error, Exception) as ex:
|
||||||
|
if not isinstance(ex, Error):
|
||||||
|
ex = Error(message=str(ex), status=500)
|
||||||
logger.exception('Error returning analysis result')
|
logger.exception('Error returning analysis result')
|
||||||
resp = ex
|
raise ex
|
||||||
except Exception as ex:
|
|
||||||
logger.exception('Error returning analysis result')
|
|
||||||
resp = Error(message=str(ex), status=500)
|
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
def _conversion_candidates(self, fromModel, toModel):
|
def _conversion_candidates(self, fromModel, toModel):
|
||||||
@@ -154,7 +186,7 @@ class Senpy(object):
|
|||||||
# logging.debug('Found candidate: {}'.format(candidate))
|
# logging.debug('Found candidate: {}'.format(candidate))
|
||||||
yield candidate
|
yield candidate
|
||||||
|
|
||||||
def convert_emotions(self, resp, plugin, params):
|
def convert_emotions(self, resp, plugins, params):
|
||||||
"""
|
"""
|
||||||
Conversion of all emotions in a response.
|
Conversion of all emotions in a response.
|
||||||
In addition to converting from one model to another, it has
|
In addition to converting from one model to another, it has
|
||||||
@@ -162,29 +194,35 @@ class Senpy(object):
|
|||||||
Needless to say, this is far from an elegant solution, but it works.
|
Needless to say, this is far from an elegant solution, but it works.
|
||||||
@todo refactor and clean up
|
@todo refactor and clean up
|
||||||
"""
|
"""
|
||||||
fromModel = plugin.get('onyx:usesEmotionModel', None)
|
|
||||||
toModel = params.get('emotionModel', None)
|
toModel = params.get('emotionModel', None)
|
||||||
output = params.get('conversion', None)
|
|
||||||
logger.debug('Asked for model: {}'.format(toModel))
|
|
||||||
logger.debug('Analysis plugin uses model: {}'.format(fromModel))
|
|
||||||
|
|
||||||
if not toModel:
|
if not toModel:
|
||||||
return
|
return
|
||||||
try:
|
|
||||||
candidate = next(self._conversion_candidates(fromModel, toModel))
|
logger.debug('Asked for model: {}'.format(toModel))
|
||||||
except StopIteration:
|
output = params.get('conversion', None)
|
||||||
e = Error(('No conversion plugin found for: '
|
candidates = {}
|
||||||
'{} -> {}'.format(fromModel, toModel)))
|
for plugin in plugins:
|
||||||
e.original_response = resp
|
try:
|
||||||
e.parameters = params
|
fromModel = plugin.get('onyx:usesEmotionModel', None)
|
||||||
raise e
|
candidates[plugin.id] = next(self._conversion_candidates(fromModel, toModel))
|
||||||
|
logger.debug('Analysis plugin {} uses model: {}'.format(plugin.id, fromModel))
|
||||||
|
except StopIteration:
|
||||||
|
e = Error(('No conversion plugin found for: '
|
||||||
|
'{} -> {}'.format(fromModel, toModel)))
|
||||||
|
e.original_response = resp
|
||||||
|
e.parameters = params
|
||||||
|
raise e
|
||||||
newentries = []
|
newentries = []
|
||||||
|
resp.analysis = set(resp.analysis)
|
||||||
for i in resp.entries:
|
for i in resp.entries:
|
||||||
if output == "full":
|
if output == "full":
|
||||||
newemotions = copy.deepcopy(i.emotions)
|
newemotions = copy.deepcopy(i.emotions)
|
||||||
else:
|
else:
|
||||||
newemotions = []
|
newemotions = []
|
||||||
for j in i.emotions:
|
for j in i.emotions:
|
||||||
|
plugname = j['prov:wasGeneratedBy']
|
||||||
|
candidate = candidates[plugname]
|
||||||
|
resp.analysis.add(candidate.id)
|
||||||
for k in candidate.convert(j, fromModel, toModel, params):
|
for k in candidate.convert(j, fromModel, toModel, params):
|
||||||
k.prov__wasGeneratedBy = candidate.id
|
k.prov__wasGeneratedBy = candidate.id
|
||||||
if output == 'nested':
|
if output == 'nested':
|
||||||
@@ -193,7 +231,6 @@ class Senpy(object):
|
|||||||
i.emotions = newemotions
|
i.emotions = newemotions
|
||||||
newentries.append(i)
|
newentries.append(i)
|
||||||
resp.entries = newentries
|
resp.entries = newentries
|
||||||
resp.analysis.append(candidate.id)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def default_plugin(self):
|
def default_plugin(self):
|
||||||
@@ -367,6 +404,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 +427,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)
|
|
||||||
}
|
|
||||||
|
@@ -13,7 +13,7 @@ from .. import models
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class SenpyPlugin(models.Plugin):
|
class Plugin(models.Plugin):
|
||||||
def __init__(self, info=None):
|
def __init__(self, info=None):
|
||||||
"""
|
"""
|
||||||
Provides a canonical name for plugins and serves as base for other
|
Provides a canonical name for plugins and serves as base for other
|
||||||
@@ -24,12 +24,24 @@ class SenpyPlugin(models.Plugin):
|
|||||||
"information for the plugin."))
|
"information for the plugin."))
|
||||||
logger.debug("Initialising {}".format(info))
|
logger.debug("Initialising {}".format(info))
|
||||||
id = 'plugins/{}_{}'.format(info['name'], info['version'])
|
id = 'plugins/{}_{}'.format(info['name'], info['version'])
|
||||||
super(SenpyPlugin, self).__init__(id=id, **info)
|
super(Plugin, self).__init__(id=id, **info)
|
||||||
self.is_activated = False
|
self.is_activated = False
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
|
|
||||||
|
SenpyPlugin = Plugin
|
||||||
|
|
||||||
|
|
||||||
|
class AnalysisPlugin(Plugin):
|
||||||
|
|
||||||
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 +60,33 @@ class SenpyPlugin(models.Plugin):
|
|||||||
for i in results.entries:
|
for i in results.entries:
|
||||||
yield i
|
yield i
|
||||||
|
|
||||||
def activate(self):
|
def analyse_entries(self, entries, parameters):
|
||||||
pass
|
for entry in entries:
|
||||||
|
logger.debug('Analysing entry with plugin {}: {}'.format(self, entry))
|
||||||
def deactivate(self):
|
for result in self.analyse_entry(entry, parameters):
|
||||||
pass
|
yield result
|
||||||
|
|
||||||
|
|
||||||
class SentimentPlugin(models.SentimentPlugin, SenpyPlugin):
|
class ConversionPlugin(Plugin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
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):
|
||||||
|
@@ -6,6 +6,33 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
class CentroidConversion(EmotionConversionPlugin):
|
class CentroidConversion(EmotionConversionPlugin):
|
||||||
|
def __init__(self, info):
|
||||||
|
if 'centroids' not in info:
|
||||||
|
raise Error('Centroid conversion plugins should provide '
|
||||||
|
'the centroids in their senpy file')
|
||||||
|
if 'onyx:doesConversion' not in info:
|
||||||
|
if 'centroids_direction' not in info:
|
||||||
|
raise Error('Please, provide centroids direction')
|
||||||
|
|
||||||
|
cf, ct = info['centroids_direction']
|
||||||
|
info['onyx:doesConversion'] = [{
|
||||||
|
'onyx:conversionFrom': cf,
|
||||||
|
'onyx:conversionTo': ct
|
||||||
|
}, {
|
||||||
|
'onyx:conversionFrom': ct,
|
||||||
|
'onyx:conversionTo': cf
|
||||||
|
}]
|
||||||
|
|
||||||
|
if 'aliases' in info:
|
||||||
|
aliases = info['aliases']
|
||||||
|
ncentroids = {}
|
||||||
|
for k1, v1 in info['centroids'].items():
|
||||||
|
nv1 = {}
|
||||||
|
for k2, v2 in v1.items():
|
||||||
|
nv1[aliases.get(k2, k2)] = v2
|
||||||
|
ncentroids[aliases.get(k1, k1)] = nv1
|
||||||
|
info['centroids'] = ncentroids
|
||||||
|
super(CentroidConversion, self).__init__(info)
|
||||||
|
|
||||||
def _forward_conversion(self, original):
|
def _forward_conversion(self, original):
|
||||||
"""Sum the VAD value of all categories found."""
|
"""Sum the VAD value of all categories found."""
|
||||||
@@ -13,7 +40,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:
|
||||||
@@ -25,7 +52,7 @@ class CentroidConversion(EmotionConversionPlugin):
|
|||||||
dimensions = list(self.centroids.values())[0]
|
dimensions = list(self.centroids.values())[0]
|
||||||
|
|
||||||
def distance(e1, e2):
|
def distance(e1, e2):
|
||||||
return sum((e1[k] - e2.get(self.aliases[k], 0)) for k in dimensions)
|
return sum((e1[k] - e2.get(k, 0)) for k in dimensions)
|
||||||
|
|
||||||
emotion = ''
|
emotion = ''
|
||||||
mindistance = 10000000000000000000000.0
|
mindistance = 10000000000000000000000.0
|
||||||
@@ -40,11 +67,12 @@ class CentroidConversion(EmotionConversionPlugin):
|
|||||||
def convert(self, emotionSet, fromModel, toModel, params):
|
def convert(self, emotionSet, fromModel, toModel, params):
|
||||||
|
|
||||||
cf, ct = self.centroids_direction
|
cf, ct = self.centroids_direction
|
||||||
logger.debug('{}\n{}\n{}\n{}'.format(emotionSet, fromModel, toModel, params))
|
logger.debug(
|
||||||
|
'{}\n{}\n{}\n{}'.format(emotionSet, fromModel, toModel, params))
|
||||||
e = EmotionSet()
|
e = EmotionSet()
|
||||||
if fromModel == cf:
|
if fromModel == cf and toModel == ct:
|
||||||
e.onyx__hasEmotion.append(self._forward_conversion(emotionSet))
|
e.onyx__hasEmotion.append(self._forward_conversion(emotionSet))
|
||||||
elif fromModel == ct:
|
elif fromModel == ct and toModel == cf:
|
||||||
for i in emotionSet.onyx__hasEmotion:
|
for i in emotionSet.onyx__hasEmotion:
|
||||||
e.onyx__hasEmotion.append(self._backwards_conversion(i))
|
e.onyx__hasEmotion.append(self._backwards_conversion(i))
|
||||||
else:
|
else:
|
||||||
|
39
senpy/plugins/conversion/emotion/ekman2fsre.senpy
Normal file
39
senpy/plugins/conversion/emotion/ekman2fsre.senpy
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
---
|
||||||
|
name: Ekman2FSRE
|
||||||
|
module: senpy.plugins.conversion.centroids
|
||||||
|
description: Plugin to convert emotion sets from Ekman to VAD
|
||||||
|
version: 0.1
|
||||||
|
# No need to specify onyx:doesConversion because centroids.py adds it automatically from centroids_direction
|
||||||
|
centroids:
|
||||||
|
anger:
|
||||||
|
A: 6.95
|
||||||
|
D: 5.1
|
||||||
|
V: 2.7
|
||||||
|
disgust:
|
||||||
|
A: 5.3
|
||||||
|
D: 8.05
|
||||||
|
V: 2.7
|
||||||
|
fear:
|
||||||
|
A: 6.5
|
||||||
|
D: 3.6
|
||||||
|
V: 3.2
|
||||||
|
happiness:
|
||||||
|
A: 7.22
|
||||||
|
D: 6.28
|
||||||
|
V: 8.6
|
||||||
|
sadness:
|
||||||
|
A: 5.21
|
||||||
|
D: 2.82
|
||||||
|
V: 2.21
|
||||||
|
centroids_direction:
|
||||||
|
- emoml:big6
|
||||||
|
- emoml:fsre-dimensions
|
||||||
|
aliases: # These are aliases for any key in the centroid, to avoid repeating a long name several times
|
||||||
|
A: emoml:arousal
|
||||||
|
V: emoml:valence
|
||||||
|
D: emoml:dominance
|
||||||
|
anger: emoml:big6anger
|
||||||
|
disgust: emoml:big6disgust
|
||||||
|
fear: emoml:big6fear
|
||||||
|
happiness: emoml:big6happiness
|
||||||
|
sadness: emoml:big6sadness
|
@@ -1,38 +1,39 @@
|
|||||||
---
|
---
|
||||||
name: Ekman2VAD
|
name: Ekman2PAD
|
||||||
module: senpy.plugins.conversion.centroids
|
module: senpy.plugins.conversion.centroids
|
||||||
description: Plugin to convert emotion sets from Ekman to VAD
|
description: Plugin to convert emotion sets from Ekman to VAD
|
||||||
version: 0.1
|
version: 0.1
|
||||||
onyx:doesConversion:
|
# No need to specify onyx:doesConversion because centroids.py adds it automatically from centroids_direction
|
||||||
- onyx:conversionFrom: emoml:big6
|
|
||||||
onyx:conversionTo: emoml:fsre-dimensions
|
|
||||||
- onyx:conversionFrom: emoml:fsre-dimensions
|
|
||||||
onyx:conversionTo: emoml:big6
|
|
||||||
centroids:
|
centroids:
|
||||||
emoml:big6anger:
|
anger:
|
||||||
A: 6.95
|
A: 6.95
|
||||||
D: 5.1
|
D: 5.1
|
||||||
V: 2.7
|
V: 2.7
|
||||||
emoml:big6disgust:
|
disgust:
|
||||||
A: 5.3
|
A: 5.3
|
||||||
D: 8.05
|
D: 8.05
|
||||||
V: 2.7
|
V: 2.7
|
||||||
emoml:big6fear:
|
fear:
|
||||||
A: 6.5
|
A: 6.5
|
||||||
D: 3.6
|
D: 3.6
|
||||||
V: 3.2
|
V: 3.2
|
||||||
emoml:big6happiness:
|
happiness:
|
||||||
A: 7.22
|
A: 7.22
|
||||||
D: 6.28
|
D: 6.28
|
||||||
V: 8.6
|
V: 8.6
|
||||||
emoml:big6sadness:
|
sadness:
|
||||||
A: 5.21
|
A: 5.21
|
||||||
D: 2.82
|
D: 2.82
|
||||||
V: 2.21
|
V: 2.21
|
||||||
centroids_direction:
|
centroids_direction:
|
||||||
- emoml:big6
|
- emoml:big6
|
||||||
- emoml:fsre-dimensions
|
- emoml:pad
|
||||||
aliases:
|
aliases: # These are aliases for any key in the centroid, to avoid repeating a long name several times
|
||||||
A: emoml:arousal
|
A: emoml:arousal
|
||||||
V: emoml:valence
|
V: emoml:valence
|
||||||
D: emoml:dominance
|
D: emoml:dominance
|
||||||
|
anger: emoml:big6anger
|
||||||
|
disgust: emoml:big6disgust
|
||||||
|
fear: emoml:big6fear
|
||||||
|
happiness: emoml:big6happiness
|
||||||
|
sadness: emoml:big6sadness
|
@@ -37,6 +37,12 @@
|
|||||||
"@type": "@id",
|
"@type": "@id",
|
||||||
"@container": "@set"
|
"@container": "@set"
|
||||||
},
|
},
|
||||||
|
"plugins": {
|
||||||
|
"@container": "@list"
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"@container": "@set"
|
||||||
|
},
|
||||||
"prov:wasGeneratedBy": {
|
"prov:wasGeneratedBy": {
|
||||||
"@type": "@id"
|
"@type": "@id"
|
||||||
},
|
},
|
||||||
|
@@ -6,6 +6,7 @@
|
|||||||
"properties": {
|
"properties": {
|
||||||
"plugins": {
|
"plugins": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
|
"default": [],
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "plugin.json"
|
"$ref": "plugin.json"
|
||||||
}
|
}
|
||||||
|
@@ -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()
|
||||||
|
@@ -4,4 +4,5 @@ from senpy.plugins import SentimentPlugin
|
|||||||
class DummyPlugin(SentimentPlugin):
|
class DummyPlugin(SentimentPlugin):
|
||||||
def analyse_entry(self, entry, params):
|
def analyse_entry(self, entry, params):
|
||||||
entry.text = entry.text[::-1]
|
entry.text = entry.text[::-1]
|
||||||
|
entry.reversed = entry.get('reversed', 0) + 1
|
||||||
yield entry
|
yield entry
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
from senpy.plugins import SenpyPlugin
|
from senpy.plugins import AnalysisPlugin
|
||||||
from time import sleep
|
from time import sleep
|
||||||
|
|
||||||
|
|
||||||
class SleepPlugin(SenpyPlugin):
|
class SleepPlugin(AnalysisPlugin):
|
||||||
def activate(self, *args, **kwargs):
|
def activate(self, *args, **kwargs):
|
||||||
sleep(self.timeout)
|
sleep(self.timeout)
|
||||||
|
|
||||||
|
@@ -10,7 +10,7 @@ except ImportError:
|
|||||||
|
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from senpy.extensions import Senpy
|
from senpy.extensions import Senpy
|
||||||
from senpy.models import Error, Results, Entry, EmotionSet, Emotion
|
from senpy.models import Error, Results, Entry, EmotionSet, Emotion, Plugin
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
|
|
||||||
@@ -98,17 +98,26 @@ class ExtensionsTest(TestCase):
|
|||||||
|
|
||||||
def test_analyse_error(self):
|
def test_analyse_error(self):
|
||||||
mm = mock.MagicMock()
|
mm = mock.MagicMock()
|
||||||
mm.analyse_entry.side_effect = Error('error on analysis', status=900)
|
mm.id = 'magic_mock'
|
||||||
|
mm.analyse_entries.side_effect = Error('error on analysis', status=500)
|
||||||
self.senpy.plugins['MOCK'] = mm
|
self.senpy.plugins['MOCK'] = mm
|
||||||
resp = self.senpy.analyse(input='nothing', algorithm='MOCK')
|
try:
|
||||||
assert resp['message'] == 'error on analysis'
|
self.senpy.analyse(input='nothing', algorithm='MOCK')
|
||||||
assert resp['status'] == 900
|
assert False
|
||||||
|
except Error as ex:
|
||||||
|
assert ex['message'] == 'error on analysis'
|
||||||
|
assert ex['status'] == 500
|
||||||
|
|
||||||
mm.analyse.side_effect = Exception('generic exception on analysis')
|
mm.analyse.side_effect = Exception('generic exception on analysis')
|
||||||
mm.analyse_entry.side_effect = Exception(
|
mm.analyse_entries.side_effect = Exception(
|
||||||
'generic exception on analysis')
|
'generic exception on analysis')
|
||||||
resp = self.senpy.analyse(input='nothing', algorithm='MOCK')
|
|
||||||
assert resp['message'] == 'generic exception on analysis'
|
try:
|
||||||
assert resp['status'] == 500
|
self.senpy.analyse(input='nothing', algorithm='MOCK')
|
||||||
|
assert False
|
||||||
|
except Error as ex:
|
||||||
|
assert ex['message'] == 'generic exception on analysis'
|
||||||
|
assert ex['status'] == 500
|
||||||
|
|
||||||
def test_filtering(self):
|
def test_filtering(self):
|
||||||
""" Filtering plugins """
|
""" Filtering plugins """
|
||||||
@@ -125,11 +134,12 @@ class ExtensionsTest(TestCase):
|
|||||||
|
|
||||||
def test_convert_emotions(self):
|
def test_convert_emotions(self):
|
||||||
self.senpy.activate_all()
|
self.senpy.activate_all()
|
||||||
plugin = {
|
plugin = Plugin({
|
||||||
'id': 'imaginary',
|
'id': 'imaginary',
|
||||||
'onyx:usesEmotionModel': 'emoml:fsre-dimensions'
|
'onyx:usesEmotionModel': 'emoml:fsre-dimensions'
|
||||||
}
|
})
|
||||||
eSet1 = EmotionSet()
|
eSet1 = EmotionSet()
|
||||||
|
eSet1.prov__wasGeneratedBy = plugin['id']
|
||||||
eSet1['onyx:hasEmotion'].append(Emotion({
|
eSet1['onyx:hasEmotion'].append(Emotion({
|
||||||
'emoml:arousal': 1,
|
'emoml:arousal': 1,
|
||||||
'emoml:potency': 0,
|
'emoml:potency': 0,
|
||||||
@@ -145,19 +155,19 @@ class ExtensionsTest(TestCase):
|
|||||||
'conversion': 'full'}
|
'conversion': 'full'}
|
||||||
r1 = deepcopy(response)
|
r1 = deepcopy(response)
|
||||||
self.senpy.convert_emotions(r1,
|
self.senpy.convert_emotions(r1,
|
||||||
plugin,
|
[plugin, ],
|
||||||
params)
|
params)
|
||||||
assert len(r1.entries[0].emotions) == 2
|
assert len(r1.entries[0].emotions) == 2
|
||||||
params['conversion'] = 'nested'
|
params['conversion'] = 'nested'
|
||||||
r2 = deepcopy(response)
|
r2 = deepcopy(response)
|
||||||
self.senpy.convert_emotions(r2,
|
self.senpy.convert_emotions(r2,
|
||||||
plugin,
|
[plugin, ],
|
||||||
params)
|
params)
|
||||||
assert len(r2.entries[0].emotions) == 1
|
assert len(r2.entries[0].emotions) == 1
|
||||||
assert r2.entries[0].emotions[0]['prov:wasDerivedFrom'] == eSet1
|
assert r2.entries[0].emotions[0]['prov:wasDerivedFrom'] == eSet1
|
||||||
params['conversion'] = 'filtered'
|
params['conversion'] = 'filtered'
|
||||||
r3 = deepcopy(response)
|
r3 = deepcopy(response)
|
||||||
self.senpy.convert_emotions(r3,
|
self.senpy.convert_emotions(r3,
|
||||||
plugin,
|
[plugin, ],
|
||||||
params)
|
params)
|
||||||
assert len(r3.entries[0].emotions) == 1
|
assert len(r3.entries[0].emotions) == 1
|
||||||
|
@@ -11,8 +11,10 @@ from senpy.models import (Emotion,
|
|||||||
Entry,
|
Entry,
|
||||||
Error,
|
Error,
|
||||||
Results,
|
Results,
|
||||||
Sentiment)
|
Sentiment,
|
||||||
from senpy.plugins import SenpyPlugin
|
Plugins,
|
||||||
|
Plugin)
|
||||||
|
from senpy import plugins
|
||||||
from pprint import pprint
|
from pprint import pprint
|
||||||
|
|
||||||
|
|
||||||
@@ -53,8 +55,8 @@ class ModelsTest(TestCase):
|
|||||||
assert (received["entries"][0]["nif:isString"] != "Not testing")
|
assert (received["entries"][0]["nif:isString"] != "Not testing")
|
||||||
|
|
||||||
def test_id(self):
|
def test_id(self):
|
||||||
''' Adding the id after creation should overwrite the automatic ID
|
""" Adding the id after creation should overwrite the automatic ID
|
||||||
'''
|
"""
|
||||||
r = Entry()
|
r = Entry()
|
||||||
j = r.jsonld()
|
j = r.jsonld()
|
||||||
assert '@id' in j
|
assert '@id' in j
|
||||||
@@ -94,8 +96,16 @@ class ModelsTest(TestCase):
|
|||||||
r.validate()
|
r.validate()
|
||||||
|
|
||||||
def test_plugins(self):
|
def test_plugins(self):
|
||||||
self.assertRaises(Error, SenpyPlugin)
|
self.assertRaises(Error, plugins.Plugin)
|
||||||
p = SenpyPlugin({"name": "dummy", "version": 0})
|
p = plugins.Plugin({"name": "dummy",
|
||||||
|
"version": 0,
|
||||||
|
"extra_params": {
|
||||||
|
"none": {
|
||||||
|
"options": ["es", ],
|
||||||
|
"required": False,
|
||||||
|
"default": "0"
|
||||||
|
}
|
||||||
|
}})
|
||||||
c = p.jsonld()
|
c = p.jsonld()
|
||||||
assert "info" not in c
|
assert "info" not in c
|
||||||
assert "repo" not in c
|
assert "repo" not in c
|
||||||
@@ -103,11 +113,13 @@ class ModelsTest(TestCase):
|
|||||||
logging.debug("Framed:")
|
logging.debug("Framed:")
|
||||||
logging.debug(c)
|
logging.debug(c)
|
||||||
p.validate()
|
p.validate()
|
||||||
|
assert "es" in c['extra_params']['none']['options']
|
||||||
|
assert isinstance(c['extra_params']['none']['options'], list)
|
||||||
|
|
||||||
def test_str(self):
|
def test_str(self):
|
||||||
"""The string representation shouldn't include private variables"""
|
"""The string representation shouldn't include private variables"""
|
||||||
r = Results()
|
r = Results()
|
||||||
p = SenpyPlugin({"name": "STR test", "version": 0})
|
p = plugins.Plugin({"name": "STR test", "version": 0})
|
||||||
p._testing = 0
|
p._testing = 0
|
||||||
s = str(p)
|
s = str(p)
|
||||||
assert "_testing" not in s
|
assert "_testing" not in s
|
||||||
@@ -143,3 +155,15 @@ class ModelsTest(TestCase):
|
|||||||
print(t)
|
print(t)
|
||||||
g = rdflib.Graph().parse(data=t, format='turtle')
|
g = rdflib.Graph().parse(data=t, format='turtle')
|
||||||
assert len(g) == len(triples)
|
assert len(g) == len(triples)
|
||||||
|
|
||||||
|
def test_single_plugin(self):
|
||||||
|
"""A response with a single plugin should still return a list"""
|
||||||
|
plugs = Plugins()
|
||||||
|
for i in range(10):
|
||||||
|
p = Plugin({'id': str(i),
|
||||||
|
'version': 0,
|
||||||
|
'description': 'dummy'})
|
||||||
|
plugs.plugins.append(p)
|
||||||
|
assert isinstance(plugs.plugins, list)
|
||||||
|
js = plugs.jsonld()
|
||||||
|
assert isinstance(js['plugins'], list)
|
||||||
|
Reference in New Issue
Block a user