1
0
mirror of https://github.com/gsi-upm/senpy synced 2025-09-18 04:22:21 +00:00

Several fixes and changes

* Added interactive debugging
* Better exception logging
* More tests for errors
* Added ONBUILD to dockerfile
  Now creating new images based on senpy's is as easy as:
  ```from senpy:<version>```. This will automatically mount the code to
  /senpy-plugins and install all dependencies
* Added /data as a VOLUME
* Added `--use-wheel` to pip install both on the image and in the
  auto-install function.
* Closes #9

Break compatibilitity:

* Removed ability to (de)activate plugins through the web
This commit is contained in:
J. Fernando Sánchez
2017-02-17 01:53:02 +01:00
parent 3311af2167
commit b4ca5f4a7c
16 changed files with 164 additions and 99 deletions

View File

@@ -1 +1 @@
0.7.1
0.7.2-dev1

View File

@@ -26,6 +26,7 @@ from gevent.wsgi import WSGIServer
from gevent.monkey import patch_all
import logging
import os
import sys
import argparse
import senpy
@@ -34,6 +35,22 @@ 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(
@@ -84,6 +101,8 @@ 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()

View File

@@ -102,9 +102,8 @@ def plugins():
@api_blueprint.route('/plugins/<plugin>/', methods=['POST', 'GET'])
@api_blueprint.route('/plugins/<plugin>/<action>', methods=['POST', 'GET'])
@basic_api
def plugin(plugin=None, action="list"):
def plugin(plugin=None):
sp = current_app.senpy
if plugin == 'default' and sp.default_plugin:
response = sp.default_plugin
@@ -113,11 +112,4 @@ def plugin(plugin=None, action="list"):
response = sp.plugins[plugin]
else:
return Error(message="Plugin not found", status=404)
if action == "list":
return response
method = "{}_plugin".format(action)
if (hasattr(sp, method)):
getattr(sp, method)(plugin)
return Response(message="Ok")
else:
return Error(message="action '{}' not allowed".format(action))
return response

View File

@@ -35,11 +35,3 @@ class Client(object):
code=response.status_code,
content=response.content))
raise ex
if __name__ == '__main__':
c = Client('http://senpy.cluster.gsi.dit.upm.es/api/')
resp = c.analyse('hello')
# print(resp)
print(resp.entries)
resp.validate()

View File

@@ -106,8 +106,12 @@ class Senpy(object):
resp = plug.analyse(**nif_params)
resp.analysis.append(plug)
logger.debug("Returning analysis result: {}".format(resp))
except Error as ex:
logger.exception('Error returning analysis result')
resp = ex
except Exception as ex:
resp = Error(message=str(ex), status=500)
logger.exception('Error returning analysis result')
return resp
@property
@@ -120,10 +124,6 @@ class Senpy(object):
else:
return None
def parameters(self, algo):
return getattr(
self.plugins.get(algo) or self.default_plugin, "extra_params", {})
def activate_all(self, sync=False):
ps = []
for plug in self.plugins.keys():
@@ -194,17 +194,6 @@ class Senpy(object):
th = Thread(target=deact)
th.start()
def reload_plugin(self, name):
logger.debug("Reloading {}".format(name))
plugin = self.plugins[name]
try:
del self.plugins[name]
nplug = self._load_plugin(plugin.module, plugin.path)
self.plugins[nplug.name] = nplug
except Exception as ex:
logger.error('Error reloading {}: {}'.format(name, ex))
self.plugins[name] = plugin
@classmethod
def validate_info(cls, info):
return all(x in info for x in ('name', 'module', 'version'))
@@ -219,6 +208,7 @@ class Senpy(object):
if requirements:
pip_args = []
pip_args.append('install')
pip_args.append('--use-wheel')
for req in requirements:
pip_args.append(req)
logger.info('Installing requirements: ' + str(requirements))
@@ -233,32 +223,27 @@ class Senpy(object):
name = info["name"]
sys.path.append(root)
(fp, pathname, desc) = imp.find_module(module, [root, ])
cls._install_deps(info)
tmp = imp.load_module(module, fp, pathname, desc)
sys.path.remove(root)
candidate = None
for _, obj in inspect.getmembers(tmp):
if inspect.isclass(obj) and inspect.getmodule(obj) == tmp:
logger.debug(("Found plugin class:"
" {}@{}").format(obj, inspect.getmodule(obj)))
candidate = obj
break
if not candidate:
logger.debug("No valid plugin for: {}".format(module))
return
module = candidate(info=info)
repo_path = root
try:
cls._install_deps(info)
tmp = imp.load_module(module, fp, pathname, desc)
sys.path.remove(root)
candidate = None
for _, obj in inspect.getmembers(tmp):
if inspect.isclass(obj) and inspect.getmodule(obj) == tmp:
logger.debug(("Found plugin class:"
" {}@{}").format(obj, inspect.getmodule(
obj)))
candidate = obj
break
if not candidate:
logger.debug("No valid plugin for: {}".format(module))
return
module = candidate(info=info)
repo_path = root
module._repo = Repo(repo_path)
except InvalidGitRepositoryError:
logger.debug("The plugin {} is not in a Git repository".format(
module))
module._repo = None
except Exception as ex:
logger.error("Exception importing {}: {}".format(module, ex))
logger.error("Trace: {}".format(traceback.format_exc()))
return None, None
return name, module
@classmethod

View File

@@ -115,7 +115,7 @@ class SenpyMixin(object):
return ser_or_down(self._plain_dict())
def jsonld(self, with_context=True, context_uri=None):
def jsonld(self, with_context=False, context_uri=None):
ser = self.serializable()
if with_context:
@@ -230,6 +230,11 @@ def from_dict(indict):
return cls(**indict)
def from_json(injson):
indict = json.loads(injson)
return from_dict(indict)
def from_schema(name, schema_file=None, base_classes=None):
base_classes = base_classes or []
base_classes.append(BaseModel)
@@ -275,6 +280,15 @@ class Error(SenpyMixin, BaseException):
self._error = _ErrorModel(message=message, *args, **kwargs)
self.message = message
def __getitem__(self, key):
return self._error[key]
def __setitem__(self, key, value):
self._error[key] = value
def __delitem__(self, key):
del self._error[key]
def __getattr__(self, key):
if key != '_error' and hasattr(self._error, key):
return getattr(self._error, key)

View File

@@ -50,6 +50,7 @@ class SentimentPlugin(SenpyPlugin, models.SentimentPlugin):
class EmotionPlugin(SentimentPlugin, models.EmotionPlugin):
def __init__(self, info, *args, **kwargs):
super(EmotionPlugin, self).__init__(info, *args, **kwargs)
self.minEmotionValue = float(info.get("minEmotionValue", 0))
self.maxEmotionValue = float(info.get("maxEmotionValue", 0))
self["@type"] = "onyx:EmotionAnalysis"

View File

@@ -13,7 +13,7 @@
"type": "list",
"items": {"type": "object"}
},
"code": {
"status": {
"type": "int"
},
"required": ["message"]