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)
TARNAME=$(NAME)-$(VERSION).tar.gz
IMAGENAME=$(REPO)/$(NAME)
IMAGEWTAG=$(IMAGENAME):$(VERSION)
DEVPORT=5000
action="test-${PYMAIN}"
all: build run
......@@ -43,7 +44,7 @@ quick_test: $(addprefix test-,$(PYMAIN))
dev-%:
@docker start $(NAME)-dev$* || (\
$(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
......@@ -86,7 +87,7 @@ pip_upload:
python setup.py sdist upload ;
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)
......
......@@ -26,7 +26,6 @@ from gevent.wsgi import WSGIServer
from gevent.monkey import patch_all
import logging
import os
import sys
import argparse
import senpy
......@@ -35,22 +34,6 @@ patch_all(thread=False)
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():
parser = argparse.ArgumentParser(description='Run a Senpy server')
parser.add_argument(
......@@ -100,22 +83,25 @@ def main():
rl.setLevel(getattr(logging, args.level))
app = Flask(__name__)
app.debug = args.debug
if args.debug:
sys.excepthook = info
sp = Senpy(app, args.plugins_folder, default_plugins=args.default_plugins)
if args.only_install:
sp.install_deps()
return
sp.activate_all()
http_server = WSGIServer((args.host, args.port), app)
try:
print('Senpy version {}'.format(senpy.__version__))
print('Server running on port %s:%d. Ctrl+C to quit' % (args.host,
args.port))
if not app.debug:
http_server = WSGIServer((args.host, args.port), app)
try:
http_server.serve_forever()
except KeyboardInterrupt:
print('Bye!')
http_server.stop()
else:
app.run(args.host,
args.port,
debug=True)
sp.deactivate_all()
......
......@@ -70,7 +70,7 @@ NIF_PARAMS = {
"aliases": ["f", "informat"],
"required": False,
"default": "text",
"options": ["turtle", "text"],
"options": ["turtle", "text", "json-ld"],
},
"intype": {
"@id": "intype",
......
......@@ -92,6 +92,9 @@ def basic_api(f):
response = f(*args, **kwargs)
except Error as ex:
response = ex
logger.error(ex)
if current_app.debug:
raise
in_headers = web_params['inHeaders'] != "0"
expanded = api_params['expanded-jsonld']
......@@ -113,11 +116,8 @@ def basic_api(f):
@api_blueprint.route('/', methods=['POST', 'GET'])
@basic_api
def api():
try:
response = current_app.senpy.analyse(**request.params)
return response
except Error as ex:
return ex
@api_blueprint.route('/plugins/', methods=['POST', 'GET'])
......
......@@ -7,7 +7,7 @@ standard_library.install_aliases()
from . import plugins
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 .api import API_PARAMS, NIF_PARAMS, parse_params
......@@ -128,7 +128,7 @@ class Senpy(object):
entry = Entry(text=params['input'])
results.entries.append(entry)
elif params['informat'] == 'json-ld':
results = from_dict(params['input'])
results = from_string(params['input'], cls=Results)
else:
raise NotImplemented('Informat {} is not implemented'.format(params['informat']))
return results
......@@ -171,7 +171,7 @@ class Senpy(object):
except (Error, Exception) as ex:
if not isinstance(ex, Error):
ex = Error(message=str(ex), status=500)
logger.exception('Error returning analysis result')
logger.error('Error returning analysis result')
raise ex
return resp
......@@ -236,7 +236,8 @@ class Senpy(object):
def default_plugin(self):
candidate = self._default
if not candidate:
candidates = self.filter_plugins(is_activated=True)
candidates = self.filter_plugins(plugin_type='analysisPlugin',
is_activated=True)
if len(candidates) > 0:
candidate = list(candidates.values())[0]
logger.debug("Default: {}".format(candidate))
......
......@@ -214,6 +214,7 @@ class BaseModel(SenpyMixin, dict):
temp['@type'] = getattr(self, '@type')
except AttributeError:
logger.warn('Creating an instance of an unknown model')
super(BaseModel, self).__init__(temp)
def _get_key(self, key):
......@@ -252,13 +253,32 @@ def register(rsubclass, rtype=None):
_subtypes[rtype or rsubclass.__name__] = rsubclass
def from_dict(indict):
def from_dict(indict, cls=None):
if not cls:
target = indict.get('@type', None)
try:
if target and target in _subtypes:
cls = _subtypes[target]
else:
cls = BaseModel
return cls(**indict)
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):
......@@ -308,7 +328,7 @@ for i in [
_ErrorModel = from_schema('error')
class Error(SenpyMixin, BaseException):
class Error(SenpyMixin, Exception):
def __init__(self, message, *args, **kwargs):
super(Error, self).__init__(self, message, message)
self._error = _ErrorModel(message=message, *args, **kwargs)
......
......@@ -19,6 +19,7 @@ def parse_resp(resp):
class BlueprintsTest(TestCase):
def setUp(self):
self.app = Flask("test_extensions")
self.app.debug = False
self.client = self.app.test_client()
self.senpy = Senpy()
self.senpy.init_app(self.app)
......
......@@ -96,6 +96,27 @@ class ExtensionsTest(TestCase):
assert r2.analysis[0] == "plugins/Dummy_0.1"
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):
mm = mock.MagicMock()
mm.id = 'magic_mock'
......
......@@ -13,7 +13,9 @@ from senpy.models import (Emotion,
Results,
Sentiment,
Plugins,
Plugin)
Plugin,
from_string,
from_dict)
from senpy import plugins
from pprint import pprint
......@@ -167,3 +169,22 @@ class ModelsTest(TestCase):
assert isinstance(plugs.plugins, list)
js = plugs.jsonld()
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