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

Compare commits

..

7 Commits
0.2.3 ... yapsy

Author SHA1 Message Date
J. Fernando Sánchez
dabf444607 Switched to Yapsy 2014-11-26 22:09:02 +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
20 changed files with 525 additions and 238 deletions

3
MANIFEST.in Normal file
View File

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

10
app.py
View File

@@ -14,14 +14,14 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# 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 Simple Sentiment Analysis server for EUROSENTIMENT
This class shows how to use the nif_server module to create custom services. This class shows how to use the nif_server module to create custom services.
''' """
import config import config
from flask import Flask from flask import Flask
from senpy import Senpy from senpy.extensions import Senpy
app = Flask(__name__) app = Flask(__name__)
@@ -29,5 +29,7 @@ sp = Senpy()
sp.init_app(app) sp.init_app(app)
if __name__ == '__main__': if __name__ == '__main__':
import logging
logging.basicConfig(level=config.DEBUG)
app.debug = config.DEBUG app.debug = config.DEBUG
app.run() app.run(host="0.0.0.0", use_reloader=False)

1
dev-requirements.txt Normal file
View File

@@ -0,0 +1 @@
mock

View File

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

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

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

View File

@@ -14,19 +14,12 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# 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.
''' """
Sentiment analysis server in Python Sentiment analysis server in Python
''' """
import extensions import extensions
import blueprints import blueprints
import plugins import plugins
__version__ = "0.2.8"
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,65 +1,39 @@
#!/usr/bin/python #!/usr/bin/python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright 2014 J. Fernando Sánchez Rada - Grupo de Sistemas Inteligentes # Copyright 2014 J. Fernando Sánchez Rada - Grupo de Sistemas Inteligentes
# DIT, UPM # DIT, UPM
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
# You may obtain a copy of the License at # You may obtain a copy of the License at
# #
# http://www.apache.org/licenses/LICENSE-2.0 # http://www.apache.org/licenses/LICENSE-2.0
# #
# Unless required by applicable law or agreed to in writing, software # Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, # distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# 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 Blueprints for Senpy
''' """
from flask import Blueprint, render_template, request, jsonify, current_app
import json import json
import logging
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__)
PARAMS = {"input": {"aliases": ["i", "input"], BASIC_PARAMS = {
"required": True, "algorithm": {"aliases": ["algorithm", "a", "algo"],
"help": "Input text" "required": False,
}, },
"informat": {"aliases": ["f", "informat"], }
"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"
},
}
def get_algorithm(req):
return get_params(req, params={"algorithm": PARAMS["algorithm"]})
def get_params(req, params=PARAMS): def get_params(req, params=BASIC_PARAMS):
indict = None
if req.method == 'POST': if req.method == 'POST':
indict = req.form indict = req.form
elif req.method == 'GET': elif req.method == 'GET':
@@ -68,37 +42,37 @@ def get_params(req, params=PARAMS):
raise ValueError("Invalid data") raise ValueError("Invalid data")
outdict = {} outdict = {}
wrongParams = {} wrong_params = {}
for param, options in params.iteritems(): for param, options in params.iteritems():
for alias in options["aliases"]: for alias in options["aliases"]:
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):
wrongParams[param] = params[param] wrong_params[param] = params[param]
else: else:
if "default" in options: if "default" in options:
outdict[param] = options["default"] outdict[param] = options["default"]
else: else:
if "options" in params[param] and \ if "options" in params[param] and outdict[param] not in params[param]["options"]:
outdict[param] not in params[param]["options"]: wrong_params[param] = params[param]
wrongParams[param] = params[param] if wrong_params:
if wrongParams: message = {"status": "failed",
message = {"status": "failed", "message": "Missing or invalid parameters"} "message": "Missing or invalid parameters",
message["parameters"] = outdict "parameters": outdict,
message["errors"] = {param:error for param, error in wrongParams.iteritems()} "errors": {param: error for param, error in wrong_params.iteritems()}
}
raise ValueError(json.dumps(message)) raise ValueError(json.dumps(message))
return outdict return outdict
def basic_analysis(params): 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')) "@base": "{}#".format(request.url.encode('utf-8'))
} }
], ],
"analysis": [{ "analysis": [{"@type": "marl:SentimentAnalysis"}],
"@type": "marl:SentimentAnalysis"
}],
"entries": [] "entries": []
} }
if "language" in params: if "language" in params:
@@ -110,42 +84,58 @@ def basic_analysis(params):
}) })
return response return response
@nif_blueprint.route('/', methods=['POST', 'GET']) @nif_blueprint.route('/', methods=['POST', 'GET'])
def home(entries=None): def home():
try: try:
algo = get_algorithm(request)["algorithm"] algo = get_params(request).get("algorithm", None)
specific_params = PARAMS.copy() specific_params = current_app.senpy.parameters(algo)
specific_params.update(current_app.senpy.parameters(algo))
params = get_params(request, specific_params) params = get_params(request, specific_params)
response = current_app.senpy.analyse(**params)
return jsonify(response)
except ValueError as ex: except ValueError as ex:
return ex.message return ex.message
response = current_app.senpy.analyse(**params) except Exception as ex:
return jsonify(response) return jsonify(status="400", message=ex.message)
@nif_blueprint.route("/default")
def default():
return current_app.senpy.default_plugin
#return plugins(action="list", plugin=current_app.senpy.default_algorithm)
@nif_blueprint.route('/plugins/', methods=['POST', 'GET']) @nif_blueprint.route('/plugins/', methods=['POST', 'GET'])
@nif_blueprint.route('/plugins/<plugin>', methods=['POST', 'GET']) @nif_blueprint.route('/plugins/<plugin>', methods=['POST', 'GET'])
@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"):
print current_app.senpy.plugins.keys() filt = {}
if plugin: if plugin:
plugs = {plugin:current_app.senpy.plugins[plugin]} filt["name"] = plugin
else: plugs = current_app.senpy.filter_plugins(**filt)
plugs = current_app.senpy.plugins if plugin and not plugs:
return "Plugin not found", 400
if action == "list": if action == "list":
dic = {plug:plugs[plug].jsonable(True) for plug in plugs} with_params = request.args.get("params", "") == "1"
dic = {plug: plugs[plug].jsonable(with_params) for plug in plugs}
return jsonify(dic) return jsonify(dic)
elif action == "disable": if action == "disable":
plugs[plugin].enabled = False current_app.senpy.deactivate_plugin(plugin)
return "Ok" return "Ok"
elif action == "enable": elif action == "enable":
plugs[plugin].enabled = True 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), 404 return "action '{}' not allowed".format(action), 400
if __name__ == '__main__': if __name__ == '__main__':
import config import config
from flask import Flask from flask import Flask
app = Flask(__name__) app = Flask(__name__)
app.register_blueprint(nif_blueprint) app.register_blueprint(nif_blueprint)
app.debug = config.DEBUG app.debug = config.DEBUG

View File

@@ -1,37 +1,48 @@
"""
"""
import os import os
import sys import sys
import importlib import imp
import logging
from flask import current_app logger = logging.getLogger(__name__)
from .plugins import SentimentPlugin, EmotionPlugin
from yapsy.PluginFileLocator import PluginFileLocator, PluginFileAnalyzerWithInfoFile
from yapsy.PluginManager import PluginManager
try: try:
from flask import _app_ctx_stack as stack from flask import _app_ctx_stack as stack
except ImportError: except ImportError:
from flask import _request_ctx_stack as stack 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
class Senpy(object): class Senpy(object):
""" Default Senpy extension for Flask """
def __init__(self, app=None, plugin_folder="plugins"): def __init__(self, app=None, plugin_folder="plugins"):
self.app = app self.app = app
base_folder = os.path.join(os.path.dirname(__file__), "plugins") base_folder = os.path.join(os.path.dirname(__file__), "plugins")
self.search_folders = (folder for folder in (base_folder, plugin_folder) self._search_folders = set()
if folder and os.path.isdir(folder)) self._outdated = True
for folder in (base_folder, plugin_folder):
self.add_folder(folder)
if app is not None: if app is not None:
self.init_app(app) 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 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. I can't think of a better way to do it.
""" """
def init_app(self, app, plugin_folder="plugins"):
app.senpy = self app.senpy = self
#app.config.setdefault('SQLITE3_DATABASE', ':memory:')
# Use the newstyle teardown_appcontext if it's available, # Use the newstyle teardown_appcontext if it's available,
# otherwise fall back to the request context # otherwise fall back to the request context
if hasattr(app, 'teardown_appcontext'): if hasattr(app, 'teardown_appcontext'):
@@ -40,48 +51,99 @@ class Senpy(object):
app.teardown_request(self.teardown) app.teardown_request(self.teardown)
app.register_blueprint(nif_blueprint) 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): def analyse(self, **params):
algo = None
logger.debug("analysing with params: {}".format(params))
if "algorithm" in params: if "algorithm" in params:
algo = params["algorithm"] algo = params["algorithm"]
if algo in self.plugins and self.plugins[algo].enabled: elif self.plugins:
algo = self.default_plugin
if algo in self.plugins:
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.jsonable()) resp.analysis.append(plug.jsonable())
return resp return resp
return {"status": 500, "message": "No valid algorithm"} logger.debug("Plugin not activated: {}".format(algo))
else:
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)}
def activate_all(self):
for plug in self.plugins.values():
plug.activate()
@property
def default_plugin(self):
candidates = self.filter_plugins(is_activated=True)
if len(candidates) > 0:
candidate = candidates.keys()[0]
logger.debug("Default: {}".format(candidate))
return candidate
else:
return None
def parameters(self, algo): def parameters(self, algo):
if algo in self.plugins: return getattr(self.plugins.get(algo or self.default_plugin), "params", {})
if hasattr(self.plugins[algo], "parameters"):
return self.plugins[algo].parameters
return {}
def _load_plugin(self, plugin, search_folder, enabled=True): def activate_plugin(self, plugin):
self.plugins[plugin].activate()
def deactivate_plugin(self, plugin):
self.plugins[plugin].deactivate()
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(plugin, search_folder, is_activated=True):
logger.debug("Loading plugins")
sys.path.append(search_folder) sys.path.append(search_folder)
tmp = importlib.import_module(plugin).plugin (fp, pathname, desc) = imp.find_module(plugin)
sys.path.remove(search_folder)
tmp.path = search_folder
try: try:
repo_path = os.path.join(search_folder, plugin) tmp = imp.load_module(plugin, fp, pathname, desc).plugin
tmp.repo = Repo(repo_path) sys.path.remove(search_folder)
except InvalidGitRepositoryError: tmp.path = search_folder
tmp.repo = None try:
if not hasattr(tmp, "enabled"): repo_path = os.path.join(search_folder, plugin)
tmp.enabled = enabled tmp.repo = Repo(repo_path)
except InvalidGitRepositoryError:
tmp.repo = None
if not hasattr(tmp, "is_activated"):
tmp.is_activated = is_activated
tmp.module = plugin
except Exception as ex:
tmp = None
logger.debug("Exception importing {}: {}".format(plugin, ex))
return tmp return tmp
def _load_plugins(self): def _load_plugins(self):
#print(sys.path)
#print(search_folder)
plugins = {} plugins = {}
for search_folder in self.search_folders: for search_folder in self._search_folders:
for item in os.listdir(search_folder): for item in os.listdir(search_folder):
if os.path.isdir(os.path.join(search_folder, item)) \ if os.path.isdir(os.path.join(search_folder, item)) \
and os.path.exists( and os.path.exists(os.path.join(search_folder,
os.path.join(search_folder, item, "__init__.py")): item,
plugins[item] = self._load_plugin(item, search_folder) "__init__.py")):
plugin = self._load_plugin(item, search_folder)
if plugin:
plugins[plugin.name] = plugin
self._outdated = False
return plugins return plugins
def teardown(self, exception): def teardown(self, exception):
@@ -89,26 +151,46 @@ class Senpy(object):
def enable_all(self): def enable_all(self):
for plugin in self.plugins: for plugin in self.plugins:
self.enable_plugin(plugin) self.activate_plugin(plugin)
def enable_plugin(self, item): @property
self.plugins[item].enabled = True def manager(self):
ctx = stack.top
def disable_plugin(self, item): if ctx is not None:
self.plugins[item].enabled = False 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. """
ctx = stack.top ctx = stack.top
if ctx is not None: if ctx is not None:
if not hasattr(self, '_plugins'): if not hasattr(ctx, 'senpy_plugins') or self._outdated:
self._plugins = self._load_plugins() ctx.senpy_plugins = {p.name:p.plugin_object for p in self.manager.getAllPlugins()}
return self._plugins return ctx.senpy_plugins
if __name__ == '__main__': def filter_plugins(self, **kwargs):
from flask import Flask """ Filter plugins by different criteria """
app = Flask(__name__)
sp = Senpy() def matches(plug):
sp.init_app(app) res = all(getattr(plug, k, None) == v for (k, v) in kwargs.items())
with app.app_context(): logger.debug("matching {} with {}: {}".format(plug.name,
sp._load_plugins() 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

@@ -2,6 +2,7 @@ import json
import os import os
from collections import defaultdict from collections import defaultdict
class Leaf(defaultdict): class Leaf(defaultdict):
def __init__(self, ofclass=list): def __init__(self, ofclass=list):
super(Leaf, self).__init__(ofclass) super(Leaf, self).__init__(ofclass)
@@ -15,6 +16,7 @@ class Leaf(defaultdict):
def __delattr__(self, name): def __delattr__(self, name):
return super(Leaf, self).__delitem__(name) return super(Leaf, self).__delitem__(name)
class Response(Leaf): class Response(Leaf):
def __init__(self, context=None): def __init__(self, context=None):
super(Response, self).__init__() super(Response, self).__init__()
@@ -22,10 +24,10 @@ class Response(Leaf):
self["entries"] = [] 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): if isinstance(context, dict):
self["@context"] = context self["@context"] = context
if isinstance(context, basestring): if isinstance(context, str) or isinstance(context, unicode):
try: try:
with open(context) as f: with open(context) as f:
self["@context"] = json.loads(f.read()) self["@context"] = json.loads(f.read())
@@ -34,26 +36,28 @@ class Response(Leaf):
class Entry(Leaf): class Entry(Leaf):
def __init__(self, text=None, emotionSets=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
if emotionSets: if emotion_sets:
self.emotionSets = emotionSets self.emotionSets = emotion_sets
if opinions: if opinions:
self.opinions = opinions self.opinions = opinions
class Opinion(Leaf): class Opinion(Leaf):
def __init__(self, polarityValue=None, polarity=None, **kwargs): def __init__(self, polarity_value=None, polarity=None, **kwargs):
super(Opinion, self).__init__(**kwargs) super(Opinion, self).__init__(**kwargs)
if polarityValue is not None: if polarity_value is not None:
self.polarityValue = polarityValue self.polarity_value = polarity_value
if polarity is not None: if polarity is not None:
self.polarity = polarity self.polarity = polarity
class EmotionSet(Leaf): class EmotionSet(Leaf):
def __init__(self, emotions=[], **kwargs): def __init__(self, emotions=None, **kwargs):
if not emotions:
emotions = []
super(EmotionSet, self).__init__(**kwargs) super(EmotionSet, self).__init__(**kwargs)
self.emotions = emotions or [] self.emotions = emotions or []

View File

@@ -1,34 +1,79 @@
class SenpyPlugin(object): import logging
def __init__(self, name=None, version=None, params=None): from yapsy.IPlugin import IPlugin
logger = logging.getLogger(__name__)
PARAMS = {"input": {"aliases": ["i", "input"],
"required": True,
"help": "Input text"
},
"informat": {"aliases": ["f", "informat"],
"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"],
},
"language": {"aliases": ["language", "l"],
"required": False,
"options": ["es", "en"],
},
"urischeme": {"aliases": ["urischeme", "u"],
"required": False,
"default": "RFC5147String",
"options": "RFC5147String"
},
}
class SenpyPlugin(IPlugin):
def __init__(self, name=None, version=None, extraparams=None, params=None):
logger.debug("Initialising {}".format(name))
self.name = name self.name = name
self.version = version self.version = version
self.params = params or [] if params:
self.params = params
else:
self.params = PARAMS.copy()
if extraparams:
self.params.update(extraparams)
self.extraparams = extraparams or {}
self.is_activated = True
def analyse(self, *args, **kwargs): def analyse(self, *args, **kwargs):
pass logger.debug("Analysing with: {} {}".format(self.name, self.version))
def activate(self):
pass
def deactivate(self):
pass pass
def jsonable(self, parameters=False): def jsonable(self, parameters=False):
resp = { resp = {
"@id": "{}_{}".format(self.name, self.version), "@id": "{}_{}".format(self.name, self.version),
"is_activated": self.is_activated,
} }
if hasattr(self, "repo") and self.repo:
resp["repo"] = self.repo.remotes[0].url
if parameters: if parameters:
resp["parameters"] = self.params, resp["parameters"] = self.params
elif self.extraparams:
resp["extra_parameters"] = self.extraparams
return resp return resp
class SentimentPlugin(SenpyPlugin): class SentimentPlugin(SenpyPlugin):
def __init__(self, def __init__(self,
minPolarityValue=0, min_polarity_value=0,
maxPolarityValue=1, max_polarity_value=1,
**kwargs): **kwargs):
super(SentimentPlugin, self).__init__(**kwargs) super(SentimentPlugin, self).__init__(**kwargs)
self.minPolarityValue = minPolarityValue self.minPolarityValue = min_polarity_value
self.maxPolarityValue = maxPolarityValue self.maxPolarityValue = max_polarity_value
def jsonable(self, *args, **kwargs): def jsonable(self, *args, **kwargs):
resp = super(SentimentPlugin, self).jsonable(*args, **kwargs) resp = super(SentimentPlugin, self).jsonable(*args, **kwargs)
@@ -36,16 +81,17 @@ class SentimentPlugin(SenpyPlugin):
resp["marl:minPolarityValue"] = self.minPolarityValue resp["marl:minPolarityValue"] = self.minPolarityValue
return resp return resp
class EmotionPlugin(SenpyPlugin): class EmotionPlugin(SenpyPlugin):
def __init__(self, def __init__(self,
minEmotionValue=0, min_emotion_value=0,
maxEmotionValue=1, max_emotion_value=1,
emotionCategory=None, emotion_category=None,
**kwargs): **kwargs):
super(EmotionPlugin, self).__init__(**kwargs) super(EmotionPlugin, self).__init__(**kwargs)
self.minEmotionValue = minEmotionValue self.minEmotionValue = min_emotion_value
self.maxEmotionValue = maxEmotionValue self.maxEmotionValue = max_emotion_value
self.emotionCategory = emotionCategory self.emotionCategory = emotion_category
def jsonable(self, *args, **kwargs): def jsonable(self, *args, **kwargs):
resp = super(EmotionPlugin, self).jsonable(*args, **kwargs) resp = super(EmotionPlugin, self).jsonable(*args, **kwargs)

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,31 @@
from setuptools import setup 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.2.8"
print(reqs)
setup( setup(
name = 'senpy', name='senpy',
packages = ['senpy'], # this must be the same as the name above packages=['senpy'], # this must be the same as the name above
version = '0.2.3', version=VERSION,
description = ''' description='''
A sentiment analysis server implementation. Designed to be \ A sentiment analysis server implementation. Designed to be \
extendable, so new algorithms and sources can be used. 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/balkian/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/balkian/senpy/archive/0.2.3.tar.gz', download_url='https://github.com/balkian/senpy/archive/{}.tar.gz'.format(VERSION),
keywords = ['eurosentiment', 'sentiment', 'emotions', 'nif'], # arbitrary keywords keywords=['eurosentiment', 'sentiment', 'emotions', 'nif'], # arbitrary keywords
classifiers = [], 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

1
tests/__init__.py Normal file
View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,38 @@
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
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)
return self.app
def test_home(self):
""" Calling with no arguments should ask the user for more arguments """
resp = self.client.get("/")
self.assert200(resp)
logging.debug(resp.json)
assert resp.json["status"] == "failed"
atleast = {
"status": "failed",
"message": "Missing or invalid parameters",
}
assert check_dict(resp.json, atleast)

View File

@@ -0,0 +1,6 @@
from senpy.plugins import SenpyPlugin
class DummyPlugin(SenpyPlugin):
def __init__(self):
super(DummyPlugin, self).__init__("dummy")

View File

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

View File

@@ -0,0 +1,70 @@
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)
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_plugin("dummy")
assert self.senpy.plugins["dummy"].is_activated
def test_disabling(self):
""" Disabling a plugin """
self.senpy.activate_plugin("dummy")
self.senpy.deactivate_plugin("dummy")
assert not self.senpy.plugins["dummy"].is_activated
def test_default(self):
""" Default plugin should be set """
assert self.senpy.default_plugin
assert self.senpy.default_plugin == "dummy"
def test_analyse(self):
""" Using a plugin """
with mock.patch.object(self.senpy.plugins["dummy"], "analyse") as mocked:
self.senpy.analyse(algorithm="dummy", 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")
for plug in self.senpy.plugins:
self.senpy.deactivate_plugin(plug)
resp = self.senpy.analyse(input="tupni")
logging.debug("Response: {}".format(resp))
assert resp["status"] == 400
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")
assert not len(self.senpy.filter_plugins(name="dummy", is_activated=True))