1
0
mirror of https://github.com/gsi-upm/senpy synced 2025-09-18 20:42:22 +00:00

Compare commits

..

1 Commits
0.4.0 ... yapsy

Author SHA1 Message Date
J. Fernando Sánchez
dabf444607 Switched to Yapsy 2014-11-26 22:09:02 +01:00
26 changed files with 370 additions and 966 deletions

View File

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

20
app.py
View File

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

View File

@@ -1,31 +0,0 @@
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

View File

@@ -1,18 +0,0 @@
{
"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

@@ -6,6 +6,19 @@ from senpy.models import Response, Opinion, Entry
class Sentiment140Plugin(SentimentPlugin): class Sentiment140Plugin(SentimentPlugin):
EXTRA_PARAMS = {
"language": {"aliases": ["language", "l"],
"required": False,
"options": ["es", "en", "auto"],
}
}
def __init__(self, **kwargs):
super(Sentiment140Plugin, self).__init__(name="sentiment140",
version="2.0",
extraparams=self.EXTRA_PARAMS,
**kwargs)
def analyse(self, **params): def analyse(self, **params):
lang = params.get("language", "auto") lang = params.get("language", "auto")
res = requests.post("http://www.sentiment140.com/api/bulkClassifyJson", res = requests.post("http://www.sentiment140.com/api/bulkClassifyJson",
@@ -15,24 +28,18 @@ class Sentiment140Plugin(SentimentPlugin):
) )
) )
p = params.get("prefix", None) response = Response()
response = Response(prefix=p) polarity_value = int(res.json()["data"][0]["polarity"]) * 25
polarity_value = self.maxPolarityValue*int(res.json()["data"][0]
["polarity"]) * 0.25
polarity = "marl:Neutral" polarity = "marl:Neutral"
if polarity_value > 50: if polarity_value > 50:
polarity = "marl:Positive" polarity = "marl:Positive"
elif polarity_value < 50: elif polarity_value < 50:
polarity = "marl:Negative" polarity = "marl:Negative"
entry = Entry(id="Entry0", entry = Entry(text=params["input"])
text=params["input"], opinion = Opinion(polarity=polarity, polarity_value=polarity_value)
prefix=p)
opinion = Opinion(id="Opinion0",
prefix=p,
hasPolarity=polarity,
polarityValue=polarity_value)
opinion["prov:wasGeneratedBy"] = self.id
entry.opinions.append(opinion) entry.opinions.append(opinion)
entry.language = lang entry.language = lang
response.entries.append(entry) response.entries.append(entry)
return response return response
plugin = Sentiment140Plugin()

View File

@@ -1,18 +0,0 @@
{
"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

@@ -0,0 +1,45 @@
import requests
import json
from senpy.plugins import SentimentPlugin
from senpy.models import Response, Opinion, Entry
class Sentiment140Plugin(SentimentPlugin):
EXTRA_PARAMS = {
"language": {"aliases": ["language", "l"],
"required": False,
"options": ["es", "en", "auto"],
}
}
def __init__(self, **kwargs):
super(Sentiment140Plugin, self).__init__(name="sentiment140",
version="2.0",
extraparams=self.EXTRA_PARAMS,
**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()
polarity_value = int(res.json()["data"][0]["polarity"]) * 25
polarity = "marl:Neutral"
if polarity_value > 50:
polarity = "marl:Positive"
elif polarity_value < 50:
polarity = "marl:Negative"
entry = Entry(text=params["input"])
opinion = Opinion(polarity=polarity, polarity_value=polarity_value)
entry.opinions.append(opinion)
entry.language = lang
response.entries.append(entry)
return response
plugin = Sentiment140Plugin()

View File

@@ -0,0 +1,8 @@
[Core]
Name = Test plugin of Yapsy
Module = prueba
[Documentation]
Description = What my plugin broadly does
Author = My very own name
Version = 0.1
Website = My very own website

View File

@@ -3,5 +3,3 @@ gunicorn==19.0.0
requests==2.4.1 requests==2.4.1
GitPython==0.3.2.RC1 GitPython==0.3.2.RC1
Yapsy>=1.10.423 Yapsy>=1.10.423
gevent>=1.0.1
PyLD>=0.6.5

View File

@@ -21,3 +21,5 @@ Sentiment analysis server in Python
import extensions import extensions
import blueprints import blueprints
import plugins import plugins
__version__ = "0.2.8"

View File

@@ -1,75 +1,7 @@
#!/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 flask import Flask
from senpy.extensions import Senpy from extensions import Senpy
from gevent.wsgi import WSGIServer app = Flask(__name__)
from gevent.monkey import patch_all sp = Senpy()
import gevent sp.init_app(app)
import logging app.debug = True
import os app.run()
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__)
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

@@ -17,35 +17,19 @@
""" """
Blueprints for Senpy Blueprints for Senpy
""" """
from flask import Blueprint, request, current_app
from .models import Error, Response
import json import json
import logging import logging
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
from flask import Blueprint, request, jsonify, current_app
nif_blueprint = Blueprint("NIF Sentiment Analysis Server", __name__) nif_blueprint = Blueprint("NIF Sentiment Analysis Server", __name__)
BASIC_PARAMS = { BASIC_PARAMS = {
"algorithm": { "algorithm": {"aliases": ["algorithm", "a", "algo"],
"aliases": ["algorithm", "a", "algo"], "required": False,
"required": False, },
},
"inHeaders": {
"aliases": ["inHeaders", "headers"],
"required": True,
"default": "0"
}
}
LIST_PARAMS = {
"params": {
"aliases": ["params", "with_params"],
"required": False,
"default": "0"
},
} }
@@ -60,40 +44,34 @@ def get_params(req, params=BASIC_PARAMS):
outdict = {} outdict = {}
wrong_params = {} wrong_params = {}
for param, options in params.iteritems(): for param, options in params.iteritems():
if param[0] != "@": # Exclude json-ld properties for alias in options["aliases"]:
logger.debug("Param: %s - Options: %s", param, options) if alias in indict:
for alias in options["aliases"]: outdict[param] = indict[alias]
if alias in indict: if param not in outdict:
outdict[param] = indict[alias] if options.get("required", False):
if param not in outdict: wrong_params[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: else:
if "options" in params[param] and \ if "default" in options:
outdict[param] not in params[param]["options"]: outdict[param] = options["default"]
wrong_params[param] = params[param] else:
if "options" in params[param] and outdict[param] not in params[param]["options"]:
wrong_params[param] = params[param]
if wrong_params: if wrong_params:
message = Error({"status": 404, message = {"status": "failed",
"message": "Missing or invalid parameters", "message": "Missing or invalid parameters",
"parameters": outdict, "parameters": outdict,
"errors": {param: error for param, error in "errors": {param: error for param, error in wrong_params.iteritems()}
wrong_params.iteritems()} }
}) raise ValueError(json.dumps(message))
raise ValueError(message)
return outdict return outdict
def basic_analysis(params): def basic_analysis(params):
response = {"@context": response = {"@context": ["http://demos.gsi.dit.upm.es/eurosentiment/static/context.jsonld",
[("http://demos.gsi.dit.upm.es/" {
"eurosentiment/static/context.jsonld"), "@base": "{}#".format(request.url.encode('utf-8'))
{ }
"@base": "{}#".format(request.url.encode('utf-8')) ],
}
],
"analysis": [{"@type": "marl:SentimentAnalysis"}], "analysis": [{"@type": "marl:SentimentAnalysis"}],
"entries": [] "entries": []
} }
@@ -110,28 +88,21 @@ def basic_analysis(params):
@nif_blueprint.route('/', methods=['POST', 'GET']) @nif_blueprint.route('/', methods=['POST', 'GET'])
def home(): def home():
try: try:
params = get_params(request) algo = get_params(request).get("algorithm", None)
algo = params.get("algorithm", None)
specific_params = current_app.senpy.parameters(algo) specific_params = current_app.senpy.parameters(algo)
logger.debug( params = get_params(request, specific_params)
"Specific params: %s", json.dumps(specific_params, indent=4))
params.update(get_params(request, specific_params))
response = current_app.senpy.analyse(**params) response = current_app.senpy.analyse(**params)
in_headers = params["inHeaders"] != "0" return jsonify(response)
return response.flask(in_headers=in_headers)
except ValueError as ex: except ValueError as ex:
return ex.message.flask() return ex.message
except Exception as ex:
return jsonify(status="400", message=ex.message)
@nif_blueprint.route("/default") @nif_blueprint.route("/default")
def default(): def default():
# return current_app.senpy.default_plugin return current_app.senpy.default_plugin
plug = current_app.senpy.default_plugin #return plugins(action="list", plugin=current_app.senpy.default_algorithm)
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/', methods=['POST', 'GET'])
@@ -139,25 +110,23 @@ def default():
@nif_blueprint.route('/plugins/<plugin>/<action>', methods=['POST', 'GET']) @nif_blueprint.route('/plugins/<plugin>/<action>', methods=['POST', 'GET'])
def plugins(plugin=None, action="list"): def plugins(plugin=None, action="list"):
filt = {} filt = {}
sp = current_app.senpy
if plugin: if plugin:
filt["name"] = plugin filt["name"] = plugin
plugs = sp.filter_plugins(**filt) plugs = current_app.senpy.filter_plugins(**filt)
if plugin and not plugs: if plugin and not plugs:
return "Plugin not found", 400 return "Plugin not found", 400
if action == "list": if action == "list":
with_params = get_params(request, LIST_PARAMS)["params"] == "1" with_params = request.args.get("params", "") == "1"
in_headers = get_params(request, BASIC_PARAMS)["inHeaders"] != "0" dic = {plug: plugs[plug].jsonable(with_params) for plug in plugs}
if plugin: return jsonify(dic)
dic = plugs[plugin] if action == "disable":
else: current_app.senpy.deactivate_plugin(plugin)
dic = Response( return "Ok"
{plug: plugs[plug].jsonld(with_params) for plug in plugs}, elif action == "enable":
frame={}) current_app.senpy.activate_plugin(plugin)
return dic.flask(in_headers=in_headers) return "Ok"
method = "{}_plugin".format(action) elif action == "reload":
if(hasattr(sp, method)): current_app.senpy.reload_plugin(plugin)
getattr(sp, method)(plugin)
return "Ok" return "Ok"
else: else:
return "action '{}' not allowed".format(action), 400 return "action '{}' not allowed".format(action), 400

View File

@@ -8,35 +8,31 @@
"nif": "http://persistence.uni-leipzig.org/nlp2rdf/ontologies/nif-core#", "nif": "http://persistence.uni-leipzig.org/nlp2rdf/ontologies/nif-core#",
"onyx": "http://www.gsi.dit.upm.es/ontologies/onyx/ns#", "onyx": "http://www.gsi.dit.upm.es/ontologies/onyx/ns#",
"emotions": { "emotions": {
"@container": "@set", "@id": "onyx:hasEmotionSet",
"@id": "onyx:hasEmotionSet" "@type": "onyx:EmotionSet"
}, },
"opinions": { "opinions": {
"@container": "@set", "@container": "@list",
"@id": "marl:hasOpinion" "@id": "marl:hasOpinion",
"@type": "marl:Opinion"
}, },
"prov": "http://www.w3.org/ns/prov#", "prov": "http://www.w3.org/ns/prov#",
"rdfs": "http://www.w3.org/2000/01/rdf-schema#", "rdfs": "http://www.w3.org/2000/01/rdf-schema#",
"analysis": { "analysis": {
"@container": "@set",
"@id": "prov:wasInformedBy" "@id": "prov:wasInformedBy"
}, },
"entries": { "entries": {
"@container": "@set",
"@id": "prov:generated" "@id": "prov:generated"
}, },
"strings": { "strings": {
"@container": "@set", "@reverse": "nif:hasContext",
"@reverse": "nif:hasContext" "@type": "nif:String"
}, },
"date": "date":
{ {
"@id": "dc:date", "@id": "dc:date",
"@type": "xsd:dateTime" "@type": "xsd:dateTime"
}, },
"text": { "@id": "nif:isString" },
"wnaffect": "http://www.gsi.dit.upm.es/ontologies/wnaffect#", "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,26 +1,26 @@
""" """
""" """
from .plugins import SenpyPlugin, SentimentPlugin, EmotionPlugin
from .models import Error
from .blueprints import nif_blueprint
from git import Repo, InvalidGitRepositoryError
from functools import partial
import os import os
import fnmatch
import inspect
import sys import sys
import imp import imp
import logging import logging
import gevent
import json
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
from .plugins import SentimentPlugin, EmotionPlugin
from yapsy.PluginFileLocator import PluginFileLocator, PluginFileAnalyzerWithInfoFile
from yapsy.PluginManager import PluginManager
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
class Senpy(object): class Senpy(object):
""" Default Senpy extension for Flask """ """ Default Senpy extension for Flask """
def __init__(self, app=None, plugin_folder="plugins"): def __init__(self, app=None, plugin_folder="plugins"):
@@ -67,78 +67,40 @@ class Senpy(object):
if "algorithm" in params: if "algorithm" in params:
algo = params["algorithm"] algo = params["algorithm"]
elif self.plugins: elif self.plugins:
algo = self.default_plugin and self.default_plugin.name algo = self.default_plugin
if not algo:
return Error(status=404,
message=("No plugins found."
" Please install one.").format(algo))
if algo in self.plugins: if algo in self.plugins:
if self.plugins[algo].is_activated: if self.plugins[algo].is_activated:
plug = self.plugins[algo] plug = self.plugins[algo]
resp = plug.analyse(**params) resp = plug.analyse(**params)
resp.analysis.append(plug) resp.analysis.append(plug.jsonable())
logger.debug("Returning analysis result: {}".format(resp))
return resp return resp
else: logger.debug("Plugin not activated: {}".format(algo))
logger.debug("Plugin not activated: {}".format(algo))
return Error(status=400,
message=("The algorithm '{}'"
" is not activated yet").format(algo))
else: else:
logger.debug(("The algorithm '{}' is not valid\n" logger.debug("The algorithm '{}' is not valid\nValid algorithms: {}".format(algo, self.plugins.keys()))
"Valid algorithms: {}").format(algo, return {"status": 400, "message": "The algorithm '{}' is not valid".format(algo)}
self.plugins.keys()))
return Error(status=400, def activate_all(self):
message="The algorithm '{}' is not valid" for plug in self.plugins.values():
.format(algo)) plug.activate()
@property @property
def default_plugin(self): def default_plugin(self):
candidates = self.filter_plugins(is_activated=True) candidates = self.filter_plugins(is_activated=True)
if len(candidates) > 0: if len(candidates) > 0:
candidate = candidates.values()[0] candidate = candidates.keys()[0]
logger.debug("Default: {}".format(candidate)) logger.debug("Default: {}".format(candidate))
return candidate return candidate
else: else:
return None return None
def parameters(self, algo): def parameters(self, algo):
return getattr(self.plugins.get(algo) or self.default_plugin, return getattr(self.plugins.get(algo or self.default_plugin), "params", {})
"params",
{})
def activate_all(self, sync=False): def activate_plugin(self, plugin):
ps = [] self.plugins[plugin].activate()
for plug in self.plugins.keys():
ps.append(self.activate_plugin(plug, sync=sync))
return ps
def deactivate_all(self, sync=False): def deactivate_plugin(self, plugin):
ps = [] self.plugins[plugin].deactivate()
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): def reload_plugin(self, plugin):
logger.debug("Reloading {}".format(plugin)) logger.debug("Reloading {}".format(plugin))
@@ -148,49 +110,38 @@ class Senpy(object):
self.plugins[nplug.name] = nplug self.plugins[nplug.name] = nplug
@staticmethod @staticmethod
def _load_plugin(root, filename): def _load_plugin(plugin, search_folder, is_activated=True):
logger.debug("Loading plugin: {}".format(filename)) logger.debug("Loading plugins")
fpath = os.path.join(root, filename) sys.path.append(search_folder)
with open(fpath, 'r') as f: (fp, pathname, desc) = imp.find_module(plugin)
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: try:
tmp = imp.load_module(module, fp, pathname, desc) tmp = imp.load_module(plugin, fp, pathname, desc).plugin
sys.path.remove(root) sys.path.remove(search_folder)
candidate = None tmp.path = search_folder
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: try:
repo_path = root repo_path = os.path.join(search_folder, plugin)
module._repo = Repo(repo_path) tmp.repo = Repo(repo_path)
except InvalidGitRepositoryError: except InvalidGitRepositoryError:
module._repo = None tmp.repo = None
if not hasattr(tmp, "is_activated"):
tmp.is_activated = is_activated
tmp.module = plugin
except Exception as ex: except Exception as ex:
logger.debug("Exception importing {}: {}".format(filename, ex)) tmp = None
return None, None logger.debug("Exception importing {}: {}".format(plugin, ex))
return name, module return tmp
def _load_plugins(self): def _load_plugins(self):
plugins = {} plugins = {}
for search_folder in self._search_folders: for search_folder in self._search_folders:
for root, dirnames, filenames in os.walk(search_folder): for item in os.listdir(search_folder):
for filename in fnmatch.filter(filenames, '*.senpy'): if os.path.isdir(os.path.join(search_folder, item)) \
name, plugin = self._load_plugin(root, filename) and os.path.exists(os.path.join(search_folder,
item,
"__init__.py")):
plugin = self._load_plugin(item, search_folder)
if plugin: if plugin:
plugins[name] = plugin plugins[plugin.name] = plugin
self._outdated = False self._outdated = False
return plugins return plugins
@@ -198,12 +149,31 @@ class Senpy(object):
def teardown(self, exception): def teardown(self, exception):
pass pass
def enable_all(self):
for plugin in self.plugins:
self.activate_plugin(plugin)
@property
def manager(self):
ctx = stack.top
if ctx is not None:
if not hasattr(ctx, 'senpy_manager'):
logger.debug("Loading manager: %s", self._search_folders)
ctx.senpy_manager = PluginManager(plugin_info_ext="senpy")
ctx.senpy_manager.getPluginLocator().setPluginPlaces(self._search_folders)
ctx.senpy_manager.locatePlugins()
ctx.senpy_manager.loadPlugins()
self.activate_all()
return ctx.senpy_manager
@property @property
def plugins(self): def plugins(self):
""" Return the plugins registered for a given application. """ """ Return the plugins registered for a given application. """
if not hasattr(self, 'senpy_plugins') or self._outdated: ctx = stack.top
self.senpy_plugins = self._load_plugins() if ctx is not None:
return self.senpy_plugins if not hasattr(ctx, 'senpy_plugins') or self._outdated:
ctx.senpy_plugins = {p.name:p.plugin_object for p in self.manager.getAllPlugins()}
return ctx.senpy_plugins
def filter_plugins(self, **kwargs): def filter_plugins(self, **kwargs):
""" Filter plugins by different criteria """ """ Filter plugins by different criteria """

View File

@@ -1,249 +1,63 @@
import json import json
import os import os
from collections import defaultdict from collections import defaultdict
from pyld import jsonld
import logging
from flask import Response as FlaskResponse
class Leaf(dict): class Leaf(defaultdict):
_prefix = None def __init__(self, ofclass=list):
_frame = {} super(Leaf, self).__init__(ofclass)
_context = {}
def __init__(self, def __getattr__(self, name):
*args, return super(Leaf, self).__getitem__(name)
**kwargs):
id = kwargs.pop("id", None) def __setattr__(self, name, value):
context = kwargs.pop("context", self._context) self[name] = value
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
def __getattr__(self, key): def __delattr__(self, name):
try: return super(Leaf, self).__delitem__(name)
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:
return json.loads(f.read())
except IOError:
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): class Response(Leaf):
_context = Leaf.get_context("{}/context.jsonld".format( def __init__(self, context=None):
os.path.dirname(os.path.realpath(__file__)))) super(Response, self).__init__()
_frame = { self["analysis"] = []
"@context": _context, self["entries"] = []
"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: if context is None:
context = self._context context = "{}/context.jsonld".format(os.path.dirname(
self.context = context os.path.realpath(__file__)))
super(Response, self).__init__( if isinstance(context, dict):
*args, context=context, frame=frame, **kwargs) self["@context"] = context
if self._frame is not None and "entries" in self._frame: if isinstance(context, str) or isinstance(context, unicode):
self.analysis = [] try:
self.entries = [] with open(context) as f:
self["@context"] = json.loads(f.read())
def jsonld(self, frame=None, options=None, context=None, removeContext={}): except IOError:
return super(Response, self).jsonld(frame, self["@context"] = context
options,
context,
removeContext)
class Entry(Leaf): class Entry(Leaf):
_context = {
"@vocab": ("http://persistence.uni-leipzig.org/"
"nlp2rdf/ontologies/nif-core#")
}
def __init__(self, text=None, emotion_sets=None, opinions=None, **kwargs): def __init__(self, text=None, emotion_sets=None, opinions=None, **kwargs):
super(Entry, self).__init__(**kwargs) super(Entry, self).__init__(**kwargs)
if text: if text:
self.text = text self.text = text
self.emotionSets = emotion_sets if emotion_sets else [] if emotion_sets:
self.opinions = opinions if opinions else [] self.emotionSets = emotion_sets
if opinions:
self.opinions = opinions
class Opinion(Leaf): class Opinion(Leaf):
_context = { def __init__(self, polarity_value=None, polarity=None, **kwargs):
"@vocab": "http://www.gsi.dit.upm.es/ontologies/marl/ns#" super(Opinion, self).__init__(**kwargs)
} if polarity_value is not None:
self.polarity_value = polarity_value
def __init__(self, polarityValue=None, hasPolarity=None, *args, **kwargs): if polarity is not None:
super(Opinion, self).__init__(*args, self.polarity = polarity
**kwargs)
if polarityValue is not None:
self.hasPolarityValue = polarityValue
if hasPolarity is not None:
self.hasPolarity = hasPolarity
class EmotionSet(Leaf): class EmotionSet(Leaf):
_context = {} def __init__(self, emotions=None, **kwargs):
def __init__(self, emotions=None, *args, **kwargs):
if not emotions: if not emotions:
emotions = [] emotions = []
super(EmotionSet, self).__init__(context=EmotionSet._context, super(EmotionSet, self).__init__(**kwargs)
*args,
**kwargs)
self.emotions = emotions or [] 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)

View File

@@ -1,123 +1,100 @@
import logging import logging
import ConfigParser from yapsy.IPlugin import IPlugin
from .models import Response, Leaf
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
PARAMS = { PARAMS = {"input": {"aliases": ["i", "input"],
"input": { "required": True,
"@id": "input", "help": "Input text"
"aliases": ["i", "input"], },
"required": True, "informat": {"aliases": ["f", "informat"],
"help": "Input text" "required": False,
}, "default": "text",
"informat": { "options": ["turtle", "text"],
"@id": "informat", },
"aliases": ["f", "informat"], "intype": {"aliases": ["intype", "t"],
"required": False, "required": False,
"default": "text", "default": "direct",
"options": ["turtle", "text"], "options": ["direct", "url", "file"],
}, },
"intype": { "outformat": {"aliases": ["outformat", "o"],
"@id": "intype", "default": "json-ld",
"aliases": ["intype", "t"], "required": False,
"required": False, "options": ["json-ld"],
"default": "direct", },
"options": ["direct", "url", "file"], "language": {"aliases": ["language", "l"],
}, "required": False,
"outformat": { "options": ["es", "en"],
"@id": "outformat", },
"aliases": ["outformat", "o"], "urischeme": {"aliases": ["urischeme", "u"],
"default": "json-ld", "required": False,
"required": False, "default": "RFC5147String",
"options": ["json-ld"], "options": "RFC5147String"
}, },
"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): class SenpyPlugin(IPlugin):
_context = Leaf.get_context(Response._context) def __init__(self, name=None, version=None, extraparams=None, params=None):
_frame = {"@context": _context, logger.debug("Initialising {}".format(name))
"name": {}, self.name = name
"extra_params": {"@container": "@index"}, self.version = version
"@explicit": True, if params:
"version": {}, self.params = params
"repo": None, else:
"is_activated": {}, self.params = PARAMS.copy()
"params": None, if extraparams:
} self.params.update(extraparams)
self.extraparams = extraparams or {}
def __init__(self, info=None): self.is_activated = True
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): def analyse(self, *args, **kwargs):
logger.debug("Analysing with: {} {}".format(self.name, self.version)) logger.debug("Analysing with: {} {}".format(self.name, self.version))
pass pass
def activate(self): def jsonable(self, parameters=False):
pass resp = {
"@id": "{}_{}".format(self.name, self.version),
def deactivate(self): "is_activated": self.is_activated,
pass }
if hasattr(self, "repo") and self.repo:
def jsonld(self, parameters=False, *args, **kwargs): resp["repo"] = self.repo.remotes[0].url
nframe = kwargs.pop("frame", self._frame)
if parameters: if parameters:
nframe = nframe.copy() resp["parameters"] = self.params
nframe["params"] = {} elif self.extraparams:
return super(SenpyPlugin, self).jsonld(frame=nframe, *args, **kwargs) resp["extra_parameters"] = self.extraparams
return resp
@property
def id(self):
return "{}_{}".format(self.name, self.version)
class SentimentPlugin(SenpyPlugin): class SentimentPlugin(SenpyPlugin):
def __init__(self,
min_polarity_value=0,
max_polarity_value=1,
**kwargs):
super(SentimentPlugin, self).__init__(**kwargs)
self.minPolarityValue = min_polarity_value
self.maxPolarityValue = max_polarity_value
def __init__(self, info, *args, **kwargs): def jsonable(self, *args, **kwargs):
super(SentimentPlugin, self).__init__(info, *args, **kwargs) resp = super(SentimentPlugin, self).jsonable(*args, **kwargs)
self.minPolarityValue = float(info.get("minPolarityValue", 0)) resp["marl:maxPolarityValue"] = self.maxPolarityValue
self.maxPolarityValue = float(info.get("maxPolarityValue", 1)) resp["marl:minPolarityValue"] = self.minPolarityValue
return resp
class EmotionPlugin(SenpyPlugin): class EmotionPlugin(SenpyPlugin):
def __init__(self,
min_emotion_value=0,
max_emotion_value=1,
emotion_category=None,
**kwargs):
super(EmotionPlugin, self).__init__(**kwargs)
self.minEmotionValue = min_emotion_value
self.maxEmotionValue = max_emotion_value
self.emotionCategory = emotion_category
def __init__(self, info, *args, **kwargs): def jsonable(self, *args, **kwargs):
resp = super(EmotionPlugin, self).__init__(info, *args, **kwargs) resp = super(EmotionPlugin, self).jsonable(*args, **kwargs)
self.minEmotionValue = float(info.get("minEmotionValue", 0)) resp["onyx:minEmotionValue"] = self.minEmotionValue
self.maxEmotionValue = float(info.get("maxEmotionValue", 0)) resp["onyx:maxEmotionValue"] = self.maxEmotionValue
return resp

View File

@@ -8,7 +8,7 @@ install_reqs = parse_requirements("requirements.txt")
# e.g. ['django==1.5.1', 'mezzanine==1.4.6'] # e.g. ['django==1.5.1', 'mezzanine==1.4.6']
reqs = [str(ir.req) for ir in install_reqs] reqs = [str(ir.req) for ir in install_reqs]
VERSION = "0.4.0" VERSION = "0.2.8"
print(reqs) print(reqs)
@@ -22,11 +22,10 @@ extendable, so new algorithms and sources can be used.
''', ''',
author='J. Fernando Sanchez', author='J. Fernando Sanchez',
author_email='balkian@gmail.com', author_email='balkian@gmail.com',
url='https://github.com/gsi-upm/senpy', # use the URL to the github repo url='https://github.com/balkian/senpy', # use the URL to the github repo
download_url='https://github.com/gsi-upm/senpy/archive/{}.tar.gz' download_url='https://github.com/balkian/senpy/archive/{}.tar.gz'.format(VERSION),
.format(VERSION), keywords=['eurosentiment', 'sentiment', 'emotions', 'nif'], # arbitrary keywords
keywords=['eurosentiment', 'sentiment', 'emotions', 'nif'],
classifiers=[], classifiers=[],
install_requires=reqs, install_requires=reqs,
include_package_data=True, include_package_data = True,
) )

