mirror of
https://github.com/gsi-upm/senpy
synced 2024-11-25 01:22:28 +00:00
Added plugin architecture
This commit is contained in:
parent
d0aa889124
commit
8405e5deef
@ -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)
|
||||
=========================================
|
||||
Example endpoint that yields results compatible with the EUROSENTIMENT format and exposes the NIF API.
|
||||
|
40
app.py
40
app.py
@ -20,47 +20,13 @@ Simple Sentiment Analysis server for EUROSENTIMENT
|
||||
This class shows how to use the nif_server module to create custom services.
|
||||
'''
|
||||
import config
|
||||
import re
|
||||
from flask import Flask
|
||||
import random
|
||||
from senpy.nif_server import *
|
||||
from senpy import Senpy
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
rgx = re.compile("(\w[\w']*\w|\w)", re.U)
|
||||
|
||||
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)
|
||||
sp = Senpy()
|
||||
sp.init_app(app)
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.debug = config.DEBUG
|
||||
|
@ -1,3 +1,5 @@
|
||||
Flask==0.10.1
|
||||
gunicorn==19.0.0
|
||||
requests==2.4.1
|
||||
Flask-Plugins==1.4
|
||||
GitPython==0.3.2.RC1
|
||||
|
@ -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
7
senpy/__main__.py
Normal 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()
|
@ -18,10 +18,9 @@
|
||||
Simple Sentiment Analysis server
|
||||
'''
|
||||
from flask import Blueprint, render_template, request, jsonify, current_app
|
||||
import config
|
||||
import json
|
||||
|
||||
nif_server = Blueprint("NIF Sentiment Analysis Server", __name__)
|
||||
nif_blueprint = Blueprint("NIF Sentiment Analysis Server", __name__)
|
||||
|
||||
PARAMS = {"input": {"aliases": ["i", "input"],
|
||||
"required": True,
|
||||
@ -42,7 +41,7 @@ PARAMS = {"input": {"aliases": ["i", "input"],
|
||||
"required": False,
|
||||
"options": ["json-ld"],
|
||||
},
|
||||
"algorithm": {"aliases": ["algorithm", "a"],
|
||||
"algorithm": {"aliases": ["algorithm", "a", "algo"],
|
||||
"required": False,
|
||||
},
|
||||
"language": {"aliases": ["language", "l"],
|
||||
@ -109,18 +108,40 @@ def basic_analysis(params):
|
||||
})
|
||||
return response
|
||||
|
||||
@nif_server.route('/', methods=['POST', 'GET'])
|
||||
@nif_blueprint.route('/', methods=['POST', 'GET'])
|
||||
def home(entries=None):
|
||||
try:
|
||||
params = get_params(request)
|
||||
except ValueError as ex:
|
||||
return ex.message
|
||||
response = current_app.analyse(params)
|
||||
response = current_app.senpy.analyse(**params)
|
||||
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__':
|
||||
import config
|
||||
from flask import Flask
|
||||
app = Flask(__name__)
|
||||
app.register_blueprint(nif_server)
|
||||
app.register_blueprint(nif_blueprint)
|
||||
app.debug = config.DEBUG
|
||||
app.run()
|
107
senpy/extensions.py
Normal file
107
senpy/extensions.py
Normal 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
34
senpy/plugin.py
Normal 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
|
48
senpy/plugins/sentiment140/__init__.py
Normal file
48
senpy/plugins/sentiment140/__init__.py
Normal 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()
|
4
setup.py
4
setup.py
@ -2,7 +2,7 @@ from distutils.core import setup
|
||||
setup(
|
||||
name = 'senpy',
|
||||
packages = ['senpy'], # this must be the same as the name above
|
||||
version = '0.1',
|
||||
version = '0.2',
|
||||
description = '''
|
||||
A sentiment analysis server implementation. Designed to be \
|
||||
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_email = 'balkian@gmail.com',
|
||||
url = 'https://github.com/balkian/senpy', # use the URL to the github repo
|
||||
download_url = 'https://github.com/balkian/senpy/archive/0.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
|
||||
classifiers = [],
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user