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

Compare commits

..

6 Commits
yapsy ... 0.3.1

Author SHA1 Message Date
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
22 changed files with 397 additions and 238 deletions

View File

@@ -1,3 +1,4 @@
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,21 +15,29 @@
# 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.
""" """
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 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() sp = Senpy(app, os.path.join(mypath, "plugins"))
sp.init_app(app) sp.activate_all()
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
app.run(host="0.0.0.0", use_reloader=False) http_server = WSGIServer(('', config.SERVER_PORT), app)
http_server.serve_forever()

View File

@@ -6,19 +6,6 @@ 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",
@@ -28,18 +15,19 @@ class Sentiment140Plugin(SentimentPlugin):
) )
) )
response = Response() response = Response(base=params.get("prefix", None))
polarity_value = int(res.json()["data"][0]["polarity"]) * 25 polarity_value = int(res.json()["data"][0]["polarity"]) * 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(text=params["input"]) entry = Entry(id="Entry0", text=params["input"])
opinion = Opinion(polarity=polarity, polarity_value=polarity_value) opinion = Opinion(id="Opinion0",
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

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

View File

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

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

@@ -2,4 +2,5 @@ Flask==0.10.1
gunicorn==19.0.0 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

View File

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

View File

@@ -1,7 +1,73 @@
#!/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 gevent.monkey import patch_all; patch_all(thread=False)
import gevent
from flask import Flask from flask import Flask
from extensions import Senpy from senpy.extensions import Senpy
app = Flask(__name__) import logging
sp = Senpy() import os
sp.init_app(app) from gevent.wsgi import WSGIServer
app.debug = True import argparse
app.run()
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",
metavar="debug",
type=bool,
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

@@ -48,7 +48,7 @@ def get_params(req, params=BASIC_PARAMS):
if alias in indict: if alias in indict:
outdict[param] = indict[alias] outdict[param] = indict[alias]
if param not in outdict: if param not in outdict:
if options.get("required", False): if options.get("required", False) and "default" not in options:
wrong_params[param] = params[param] wrong_params[param] = params[param]
else: else:
if "default" in options: if "default" in options:
@@ -110,23 +110,22 @@ 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 = current_app.senpy.filter_plugins(**filt) plugs = sp.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 = request.args.get("params", "") == "1" with_params = request.args.get("params", "") == "1"
dic = {plug: plugs[plug].jsonable(with_params) for plug in plugs} if plugin:
dic = plugs[plugin].jsonable(with_params)
else:
dic = {plug: plugs[plug].jsonable(with_params) for plug in plugs}
return jsonify(dic) return jsonify(dic)
if action == "disable": method = "{}_plugin".format(action)
current_app.senpy.deactivate_plugin(plugin) if(hasattr(sp, method)):
return "Ok" getattr(sp, method)(plugin)
elif action == "enable":
current_app.senpy.activate_plugin(plugin)
return "Ok"
elif action == "reload":
current_app.senpy.reload_plugin(plugin)
return "Ok" return "Ok"
else: else:
return "action '{}' not allowed".format(action), 400 return "action '{}' not allowed".format(action), 400

View File

@@ -8,31 +8,33 @@
"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": {
"@id": "onyx:hasEmotionSet", "@container": "@set",
"@type": "onyx:EmotionSet" "@id": "onyx:hasEmotionSet"
}, },
"opinions": { "opinions": {
"@container": "@list", "@container": "@set",
"@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": {
"@reverse": "nif:hasContext", "@container": "@set",
"@type": "nif:String" "@reverse": "nif:hasContext"
}, },
"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#"
} }

View File

