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

Compare commits

...

16 Commits
0.2.2 ... 0.4.0

Author SHA1 Message Date
J. Fernando Sánchez
d1006bbc92 PEP8+Better JSON-LD support
* The API has also changed, there are new parameters to send the
context as part of the headers.
* Improved tests
* PEP8 compliance (despite the line about gevent)
2015-02-24 07:15:25 +01:00
J. Fernando Sánchez
d58137e8f9 Changed to GSI-UPM 2015-02-23 20:22:58 +01:00
J. Fernando Sánchez
79c83e34a3 Added random plugin and other features 2015-02-23 02:13:31 +01:00
J. Fernando Sánchez
37a098109f Module script and improvement in JSON-LD 2014-12-02 13:31:15 +01:00
J. Fernando Sánchez
ff14925056 Improved plugins, better tests, gevent
Moved from Yapsy again (it is not flexible enough), now we use a
custom solution.
The activation and deactivation of plugins is asynchronous, so
that plugins that take a long time don't interfere with the rest.
2014-12-01 18:27:20 +01:00
J. Fernando Sánchez
10f4782ad7 Better NIF compliance 2014-12-01 09:38:23 +01:00
J. Fernando Sánchez
4351f76b60 Removed unnecessary contexts 2014-11-27 17:43:19 +01:00
J. Fernando Sánchez
86f45f8147 JSON-LD contexts and prefixes 2014-11-27 17:39:36 +01:00
J. Fernando Sánchez
2834967026 Better jsonld support 2014-11-27 11:27:05 +01:00
J. Fernando Sánchez
2f7a8d7267 Fixed setup.py and pip 2014-11-20 20:54:57 +01:00
J. Fernando Sánchez
2b68838514 PEP8 compliance 2014-11-20 19:29:49 +01:00
J. Fernando Sánchez
eaf65f0c6b First tests 2014-11-07 19:12:21 +01:00
J. Fernando Sánchez
a5e79bead3 Version 0.2.5 - Pypi 2014-11-05 19:21:17 +01:00
J. Fernando Sánchez
54492472ba Files for deployment 2014-11-05 19:17:27 +01:00
J. Fernando Sánchez
ff8d12074b Improved plugins (reload, imp) 2014-11-04 21:31:41 +01:00
J. Fernando Sánchez
bdf1992775 Fixed plugins 2014-10-17 19:14:49 +02:00
29 changed files with 1267 additions and 384 deletions

4
MANIFEST.in Normal file
View File

@@ -0,0 +1,4 @@
include requirements.txt
include README.md
include senpy/context.jsonld
recursive-include *.senpy

28
app.py
View File