View File

@@ -0,0 +1 @@

View File

@@ -1,3 +1,4 @@
import os import os
import logging import logging
@@ -8,8 +9,6 @@ except ImportError:
from senpy.extensions import Senpy from senpy.extensions import Senpy
from flask import Flask from flask import Flask
from flask.ext.testing import TestCase from flask.ext.testing import TestCase
from gevent import sleep
from itertools import product
def check_dict(indic, template): def check_dict(indic, template):
@@ -17,98 +16,23 @@ def check_dict(indic, template):
class BlueprintsTest(TestCase): class BlueprintsTest(TestCase):
def create_app(self): def create_app(self):
self.app = Flask("test_extensions") self.app = Flask("test_extensions")
self.senpy = Senpy() self.senpy = Senpy()
self.senpy.init_app(self.app) self.senpy.init_app(self.app)
self.dir = os.path.join(os.path.dirname(__file__), "..") self.dir = os.path.join(os.path.dirname(__file__), "..")
self.senpy.add_folder(self.dir) self.senpy.add_folder(self.dir)
self.senpy.activate_plugin("Dummy", sync=True)
return self.app return self.app
def test_home(self): def test_home(self):
""" """ Calling with no arguments should ask the user for more arguments """
Calling with no arguments should ask the user for more arguments
"""
resp = self.client.get("/") resp = self.client.get("/")
self.assert404(resp) self.assert200(resp)
logging.debug(resp.json) logging.debug(resp.json)
assert resp.json["status"] == 404 assert resp.json["status"] == "failed"
atleast = { atleast = {
"status": 404, "status": "failed",
"message": "Missing or invalid parameters", "message": "Missing or invalid parameters",
} }
assert check_dict(resp.json, atleast) 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)

View File

@@ -1,40 +0,0 @@
{
"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

@@ -1,8 +1,6 @@
from senpy.plugins import SentimentPlugin from senpy.plugins import SenpyPlugin
from senpy.models import Response
class DummyPlugin(SenpyPlugin):
def __init__(self):
super(DummyPlugin, self).__init__("dummy")
class DummyPlugin(SentimentPlugin):
def analyse(self, *args, **kwargs):
return Response()

View File

@@ -1,7 +1,8 @@
{ [Core]
"name": "Dummy", Name = dummy
"module": "dummy", Module = dummy
"description": "I am dummy", [Documentation]
"author": "@balkian", Description = What my plugin broadly does
"version": "0.1" Author = My very own name
} Version = 0.1
Website = My very own website

View File

@@ -11,13 +11,11 @@ from flask.ext.testing import TestCase
class ExtensionsTest(TestCase): class ExtensionsTest(TestCase):
def create_app(self): def create_app(self):
self.app = Flask("test_extensions") self.app = Flask("test_extensions")
self.dir = os.path.join(os.path.dirname(__file__), "..") self.dir = os.path.join(os.path.dirname(__file__), "..")
self.senpy = Senpy(plugin_folder=self.dir) self.senpy = Senpy(plugin_folder=self.dir)
self.senpy.init_app(self.app) self.senpy.init_app(self.app)
self.senpy.activate_plugin("Dummy", sync=True)
return self.app return self.app
def test_init(self): def test_init(self):
@@ -32,55 +30,41 @@ class ExtensionsTest(TestCase):
# noinspection PyProtectedMember # noinspection PyProtectedMember
assert self.dir in self.senpy._search_folders assert self.dir in self.senpy._search_folders
print self.senpy.plugins print self.senpy.plugins
assert "Dummy" in self.senpy.plugins assert "dummy" in self.senpy.plugins
def test_enabling(self): def test_enabling(self):
""" Enabling a plugin """ """ Enabling a plugin """
self.senpy.activate_all(sync=True) self.senpy.activate_plugin("dummy")
assert len(self.senpy.plugins) == 2 assert self.senpy.plugins["dummy"].is_activated
assert self.senpy.plugins["Sleep"].is_activated
def test_disabling(self): def test_disabling(self):
""" Disabling a plugin """ """ Disabling a plugin """
self.senpy.deactivate_all(sync=True) self.senpy.activate_plugin("dummy")
assert not self.senpy.plugins["Dummy"].is_activated self.senpy.deactivate_plugin("dummy")
assert not self.senpy.plugins["Sleep"].is_activated assert not self.senpy.plugins["dummy"].is_activated
def test_default(self): def test_default(self):
""" Default plugin should be set """ """ Default plugin should be set """
assert self.senpy.default_plugin assert self.senpy.default_plugin
assert self.senpy.default_plugin.name == "Dummy" assert self.senpy.default_plugin == "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): def test_analyse(self):
""" Using a plugin """ """ Using a plugin """
# I was using mock until plugin started inheriting with mock.patch.object(self.senpy.plugins["dummy"], "analyse") as mocked:
# Leaf (defaultdict with __setattr__ and __getattr__. self.senpy.analyse(algorithm="dummy", input="tupni", output="tuptuo")
r1 = self.senpy.analyse( self.senpy.analyse(input="tupni", output="tuptuo")
algorithm="Dummy", input="tupni", output="tuptuo") mocked.assert_any_call(input="tupni", output="tuptuo", algorithm="dummy")
r2 = self.senpy.analyse(input="tupni", output="tuptuo") mocked.assert_any_call(input="tupni", output="tuptuo")
assert r1.analysis[0].id[:5] == "Dummy"
assert r2.analysis[0].id[:5] == "Dummy"
for plug in self.senpy.plugins: for plug in self.senpy.plugins:
self.senpy.deactivate_plugin(plug, sync=True) self.senpy.deactivate_plugin(plug)
resp = self.senpy.analyse(input="tupni") resp = self.senpy.analyse(input="tupni")
logging.debug("Response: {}".format(resp)) logging.debug("Response: {}".format(resp))
assert resp["status"] == 404 assert resp["status"] == 400
def test_filtering(self): def test_filtering(self):
""" Filtering plugins """ """ Filtering plugins """
assert len(self.senpy.filter_plugins(name="Dummy")) > 0 assert len(self.senpy.filter_plugins(name="dummy")) > 0
assert not len(self.senpy.filter_plugins(name="notdummy")) assert not len(self.senpy.filter_plugins(name="notdummy"))
assert self.senpy.filter_plugins(name="Dummy", is_activated=True) assert self.senpy.filter_plugins(name="dummy", is_activated=True)
self.senpy.deactivate_plugin("Dummy", sync=True) self.senpy.deactivate_plugin("dummy")
assert not len( assert not len(self.senpy.filter_plugins(name="dummy", is_activated=True))
self.senpy.filter_plugins(name="Dummy", is_activated=True))

View File

@@ -1,81 +0,0 @@
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

@@ -1,16 +0,0 @@
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

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