1
0
mirror of https://github.com/gsi-upm/senpy synced 2025-09-17 12:02:21 +00:00

Compare commits

..

7 Commits
0.8.1 ... 0.8.3

Author SHA1 Message Date
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
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
13 changed files with 200 additions and 92 deletions

View File

@@ -6,7 +6,7 @@ RUN mkdir /cache/ /senpy-plugins /data/
VOLUME /data/
ENV PIP_CACHE_DIR=/cache/
ENV PIP_CACHE_DIR=/cache/ SENPY_DATA=/data
ONBUILD COPY . /senpy-plugins/
ONBUILD RUN python -m senpy --only-install -f /senpy-plugins

View File

@@ -95,7 +95,7 @@ run: run-$(PYMAIN)
push-latest: build-$(PYMAIN)
docker tag '$(IMAGEWTAG)-python$(PYMAIN)' '$(IMAGEWTAG)'
docker tag '$(IMAGEWTAG)-python$(PYMAIN)' '$(IMAGENAME)'
docker push '$(IMAGENAME)'
docker push '$(IMAGENAME):latest'
docker push '$(IMAGEWTAG)'
push-%: build-%

View File

@@ -62,6 +62,7 @@ NIF API
:query o outformat: one of `turtle` (default), `text`, `json-ld`
: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 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
:mailheader:`Accept` header
@@ -69,6 +70,7 @@ NIF API
header of request
:statuscode 200: no error
:statuscode 404: service not found
:statuscode 400: error while processing the request
.. http:post:: /api
@@ -94,7 +96,9 @@ NIF API
"@context": {
...
},
"sentiment140": {
"@type": "plugins",
"plugins": [
{
"name": "sentiment140",
"is_activated": true,
"version": "0.1",
@@ -115,8 +119,7 @@ NIF API
}
},
"@id": "sentiment140_0.1"
},
"rand": {
}, {
"name": "rand",
"is_activated": true,
"version": "0.1",
@@ -138,6 +141,7 @@ NIF API
},
"@id": "rand_0.1"
}
]
}
@@ -148,7 +152,7 @@ NIF API
.. sourcecode:: http
GET /api/plugins/rand HTTP/1.1
GET /api/plugins/rand/ HTTP/1.1
Host: localhost
Accept: application/json, text/javascript
@@ -159,6 +163,7 @@ NIF API
{
"@id": "rand_0.1",
"@type": "sentimentPlugin",
"extra_params": {
"@id": "extra_params_rand_0.1",
"language": {
@@ -185,24 +190,3 @@ NIF API
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 @@
Conversion
----------
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,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!
=================================
@@ -15,5 +10,6 @@ Contents:
api
schema
plugins
conversion
demo
:maxdepth: 2

View File

@@ -1,5 +1,7 @@
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:
- 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_.
Video example
=============
CLI
===
This video shows how to use senpy through command-line tool.
@@ -58,18 +58,23 @@ https://asciinema.org/a/9uwef1ghkjk062cw2t4mhzpyk
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
import requests
import json
from senpy.client import Client
r = requests.get('http://127.0.0.1:5000/api/?algo=rand&i=Testing')
response = r.content.decode('utf-8')
response_json = json.loads(response)
c = Client('http://127.0.0.1:5000/api/')
r = c.analyse('hello world')
for entry in r.entries:
print('{} -> {}'.format(entry.text, entry.emotions))
.. _section: http://senpy.readthedocs.org/en/latest/api.html
Conversion
==========
See :doc:`conversion`

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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