@@ -14,20 +14,30 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
'''
Simple Sentiment Analysis server for EUROSENTIMENT
"""
This is a helper for development. If you want to run Senpy use:
This class shows how to use the nif_server module to create custom services.
'''
python -m senpy
"""
from gevent.monkey import patch_all patch_all()
import gevent
import config
from flask import Flask
from senpy import Senpy
from senpy.extensions import Senpy
import logging
import os
from gevent.wsgi import WSGIServer
logging.basicConfig(level=logging.DEBUG)
app = Flask(__name__)
sp = Senpy()
sp.init_app(app)
mypath = os.path.dirname(os.path.realpath(__file__))
sp = Senpy(app, os.path.join(mypath, "plugins"))
sp.activate_all()
if __name__ == '__main__':
import logging
logging.basicConfig(level=config.DEBUG)
app.debug = config.DEBUG
app.run()
http_server = WSGIServer(('', config.SERVER_PORT), app)
http_server.serve_forever()

1
dev-requirements.txt Normal file
View File

@@ -0,0 +1 @@
mock

31
plugins/rand/rand.py Normal file
View File

@@ -0,0 +1,31 @@
import json
import random
from senpy.plugins import SentimentPlugin
from senpy.models import Response, Opinion, Entry
class Sentiment140Plugin(SentimentPlugin):
def analyse(self, **params):
lang = params.get("language", "auto")
p = params.get("prefix", None)
response = Response(prefix=p)
polarity_value = max(-1, min(1, random.gauss(0.2, 0.2)))
polarity = "marl:Neutral"
if polarity_value > 0:
polarity = "marl:Positive"
elif polarity_value < 0:
polarity = "marl:Negative"
entry = Entry(id="Entry0",
text=params["input"],
prefix=p)
opinion = Opinion(id="Opinion0",
prefix=p,
hasPolarity=polarity,
polarityValue=polarity_value)
opinion["prov:wasGeneratedBy"] = self.id
entry.opinions.append(opinion)
entry.language = lang
response.entries.append(entry)
return response

18
plugins/rand/rand.senpy Normal file
View File

@@ -0,0 +1,18 @@
{
"name": "rand",
"module": "rand",
"description": "What my plugin broadly does",
"author": "@balkian",
"version": "0.1",
"extra_params": {
"language": {
"@id": "lang_rand",
"aliases": ["language", "l"],
"required": false,
"options": ["es", "en", "auto"]
}
},
"requirements": {},
"marl:maxPolarityValue": "1",
"marl:minPolarityValue": "-1"
}

View File

@@ -0,0 +1,38 @@
import requests
import json
from senpy.plugins import SentimentPlugin
from senpy.models import Response, Opinion, Entry
class Sentiment140Plugin(SentimentPlugin):
def analyse(self, **params):
lang = params.get("language", "auto")
res = requests.post("http://www.sentiment140.com/api/bulkClassifyJson",
json.dumps({"language": lang,
"data": [{"text": params["input"]}]
}
)
)
p = params.get("prefix", None)
response = Response(prefix=p)
polarity_value = self.maxPolarityValue*int(res.json()["data"][0]
["polarity"]) * 0.25
polarity = "marl:Neutral"
if polarity_value > 50:
polarity = "marl:Positive"
elif polarity_value < 50:
polarity = "marl:Negative"
entry = Entry(id="Entry0",
text=params["input"],
prefix=p)
opinion = Opinion(id="Opinion0",
prefix=p,
hasPolarity=polarity,
polarityValue=polarity_value)
opinion["prov:wasGeneratedBy"] = self.id
entry.opinions.append(opinion)
entry.language = lang
response.entries.append(entry)
return response

View File

@@ -0,0 +1,18 @@
{
"name": "sentiment140",
"module": "sentiment140",
"description": "What my plugin broadly does",
"author": "@balkian",
"version": "0.1",
"extra_params": {
"language": {
"@id": "lang_sentiment140",
"aliases": ["language", "l"],
"required": false,
"options": ["es", "en", "auto"]
}
},
"requirements": {},
"maxPolarityValue": "1",
"minPolarityValue": "0"
}

View File

@@ -1,5 +1,7 @@
Flask==0.10.1
gunicorn==19.0.0
requests==2.4.1
Flask-Plugins==1.4
GitPython==0.3.2.RC1
Yapsy>=1.10.423
gevent>=1.0.1
PyLD>=0.6.5

View File

@@ -14,19 +14,10 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
'''
"""
Sentiment analysis server in Python
'''
"""
import extensions
import blueprints
import plugins
if __name__ == '__main__':
from flask import Flask
app = Flask(__name__)
sp = extensions.Senpy()
sp.init_app(app)
app.debug = config.DEBUG
app.run()

View File

@@ -1,7 +1,75 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright 2014 J. Fernando Sánchez Rada - Grupo de Sistemas Inteligentes
# DIT, UPM
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Senpy is a modular sentiment analysis server. This script runs an instance of
the server.
"""
from flask import Flask
from extensions import Senpy
from senpy.extensions import Senpy
from gevent.wsgi import WSGIServer
from gevent.monkey import patch_all
import gevent
import logging
import os
import argparse
patch_all(thread=False)
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Run a Senpy server')
parser.add_argument('--level',
"-l",
metavar="logging_level",
type=str,
default="INFO",
help='Logging level')
parser.add_argument('--debug',
"-d",
action='store_true',
default=False,
help='Run the application in debug mode')
parser.add_argument('--host',
type=str,
default="127.0.0.1",
help='Use 0.0.0.0 to accept requests from any host.')
parser.add_argument('--port',
'-p',
type=int,
default=5000,
help='Port to listen on.')
parser.add_argument('--plugins-folder',
'-f',
type=str,
default="plugins",
help='Where to look for plugins.')
args = parser.parse_args()
logging.basicConfig(level=getattr(logging, args.level))
app = Flask(__name__)
sp = Senpy()
sp.init_app(app)
app.debug = True
app.run()
app.debug = args.debug
sp = Senpy(app, args.plugins_folder)
sp.activate_all()
import logging
http_server = WSGIServer((args.host, args.port), app)
try:
print "Server running on port %s:%d. Ctrl+C to quit" % (args.host,
args.port)
http_server.serve_forever()
except KeyboardInterrupt:
http_server.stop()
print "Bye!"

View File

@@ -14,52 +14,42 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
'''
Simple Sentiment Analysis server
'''
from flask import Blueprint, render_template, request, jsonify, current_app
"""
Blueprints for Senpy
"""
from flask import Blueprint, request, current_app
from .models import Error, Response
import json
import logging
logger = logging.getLogger(__name__)
nif_blueprint = Blueprint("NIF Sentiment Analysis Server", __name__)
PARAMS = {"input": {"aliases": ["i", "input"],
BASIC_PARAMS = {
"algorithm": {
"aliases": ["algorithm", "a", "algo"],
"required": False,
},
"inHeaders": {
"aliases": ["inHeaders", "headers"],
"required": True,
"help": "Input text"
},
"informat": {"aliases": ["f", "informat"],
"default": "0"
}
}
LIST_PARAMS = {
"params": {
"aliases": ["params", "with_params"],
"required": False,
"default": "text",
"options": ["turtle", "text"],
},
"intype": {"aliases": ["intype", "t"],
"required": False,
"default": "direct",
"options": ["direct", "url", "file"],
},
"outformat": {"aliases": ["outformat", "o"],
"default": "json-ld",
"required": False,
"options": ["json-ld"],
},
"algorithm": {"aliases": ["algorithm", "a", "algo"],
"required": False,
},
"language": {"aliases": ["language", "l"],
"required": False,
"options": ["es", "en"],
},
"urischeme": {"aliases": ["urischeme", "u"],
"required": False,
"default": "RFC5147String",
"options": "RFC5147String"
"default": "0"
},
}
def get_algorithm(req):
return get_params(req, params={"algorithm": PARAMS["algorithm"]})
def get_params(req, params=PARAMS):
indict = None
def get_params(req, params=BASIC_PARAMS):
if req.method == 'POST':
indict = req.form
elif req.method == 'GET':
@@ -68,37 +58,43 @@ def get_params(req, params=PARAMS):
raise ValueError("Invalid data")
outdict = {}
wrongParams = {}
wrong_params = {}
for param, options in params.iteritems():
if param[0] != "@": # Exclude json-ld properties
logger.debug("Param: %s - Options: %s", param, options)
for alias in options["aliases"]:
if alias in indict:
outdict[param] = indict[alias]
if param not in outdict:
if options.get("required", False):
wrongParams[param] = params[param]
if options.get("required", False) and "default" not in options:
wrong_params[param] = params[param]
else:
if "default" in options:
outdict[param] = options["default"]
else:
if "options" in params[param] and \
outdict[param] not in params[param]["options"]:
wrongParams[param] = params[param]
if wrongParams:
message = {"status": "failed", "message": "Missing or invalid parameters"}
message["parameters"] = outdict
message["errors"] = {param:error for param, error in wrongParams.iteritems()}
raise ValueError(json.dumps(message))
wrong_params[param] = params[param]
if wrong_params:
message = Error({"status": 404,
"message": "Missing or invalid parameters",
"parameters": outdict,
"errors": {param: error for param, error in
wrong_params.iteritems()}
})
raise ValueError(message)
return outdict
def basic_analysis(params):
response = {"@context": ["http://demos.gsi.dit.upm.es/eurosentiment/static/context.jsonld",
response = {"@context":
[("http://demos.gsi.dit.upm.es/"
"eurosentiment/static/context.jsonld"),
{
"@base": "{}#".format(request.url.encode('utf-8'))
}
],
"analysis": [{
"@type": "marl:SentimentAnalysis"
}],
"analysis": [{"@type": "marl:SentimentAnalysis"}],
"entries": []
}
if "language" in params:
@@ -110,42 +106,67 @@ def basic_analysis(params):
})
return response
@nif_blueprint.route('/', methods=['POST', 'GET'])
def home(entries=None):
def home():
try:
algo = get_algorithm(request)["algorithm"]
specific_params = PARAMS.copy()
specific_params.update(current_app.senpy.parameters(algo))
params = get_params(request, specific_params)
except ValueError as ex:
return ex.message
params = get_params(request)
algo = params.get("algorithm", None)
specific_params = current_app.senpy.parameters(algo)
logger.debug(
"Specific params: %s", json.dumps(specific_params, indent=4))
params.update(get_params(request, specific_params))
response = current_app.senpy.analyse(**params)
return jsonify(response)
in_headers = params["inHeaders"] != "0"
return response.flask(in_headers=in_headers)
except ValueError as ex:
return ex.message.flask()
@nif_blueprint.route("/default")
def default():
# return current_app.senpy.default_plugin
plug = current_app.senpy.default_plugin
if plug:
return plugins(action="list", plugin=plug.name)
else:
error = Error(status=404, message="No plugins found")
return error.flask()
@nif_blueprint.route('/plugins/', methods=['POST', 'GET'])
@nif_blueprint.route('/plugins/<plugin>', methods=['POST', 'GET'])
@nif_blueprint.route('/plugins/<plugin>/<action>', methods=['POST', 'GET'])
def plugins(plugin=None, action="list"):
print current_app.senpy.plugins.keys()
filt = {}
sp = current_app.senpy
if plugin:
plugs = {plugin:current_app.senpy.plugins[plugin]}
else:
plugs = current_app.senpy.plugins
filt["name"] = plugin
plugs = sp.filter_plugins(**filt)
if plugin and not plugs:
return "Plugin not found", 400
if action == "list":
dic = {plug:plugs[plug].jsonable(True) for plug in plugs}
return jsonify(dic)
elif action == "disable":
plugs[plugin].enabled = False
return "Ok"
elif action == "enable":
plugs[plugin].enabled = True
with_params = get_params(request, LIST_PARAMS)["params"] == "1"
in_headers = get_params(request, BASIC_PARAMS)["inHeaders"] != "0"
if plugin:
dic = plugs[plugin]
else:
dic = Response(
{plug: plugs[plug].jsonld(with_params) for plug in plugs},
frame={})
return dic.flask(in_headers=in_headers)
method = "{}_plugin".format(action)
if(hasattr(sp, method)):
getattr(sp, method)(plugin)
return "Ok"
else:
return "action '{}' not allowed".format(action), 404
return "action '{}' not allowed".format(action), 400
if __name__ == '__main__':
import config
from flask import Flask
app = Flask(__name__)
app.register_blueprint(nif_blueprint)
app.debug = config.DEBUG

View File

@@ -8,31 +8,35 @@
"nif": "http://persistence.uni-leipzig.org/nlp2rdf/ontologies/nif-core#",
"onyx": "http://www.gsi.dit.upm.es/ontologies/onyx/ns#",
"emotions": {
"@id": "onyx:hasEmotionSet",
"@type": "onyx:EmotionSet"
"@container": "@set",
"@id": "onyx:hasEmotionSet"
},
"opinions": {
"@container": "@list",
"@id": "marl:hasOpinion",
"@type": "marl:Opinion"
"@container": "@set",
"@id": "marl:hasOpinion"
},
"prov": "http://www.w3.org/ns/prov#",
"rdfs": "http://www.w3.org/2000/01/rdf-schema#",
"analysis": {
"@container": "@set",
"@id": "prov:wasInformedBy"
},
"entries": {
"@container": "@set",
"@id": "prov:generated"
},
"strings": {
"@reverse": "nif:hasContext",
"@type": "nif:String"
"@container": "@set",
"@reverse": "nif:hasContext"
},
"date":
{
"@id": "dc:date",
"@type": "xsd:dateTime"
},
"text": { "@id": "nif:isString" },
"wnaffect": "http://www.gsi.dit.upm.es/ontologies/wnaffect#",
"xsd": "http://www.w3.org/2001/XMLSchema#"
"xsd": "http://www.w3.org/2001/XMLSchema#",
"senpy": "http://www.gsi.dit.upm.es/ontologies/senpy/ns#",
"@vocab": "http://www.gsi.dit.upm.es/ontologies/senpy/ns#"
}

View File

@@ -1,37 +1,48 @@
import os
import sys
import importlib
"""
"""
from .plugins import SenpyPlugin, SentimentPlugin, EmotionPlugin
from .models import Error
from .blueprints import nif_blueprint
from flask import current_app
try:
from flask import _app_ctx_stack as stack
except ImportError:
from flask import _request_ctx_stack as stack
from blueprints import nif_blueprint
from git import Repo, InvalidGitRepositoryError
from functools import partial
import os
import fnmatch
import inspect
import sys
import imp
import logging
import gevent
import json
logger = logging.getLogger(__name__)
class Senpy(object):
""" Default Senpy extension for Flask """
def __init__(self, app=None, plugin_folder="plugins"):
self.app = app
base_folder = os.path.join(os.path.dirname(__file__), "plugins")
self.search_folders = (folder for folder in (base_folder, plugin_folder)
if folder and os.path.isdir(folder))
self._search_folders = set()
self._outdated = True
for folder in (base_folder, plugin_folder):
self.add_folder(folder)
if app is not None:
self.init_app(app)
def init_app(self, app):
""" Initialise a flask app to add plugins to its context """
"""
Note: I'm not particularly fond of adding self.app and app.senpy, but
I can't think of a better way to do it.
"""
def init_app(self, app, plugin_folder="plugins"):
app.senpy = self
#app.config.setdefault('SQLITE3_DATABASE', ':memory:')
# Use the newstyle teardown_appcontext if it's available,
# otherwise fall back to the request context
if hasattr(app, 'teardown_appcontext'):
@@ -40,75 +51,176 @@ class Senpy(object):
app.teardown_request(self.teardown)
app.register_blueprint(nif_blueprint)
def add_folder(self, folder):
logger.debug("Adding folder: %s", folder)
if os.path.isdir(folder):
self._search_folders.add(folder)
self._outdated = True
return True
else:
logger.debug("Not a folder: %s", folder)
return False
def analyse(self, **params):
algo = None
logger.debug("analysing with params: {}".format(params))
if "algorithm" in params:
algo = params["algorithm"]
if algo in self.plugins and self.plugins[algo].enabled:
elif self.plugins:
algo = self.default_plugin and self.default_plugin.name
if not algo:
return Error(status=404,
message=("No plugins found."
" Please install one.").format(algo))
if algo in self.plugins:
if self.plugins[algo].is_activated:
plug = self.plugins[algo]
resp = plug.analyse(**params)
resp.analysis.append(plug.jsonable())
resp.analysis.append(plug)
logger.debug("Returning analysis result: {}".format(resp))
return resp
return {"status": 500, "message": "No valid algorithm"}
else:
logger.debug("Plugin not activated: {}".format(algo))
return Error(status=400,
message=("The algorithm '{}'"
" is not activated yet").format(algo))
else:
logger.debug(("The algorithm '{}' is not valid\n"
"Valid algorithms: {}").format(algo,
self.plugins.keys()))
return Error(status=400,
message="The algorithm '{}' is not valid"
.format(algo))
@property
def default_plugin(self):
candidates = self.filter_plugins(is_activated=True)
if len(candidates) > 0:
candidate = candidates.values()[0]
logger.debug("Default: {}".format(candidate))
return candidate
else:
return None
def parameters(self, algo):
if algo in self.plugins:
if hasattr(self.plugins[algo], "parameters"):
return self.plugins[algo].parameters
return {}
return getattr(self.plugins.get(algo) or self.default_plugin,
"params",
{})
def _load_plugin(self, plugin, search_folder, enabled=True):
sys.path.append(search_folder)
tmp = importlib.import_module(plugin).plugin
sys.path.remove(search_folder)
tmp.path = search_folder
def activate_all(self, sync=False):
ps = []
for plug in self.plugins.keys():
ps.append(self.activate_plugin(plug, sync=sync))
return ps
def deactivate_all(self, sync=False):
ps = []
for plug in self.plugins.keys():
ps.append(self.deactivate_plugin(plug, sync=sync))
return ps
def _set_active_plugin(self, plugin_name, active=True, *args, **kwargs):
self.plugins[plugin_name].is_activated = active
def activate_plugin(self, plugin_name, sync=False):
plugin = self.plugins[plugin_name]
th = gevent.spawn(plugin.activate)
th.link_value(partial(self._set_active_plugin, plugin_name, True))
if sync:
th.join()
else:
return th
def deactivate_plugin(self, plugin_name, sync=False):
plugin = self.plugins[plugin_name]
th = gevent.spawn(plugin.deactivate)
th.link_value(partial(self._set_active_plugin, plugin_name, False))
if sync:
th.join()
else:
return th
def reload_plugin(self, plugin):
logger.debug("Reloading {}".format(plugin))
plug = self.plugins[plugin]
nplug = self._load_plugin(plug.module, plug.path)
del self.plugins[plugin]
self.plugins[nplug.name] = nplug
@staticmethod
def _load_plugin(root, filename):
logger.debug("Loading plugin: {}".format(filename))
fpath = os.path.join(root, filename)
with open(fpath, 'r') as f:
info = json.load(f)
logger.debug("Info: {}".format(info))
sys.path.append(root)
module = info["module"]
name = info["name"]
(fp, pathname, desc) = imp.find_module(module, [root, ])
try:
repo_path = os.path.join(search_folder, plugin)
tmp.repo = Repo(repo_path)
tmp = imp.load_module(module, fp, pathname, desc)
sys.path.remove(root)
candidate = None
for _, obj in inspect.getmembers(tmp):
if inspect.isclass(obj) and inspect.getmodule(obj) == tmp:
logger.debug(("Found plugin class:"
" {}@{}").format(obj, inspect.getmodule(obj))
)
candidate = obj
break
if not candidate:
logger.debug("No valid plugin for: {}".format(filename))
return
module = candidate(info=info)
try:
repo_path = root
module._repo = Repo(repo_path)
except InvalidGitRepositoryError:
tmp.repo = None
if not hasattr(tmp, "enabled"):
tmp.enabled = enabled
return tmp
module._repo = None
except Exception as ex:
logger.debug("Exception importing {}: {}".format(filename, ex))
return None, None
return name, module
def _load_plugins(self):
#print(sys.path)
#print(search_folder)
plugins = {}
for search_folder in self.search_folders:
for item in os.listdir(search_folder):
if os.path.isdir(os.path.join(search_folder, item)) \
and os.path.exists(
os.path.join(search_folder, item, "__init__.py")):
plugins[item] = self._load_plugin(item, search_folder)
for search_folder in self._search_folders:
for root, dirnames, filenames in os.walk(search_folder):
for filename in fnmatch.filter(filenames, '*.senpy'):
name, plugin = self._load_plugin(root, filename)
if plugin:
plugins[name] = plugin
self._outdated = False
return plugins
def teardown(self, exception):
pass
def enable_all(self):
for plugin in self.plugins:
self.enable_plugin(plugin)
def enable_plugin(self, item):
self.plugins[item].enabled = True
def disable_plugin(self, item):
self.plugins[item].enabled = False
@property
def plugins(self):
ctx = stack.top
if ctx is not None:
if not hasattr(self, '_plugins'):
self._plugins = self._load_plugins()
return self._plugins
""" Return the plugins registered for a given application. """
if not hasattr(self, 'senpy_plugins') or self._outdated:
self.senpy_plugins = self._load_plugins()
return self.senpy_plugins
if __name__ == '__main__':
from flask import Flask
app = Flask(__name__)
sp = Senpy()
sp.init_app(app)
with app.app_context():
sp._load_plugins()
def filter_plugins(self, **kwargs):
""" Filter plugins by different criteria """
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 not kwargs:
return self.plugins
else:
return {n: p for n, p in self.plugins.items() if matches(p)}
def sentiment_plugins(self):
""" Return only the sentiment plugins """
return {p: plugin for p, plugin in self.plugins.items() if
isinstance(plugin, SentimentPlugin)}

View File

@@ -1,59 +1,249 @@
import json
import os
from collections import defaultdict
from pyld import jsonld
import logging
from flask import Response as FlaskResponse
class Leaf(defaultdict):
def __init__(self, ofclass=list):
super(Leaf, self).__init__(ofclass)
def __getattr__(self, name):
return super(Leaf, self).__getitem__(name)
class Leaf(dict):
_prefix = None
_frame = {}
_context = {}
def __setattr__(self, name, value):
self[name] = value
def __init__(self,
*args,
**kwargs):
def __delattr__(self, name):
return super(Leaf, self).__delitem__(name)
id = kwargs.pop("id", None)
context = kwargs.pop("context", self._context)
vocab = kwargs.pop("vocab", None)
prefix = kwargs.pop("prefix", None)
frame = kwargs.pop("frame", None)
super(Leaf, self).__init__(*args, **kwargs)
if context is not None:
self.context = context
if frame is not None:
self._frame = frame
self._prefix = prefix
self.id = id
class Response(Leaf):
def __init__(self, context=None):
super(Response, self).__init__()
self["analysis"] = []
self["entries"] = []
if context is None:
context = "{}/context.jsonld".format(os.path.dirname(
os.path.realpath(__file__)))
if isinstance(context, dict):
self["@context"] = context
if isinstance(context, basestring):
def __getattr__(self, key):
try:
return object.__getattr__(self, key)
except AttributeError:
try:
return super(Leaf, self).__getitem__(self._get_key(key))
except KeyError:
raise AttributeError()
def __setattr__(self, key, value):
try:
object.__getattr__(self, key)
object.__setattr__(self, key, value)
except AttributeError:
key = self._get_key(key)
if key == "@context":
value = self.get_context(value)
elif key == "@id":
value = self.get_id(value)
if key[0] == "_":
object.__setattr__(self, key, value)
else:
if value is None:
try:
super(Leaf, self).__delitem__(key)
except KeyError:
pass
else:
super(Leaf, self).__setitem__(key, value)
def get_id(self, id):
"""
Get id, dealing with prefixes
"""
# This is not the most elegant solution to change the @id attribute,
# but it is the quickest way to have it included in the dictionary
# without extra boilerplate.
if id and self._prefix and ":" not in id:
return "{}{}".format(self._prefix, id)
else:
return id
def __delattr__(self, key):
return super(Leaf, self).__delitem__(self._get_key(key))
def _get_key(self, key):
if key[0] == "_":
return key
elif key in ["context", "id"]:
return "@{}".format(key)
else:
return key
@staticmethod
def get_context(context):
if isinstance(context, list):
contexts = []
for c in context:
contexts.append(Response.get_context(c))
return contexts
elif isinstance(context, dict):
return context
elif isinstance(context, basestring):
try:
with open(context) as f:
self["@context"] = json.loads(f.read())
return json.loads(f.read())
except IOError:
self["@context"] = context
return context
def compact(self):
return jsonld.compact(self, self.get_context(self.context))
def frame(self, frame=None, options=None):
if frame is None:
frame = self._frame
if options is None:
options = {}
return jsonld.frame(self, frame, options)
def jsonld(self, frame=None, options=None,
context=None, removeContext=None):
if removeContext is None:
removeContext = Response._context # Loop?
if frame is None:
frame = self._frame
if context is None:
context = self.context
else:
context = self.get_context(context)
# For some reason, this causes errors with pyld
# if options is None:
# options = {"expandContext": context.copy() }
js = self
if frame:
logging.debug("Framing: %s", json.dumps(self, indent=4))
logging.debug("Framing with %s", json.dumps(frame, indent=4))
js = jsonld.frame(js, frame, options)
logging.debug("Result: %s", json.dumps(js, indent=4))
logging.debug("Compacting with %s", json.dumps(context, indent=4))
js = jsonld.compact(js, context, options)
logging.debug("Result: %s", json.dumps(js, indent=4))
if removeContext == context:
del js["@context"]
return js
def to_JSON(self, removeContext=None):
return json.dumps(self.jsonld(removeContext=removeContext),
default=lambda o: o.__dict__,
sort_keys=True, indent=4)
def flask(self,
in_headers=False,
url="http://demos.gsi.dit.upm.es/senpy/senpy.jsonld"):
"""
Return the values and error to be used in flask
"""
js = self.jsonld()
headers = None
if in_headers:
ctx = js["@context"]
headers = {
"Link": ('<%s>;'
'rel="http://www.w3.org/ns/json-ld#context";'
' type="application/ld+json"' % url)
}
del js["@context"]
return FlaskResponse(json.dumps(js),
status=self.get("status", 200),
headers=headers,
mimetype="application/json")
class Response(Leaf):
_context = Leaf.get_context("{}/context.jsonld".format(
os.path.dirname(os.path.realpath(__file__))))
_frame = {
"@context": _context,
"analysis": {
"@explicit": True,
"maxPolarityValue": {},
"minPolarityValue": {},
"name": {},
"version": {},
},
"entries": {}
}
def __init__(self, *args, **kwargs):
context = kwargs.pop("context", None)
frame = kwargs.pop("frame", None)
if context is None:
context = self._context
self.context = context
super(Response, self).__init__(
*args, context=context, frame=frame, **kwargs)
if self._frame is not None and "entries" in self._frame:
self.analysis = []
self.entries = []
def jsonld(self, frame=None, options=None, context=None, removeContext={}):
return super(Response, self).jsonld(frame,
options,
context,
removeContext)
class Entry(Leaf):
def __init__(self, text=None, emotionSets=None, opinions=None, **kwargs):
_context = {
"@vocab": ("http://persistence.uni-leipzig.org/"
"nlp2rdf/ontologies/nif-core#")
}
def __init__(self, text=None, emotion_sets=None, opinions=None, **kwargs):
super(Entry, self).__init__(**kwargs)
if text:
self.text = text
if emotionSets:
self.emotionSets = emotionSets
if opinions:
self.opinions = opinions
self.emotionSets = emotion_sets if emotion_sets else []
self.opinions = opinions if opinions else []
class Opinion(Leaf):
def __init__(self, polarityValue=None, polarity=None, **kwargs):
super(Opinion, self).__init__(**kwargs)
if polarityValue is not None:
self.polarityValue = polarityValue
if polarity is not None:
self.polarity = polarity
_context = {
"@vocab": "http://www.gsi.dit.upm.es/ontologies/marl/ns#"
}
def __init__(self, polarityValue=None, hasPolarity=None, *args, **kwargs):
super(Opinion, self).__init__(*args,
**kwargs)
if polarityValue is not None:
self.hasPolarityValue = polarityValue
if hasPolarity is not None:
self.hasPolarity = hasPolarity
class EmotionSet(Leaf):
def __init__(self, emotions=[], **kwargs):
super(EmotionSet, self).__init__(**kwargs)
_context = {}
def __init__(self, emotions=None, *args, **kwargs):
if not emotions:
emotions = []
super(EmotionSet, self).__init__(context=EmotionSet._context,
*args,
**kwargs)
self.emotions = emotions or []
class Emotion(Leaf):
_context = {}
class Error(Response):
# A better pattern would be this:
# http://flask.pocoo.org/docs/0.10/patterns/apierrors/
_frame = {}
_context = {}
def __init__(self, *args, **kwargs):
super(Error, self).__init__(*args, **kwargs)

123
senpy/plugins.py Normal file
View File

@@ -0,0 +1,123 @@
import logging
import ConfigParser
from .models import Response, Leaf
logger = logging.getLogger(__name__)
PARAMS = {
"input": {
"@id": "input",
"aliases": ["i", "input"],
"required": True,
"help": "Input text"
},
"informat": {
"@id": "informat",
"aliases": ["f", "informat"],
"required": False,
"default": "text",
"options": ["turtle", "text"],
},
"intype": {
"@id": "intype",
"aliases": ["intype", "t"],
"required": False,
"default": "direct",
"options": ["direct", "url", "file"],
},
"outformat": {
"@id": "outformat",
"aliases": ["outformat", "o"],
"default": "json-ld",
"required": False,
"options": ["json-ld"],
},
"language": {
"@id": "language",
"aliases": ["language", "l"],
"required": False,
},
"prefix": {
"@id": "prefix",
"aliases": ["prefix", "p"],
"required": True,
"default": "",
},
"urischeme": {
"@id": "urischeme",
"aliases": ["urischeme", "u"],
"required": False,
"default": "RFC5147String",
"options": "RFC5147String"
},
}
class SenpyPlugin(Leaf):
_context = Leaf.get_context(Response._context)
_frame = {"@context": _context,
"name": {},
"extra_params": {"@container": "@index"},
"@explicit": True,
"version": {},
"repo": None,
"is_activated": {},
"params": None,
}
def __init__(self, info=None):
if not info:
raise ValueError(("You need to provide configuration"
"information for the plugin."))
logger.debug("Initialising {}".format(info))
super(SenpyPlugin, self).__init__()
self.name = info["name"]
self.version = info["version"]
self.id = "{}_{}".format(self.name, self.version)
self.params = info.get("params", PARAMS.copy())
if "@id" not in self.params:
self.params["@id"] = "params_%s" % self.id
self.extra_params = info.get("extra_params", {})
self.params.update(self.extra_params.copy())
if "@id" not in self.extra_params:
self.extra_params["@id"] = "extra_params_%s" % self.id
self.is_activated = False
self._info = info
def analyse(self, *args, **kwargs):
logger.debug("Analysing with: {} {}".format(self.name, self.version))
pass
def activate(self):
pass
def deactivate(self):
pass
def jsonld(self, parameters=False, *args, **kwargs):
nframe = kwargs.pop("frame", self._frame)
if parameters:
nframe = nframe.copy()
nframe["params"] = {}
return super(SenpyPlugin, self).jsonld(frame=nframe, *args, **kwargs)
@property
def id(self):
return "{}_{}".format(self.name, self.version)
class SentimentPlugin(SenpyPlugin):
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))
class EmotionPlugin(SenpyPlugin):
def __init__(self, info, *args, **kwargs):
resp = super(EmotionPlugin, self).__init__(info, *args, **kwargs)
self.minEmotionValue = float(info.get("minEmotionValue", 0))
self.maxEmotionValue = float(info.get("maxEmotionValue", 0))

View File

@@ -1,54 +0,0 @@
class SenpyPlugin(object):
def __init__(self, name=None, version=None, params=None):
self.name = name
self.version = version
self.params = params or []
def analyse(self, *args, **kwargs):
pass
def activate(self):
pass
def deactivate(self):
pass
def jsonable(self, parameters=False):
resp = {
"@id": "{}_{}".format(self.name, self.version),
}
if parameters:
resp["parameters"] = self.params,
return resp
class SentimentPlugin(SenpyPlugin):
def __init__(self,
minPolarityValue=0,
maxPolarityValue=1,
**kwargs):
super(SentimentPlugin, self).__init__(**kwargs)
self.minPolarityValue = minPolarityValue
self.maxPolarityValue = maxPolarityValue
def jsonable(self, *args, **kwargs):
resp = super(SentimentPlugin, self).jsonable(*args, **kwargs)
resp["marl:maxPolarityValue"] = self.maxPolarityValue
resp["marl:minPolarityValue"] = self.minPolarityValue
return resp
class EmotionPlugin(SenpyPlugin):
def __init__(self,
minEmotionValue=0,
maxEmotionValue=1,
emotionCategory=None,
**kwargs):
super(EmotionPlugin, self).__init__(**kwargs)
self.minEmotionValue = minEmotionValue
self.maxEmotionValue = maxEmotionValue
self.emotionCategory = emotionCategory
def jsonable(self, *args, **kwargs):
resp = super(EmotionPlugin, self).jsonable(*args, **kwargs)
resp["onyx:minEmotionValue"] = self.minEmotionValue
resp["onyx:maxEmotionValue"] = self.maxEmotionValue
return resp

View File

@@ -1,48 +0,0 @@
import requests
import json
import sys
print(sys.path)
from senpy.plugins import SentimentPlugin
from senpy.models import Response, Opinion, Entry
class Sentiment140Plugin(SentimentPlugin):
parameters = {
"language": {"aliases": ["language", "l"],
"required": False,
"options": ["es", "en", "auto"],
}
}
def __init__(self, **kwargs):
super(Sentiment140Plugin, self).__init__(name="Sentiment140",
version="1.0",
**kwargs)
def analyse(self, **params):
lang = params.get("language", "auto")
res = requests.post("http://www.sentiment140.com/api/bulkClassifyJson",
json.dumps({
"language": lang,
"data": [{"text": params["input"]}]}
))
response = Response()
polarityValue = int(res.json()["data"][0]["polarity"]) * 25
polarity = "marl:Neutral"
if polarityValue > 50:
polarity = "marl:Positive"
elif polarityValue < 50:
polarity = "marl:Negative"
entry = Entry(text=params["input"])
opinion = Opinion(polarity=polarity, polarityValue=polarityValue)
entry.opinions.append(opinion)
entry.language = lang
response.entries.append(entry)
return response
plugin = Sentiment140Plugin()

View File

@@ -1,41 +0,0 @@
'''
SENTIMENT140
=============
* http://www.sentiment140.com/api/bulkClassifyJson
* Method: POST
* Parameters: JSON Object (that is copied to the result)
* text
* query
* language
* topic
* Example response:
```json
{"data": [{"text": "I love Titanic.", "id":1234, "polarity": 4},
{"text": "I hate Titanic.", "id":4567, "polarity": 0}]}
```
'''
import requests
import json
ENDPOINT_URI = "http://www.sentiment140.com/api/bulkClassifyJson"
def analyse(texts):
parameters = {"data": []}
if isinstance(texts, list):
for text in texts:
parameters["data"].append({"text": text})
else:
parameters["data"].append({"text": texts})
res = requests.post(ENDPOINT_URI, json.dumps(parameters))
res.json()
return res.json()
def test():
print analyse("I love Titanic")
print analyse(["I love Titanic", "I hate Titanic"])
if __name__ == "__main__":
test()

View File

@@ -1,17 +1,32 @@
from setuptools import setup
from pip.req import parse_requirements
# parse_requirements() returns generator of pip.req.InstallRequirement objects
install_reqs = parse_requirements("requirements.txt")
# reqs is a list of requirement
# e.g. ['django==1.5.1', 'mezzanine==1.4.6']
reqs = [str(ir.req) for ir in install_reqs]
VERSION = "0.4.0"
print(reqs)
setup(
name='senpy',
packages=['senpy'], # this must be the same as the name above
version = '0.2.2',
version=VERSION,
description='''
A sentiment analysis server implementation. Designed to be \
extendable, so new algorithms and sources can be used.
''',
author='J. Fernando Sanchez',
author_email='balkian@gmail.com',
url = 'https://github.com/balkian/senpy', # use the URL to the github repo
download_url = 'https://github.com/balkian/senpy/archive/0.2.2.tar.gz',
keywords = ['eurosentiment', 'sentiment', 'emotions', 'nif'], # arbitrary keywords
url='https://github.com/gsi-upm/senpy', # use the URL to the github repo
download_url='https://github.com/gsi-upm/senpy/archive/{}.tar.gz'
.format(VERSION),
keywords=['eurosentiment', 'sentiment', 'emotions', 'nif'],
classifiers=[],
install_requires=reqs,
include_package_data=True,
)

20
supervisord.conf Normal file
View File

@@ -0,0 +1,20 @@
[unix_http_server]
file=/tmp/senpy.sock ; path to your socket file
[supervisord]
logfile = %(here)s/logs/supervisor.log
childlogdir = %(here)s/logs/
[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
[supervisorctl]
logfile = %(here)s/logs/supervisorctl.log
serverurl=unix:///tmp/senpy.sock ; use a unix:// URL for a unix socket
[program:senpy]
command = venv/bin/gunicorn -w 1 -b 0.0.0.0:6666 --log-file %(here)s/logs/gunicorn.log --access-logfile - app:app
directory = %(here)s
environment = PATH=%(here)s/venv/bin/
logfile = %(here)s/logs/senpy.log

0
tests/__init__.py Normal file
View File

View File

@@ -0,0 +1,114 @@
import os
import logging
try:
import unittest.mock as mock
except ImportError:
import mock
from senpy.extensions import Senpy
from flask import Flask
from flask.ext.testing import TestCase
from gevent import sleep
from itertools import product
def check_dict(indic, template):
return all(item in indic.items() for item in template.items())
class BlueprintsTest(TestCase):
def create_app(self):
self.app = Flask("test_extensions")
self.senpy = Senpy()
self.senpy.init_app(self.app)
self.dir = os.path.join(os.path.dirname(__file__), "..")
self.senpy.add_folder(self.dir)
self.senpy.activate_plugin("Dummy", sync=True)
return self.app
def test_home(self):
"""
Calling with no arguments should ask the user for more arguments
"""
resp = self.client.get("/")
self.assert404(resp)
logging.debug(resp.json)
assert resp.json["status"] == 404
atleast = {
"status": 404,
"message": "Missing or invalid parameters",
}
assert check_dict(resp.json, atleast)
def test_analysis(self):
"""
The dummy plugin returns an empty response,\
it should contain the context
"""
resp = self.client.get("/?i=My aloha mohame")
self.assert200(resp)
logging.debug("Got response: %s", resp.json)
assert "@context" in resp.json
assert check_dict(
resp.json["@context"],
{"marl": "http://www.gsi.dit.upm.es/ontologies/marl/ns#"})
assert "entries" in resp.json
def test_list(self):
""" List the plugins """
resp = self.client.get("/plugins/")
self.assert200(resp)
logging.debug(resp.json)
assert "Dummy" in resp.json
assert "@context" in resp.json
def test_headers(self):
for i, j in product(["/plugins/?nothing=", "/?i=test&"],
["headers", "inHeaders"]):
resp = self.client.get("%s" % (i))
assert "@context" in resp.json
resp = self.client.get("%s&%s=0" % (i, j))
assert "@context" in resp.json
resp = self.client.get("%s&%s=1" % (i, j))
assert "@context" not in resp.json
resp = self.client.get("%s&%s=true" % (i, j))
assert "@context" not in resp.json
def test_detail(self):
""" Show only one plugin"""
resp = self.client.get("/plugins/Dummy")
self.assert200(resp)
logging.debug(resp.json)
assert "@id" in resp.json
assert resp.json["@id"] == "Dummy_0.1"
def test_activate(self):
""" Activate and deactivate one plugin """
resp = self.client.get("/plugins/Dummy/deactivate")
self.assert200(resp)
sleep(0.5)
resp = self.client.get("/plugins/Dummy")
self.assert200(resp)
assert "is_activated" in resp.json
assert resp.json["is_activated"] == False
resp = self.client.get("/plugins/Dummy/activate")
self.assert200(resp)
sleep(0.5)
resp = self.client.get("/plugins/Dummy")
self.assert200(resp)
assert "is_activated" in resp.json
assert resp.json["is_activated"] == True
def test_default(self):
""" Show only one plugin"""
resp = self.client.get("/default")
self.assert200(resp)
logging.debug(resp.json)
assert "@id" in resp.json
assert resp.json["@id"] == "Dummy_0.1"
resp = self.client.get("/plugins/Dummy/deactivate")
self.assert200(resp)
sleep(0.5)
resp = self.client.get("/default")
self.assert404(resp)

40
tests/context.jsonld Normal file
View File

@@ -0,0 +1,40 @@
{
"dc": "http://purl.org/dc/terms/",
"dc:subject": {
"@type": "@id"
},
"xsd": "http://www.w3.org/2001/XMLSchema#",
"marl": "http://www.gsi.dit.upm.es/ontologies/marl/ns#",
"nif": "http://persistence.uni-leipzig.org/nlp2rdf/ontologies/nif-core#",
"onyx": "http://www.gsi.dit.upm.es/ontologies/onyx/ns#",
"emotions": {
"@container": "@set",
"@id": "onyx:hasEmotionSet"
},
"opinions": {
"@container": "@set",
"@id": "marl:hasOpinion"
},
"prov": "http://www.w3.org/ns/prov#",
"rdfs": "http://www.w3.org/2000/01/rdf-schema#",
"analysis": {
"@container": "@set",
"@id": "prov:wasInformedBy"
},
"entries": {
"@container": "@set",
"@id": "prov:generated"
},
"strings": {
"@container": "@set",
"@reverse": "nif:hasContext"
},
"date":
{
"@id": "dc:date",
"@type": "xsd:dateTime"
},
"text": { "@id": "nif:isString" },
"wnaffect": "http://www.gsi.dit.upm.es/ontologies/wnaffect#",
"xsd": "http://www.w3.org/2001/XMLSchema#"
}

View File

@@ -0,0 +1,8 @@
from senpy.plugins import SentimentPlugin
from senpy.models import Response
class DummyPlugin(SentimentPlugin):
def analyse(self, *args, **kwargs):
return Response()

View File

@@ -0,0 +1,7 @@
{
"name": "Dummy",
"module": "dummy",
"description": "I am dummy",
"author": "@balkian",
"version": "0.1"
}

View File

@@ -0,0 +1,86 @@
import os
import logging
try:
import unittest.mock as mock
except ImportError:
import mock
from senpy.extensions import Senpy
from flask import Flask
from flask.ext.testing import TestCase
class ExtensionsTest(TestCase):
def create_app(self):
self.app = Flask("test_extensions")
self.dir = os.path.join(os.path.dirname(__file__), "..")
self.senpy = Senpy(plugin_folder=self.dir)
self.senpy.init_app(self.app)
self.senpy.activate_plugin("Dummy", sync=True)
return self.app
def test_init(self):
""" Initialising the app with the extension. """
assert hasattr(self.app, "senpy")
tapp = Flask("temp app")
self.senpy.init_app(tapp)
assert hasattr(tapp, "senpy")
def test_discovery(self):
""" Discovery of plugins in given folders. """
# noinspection PyProtectedMember
assert self.dir in self.senpy._search_folders
print self.senpy.plugins
assert "Dummy" in self.senpy.plugins
def test_enabling(self):
""" Enabling a plugin """
self.senpy.activate_all(sync=True)
assert len(self.senpy.plugins) == 2
assert self.senpy.plugins["Sleep"].is_activated
def test_disabling(self):
""" Disabling a plugin """
self.senpy.deactivate_all(sync=True)
assert not self.senpy.plugins["Dummy"].is_activated
assert not self.senpy.plugins["Sleep"].is_activated
def test_default(self):
""" Default plugin should be set """
assert self.senpy.default_plugin
assert self.senpy.default_plugin.name == "Dummy"
self.senpy.deactivate_all(sync=True)
logging.debug("Default: {}".format(self.senpy.default_plugin))
assert self.senpy.default_plugin is None
def test_noplugin(self):
""" Don't analyse if there isn't any plugin installed """
self.senpy.deactivate_all(sync=True)
resp = self.senpy.analyse(input="tupni")
logging.debug("Response: {}".format(resp))
assert resp["status"] == 404
def test_analyse(self):
""" Using a plugin """
# I was using mock until plugin started inheriting
# Leaf (defaultdict with __setattr__ and __getattr__.
r1 = self.senpy.analyse(
algorithm="Dummy", input="tupni", output="tuptuo")
r2 = self.senpy.analyse(input="tupni", output="tuptuo")
assert r1.analysis[0].id[:5] == "Dummy"
assert r2.analysis[0].id[:5] == "Dummy"
for plug in self.senpy.plugins:
self.senpy.deactivate_plugin(plug, sync=True)
resp = self.senpy.analyse(input="tupni")
logging.debug("Response: {}".format(resp))
assert resp["status"] == 404
def test_filtering(self):
""" Filtering plugins """
assert len(self.senpy.filter_plugins(name="Dummy")) > 0
assert not len(self.senpy.filter_plugins(name="notdummy"))
assert self.senpy.filter_plugins(name="Dummy", is_activated=True)
self.senpy.deactivate_plugin("Dummy", sync=True)
assert not len(
self.senpy.filter_plugins(name="Dummy", is_activated=True))

View File

@@ -0,0 +1,81 @@
import os
import logging
try:
import unittest.mock as mock
except ImportError:
import mock
import json
import os
from unittest import TestCase
from senpy.models import Response, Entry
from senpy.plugins import SenpyPlugin
class ModelsTest(TestCase):
def test_response(self):
r = Response(context=os.path.normpath(
os.path.join(__file__, "..", "..", "context.jsonld")))
assert("@context" in r)
assert(r._frame)
logging.debug("Default frame: %s", r._frame)
assert("marl" in r.context)
assert("entries" in r.context)
r2 = Response(context=json.loads('{"test": "roger"}'))
assert("test" in r2.context)
r3 = Response(context=None)
del r3.context
assert("@context" not in r3)
assert("entries" in r3)
assert("analysis" in r3)
r4 = Response()
assert("@context" in r4)
assert("entries" in r4)
assert("analysis" in r4)
dummy = SenpyPlugin({"name": "dummy", "version": 0})
r5 = Response({"dummy": dummy}, context=None, frame=None)
logging.debug("Response 5: %s", r5)
assert("dummy" in r5)
assert(r5["dummy"].name == "dummy")
js = r5.jsonld(context={}, frame={})
logging.debug("jsonld 5: %s", js)
assert("dummy" in js)
assert(js["dummy"].name == "dummy")
r6 = Response()
r6.entries.append(Entry(text="Just testing"))
logging.debug("Reponse 6: %s", r6)
assert("@context" in r6)
assert("marl" in r6.context)
assert("entries" in r6.context)
js = r6.jsonld()
logging.debug("jsonld: %s", js)
assert("entries" in js)
assert("entries" in js)
assert("analysis" in js)
resp = r6.flask()
received = json.loads(resp.data)
logging.debug("Response: %s", js)
assert(received["entries"])
assert(received["entries"][0]["text"] == "Just testing")
assert(received["entries"][0]["text"] != "Not testing")
def test_opinions(self):
pass
def test_plugins(self):
p = SenpyPlugin({"name": "dummy", "version": 0})
c = p.jsonld()
assert "info" not in c
assert "repo" not in c
assert "params" not in c
logging.debug("Framed: %s", c)
assert "extra_params" in c
def test_frame_response(self):
pass

View File

@@ -0,0 +1,16 @@
from senpy.plugins import SenpyPlugin
from senpy.models import Response
from time import sleep
class SleepPlugin(SenpyPlugin):
def __init__(self, info, *args, **kwargs):
super(SleepPlugin, self).__init__(info, *args, **kwargs)
self.timeout = int(info["timeout"])
def activate(self, *args, **kwargs):
sleep(self.timeout)
def analyse(self, *args, **kwargs):
return Response()

View File

@@ -0,0 +1,8 @@
{
"name": "Sleep",
"module": "sleep",
"description": "I am dummy",
"author": "@balkian",
"version": "0.1",
"timeout": "2"
}