diff --git a/senpy/__init__.py b/senpy/__init__.py index 631e09c..bb7e137 100644 --- a/senpy/__init__.py +++ b/senpy/__init__.py @@ -18,13 +18,15 @@ Sentiment analysis server in Python ''' -from blueprints import nif_blueprint -from extensions import Senpy +import extensions +import blueprints +import plugins if __name__ == '__main__': from flask import Flask app = Flask(__name__) - app.register_blueprint(nif_server) + sp = extensions.Senpy() + sp.init_app(app) app.debug = config.DEBUG app.run() diff --git a/senpy/__main__.py b/senpy/__main__.py index fe412c3..f5db480 100644 --- a/senpy/__main__.py +++ b/senpy/__main__.py @@ -1,7 +1,7 @@ from flask import Flask from extensions import Senpy app = Flask(__name__) -app.debug = True sp = Senpy() sp.init_app(app) +app.debug = True app.run() diff --git a/senpy/blueprints.py b/senpy/blueprints.py index 6170662..7e38dae 100644 --- a/senpy/blueprints.py +++ b/senpy/blueprints.py @@ -55,8 +55,10 @@ PARAMS = {"input": {"aliases": ["i", "input"], }, } +def get_algorithm(req): + return get_params(req, params={"algorithm": PARAMS["algorithm"]}) -def get_params(req): +def get_params(req, params=PARAMS): indict = None if req.method == 'POST': indict = req.form @@ -67,20 +69,20 @@ def get_params(req): outdict = {} wrongParams = {} - for param, options in PARAMS.iteritems(): + for param, options in params.iteritems(): for alias in options["aliases"]: if alias in indict: outdict[param] = indict[alias] if param not in outdict: if options.get("required", False): - wrongParams[param] = PARAMS[param] + wrongParams[param] = params[param] else: if "default" in options: outdict[param] = options["default"] else: - if "options" in PARAMS[param] and \ - outdict[param] not in PARAMS[param]["options"]: - wrongParams[param] = PARAMS[param] + if "options" in params[param] and \ + outdict[param] not in params[param]["options"]: + wrongParams[param] = params[param] if wrongParams: message = {"status": "failed", "message": "Missing or invalid parameters"} message["parameters"] = outdict @@ -111,7 +113,10 @@ def basic_analysis(params): @nif_blueprint.route('/', methods=['POST', 'GET']) def home(entries=None): try: - params = get_params(request) + algo = get_algorithm(request)["algorithm"] + specific_params = PARAMS.copy() + specific_params.update(current_app.senpy.parameters(algo)) + params = get_params(request, specific_params) except ValueError as ex: return ex.message response = current_app.senpy.analyse(**params) @@ -127,7 +132,7 @@ def plugins(plugin=None, action="list"): else: plugs = current_app.senpy.plugins if action == "list": - dic = {plug:plugs[plug].enabled for plug in plugs} + dic = {plug:plugs[plug].jsonable(True) for plug in plugs} return jsonify(dic) elif action == "disable": plugs[plugin].enabled = False diff --git a/senpy/context.jsonld b/senpy/context.jsonld new file mode 100644 index 0000000..c87f904 --- /dev/null +++ b/senpy/context.jsonld @@ -0,0 +1,38 @@ +{ + "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": { + "@id": "onyx:hasEmotionSet", + "@type": "onyx:EmotionSet" + }, + "opinions": { + "@container": "@list", + "@id": "marl:hasOpinion", + "@type": "marl:Opinion" + }, + "prov": "http://www.w3.org/ns/prov#", + "rdfs": "http://www.w3.org/2000/01/rdf-schema#", + "analysis": { + "@id": "prov:wasInformedBy" + }, + "entries": { + "@id": "prov:generated" + }, + "strings": { + "@reverse": "nif:hasContext", + "@type": "nif:String" + }, + "date": + { + "@id": "dc:date", + "@type": "xsd:dateTime" + }, + "wnaffect": "http://www.gsi.dit.upm.es/ontologies/wnaffect#", + "xsd": "http://www.w3.org/2001/XMLSchema#" +} diff --git a/senpy/extensions.py b/senpy/extensions.py index ae33d1e..0d1ee92 100644 --- a/senpy/extensions.py +++ b/senpy/extensions.py @@ -44,13 +44,21 @@ class Senpy(object): if "algorithm" in params: algo = params["algorithm"] if algo in self.plugins and self.plugins[algo].enabled: - return self.plugins[algo].plugin.analyse(**params) + plug = self.plugins[algo] + resp = plug.analyse(**params) + resp.analysis.append(plug.jsonable()) + return resp return {"status": 500, "message": "No valid algorithm"} + def parameters(self, algo): + if algo in self.plugins: + if hasattr(self.plugins[algo], "parameters"): + return self.plugins[algo].parameters + return {} def _load_plugin(self, plugin, search_folder, enabled=True): sys.path.append(search_folder) - tmp = importlib.import_module(plugin) + tmp = importlib.import_module(plugin).plugin sys.path.remove(search_folder) tmp.path = search_folder try: @@ -95,7 +103,6 @@ class Senpy(object): if ctx is not None: if not hasattr(self, '_plugins'): self._plugins = self._load_plugins() - print("Already plugins") return self._plugins if __name__ == '__main__': diff --git a/senpy/models.py b/senpy/models.py new file mode 100644 index 0000000..7dcbf0c --- /dev/null +++ b/senpy/models.py @@ -0,0 +1,59 @@ +import json +import os +from collections import defaultdict + +class Leaf(defaultdict): + def __init__(self, ofclass=list): + super(Leaf, self).__init__(ofclass) + + def __getattr__(self, name): + return super(Leaf, self).__getitem__(name) + + def __setattr__(self, name, value): + self[name] = value + + def __delattr__(self, name): + return super(Leaf, self).__delitem__(name) + +class Response(Leaf): + def __init__(self, context=None): + super(Response, self).__init__() + self["analysis"] = [] + self["entries"] = [] + if context is None: + context = "{}/context.jsonld".format(os.path.dirname( + os.path.realpath(__file__))) + if isinstance(context, dict): + self["@context"] = context + if isinstance(context, basestring): + try: + with open(context) as f: + self["@context"] = json.loads(f.read()) + except IOError: + self["@context"] = context + + +class Entry(Leaf): + def __init__(self, text=None, emotionSets=None, opinions=None, **kwargs): + super(Entry, self).__init__(**kwargs) + if text: + self.text = text + if emotionSets: + self.emotionSets = emotionSets + if opinions: + self.opinions = opinions + +class Opinion(Leaf): + def __init__(self, polarityValue=None, polarity=None, **kwargs): + super(Opinion, self).__init__(**kwargs) + if polarityValue is not None: + self.polarityValue = polarityValue + if polarity is not None: + self.polarity = polarity + + + +class EmotionSet(Leaf): + def __init__(self, emotions=[], **kwargs): + super(EmotionSet, self).__init__(**kwargs) + self.emotions = emotions or [] diff --git a/senpy/plugin.py b/senpy/plugin.py deleted file mode 100644 index d4e1530..0000000 --- a/senpy/plugin.py +++ /dev/null @@ -1,34 +0,0 @@ -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 diff --git a/senpy/plugins/__init__.py b/senpy/plugins/__init__.py index e69de29..5b04937 100644 --- a/senpy/plugins/__init__.py +++ b/senpy/plugins/__init__.py @@ -0,0 +1,54 @@ +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 + + def jsonable(self, parameters=False): + resp = { + "@id": "{}_{}".format(self.name, self.version), + } + if parameters: + resp["parameters"] = self.params, + return resp + +class SentimentPlugin(SenpyPlugin): + def __init__(self, + minPolarityValue=0, + maxPolarityValue=1, + **kwargs): + super(SentimentPlugin, self).__init__(**kwargs) + self.minPolarityValue = minPolarityValue + self.maxPolarityValue = maxPolarityValue + + def jsonable(self, *args, **kwargs): + resp = super(SentimentPlugin, self).jsonable(*args, **kwargs) + resp["marl:maxPolarityValue"] = self.maxPolarityValue + resp["marl:minPolarityValue"] = self.minPolarityValue + return resp + +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 + + def jsonable(self, *args, **kwargs): + resp = super(EmotionPlugin, self).jsonable(*args, **kwargs) + resp["onyx:minEmotionValue"] = self.minEmotionValue + resp["onyx:maxEmotionValue"] = self.maxEmotionValue + return resp diff --git a/senpy/plugins/sentiment140/__init__.py b/senpy/plugins/sentiment140/__init__.py index 0dacde5..5bca403 100644 --- a/senpy/plugins/sentiment140/__init__.py +++ b/senpy/plugins/sentiment140/__init__.py @@ -4,26 +4,31 @@ import json import sys print(sys.path) -from senpy.plugin import SentimentPlugin +from senpy.plugins import SentimentPlugin +from senpy.models import Response, Opinion, Entry class Sentiment140Plugin(SentimentPlugin): + parameters = { + "language": {"aliases": ["language", "l"], + "required": False, + "options": ["es", "en", "auto"], + } + } def __init__(self, **kwargs): super(Sentiment140Plugin, self).__init__(name="Sentiment140", version="1.0", **kwargs) def analyse(self, **params): + lang = params.get("language", "auto") res = requests.post("http://www.sentiment140.com/api/bulkClassifyJson", json.dumps({ - "language": "auto", + "language": lang, "data": [{"text": params["input"]}]} )) - response = {"analysis": [{}], "entries": []} - response["analysis"][0].update({ "marl:algorithm": "SimpleAlgorithm", - "marl:minPolarityValue": 0, - "marl:maxPolarityValue": 100}) + response = Response() polarityValue = int(res.json()["data"][0]["polarity"]) * 25 polarity = "marl:Neutral" if polarityValue > 50: @@ -31,17 +36,12 @@ class Sentiment140Plugin(SentimentPlugin): elif polarityValue < 50: polarity = "marl:Negative" - response["entries"] = [ - { - "isString": params["input"], - "opinions": [{ - "marl:polarityValue": polarityValue, - "marl:hasPolarity": polarity - - }] - } - ] + entry = Entry(text=params["input"]) + opinion = Opinion(polarity=polarity, polarityValue=polarityValue) + entry.opinions.append(opinion) + entry.language = lang + response.entries.append(entry) return response diff --git a/setup.py b/setup.py index 8544350..219ecaa 100644 --- a/setup.py +++ b/setup.py @@ -1,8 +1,9 @@ -from distutils.core import setup +from setuptools import setup + setup( name = 'senpy', packages = ['senpy'], # this must be the same as the name above - version = '0.2', + version = '0.2.2', description = ''' A sentiment analysis server implementation. Designed to be \ extendable, so new algorithms and sources can be used. @@ -10,7 +11,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.2.tar.gz', # I'll explain this in a second + download_url = 'https://github.com/balkian/senpy/archive/0.2.2.tar.gz', keywords = ['eurosentiment', 'sentiment', 'emotions', 'nif'], # arbitrary keywords classifiers = [], )