@@ -1,23 +1,21 @@
""" """
""" """
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 .plugins import SenpyPlugin, 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 .blueprints import nif_blueprint
from git import Repo, InvalidGitRepositoryError from git import Repo, InvalidGitRepositoryError
from functools import partial
class Senpy(object): class Senpy(object):
@@ -79,10 +77,6 @@ class Senpy(object):
logger.debug("The algorithm '{}' is not valid\nValid algorithms: {}".format(algo, self.plugins.keys())) logger.debug("The algorithm '{}' is not valid\nValid algorithms: {}".format(algo, self.plugins.keys()))
return {"status": 400, "message": "The algorithm '{}' is not valid".format(algo)} return {"status": 400, "message": "The algorithm '{}' is not valid".format(algo)}
def activate_all(self):
for plug in self.plugins.values():
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)
@@ -96,11 +90,38 @@ class Senpy(object):
def parameters(self, algo): def parameters(self, algo):
return getattr(self.plugins.get(algo or self.default_plugin), "params", {}) return getattr(self.plugins.get(algo or self.default_plugin), "params", {})
def activate_plugin(self, plugin): def activate_all(self, sync=False):
self.plugins[plugin].activate() ps = []
for plug in self.plugins.keys():
ps.append(self.activate_plugin(plug, sync=sync))
return ps
def deactivate_plugin(self, plugin): def deactivate_all(self, sync=False):
self.plugins[plugin].deactivate() 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): def reload_plugin(self, plugin):
logger.debug("Reloading {}".format(plugin)) logger.debug("Reloading {}".format(plugin))
@@ -110,38 +131,47 @@ class Senpy(object):
self.plugins[nplug.name] = nplug self.plugins[nplug.name] = nplug
@staticmethod @staticmethod
def _load_plugin(plugin, search_folder, is_activated=True): def _load_plugin(root, filename):
logger.debug("Loading plugins") logger.debug("Loading plugin: {}".format(filename))
sys.path.append(search_folder) fpath = os.path.join(root, filename)
(fp, pathname, desc) = imp.find_module(plugin) 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: try:
tmp = imp.load_module(plugin, fp, pathname, desc).plugin tmp = imp.load_module(module, fp, pathname, desc)
sys.path.remove(search_folder) sys.path.remove(root)
tmp.path = search_folder 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: try:
repo_path = os.path.join(search_folder, plugin) repo_path = root
tmp.repo = Repo(repo_path) module.repo = Repo(repo_path)
except InvalidGitRepositoryError: except InvalidGitRepositoryError:
tmp.repo = None module.repo = None
if not hasattr(tmp, "is_activated"):
tmp.is_activated = is_activated
tmp.module = plugin
except Exception as ex: except Exception as ex:
tmp = None logger.debug("Exception importing {}: {}".format(filename, ex))
logger.debug("Exception importing {}: {}".format(plugin, ex)) return None, None
return tmp return name, module
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 item in os.listdir(search_folder): for root, dirnames, filenames in os.walk(search_folder):
if os.path.isdir(os.path.join(search_folder, item)) \ for filename in fnmatch.filter(filenames, '*.senpy'):
and os.path.exists(os.path.join(search_folder, name, plugin = self._load_plugin(root, filename)
item,
"__init__.py")):
plugin = self._load_plugin(item, search_folder)
if plugin: if plugin:
plugins[plugin.name] = plugin plugins[name] = plugin
self._outdated = False self._outdated = False
return plugins return plugins
@@ -149,31 +179,12 @@ 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. """
ctx = stack.top if not hasattr(self, 'senpy_plugins') or self._outdated:
if ctx is not None: self.senpy_plugins = self._load_plugins()
if not hasattr(ctx, 'senpy_plugins') or self._outdated: return self.senpy_plugins
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 """
@@ -193,4 +204,4 @@ class Senpy(object):
def sentiment_plugins(self): def sentiment_plugins(self):
""" Return only the sentiment plugins """ """ Return only the sentiment plugins """
return {p: plugin for p, plugin in self.plugins.items() if return {p: plugin for p, plugin in self.plugins.items() if
isinstance(plugin, SentimentPlugin)} isinstance(plugin, SentimentPlugin)}

View File

