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

Compare commits

...

3 Commits

Author SHA1 Message Date
J. Fernando Sánchez
99403b3443 Fix for async
Should fix #11
2017-03-01 12:25:07 +01:00
J. Fernando Sánchez
a0ff528a4b Improved docs and client
* Client now raises an exception on error
* Added conversion to the documentation
2017-02-28 19:38:01 +01:00
J. Fernando Sánchez
97bd245dfc Changed data directory 2017-02-28 18:31:43 +01:00
9 changed files with 193 additions and 84 deletions

View File

@@ -6,7 +6,7 @@ RUN mkdir /cache/ /senpy-plugins /data/
VOLUME /data/ VOLUME /data/
ENV PIP_CACHE_DIR=/cache/ ENV PIP_CACHE_DIR=/cache/ SENPY_DATA=/data
ONBUILD COPY . /senpy-plugins/ ONBUILD COPY . /senpy-plugins/
ONBUILD RUN python -m senpy --only-install -f /senpy-plugins ONBUILD RUN python -m senpy --only-install -f /senpy-plugins
@@ -19,4 +19,4 @@ RUN pip install --use-wheel -r test-requirements.txt -r requirements.txt
COPY . /usr/src/app/ COPY . /usr/src/app/
RUN pip install --no-deps --no-index . RUN pip install --no-deps --no-index .
ENTRYPOINT ["python", "-m", "senpy", "-f", "/senpy-plugins/", "--host", "0.0.0.0"] ENTRYPOINT ["python", "-m", "senpy", "-f", "/senpy-plugins/", "--host", "0.0.0.0"]

View File

