diff --git a/Makefile b/Makefile index 9bfb268..0677ab7 100644 --- a/Makefile +++ b/Makefile @@ -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) diff --git a/senpy/__main__.py b/senpy/__main__.py index 932f57c..dd711e4 100644 --- a/senpy/__main__.py +++ b/senpy/__main__.py @@ -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)) - http_server.serve_forever() - except KeyboardInterrupt: - print('Bye!') - http_server.stop() + 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() diff --git a/senpy/api.py b/senpy/api.py index 7158727..886901b 100644 --- a/senpy/api.py +++ b/senpy/api.py @@ -70,7 +70,7 @@ NIF_PARAMS = { "aliases": ["f", "informat"], "required": False, "default": "text", - "options": ["turtle", "text"], + "options": ["turtle", "text", "json-ld"], }, "intype": { "@id": "intype", diff --git a/senpy/blueprints.py b/senpy/blueprints.py index d0a9cf8..c0a66db 100644 --- a/senpy/blueprints.py +++ b/senpy/blueprints.py @@ -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 + response = current_app.senpy.analyse(**request.params) + return response @api_blueprint.route('/plugins/', methods=['POST', 'GET']) diff --git a/senpy/extensions.py b/senpy/extensions.py index d5bdc80..2cc5247 100644 --- a/senpy/extensions.py +++ b/senpy/extensions.py @@ -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)) diff --git a/senpy/models.py b/senpy/models.py index 72c3d7f..9ce3f66 100644 --- a/senpy/models.py +++ b/senpy/models.py @@ -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): - target = indict.get('@type', None) - if target and target in _subtypes: - cls = _subtypes[target] - else: - cls = BaseModel - return cls(**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 + 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) diff --git a/tests/test_blueprints.py b/tests/test_blueprints.py index 2eaae64..78b22ce 100644 --- a/tests/test_blueprints.py +++ b/tests/test_blueprints.py @@ -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) diff --git a/tests/test_extensions.py b/tests/test_extensions.py index c1722c8..ba854e3 100644 --- a/tests/test_extensions.py +++ b/tests/test_extensions.py @@ -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' diff --git a/tests/test_models.py b/tests/test_models.py index 3f0d736..7e3f020 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -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)