@@ -4,35 +4,69 @@ from collections import defaultdict
class Leaf(defaultdict): class Leaf(defaultdict):
def __init__(self, ofclass=list): _prefix = None
def __init__(self, id=None, context=None, prefix=None, ofclass=list):
super(Leaf, self).__init__(ofclass) super(Leaf, self).__init__(ofclass)
if context:
self.context = context
if id:
self.id = id
self._prefix = prefix
def __getattr__(self, name): def __getattr__(self, key):
return super(Leaf, self).__getitem__(name) return super(Leaf, self).__getitem__(self._get_key(key))
def __setattr__(self, name, value): def __setattr__(self, key, value):
self[name] = value try:
object.__getattr__(self, key)
object.__setattr__(self, key, value)
except AttributeError:
key = self._get_key(key)
value = self.get_context(value) if key == "@context" else value
if key[0] == "_":
object.__setattr__(self, key, value)
else:
super(Leaf, self).__setitem__(key, value)
def __delattr__(self, name): def __delattr__(self, key):
return super(Leaf, self).__delitem__(name) return super(Leaf, self).__delitem__(self._get_key(key))
def _get_key(self, key):
if key in ["context", "id"]:
return "@{}".format(key)
elif self._prefix:
return "{}:{}".format(self._prefix, 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
class Response(Leaf): class Response(Leaf):
def __init__(self, context=None): def __init__(self, context=None, base=None, *args, **kwargs):
super(Response, self).__init__()
self["analysis"] = []
self["entries"] = []
if context is None: if context is None:
context = "{}/context.jsonld".format(os.path.dirname( context = "{}/context.jsonld".format(os.path.dirname(
os.path.realpath(__file__))) os.path.realpath(__file__)))
if isinstance(context, dict): super(Response, self).__init__(*args, context=context, **kwargs)
self["@context"] = context if base:
if isinstance(context, str) or isinstance(context, unicode): self.context["@base"] = base
try: self["analysis"] = []
with open(context) as f: self["entries"] = []
self["@context"] = json.loads(f.read())
except IOError:
self["@context"] = context
class Entry(Leaf): class Entry(Leaf):
@@ -47,17 +81,30 @@ class Entry(Leaf):
class Opinion(Leaf): class Opinion(Leaf):
def __init__(self, polarity_value=None, polarity=None, **kwargs): #opinionContext = {"@vocab": "http://www.gsi.dit.upm.es/ontologies/marl/ns#"}
super(Opinion, self).__init__(**kwargs) def __init__(self, polarityValue=None, hasPolarity=None, *args, **kwargs):
if polarity_value is not None: super(Opinion, self).__init__( prefix="marl",
self.polarity_value = polarity_value *args,
if polarity is not None: **kwargs)
self.polarity = polarity if polarityValue is not None:
self.polarityValue = polarityValue
if hasPolarity is not None:
self.hasPolarity = hasPolarity
class EmotionSet(Leaf): class EmotionSet(Leaf):
def __init__(self, emotions=None, **kwargs): emotionContext = {}
def __init__(self, emotions=None, *args, **kwargs):
if not emotions: if not emotions:
emotions = [] emotions = []
super(EmotionSet, self).__init__(**kwargs) super(EmotionSet, self).__init__(context=EmotionSet.emotionContext,
*args,
**kwargs)
self.emotions = emotions or [] self.emotions = emotions or []
class Emotion(Leaf):
emotionContext = {}
def __init__(self, emotions=None, *args, **kwargs):
super(EmotionSet, self).__init__(context=Emotion.emotionContext,
*args,
**kwargs)

View File

@@ -1,5 +1,5 @@
import logging import logging
from yapsy.IPlugin import IPlugin import ConfigParser
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -26,6 +26,10 @@ PARAMS = {"input": {"aliases": ["i", "input"],
"required": False, "required": False,
"options": ["es", "en"], "options": ["es", "en"],
}, },
"prefix": {"aliases": ["prefix", "p"],
"required": True,
"default": "",
},
"urischeme": {"aliases": ["urischeme", "u"], "urischeme": {"aliases": ["urischeme", "u"],
"required": False, "required": False,
"default": "RFC5147String", "default": "RFC5147String",
@@ -34,24 +38,34 @@ PARAMS = {"input": {"aliases": ["i", "input"],
} }
class SenpyPlugin(IPlugin): class SenpyPlugin(object):
def __init__(self, name=None, version=None, extraparams=None, params=None): def __init__(self, info=None):
logger.debug("Initialising {}".format(name)) if not info:
self.name = name raise ValueError("You need to provide configuration information for the plugin.")
self.version = version logger.debug("Initialising {}".format(info))
if params: self.name = info["name"]
self.params = params self.version = info["version"]
else: self.params = info.get("params", PARAMS.copy())
self.params = PARAMS.copy() self.extra_params = info.get("extra_params", {})
if extraparams: self.params.update(self.extra_params)
self.params.update(extraparams) self.is_activated = False
self.extraparams = extraparams or {} self.info = info
self.is_activated = True
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):
pass
def deactivate(self):
pass
@property
def id(self):
return "{}_{}".format(self.name, self.version)
def jsonable(self, parameters=False): def jsonable(self, parameters=False):
resp = { resp = {
"@id": "{}_{}".format(self.name, self.version), "@id": "{}_{}".format(self.name, self.version),
@@ -61,8 +75,8 @@ class SenpyPlugin(IPlugin):
resp["repo"] = self.repo.remotes[0].url resp["repo"] = self.repo.remotes[0].url
if parameters: if parameters:
resp["parameters"] = self.params resp["parameters"] = self.params
elif self.extraparams: elif self.extra_params:
resp["extra_parameters"] = self.extraparams resp["extra_parameters"] = self.extra_params
return resp 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.2.8" VERSION = "0.3.1"
print(reqs) print(reqs)

View File

@@ -1 +0,0 @@

View File

@@ -9,6 +9,7 @@ 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
def check_dict(indic, template): def check_dict(indic, template):
@@ -22,6 +23,7 @@ class BlueprintsTest(TestCase):
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):
@@ -36,3 +38,43 @@ class BlueprintsTest(TestCase):
} }
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(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
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

View File

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

View File

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

View File

@@ -16,6 +16,7 @@ class ExtensionsTest(TestCase):
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):
@@ -30,41 +31,42 @@ 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_plugin("dummy") self.senpy.activate_all(sync=True)
assert self.senpy.plugins["dummy"].is_activated assert len(self.senpy.plugins) == 2
assert self.senpy.plugins["Sleep"].is_activated
def test_disabling(self): def test_disabling(self):
""" Disabling a plugin """ """ Disabling a plugin """
self.senpy.activate_plugin("dummy") self.senpy.deactivate_all(sync=True)
self.senpy.deactivate_plugin("dummy") assert self.senpy.plugins["Dummy"].is_activated == False
assert not self.senpy.plugins["dummy"].is_activated assert self.senpy.plugins["Sleep"].is_activated == False
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 == "dummy" assert self.senpy.default_plugin == "Dummy"
def test_analyse(self): def test_analyse(self):
""" Using a plugin """ """ Using a plugin """
with mock.patch.object(self.senpy.plugins["dummy"], "analyse") as mocked: with mock.patch.object(self.senpy.plugins["Dummy"], "analyse") as mocked:
self.senpy.analyse(algorithm="dummy", input="tupni", output="tuptuo") self.senpy.analyse(algorithm="Dummy", input="tupni", output="tuptuo")
self.senpy.analyse(input="tupni", output="tuptuo") self.senpy.analyse(input="tupni", output="tuptuo")
mocked.assert_any_call(input="tupni", output="tuptuo", algorithm="dummy") mocked.assert_any_call(input="tupni", output="tuptuo", algorithm="Dummy")
mocked.assert_any_call(input="tupni", output="tuptuo") mocked.assert_any_call(input="tupni", output="tuptuo")
for plug in self.senpy.plugins: for plug in self.senpy.plugins:
self.senpy.deactivate_plugin(plug) self.senpy.deactivate_plugin(plug, sync=True)
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"] == 400 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") self.senpy.deactivate_plugin("Dummy", sync=True)
assert not len(self.senpy.filter_plugins(name="dummy", is_activated=True)) assert not len(self.senpy.filter_plugins(name="Dummy", is_activated=True))

View File

@@ -0,0 +1,10 @@
from senpy.plugins import SenpyPlugin
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)

View File

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