Commit 0c8f98d4 authored by J. Fernando Sánchez's avatar J. Fernando Sánchez

Pre-0.8.6

* Improved debugging (back to using Flask's built-in mechanisms)
* Recursive model loading from json
* Added DEVPORT to Makefile
* Accept json-ld input. Closes #16
* Improved Exception handling in client
* Modified default plugin selection to only include analysis plugins
* More tests
parent cc298742
...@@ -6,6 +6,7 @@ VERSION=$(shell git describe --tags --dirty 2>/dev/null) ...@@ -6,6 +6,7 @@ VERSION=$(shell git describe --tags --dirty 2>/dev/null)
TARNAME=$(NAME)-$(VERSION).tar.gz TARNAME=$(NAME)-$(VERSION).tar.gz
IMAGENAME=$(REPO)/$(NAME) IMAGENAME=$(REPO)/$(NAME)
IMAGEWTAG=$(IMAGENAME):$(VERSION) IMAGEWTAG=$(IMAGENAME):$(VERSION)
DEVPORT=5000
action="test-${PYMAIN}" action="test-${PYMAIN}"
all: build run all: build run
...@@ -43,7 +44,7 @@ quick_test: $(addprefix test-,$(PYMAIN)) ...@@ -43,7 +44,7 @@ quick_test: $(addprefix test-,$(PYMAIN))
dev-%: dev-%:
@docker start $(NAME)-dev$* || (\ @docker start $(NAME)-dev$* || (\
$(MAKE) build-$*; \ $(MAKE) build-$*; \
docker run -d -w /usr/src/app/ -v $$PWD:/usr/src/app --entrypoint=/bin/bash -ti --name $(NAME)-dev$* '$(IMAGEWTAG)-python$*'; \ docker run -d -w /usr/src/app/ -p $(DEVPORT):5000 -v $$PWD:/usr/src/app --entrypoint=/bin/bash -ti --name $(NAME)-dev$* '$(IMAGEWTAG)-python$*'; \
)\ )\
docker exec -ti $(NAME)-dev$* bash docker exec -ti $(NAME)-dev$* bash
...@@ -86,7 +87,7 @@ pip_upload: ...@@ -86,7 +87,7 @@ pip_upload:
python setup.py sdist upload ; python setup.py sdist upload ;
run-%: build-% run-%: build-%
docker run --rm -p 5000:5000 -ti '$(IMAGEWTAG)-python$(PYMAIN)' --default-plugins docker run --rm -p $(DEVPORT):5000 -ti '$(IMAGEWTAG)-python$(PYMAIN)' --default-plugins
run: run-$(PYMAIN) run: run-$(PYMAIN)
......
...@@ -26,7 +26,6 @@ from gevent.wsgi import WSGIServer ...@@ -26,7 +26,6 @@ from gevent.wsgi import WSGIServer
from gevent.monkey import patch_all from gevent.monkey import patch_all
import logging import logging
import os import os
import sys
import argparse import argparse
import senpy import senpy
...@@ -35,22 +34,6 @@ patch_all(thread=False) ...@@ -35,22 +34,6 @@ patch_all(thread=False)
SERVER_PORT = os.environ.get("PORT", 5000) SERVER_PORT = os.environ.get("PORT", 5000)
def info(type, value, tb):
if hasattr(sys, 'ps1') or not sys.stderr.isatty():
# we are in interactive mode or we don't have a tty-like
# device, so we call the default hook
sys.__excepthook__(type, value, tb)
else:
import traceback
import pdb
# we are NOT in interactive mode, print the exception...
traceback.print_exception(type, value, tb)
print
# ...then start the debugger in post-mortem mode.
# pdb.pm() # deprecated
pdb.post_mortem(tb) # more "modern"
def main(): def main():
parser = argparse.ArgumentParser(description='Run a Senpy server') parser = argparse.ArgumentParser(description='Run a Senpy server')
parser.add_argument( parser.add_argument(
...@@ -100,22 +83,25 @@ def main(): ...@@ -100,22 +83,25 @@ def main():
rl.setLevel(getattr(logging, args.level)) rl.setLevel(getattr(logging, args.level))
app = Flask(__name__) app = Flask(__name__)
app.debug = args.debug app.debug = args.debug
if args.debug:
sys.excepthook = info
sp = Senpy(app, args.plugins_folder, default_plugins=args.default_plugins) sp = Senpy(app, args.plugins_folder, default_plugins=args.default_plugins)
if args.only_install: if args.only_install:
sp.install_deps() sp.install_deps()
return return
sp.activate_all() sp.activate_all()
http_server = WSGIServer((args.host, args.port), app) print('Senpy version {}'.format(senpy.__version__))
try: print('Server running on port %s:%d. Ctrl+C to quit' % (args.host,
print('Senpy version {}'.format(senpy.__version__)) args.port))
print('Server running on port %s:%d. Ctrl+C to quit' % (args.host, if not app.debug:
args.port)) http_server = WSGIServer((args.host, args.port), app)
http_server.serve_forever() try:
except KeyboardInterrupt: http_server.serve_forever()
print('Bye!') except KeyboardInterrupt:
http_server.stop() print('Bye!')
http_server.stop()
else:
app.run(args.host,
args.port,
debug=True)
sp.deactivate_all() sp.deactivate_all()
......
...@@ -70,7 +70,7 @@ NIF_PARAMS = { ...@@ -70,7 +70,7 @@ NIF_PARAMS = {
"aliases": ["f", "informat"], "aliases": ["f", "informat"],
"required": False, "required": False,
"default": "text", "default": "text",
"options": ["turtle", "text"], "options": ["turtle", "text", "json-ld"],
}, },
"intype": { "intype": {
"@id": "intype", "@id": "intype",
......
...@@ -92,6 +92,9 @@ def basic_api(f): ...@@ -92,6 +92,9 @@ def basic_api(f):
response = f(*args, **kwargs) response = f(*args, **kwargs)
except Error as ex: except Error as ex:
response = ex response = ex
logger.error(ex)
if current_app.debug:
raise
in_headers = web_params['inHeaders'] != "0" in_headers = web_params['inHeaders'] != "0"
expanded = api_params['expanded-jsonld'] expanded = api_params['expanded-jsonld']
...@@ -113,11 +116,8 @@ def basic_api(f): ...@@ -113,11 +116,8 @@ def basic_api(f):
@api_blueprint.route('/', methods=['POST', 'GET']) @api_blueprint.route('/', methods=['POST', 'GET'])
@basic_api @basic_api
def api(): def api():
try: response = current_app.senpy.analyse(**request.params)
response = current_app.senpy.analyse(**request.params) return response
return response
except Error as ex:
return ex
@api_blueprint.route('/plugins/', methods=['POST', 'GET']) @api_blueprint.route('/plugins/', methods=['POST', 'GET'])
......
...@@ -7,7 +7,7 @@ standard_library.install_aliases() ...@@ -7,7 +7,7 @@ standard_library.install_aliases()
from . import plugins from . import plugins
from .plugins import SenpyPlugin from .plugins import SenpyPlugin
from .models import Error, Entry, Results, from_dict from .models import Error, Entry, Results, from_string
from .blueprints import api_blueprint, demo_blueprint, ns_blueprint from .blueprints import api_blueprint, demo_blueprint, ns_blueprint
from .api import API_PARAMS, NIF_PARAMS, parse_params from .api import API_PARAMS, NIF_PARAMS, parse_params
...@@ -128,7 +128,7 @@ class Senpy(object): ...@@ -128,7 +128,7 @@ class Senpy(object):
entry = Entry(text=params['input']) entry = Entry(text=params['input'])
results.entries.append(entry) results.entries.append(entry)
elif params['informat'] == 'json-ld': elif params['informat'] == 'json-ld':
results = from_dict(params['input']) results = from_string(params['input'], cls=Results)
else: else:
raise NotImplemented('Informat {} is not implemented'.format(params['informat'])) raise NotImplemented('Informat {} is not implemented'.format(params['informat']))
return results return results
...@@ -171,7 +171,7 @@ class Senpy(object): ...@@ -171,7 +171,7 @@ class Senpy(object):
except (Error, Exception) as ex: except (Error, Exception) as ex:
if not isinstance(ex, Error): if not isinstance(ex, Error):
ex = Error(message=str(ex), status=500) ex = Error(message=str(ex), status=500)
logger.exception('Error returning analysis result') logger.error('Error returning analysis result')
raise ex raise ex
return resp return resp
...@@ -236,7 +236,8 @@ class Senpy(object): ...@@ -236,7 +236,8 @@ class Senpy(object):
def default_plugin(self): def default_plugin(self):
candidate = self._default candidate = self._default
if not candidate: if not candidate:
candidates = self.filter_plugins(is_activated=True) candidates = self.filter_plugins(plugin_type='analysisPlugin',
is_activated=True)
if len(candidates) > 0: if len(candidates) > 0:
candidate = list(candidates.values())[0] candidate = list(candidates.values())[0]
logger.debug("Default: {}".format(candidate)) logger.debug("Default: {}".format(candidate))
......
...@@ -214,6 +214,7 @@ class BaseModel(SenpyMixin, dict): ...@@ -214,6 +214,7 @@ class BaseModel(SenpyMixin, dict):
temp['@type'] = getattr(self, '@type') temp['@type'] = getattr(self, '@type')
except AttributeError: except AttributeError:
logger.warn('Creating an instance of an unknown model') logger.warn('Creating an instance of an unknown model')
super(BaseModel, self).__init__(temp) super(BaseModel, self).__init__(temp)
def _get_key(self, key): def _get_key(self, key):
...@@ -252,13 +253,32 @@ def register(rsubclass, rtype=None): ...@@ -252,13 +253,32 @@ def register(rsubclass, rtype=None):
_subtypes[rtype or rsubclass.__name__] = rsubclass _subtypes[rtype or rsubclass.__name__] = rsubclass
def from_dict(indict): def from_dict(indict, cls=None):
target = indict.get('@type', None) if not cls:
if target and target in _subtypes: target = indict.get('@type', None)
cls = _subtypes[target] try:
else: if target and target in _subtypes:
cls = BaseModel cls = _subtypes[target]
return cls(**indict) else:
cls = BaseModel
except Exception:
cls = BaseModel
outdict = dict()
for k, v in indict.items():
if k == '@context':
pass
elif isinstance(v, dict):
v = from_dict(indict[k])
elif isinstance(v, list):
for ix, v2 in enumerate(v):
if isinstance(v2, dict):
v[ix] = from_dict(v2)
outdict[k] = v
return cls(**outdict)
def from_string(string, **kwargs):
return from_dict(json.loads(string), **kwargs)
def from_json(injson): def from_json(injson):
...@@ -308,7 +328,7 @@ for i in [ ...@@ -308,7 +328,7 @@ for i in [
_ErrorModel = from_schema('error') _ErrorModel = from_schema('error')
class Error(SenpyMixin, BaseException): class Error(SenpyMixin, Exception):
def __init__(self, message, *args, **kwargs): def __init__(self, message, *args, **kwargs):
super(Error, self).__init__(self, message, message) super(Error, self).__init__(self, message, message)
self._error = _ErrorModel(message=message, *args, **kwargs) self._error = _ErrorModel(message=message, *args, **kwargs)
......
...@@ -19,6 +19,7 @@ def parse_resp(resp): ...@@ -19,6 +19,7 @@ def parse_resp(resp):
class BlueprintsTest(TestCase): class BlueprintsTest(TestCase):
def setUp(self): def setUp(self):
self.app = Flask("test_extensions") self.app = Flask("test_extensions")
self.app.debug = False
self.client = self.app.test_client() self.client = self.app.test_client()
self.senpy = Senpy() self.senpy = Senpy()
self.senpy.init_app(self.app) self.senpy.init_app(self.app)
......
...@@ -96,6 +96,27 @@ class ExtensionsTest(TestCase): ...@@ -96,6 +96,27 @@ class ExtensionsTest(TestCase):
assert r2.analysis[0] == "plugins/Dummy_0.1" assert r2.analysis[0] == "plugins/Dummy_0.1"
assert r1.entries[0].text == 'input' assert r1.entries[0].text == 'input'
def test_analyse_jsonld(self):
""" Using a plugin with JSON-LD input"""
js_input = '''{
"@id": "prueba",
"@type": "results",
"entries": [
{"@id": "entry1",
"text": "tupni",
"@type": "entry"
}
]
}'''
r1 = self.senpy.analyse(algorithm="Dummy",
input=js_input,
informat="json-ld",
output="tuptuo")
r2 = self.senpy.analyse(input="tupni", output="tuptuo")
assert r1.analysis[0] == "plugins/Dummy_0.1"
assert r2.analysis[0] == "plugins/Dummy_0.1"
assert r1.entries[0].text == 'input'
def test_analyse_error(self): def test_analyse_error(self):
mm = mock.MagicMock() mm = mock.MagicMock()
mm.id = 'magic_mock' mm.id = 'magic_mock'
......
...@@ -13,7 +13,9 @@ from senpy.models import (Emotion, ...@@ -13,7 +13,9 @@ from senpy.models import (Emotion,
Results, Results,
Sentiment, Sentiment,
Plugins, Plugins,
Plugin) Plugin,
from_string,
from_dict)
from senpy import plugins from senpy import plugins
from pprint import pprint from pprint import pprint
...@@ -167,3 +169,22 @@ class ModelsTest(TestCase): ...@@ -167,3 +169,22 @@ class ModelsTest(TestCase):
assert isinstance(plugs.plugins, list) assert isinstance(plugs.plugins, list)
js = plugs.jsonld() js = plugs.jsonld()
assert isinstance(js['plugins'], list) assert isinstance(js['plugins'], list)
def test_from_string(self):
results = {
'@type': 'results',
'@id': 'prueba',
'entries': [{
'@id': 'entry1',
'@type': 'entry',
'text': 'TEST'
}]
}
recovered = from_dict(results)
assert isinstance(recovered, Results)
assert isinstance(recovered.entries[0], Entry)
string = json.dumps(results)
recovered = from_string(string)
assert isinstance(recovered, Results)
assert isinstance(recovered.entries[0], Entry)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment