1
0
mirror of https://github.com/gsi-upm/senpy synced 2024-11-22 08:12:27 +00:00

Added plugin architecture

This commit is contained in:
J. Fernando Sánchez 2014-10-17 12:47:17 +02:00
parent d0aa889124
commit 8405e5deef
12 changed files with 261 additions and 46 deletions

View File

@ -1,4 +1,4 @@
![GSI Logo](http://gsi.dit.upm.es/templates/jgsi/images/logo.png) ![GSI Logo](logo.png)
[Senpy](http://senpy.herokuapp.com) [Senpy](http://senpy.herokuapp.com)
========================================= =========================================
Example endpoint that yields results compatible with the EUROSENTIMENT format and exposes the NIF API. Example endpoint that yields results compatible with the EUROSENTIMENT format and exposes the NIF API.

40
app.py
View File

@ -20,47 +20,13 @@ 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
import re
from flask import Flask from flask import Flask
import random from senpy import Senpy
from senpy.nif_server import *
app = Flask(__name__) app = Flask(__name__)
rgx = re.compile("(\w[\w']*\w|\w)", re.U) sp = Senpy()
sp.init_app(app)
def hard_analysis(params):
response = basic_analysis(params)
response["analysis"][0].update({ "marl:algorithm": "SimpleAlgorithm",
"marl:minPolarityValue": -1,
"marl:maxPolarityValue": 1})
for i in response["entries"]:
contextid = i["@id"]
random.seed(str(params))
polValue = 2*random.random()-1
if polValue > 0:
pol = "marl:Positive"
elif polValue == 0:
pol = "marl:Neutral"
else:
pol = "marl:Negative"
i["opinions"] = [{"marl:polarityValue": polValue,
"marl:hasPolarity": pol
}]
i["strings"] = []
for m in rgx.finditer(i["nif:isString"]):
i["strings"].append({
"@id": "{}#char={},{}".format(contextid, m.start(), m.end()),
"nif:beginIndex": m.start(),
"nif:endIndex": m.end(),
"nif:anchorOf": m.group(0)
})
return response
app.analyse = hard_analysis
app.register_blueprint(nif_server)
if __name__ == '__main__': if __name__ == '__main__':
app.debug = config.DEBUG app.debug = config.DEBUG

BIN
logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

View File

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

View File

@ -0,0 +1,30 @@
#!/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.
'''
Sentiment analysis server in Python
'''
from blueprints import nif_blueprint
from extensions import Senpy
if __name__ == '__main__':
from flask import Flask
app = Flask(__name__)
app.register_blueprint(nif_server)
app.debug = config.DEBUG
app.run()

7
senpy/__main__.py Normal file
View File

@ -0,0 +1,7 @@
from flask import Flask
from extensions import Senpy
app = Flask(__name__)
app.debug = True
sp = Senpy()
sp.init_app(app)
app.run()

View File

@ -18,10 +18,9 @@
Simple Sentiment Analysis server Simple Sentiment Analysis server
''' '''
from flask import Blueprint, render_template, request, jsonify, current_app from flask import Blueprint, render_template, request, jsonify, current_app
import config
import json import json
nif_server = Blueprint("NIF Sentiment Analysis Server", __name__) nif_blueprint = Blueprint("NIF Sentiment Analysis Server", __name__)
PARAMS = {"input": {"aliases": ["i", "input"], PARAMS = {"input": {"aliases": ["i", "input"],
"required": True, "required": True,
@ -42,7 +41,7 @@ PARAMS = {"input": {"aliases": ["i", "input"],
"required": False, "required": False,
"options": ["json-ld"], "options": ["json-ld"],
}, },
"algorithm": {"aliases": ["algorithm", "a"], "algorithm": {"aliases": ["algorithm", "a", "algo"],
"required": False, "required": False,
}, },
"language": {"aliases": ["language", "l"], "language": {"aliases": ["language", "l"],
@ -109,18 +108,40 @@ def basic_analysis(params):
}) })
return response return response
@nif_server.route('/', methods=['POST', 'GET']) @nif_blueprint.route('/', methods=['POST', 'GET'])
def home(entries=None): def home(entries=None):
try: try:
params = get_params(request) params = get_params(request)
except ValueError as ex: except ValueError as ex:
return ex.message return ex.message
response = current_app.analyse(params) response = current_app.senpy.analyse(**params)
return jsonify(response) return jsonify(response)
@nif_blueprint.route('/plugins/', methods=['POST', 'GET'])
@nif_blueprint.route('/plugins/<plugin>', methods=['POST', 'GET'])
@nif_blueprint.route('/plugins/<plugin>/<action>', methods=['POST', 'GET'])
def plugins(plugin=None, action="list"):
print current_app.senpy.plugins.keys()
if plugin:
plugs = {plugin:current_app.senpy.plugins[plugin]}
else:
plugs = current_app.senpy.plugins
if action == "list":
dic = {plug:plugs[plug].enabled for plug in plugs}
return jsonify(dic)
elif action == "disable":
plugs[plugin].enabled = False
return "Ok"
elif action == "enable":
plugs[plugin].enabled = True
return "Ok"
else:
return "action '{}' not allowed".format(action), 404
if __name__ == '__main__': if __name__ == '__main__':
import config
from flask import Flask from flask import Flask
app = Flask(__name__) app = Flask(__name__)
app.register_blueprint(nif_server) app.register_blueprint(nif_blueprint)
app.debug = config.DEBUG app.debug = config.DEBUG
app.run() app.run()

107
senpy/extensions.py Normal file
View File

@ -0,0 +1,107 @@
import os
import sys
import importlib
from flask import current_app
try:
from flask import _app_ctx_stack as stack
except ImportError:
from flask import _request_ctx_stack as stack
from blueprints import nif_blueprint
from git import Repo, InvalidGitRepositoryError
class Senpy(object):
def __init__(self, app=None, plugin_folder="plugins"):
self.app = app
base_folder = os.path.join(os.path.dirname(__file__), "plugins")
self.search_folders = (folder for folder in (base_folder, plugin_folder)
if folder and os.path.isdir(folder))
if app is not None:
self.init_app(app)
"""
Note: I'm not particularly fond of adding self.app and app.senpy, but
I can't think of a better way to do it.
"""
def init_app(self, app, plugin_folder="plugins"):
app.senpy = self
#app.config.setdefault('SQLITE3_DATABASE', ':memory:')
# Use the newstyle teardown_appcontext if it's available,
# otherwise fall back to the request context
if hasattr(app, 'teardown_appcontext'):
app.teardown_appcontext(self.teardown)
else:
app.teardown_request(self.teardown)
app.register_blueprint(nif_blueprint)
def analyse(self, **params):
if "algorithm" in params:
algo = params["algorithm"]
if algo in self.plugins and self.plugins[algo].enabled:
return self.plugins[algo].plugin.analyse(**params)
return {"status": 500, "message": "No valid algorithm"}
def _load_plugin(self, plugin, search_folder, enabled=True):
sys.path.append(search_folder)
tmp = importlib.import_module(plugin)
sys.path.remove(search_folder)
tmp.path = search_folder
try:
repo_path = os.path.join(search_folder, plugin)
tmp.repo = Repo(repo_path)
except InvalidGitRepositoryError:
tmp.repo = None
if not hasattr(tmp, "enabled"):
tmp.enabled = enabled
return tmp
def _load_plugins(self):
#print(sys.path)
#print(search_folder)
plugins = {}
for search_folder in self.search_folders:
for item in os.listdir(search_folder):
if os.path.isdir(os.path.join(search_folder, item)) \
and os.path.exists(
os.path.join(search_folder, item, "__init__.py")):
plugins[item] = self._load_plugin(item, search_folder)
return plugins
def teardown(self, exception):
pass
def enable_all(self):
for plugin in self.plugins:
self.enable_plugin(plugin)
def enable_plugin(self, item):
self.plugins[item].enabled = True
def disable_plugin(self, item):
self.plugins[item].enabled = False
@property
def plugins(self):
ctx = stack.top
if ctx is not None:
if not hasattr(self, '_plugins'):
self._plugins = self._load_plugins()
print("Already plugins")
return self._plugins
if __name__ == '__main__':
from flask import Flask
app = Flask(__name__)
sp = Senpy()
sp.init_app(app)
with app.app_context():
sp._load_plugins()

34
senpy/plugin.py Normal file
View File

@ -0,0 +1,34 @@
class SenpyPlugin(object):
def __init__(self, name=None, version=None, params=None):
self.name = name
self.version = version
self.params = params or []
def analyse(self, *args, **kwargs):
pass
def activate(self):
pass
def deactivate(self):
pass
class SentimentPlugin(SenpyPlugin):
def __init__(self,
minPolarity=0,
maxPolarity=1,
**kwargs):
super(SentimentPlugin, self).__init__(**kwargs)
self.minPolarity = minPolarity
self.maxPolarity = maxPolarity
class EmotionPlugin(SenpyPlugin):
def __init__(self,
minEmotionValue=0,
maxEmotionValue=1,
emotionCategory=None,
**kwargs):
super(EmotionPlugin, self).__init__(**kwargs)
self.minEmotionValue = minEmotionValue
self.maxEmotionValue = maxEmotionValue
self.emotionCategory = emotionCategory

View File

@ -0,0 +1,48 @@
import requests
import json
import sys
print(sys.path)
from senpy.plugin import SentimentPlugin
class Sentiment140Plugin(SentimentPlugin):
def __init__(self, **kwargs):
super(Sentiment140Plugin, self).__init__(name="Sentiment140",
version="1.0",
**kwargs)
def analyse(self, **params):
res = requests.post("http://www.sentiment140.com/api/bulkClassifyJson",
json.dumps({
"language": "auto",
"data": [{"text": params["input"]}]}
))
response = {"analysis": [{}], "entries": []}
response["analysis"][0].update({ "marl:algorithm": "SimpleAlgorithm",
"marl:minPolarityValue": 0,
"marl:maxPolarityValue": 100})
polarityValue = int(res.json()["data"][0]["polarity"]) * 25
polarity = "marl:Neutral"
if polarityValue > 50:
polarity = "marl:Positive"
elif polarityValue < 50:
polarity = "marl:Negative"
response["entries"] = [
{
"isString": params["input"],
"opinions": [{
"marl:polarityValue": polarityValue,
"marl:hasPolarity": polarity
}]
}
]
return response
plugin = Sentiment140Plugin()

View File

@ -2,7 +2,7 @@ from distutils.core import setup
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.1', version = '0.2',
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.
@ -10,7 +10,7 @@ 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.1.tar.gz', # I'll explain this in a second download_url = 'https://github.com/balkian/senpy/archive/0.2.tar.gz', # I'll explain this in a second
keywords = ['eurosentiment', 'sentiment', 'emotions', 'nif'], # arbitrary keywords keywords = ['eurosentiment', 'sentiment', 'emotions', 'nif'], # arbitrary keywords
classifiers = [], classifiers = [],
) )