@@ -62,6 +62,7 @@ NIF API
:query o outformat: one of `turtle` (default), `text`, `json-ld` :query o outformat: one of `turtle` (default), `text`, `json-ld`
:query p prefix: prefix for the URIs :query p prefix: prefix for the URIs
:query algo algorithm: algorithm/plugin to use for the analysis. For a list of options, see :http:get:`/api/plugins`. If not provided, the default plugin will be used (:http:get:`/api/plugins/default`). :query algo algorithm: algorithm/plugin to use for the analysis. For a list of options, see :http:get:`/api/plugins`. If not provided, the default plugin will be used (:http:get:`/api/plugins/default`).
:query algo emotionModel: desired emotion model in the results. If the requested algorithm does not use that emotion model, there are conversion plugins specifically for this. If none of the plugins match, an error will be returned, which includes the results *as is*.
:reqheader Accept: the response content type depends on :reqheader Accept: the response content type depends on
:mailheader:`Accept` header :mailheader:`Accept` header
@@ -69,6 +70,7 @@ NIF API
header of request header of request
:statuscode 200: no error :statuscode 200: no error
:statuscode 404: service not found :statuscode 404: service not found
:statuscode 400: error while processing the request
.. http:post:: /api .. http:post:: /api
@@ -93,51 +95,53 @@ NIF API
{ {
"@context": { "@context": {
... ...
}, },
"sentiment140": { "@type": "plugins",
"name": "sentiment140", "plugins": [
"is_activated": true, {
"version": "0.1", "name": "sentiment140",
"extra_params": { "is_activated": true,
"@id": "extra_params_sentiment140_0.1", "version": "0.1",
"language": { "extra_params": {
"required": false, "@id": "extra_params_sentiment140_0.1",
"@id": "lang_sentiment140", "language": {
"options": [ "required": false,
"es", "@id": "lang_sentiment140",
"en", "options": [
"auto" "es",
], "en",
"aliases": [ "auto"
"language", ],
"l" "aliases": [
] "language",
} "l"
}, ]
"@id": "sentiment140_0.1" }
}, },
"rand": { "@id": "sentiment140_0.1"
"name": "rand", }, {
"is_activated": true, "name": "rand",
"version": "0.1", "is_activated": true,
"extra_params": { "version": "0.1",
"@id": "extra_params_rand_0.1", "extra_params": {
"language": { "@id": "extra_params_rand_0.1",
"required": false, "language": {
"@id": "lang_rand", "required": false,
"options": [ "@id": "lang_rand",
"es", "options": [
"en", "es",
"auto" "en",
], "auto"
"aliases": [ ],
"language", "aliases": [
"l" "language",
] "l"
} ]
}, }
"@id": "rand_0.1" },
} "@id": "rand_0.1"
}
]
} }
@@ -148,7 +152,7 @@ NIF API
.. sourcecode:: http .. sourcecode:: http
GET /api/plugins/rand HTTP/1.1 GET /api/plugins/rand/ HTTP/1.1
Host: localhost Host: localhost
Accept: application/json, text/javascript Accept: application/json, text/javascript
@@ -159,6 +163,7 @@ NIF API
{ {
"@id": "rand_0.1", "@id": "rand_0.1",
"@type": "sentimentPlugin",
"extra_params": { "extra_params": {
"@id": "extra_params_rand_0.1", "@id": "extra_params_rand_0.1",
"language": { "language": {
@@ -185,24 +190,3 @@ NIF API
Return the information about the default plugin. Return the information about the default plugin.
.. http:get:: /api/plugins/<pluginname>/{de}activate
{De}activate a plugin.
**Example request**:
.. sourcecode:: http
GET /api/plugins/rand/deactivate HTTP/1.1
Host: localhost
Accept: application/json, text/javascript
**Example response**:
.. sourcecode:: http
{
"@context": {},
"message": "Ok"
}

116
docs/conversion.rst Normal file
View File

@@ -0,0 +1,116 @@
Introduction
------------
Senpy includes experimental support for emotion/sentiment conversion plugins.
Use
---
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):
.. code:: json
... rest of the document ...
{
"@type": "emotionSet",
"onyx:hasEmotion": {
"@type": "emotion",
"onyx:hasEmotionCategory": "emoml:big6anger"
},
"prov:wasGeneratedBy": "plugins/emoRand_0.1"
}
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`_
This call, provided there is a valid conversion plugin from Ekman's to VAD, would return something like this:
.. code:: json
... rest of the document ...
{
"@type": "emotionSet",
"onyx:hasEmotion": {
"@type": "emotion",
"onyx:hasEmotionCategory": "emoml:big6anger"
},
"prov:wasGeneratedBy": "plugins/emoRand_0.1"
}, {
"@type": "emotionSet",
"onyx:hasEmotion": {
"@type": "emotion",
"A": 7.22,
"D": 6.28,
"V": 8.6
},
"prov:wasGeneratedBy": "plugins/Ekman2VAD_0.1"
}
That is called a *full* response, as it simply adds the converted emotion alongside.
It is also possible to get the original emotion nested within the new converted emotion, using the `conversion=nested` parameter:
.. code:: json
... rest of the document ...
{
"@type": "emotionSet",
"onyx:hasEmotion": {
"@type": "emotion",
"onyx:hasEmotionCategory": "emoml:big6anger"
},
"prov:wasGeneratedBy": "plugins/emoRand_0.1"
"onyx:wasDerivedFrom": {
"@type": "emotionSet",
"onyx:hasEmotion": {
"@type": "emotion",
"A": 7.22,
"D": 6.28,
"V": 8.6
},
"prov:wasGeneratedBy": "plugins/Ekman2VAD_0.1"
}
}
Lastly, `conversion=filtered` would only return the converted emotions.
Developing a conversion 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.
For instance, an emotion conversion plugin needs the following:
.. code:: yaml
---
onyx:doesConversion:
- onyx:conversionFrom: emoml:big6
onyx:conversionTo: emoml:fsre-dimensions
- onyx:conversionFrom: emoml:fsre-dimensions
onyx:conversionTo: emoml:big6
.. code:: python
class MyConversion(EmotionConversionPlugin):
def convert(self, emotionSet, fromModel, toModel, params):
pass

View File

@@ -1,5 +1,7 @@
Developing new plugins Developing new plugins
---------------------- ----------------------
This document describes how to develop a new analysis plugin. For an example of conversion plugins, see :doc:`conversion`.
Each plugin represents a different analysis process.There are two types of files that are needed by senpy for loading a plugin: Each plugin represents a different analysis process.There are two types of files that are needed by senpy for loading a plugin:
- Definition file, has the ".senpy" extension. - Definition file, has the ".senpy" extension.

View File

@@ -48,8 +48,8 @@ Once the server is launched, there is a basic endpoint in the server, which prov
In case you want to know the different endpoints of the server, there is more information available in the NIF API section_. In case you want to know the different endpoints of the server, there is more information available in the NIF API section_.
Video example CLI
============= ===
This video shows how to use senpy through command-line tool. This video shows how to use senpy through command-line tool.
@@ -58,16 +58,17 @@ https://asciinema.org/a/9uwef1ghkjk062cw2t4mhzpyk
Request example in python Request example in python
========================= =========================
This example shows how to make a request to a plugin. This example shows how to make a request to the default plugin:
.. code:: python .. code:: python
import requests from senpy.client import Client
import json
r = requests.get('http://127.0.0.1:5000/api/?algo=rand&i=Testing') c = Client('http://127.0.0.1:5000/api/')
response = r.content.decode('utf-8') r = c.analyse('hello world')
response_json = json.loads(response)
for entry in r.entries:
print('{} -> {}'.format(entry.text, entry.emotions))

View File

@@ -18,7 +18,6 @@ class Client(object):
try: try:
resp = models.from_dict(response.json()) resp = models.from_dict(response.json())
resp.validate(resp) resp.validate(resp)
return resp
except Exception as ex: except Exception as ex:
logger.error(('There seems to be a problem with the response:\n' logger.error(('There seems to be a problem with the response:\n'
'\tURL: {url}\n' '\tURL: {url}\n'
@@ -33,3 +32,6 @@ class Client(object):
code=response.status_code, code=response.status_code,
content=response.content)) content=response.content))
raise ex raise ex
if isinstance(resp, models.Error):
raise resp
return resp

View File

@@ -253,7 +253,7 @@ class Senpy(object):
logger.error(msg) logger.error(msg)
raise Error(msg) raise Error(msg)
if sync: if sync or 'async' in plugin and not plugin.async:
act() act()
else: else:
th = Thread(target=act) th = Thread(target=act)
@@ -277,7 +277,7 @@ class Senpy(object):
"Error deactivating plugin {}: {}".format(plugin.name, ex)) "Error deactivating plugin {}: {}".format(plugin.name, ex))
logger.error("Trace: {}".format(traceback.format_exc())) logger.error("Trace: {}".format(traceback.format_exc()))
if sync: if sync or 'async' in plugin and not plugin.async:
deact() deact()
else: else:
th = Thread(target=deact) th = Thread(target=deact)

View File

@@ -3,6 +3,7 @@ standard_library.install_aliases()
import inspect import inspect
import os.path import os.path
import os
import pickle import pickle
import logging import logging
import tempfile import tempfile
@@ -92,8 +93,8 @@ class ShelfMixin(object):
@property @property
def shelf_file(self): def shelf_file(self):
if 'shelf_file' not in self or not self['shelf_file']: if 'shelf_file' not in self or not self['shelf_file']:
self.shelf_file = os.path.join(tempfile.gettempdir(), sd = os.environ.get('SENPY_DATA', tempfile.gettempdir())
self.name + '.p') self.shelf_file = os.path.join(sd, self.name + '.p')
return self['shelf_file'] return self['shelf_file']
def save(self): def save(self):

View File

@@ -34,8 +34,11 @@ class ModelsTest(TestCase):
url=endpoint + '/', method='GET', params={'input': 'hello'}) url=endpoint + '/', method='GET', params={'input': 'hello'})
error = Call(Error('Nothing')) error = Call(Error('Nothing'))
with patch('requests.request', return_value=error) as patched: with patch('requests.request', return_value=error) as patched:
resp = client.analyse(input='hello', algorithm='NONEXISTENT') try:
assert isinstance(resp, Error) client.analyse(input='hello', algorithm='NONEXISTENT')
raise Exception('Exceptions should be raised. This is not golang')
except Error:
pass
patched.assert_called_with( patched.assert_called_with(
url=endpoint + '/', url=endpoint + '/',
method='GET', method='GET',