mirror of
https://github.com/gsi-upm/senpy
synced 2025-09-17 20:12:22 +00:00
Compare commits
7 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
c939b095de | ||
|
6dd4a44924 | ||
|
4291c5eabf | ||
|
7c7a815d1a | ||
|
a3eb8f196c | ||
|
00ffbb3804 | ||
|
13cf0c71c5 |
@@ -12,3 +12,4 @@ rdflib-jsonld
|
|||||||
numpy
|
numpy
|
||||||
scipy
|
scipy
|
||||||
scikit-learn
|
scikit-learn
|
||||||
|
responses
|
||||||
|
@@ -78,10 +78,15 @@ def main():
|
|||||||
help='Do not run a server, only install plugin dependencies')
|
help='Do not run a server, only install plugin dependencies')
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--only-test',
|
'--only-test',
|
||||||
'-t',
|
|
||||||
action='store_true',
|
action='store_true',
|
||||||
default=False,
|
default=False,
|
||||||
help='Do not run a server, just test all plugins')
|
help='Do not run a server, just test all plugins')
|
||||||
|
parser.add_argument(
|
||||||
|
'--test',
|
||||||
|
'-t',
|
||||||
|
action='store_true',
|
||||||
|
default=False,
|
||||||
|
help='Test all plugins before launching the server')
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--only-list',
|
'--only-list',
|
||||||
'--list',
|
'--list',
|
||||||
@@ -99,6 +104,12 @@ def main():
|
|||||||
action='store_false',
|
action='store_false',
|
||||||
default=True,
|
default=True,
|
||||||
help='Run a threaded server')
|
help='Run a threaded server')
|
||||||
|
parser.add_argument(
|
||||||
|
'--no-deps',
|
||||||
|
'-n',
|
||||||
|
action='store_true',
|
||||||
|
default=False,
|
||||||
|
help='Skip installing dependencies')
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--version',
|
'--version',
|
||||||
'-v',
|
'-v',
|
||||||
@@ -125,18 +136,26 @@ def main():
|
|||||||
data_folder=args.data_folder)
|
data_folder=args.data_folder)
|
||||||
if args.only_list:
|
if args.only_list:
|
||||||
plugins = sp.plugins()
|
plugins = sp.plugins()
|
||||||
maxwidth = max(len(x.id) for x in plugins)
|
maxname = max(len(x.name) for x in plugins)
|
||||||
|
maxversion = max(len(x.version) for x in plugins)
|
||||||
|
print('Found {} plugins:'.format(len(plugins)))
|
||||||
for plugin in plugins:
|
for plugin in plugins:
|
||||||
import inspect
|
import inspect
|
||||||
fpath = inspect.getfile(plugin.__class__)
|
fpath = inspect.getfile(plugin.__class__)
|
||||||
print('{: <{width}} @ {}'.format(plugin.id, fpath, width=maxwidth))
|
print('\t{: <{maxname}} @ {: <{maxversion}} -> {}'.format(plugin.name,
|
||||||
|
plugin.version,
|
||||||
|
fpath,
|
||||||
|
maxname=maxname,
|
||||||
|
maxversion=maxversion))
|
||||||
return
|
return
|
||||||
|
if not args.no_deps:
|
||||||
sp.install_deps()
|
sp.install_deps()
|
||||||
if args.only_install:
|
if args.only_install:
|
||||||
return
|
return
|
||||||
sp.activate_all(allow_fail=args.allow_fail)
|
sp.activate_all(allow_fail=args.allow_fail)
|
||||||
if args.only_test:
|
if args.test or args.only_test:
|
||||||
easy_test(sp.plugins(), debug=args.debug)
|
easy_test(sp.plugins(), debug=args.debug)
|
||||||
|
if args.only_test:
|
||||||
return
|
return
|
||||||
print('Senpy version {}'.format(senpy.__version__))
|
print('Senpy version {}'.format(senpy.__version__))
|
||||||
print('Server running on port %s:%d. Ctrl+C to quit' % (args.host,
|
print('Server running on port %s:%d. Ctrl+C to quit' % (args.host,
|
||||||
|
23
senpy/api.py
23
senpy/api.py
@@ -3,6 +3,10 @@ from .models import Error, Results, Entry, from_string
|
|||||||
import logging
|
import logging
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
boolean = [True, False]
|
||||||
|
|
||||||
|
|
||||||
API_PARAMS = {
|
API_PARAMS = {
|
||||||
"algorithm": {
|
"algorithm": {
|
||||||
"aliases": ["algorithms", "a", "algo"],
|
"aliases": ["algorithms", "a", "algo"],
|
||||||
@@ -13,14 +17,14 @@ API_PARAMS = {
|
|||||||
"expanded-jsonld": {
|
"expanded-jsonld": {
|
||||||
"@id": "expanded-jsonld",
|
"@id": "expanded-jsonld",
|
||||||
"aliases": ["expanded"],
|
"aliases": ["expanded"],
|
||||||
"options": "boolean",
|
"options": boolean,
|
||||||
"required": True,
|
"required": True,
|
||||||
"default": False
|
"default": False
|
||||||
},
|
},
|
||||||
"with_parameters": {
|
"with_parameters": {
|
||||||
"aliases": ['withparameters',
|
"aliases": ['withparameters',
|
||||||
'with-parameters'],
|
'with-parameters'],
|
||||||
"options": "boolean",
|
"options": boolean,
|
||||||
"default": False,
|
"default": False,
|
||||||
"required": True
|
"required": True
|
||||||
},
|
},
|
||||||
@@ -29,14 +33,14 @@ API_PARAMS = {
|
|||||||
"aliases": ["o"],
|
"aliases": ["o"],
|
||||||
"default": "json-ld",
|
"default": "json-ld",
|
||||||
"required": True,
|
"required": True,
|
||||||
"options": ["json-ld", "turtle"],
|
"options": ["json-ld", "turtle", "ntriples"],
|
||||||
},
|
},
|
||||||
"help": {
|
"help": {
|
||||||
"@id": "help",
|
"@id": "help",
|
||||||
"description": "Show additional help to know more about the possible parameters",
|
"description": "Show additional help to know more about the possible parameters",
|
||||||
"aliases": ["h"],
|
"aliases": ["h"],
|
||||||
"required": True,
|
"required": True,
|
||||||
"options": "boolean",
|
"options": boolean,
|
||||||
"default": False
|
"default": False
|
||||||
},
|
},
|
||||||
"emotionModel": {
|
"emotionModel": {
|
||||||
@@ -83,7 +87,7 @@ WEB_PARAMS = {
|
|||||||
"aliases": ["headers"],
|
"aliases": ["headers"],
|
||||||
"required": True,
|
"required": True,
|
||||||
"default": False,
|
"default": False,
|
||||||
"options": "boolean"
|
"options": boolean
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,7 +136,7 @@ NIF_PARAMS = {
|
|||||||
"aliases": ["u"],
|
"aliases": ["u"],
|
||||||
"required": False,
|
"required": False,
|
||||||
"default": "RFC5147String",
|
"default": "RFC5147String",
|
||||||
"options": "RFC5147String"
|
"options": ["RFC5147String", ]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,7 +163,7 @@ def parse_params(indict, *specs):
|
|||||||
wrong_params[param] = spec[param]
|
wrong_params[param] = spec[param]
|
||||||
continue
|
continue
|
||||||
if "options" in options:
|
if "options" in options:
|
||||||
if options["options"] == "boolean":
|
if options["options"] == boolean:
|
||||||
outdict[param] = outdict[param] in [None, True, 'true', '1']
|
outdict[param] = outdict[param] in [None, True, 'true', '1']
|
||||||
elif outdict[param] not in options["options"]:
|
elif outdict[param] not in options["options"]:
|
||||||
wrong_params[param] = spec[param]
|
wrong_params[param] = spec[param]
|
||||||
@@ -172,7 +176,7 @@ def parse_params(indict, *specs):
|
|||||||
errors=wrong_params)
|
errors=wrong_params)
|
||||||
raise message
|
raise message
|
||||||
if 'algorithm' in outdict and not isinstance(outdict['algorithm'], list):
|
if 'algorithm' in outdict and not isinstance(outdict['algorithm'], list):
|
||||||
outdict['algorithm'] = outdict['algorithm'].split(',')
|
outdict['algorithm'] = list(outdict['algorithm'].split(','))
|
||||||
return outdict
|
return outdict
|
||||||
|
|
||||||
|
|
||||||
@@ -190,7 +194,8 @@ def parse_call(params):
|
|||||||
params = parse_params(params, NIF_PARAMS)
|
params = parse_params(params, NIF_PARAMS)
|
||||||
if params['informat'] == 'text':
|
if params['informat'] == 'text':
|
||||||
results = Results()
|
results = Results()
|
||||||
entry = Entry(nif__isString=params['input'])
|
entry = Entry(nif__isString=params['input'],
|
||||||
|
id='#') # Use @base
|
||||||
results.entries.append(entry)
|
results.entries.append(entry)
|
||||||
elif params['informat'] == 'json-ld':
|
elif params['informat'] == 'json-ld':
|
||||||
results = from_string(params['input'], cls=Results)
|
results = from_string(params['input'], cls=Results)
|
||||||
|
@@ -18,15 +18,15 @@
|
|||||||
Blueprints for Senpy
|
Blueprints for Senpy
|
||||||
"""
|
"""
|
||||||
from flask import (Blueprint, request, current_app, render_template, url_for,
|
from flask import (Blueprint, request, current_app, render_template, url_for,
|
||||||
jsonify)
|
jsonify, redirect)
|
||||||
from .models import Error, Response, Help, Plugins, read_schema, dump_schema, Datasets
|
from .models import Error, Response, Help, Plugins, read_schema, dump_schema, Datasets
|
||||||
from . import api
|
from . import api
|
||||||
from .version import __version__
|
from .version import __version__
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import traceback
|
|
||||||
import json
|
import json
|
||||||
|
import base64
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -34,6 +34,24 @@ api_blueprint = Blueprint("api", __name__)
|
|||||||
demo_blueprint = Blueprint("demo", __name__, template_folder='templates')
|
demo_blueprint = Blueprint("demo", __name__, template_folder='templates')
|
||||||
ns_blueprint = Blueprint("ns", __name__)
|
ns_blueprint = Blueprint("ns", __name__)
|
||||||
|
|
||||||
|
_mimetypes_r = {'json-ld': ['application/ld+json'],
|
||||||
|
'turtle': ['text/turtle'],
|
||||||
|
'ntriples': ['application/n-triples'],
|
||||||
|
'text': ['text/plain']}
|
||||||
|
|
||||||
|
MIMETYPES = {}
|
||||||
|
|
||||||
|
for k, vs in _mimetypes_r.items():
|
||||||
|
for v in vs:
|
||||||
|
if v in MIMETYPES:
|
||||||
|
raise Exception('MIMETYPE {} specified for two formats: {} and {}'.format(v,
|
||||||
|
v,
|
||||||
|
MIMETYPES[v]))
|
||||||
|
MIMETYPES[v] = k
|
||||||
|
|
||||||
|
DEFAULT_MIMETYPE = 'application/ld+json'
|
||||||
|
DEFAULT_FORMAT = 'json-ld'
|
||||||
|
|
||||||
|
|
||||||
def get_params(req):
|
def get_params(req):
|
||||||
if req.method == 'POST':
|
if req.method == 'POST':
|
||||||
@@ -45,6 +63,30 @@ def get_params(req):
|
|||||||
return indict
|
return indict
|
||||||
|
|
||||||
|
|
||||||
|
def encoded_url(url=None, base=None):
|
||||||
|
code = ''
|
||||||
|
if not url:
|
||||||
|
if request.method == 'GET':
|
||||||
|
url = request.full_path[1:] # Remove the first slash
|
||||||
|
else:
|
||||||
|
hash(frozenset(request.parameters.items()))
|
||||||
|
code = 'hash:{}'.format(hash)
|
||||||
|
|
||||||
|
code = code or base64.urlsafe_b64encode(url.encode()).decode()
|
||||||
|
|
||||||
|
if base:
|
||||||
|
return base + code
|
||||||
|
return url_for('api.decode', code=code, _external=True)
|
||||||
|
|
||||||
|
|
||||||
|
def decoded_url(code, base=None):
|
||||||
|
if code.startswith('hash:'):
|
||||||
|
raise Exception('Can not decode a URL for a POST request')
|
||||||
|
base = base or request.url_root
|
||||||
|
path = base64.urlsafe_b64decode(code.encode()).decode()
|
||||||
|
return base + path
|
||||||
|
|
||||||
|
|
||||||
@demo_blueprint.route('/')
|
@demo_blueprint.route('/')
|
||||||
def index():
|
def index():
|
||||||
ev = str(get_params(request).get('evaluation', False))
|
ev = str(get_params(request).get('evaluation', False))
|
||||||
@@ -59,13 +101,22 @@ def index():
|
|||||||
def context(entity="context"):
|
def context(entity="context"):
|
||||||
context = Response._context
|
context = Response._context
|
||||||
context['@vocab'] = url_for('ns.index', _external=True)
|
context['@vocab'] = url_for('ns.index', _external=True)
|
||||||
|
context['endpoint'] = url_for('api.api_root', _external=True)
|
||||||
return jsonify({"@context": context})
|
return jsonify({"@context": context})
|
||||||
|
|
||||||
|
|
||||||
|
@api_blueprint.route('/d/<code>')
|
||||||
|
def decode(code):
|
||||||
|
try:
|
||||||
|
return redirect(decoded_url(code))
|
||||||
|
except Exception:
|
||||||
|
return Error('invalid URL').flask()
|
||||||
|
|
||||||
|
|
||||||
@ns_blueprint.route('/') # noqa: F811
|
@ns_blueprint.route('/') # noqa: F811
|
||||||
def index():
|
def index():
|
||||||
context = Response._context
|
context = Response._context.copy()
|
||||||
context['@vocab'] = url_for('.ns', _external=True)
|
context['endpoint'] = url_for('api.api_root', _external=True)
|
||||||
return jsonify({"@context": context})
|
return jsonify({"@context": context})
|
||||||
|
|
||||||
|
|
||||||
@@ -81,7 +132,7 @@ def basic_api(f):
|
|||||||
default_params = {
|
default_params = {
|
||||||
'inHeaders': False,
|
'inHeaders': False,
|
||||||
'expanded-jsonld': False,
|
'expanded-jsonld': False,
|
||||||
'outformat': 'json-ld',
|
'outformat': None,
|
||||||
'with_parameters': True,
|
'with_parameters': True,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,42 +151,52 @@ def basic_api(f):
|
|||||||
request.parameters = params
|
request.parameters = params
|
||||||
response = f(*args, **kwargs)
|
response = f(*args, **kwargs)
|
||||||
except (Exception) as ex:
|
except (Exception) as ex:
|
||||||
if current_app.debug:
|
if current_app.debug or current_app.config['TESTING']:
|
||||||
raise
|
raise
|
||||||
if not isinstance(ex, Error):
|
if not isinstance(ex, Error):
|
||||||
msg = "{}:\n\t{}".format(ex,
|
msg = "{}".format(ex)
|
||||||
traceback.format_exc())
|
|
||||||
ex = Error(message=msg, status=500)
|
ex = Error(message=msg, status=500)
|
||||||
logger.exception('Error returning analysis result')
|
|
||||||
response = ex
|
response = ex
|
||||||
response.parameters = raw_params
|
response.parameters = raw_params
|
||||||
logger.error(ex)
|
logger.exception(ex)
|
||||||
|
|
||||||
if 'parameters' in response and not params['with_parameters']:
|
if 'parameters' in response and not params['with_parameters']:
|
||||||
del response.parameters
|
del response.parameters
|
||||||
|
|
||||||
logger.info('Response: {}'.format(response))
|
logger.info('Response: {}'.format(response))
|
||||||
|
mime = request.accept_mimetypes\
|
||||||
|
.best_match(MIMETYPES.keys(),
|
||||||
|
DEFAULT_MIMETYPE)
|
||||||
|
|
||||||
|
mimeformat = MIMETYPES.get(mime, DEFAULT_FORMAT)
|
||||||
|
outformat = params['outformat'] or mimeformat
|
||||||
|
|
||||||
return response.flask(
|
return response.flask(
|
||||||
in_headers=params['inHeaders'],
|
in_headers=params['inHeaders'],
|
||||||
headers=headers,
|
headers=headers,
|
||||||
prefix=url_for('.api_root', _external=True),
|
prefix=params.get('prefix', encoded_url()),
|
||||||
context_uri=url_for('api.context',
|
context_uri=url_for('api.context',
|
||||||
entity=type(response).__name__,
|
entity=type(response).__name__,
|
||||||
_external=True),
|
_external=True),
|
||||||
outformat=params['outformat'],
|
outformat=outformat,
|
||||||
expanded=params['expanded-jsonld'])
|
expanded=params['expanded-jsonld'])
|
||||||
|
|
||||||
return decorated_function
|
return decorated_function
|
||||||
|
|
||||||
|
|
||||||
@api_blueprint.route('/', methods=['POST', 'GET'])
|
@api_blueprint.route('/', defaults={'plugin': None}, methods=['POST', 'GET'])
|
||||||
|
@api_blueprint.route('/<path:plugin>', methods=['POST', 'GET'])
|
||||||
@basic_api
|
@basic_api
|
||||||
def api_root():
|
def api_root(plugin):
|
||||||
if request.parameters['help']:
|
if request.parameters['help']:
|
||||||
dic = dict(api.API_PARAMS, **api.NIF_PARAMS)
|
dic = dict(api.API_PARAMS, **api.NIF_PARAMS)
|
||||||
response = Help(valid_parameters=dic)
|
response = Help(valid_parameters=dic)
|
||||||
return response
|
return response
|
||||||
req = api.parse_call(request.parameters)
|
req = api.parse_call(request.parameters)
|
||||||
|
if plugin:
|
||||||
|
plugin = plugin.replace('+', '/')
|
||||||
|
plugin = plugin.split('/')
|
||||||
|
req.parameters['algorithm'] = plugin
|
||||||
return current_app.senpy.analyse(req)
|
return current_app.senpy.analyse(req)
|
||||||
|
|
||||||
|
|
||||||
@@ -165,7 +226,7 @@ def plugins():
|
|||||||
|
|
||||||
@api_blueprint.route('/plugins/<plugin>/', methods=['POST', 'GET'])
|
@api_blueprint.route('/plugins/<plugin>/', methods=['POST', 'GET'])
|
||||||
@basic_api
|
@basic_api
|
||||||
def plugin(plugin=None):
|
def plugin(plugin):
|
||||||
sp = current_app.senpy
|
sp = current_app.senpy
|
||||||
return sp.get_plugin(plugin)
|
return sp.get_plugin(plugin)
|
||||||
|
|
||||||
|
@@ -24,7 +24,7 @@ class Client(object):
|
|||||||
return {d.name: d for d in resp}
|
return {d.name: d for d in resp}
|
||||||
|
|
||||||
def request(self, path=None, method='GET', **params):
|
def request(self, path=None, method='GET', **params):
|
||||||
url = '{}{}'.format(self.endpoint, path)
|
url = '{}{}'.format(self.endpoint.rstrip('/'), path)
|
||||||
response = requests.request(method=method, url=url, params=params)
|
response = requests.request(method=method, url=url, params=params)
|
||||||
try:
|
try:
|
||||||
resp = models.from_dict(response.json())
|
resp = models.from_dict(response.json())
|
||||||
|
@@ -18,14 +18,9 @@ import errno
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
from . import gsitk_compat
|
||||||
|
|
||||||
try:
|
logger = logging.getLogger(__name__)
|
||||||
from gsitk.datasets.datasets import DatasetManager
|
|
||||||
GSITK_AVAILABLE = True
|
|
||||||
except ImportError:
|
|
||||||
logger.warn('GSITK is not installed. Some functions will be unavailable.')
|
|
||||||
GSITK_AVAILABLE = False
|
|
||||||
|
|
||||||
|
|
||||||
class Senpy(object):
|
class Senpy(object):
|
||||||
@@ -95,7 +90,7 @@ class Senpy(object):
|
|||||||
if plugin in self._plugins:
|
if plugin in self._plugins:
|
||||||
return self._plugins[plugin]
|
return self._plugins[plugin]
|
||||||
|
|
||||||
results = self.plugins(id='plugins/{}'.format(name))
|
results = self.plugins(id='endpoint:plugins/{}'.format(name))
|
||||||
|
|
||||||
if not results:
|
if not results:
|
||||||
return Error(message="Plugin not found", status=404)
|
return Error(message="Plugin not found", status=404)
|
||||||
@@ -158,7 +153,6 @@ class Senpy(object):
|
|||||||
yield i
|
yield i
|
||||||
return
|
return
|
||||||
plugin = plugins[0]
|
plugin = plugins[0]
|
||||||
self._activate(plugin) # Make sure the plugin is activated
|
|
||||||
specific_params = api.parse_extra_params(req, plugin)
|
specific_params = api.parse_extra_params(req, plugin)
|
||||||
req.analysis.append({'plugin': plugin,
|
req.analysis.append({'plugin': plugin,
|
||||||
'parameters': specific_params})
|
'parameters': specific_params})
|
||||||
@@ -167,8 +161,7 @@ class Senpy(object):
|
|||||||
yield i
|
yield i
|
||||||
|
|
||||||
def install_deps(self):
|
def install_deps(self):
|
||||||
for plugin in self.plugins(is_activated=True):
|
plugins.install_deps(*self.plugins())
|
||||||
plugins.install_deps(plugin)
|
|
||||||
|
|
||||||
def analyse(self, request):
|
def analyse(self, request):
|
||||||
"""
|
"""
|
||||||
@@ -203,16 +196,14 @@ class Senpy(object):
|
|||||||
raise Error(
|
raise Error(
|
||||||
status=404,
|
status=404,
|
||||||
message="The dataset '{}' is not valid".format(dataset))
|
message="The dataset '{}' is not valid".format(dataset))
|
||||||
dm = DatasetManager()
|
dm = gsitk_compat.DatasetManager()
|
||||||
datasets = dm.prepare_datasets(datasets_name)
|
datasets = dm.prepare_datasets(datasets_name)
|
||||||
return datasets
|
return datasets
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def datasets(self):
|
def datasets(self):
|
||||||
if not GSITK_AVAILABLE:
|
|
||||||
raise Exception('GSITK is not available. Install it to use this function.')
|
|
||||||
self._dataset_list = {}
|
self._dataset_list = {}
|
||||||
dm = DatasetManager()
|
dm = gsitk_compat.DatasetManager()
|
||||||
for item in dm.get_datasets():
|
for item in dm.get_datasets():
|
||||||
for key in item:
|
for key in item:
|
||||||
if key in self._dataset_list:
|
if key in self._dataset_list:
|
||||||
@@ -223,8 +214,6 @@ class Senpy(object):
|
|||||||
return self._dataset_list
|
return self._dataset_list
|
||||||
|
|
||||||
def evaluate(self, params):
|
def evaluate(self, params):
|
||||||
if not GSITK_AVAILABLE:
|
|
||||||
raise Exception('GSITK is not available. Install it to use this function.')
|
|
||||||
logger.debug("evaluating request: {}".format(params))
|
logger.debug("evaluating request: {}".format(params))
|
||||||
results = AggregatedEvaluation()
|
results = AggregatedEvaluation()
|
||||||
results.parameters = params
|
results.parameters = params
|
||||||
@@ -351,6 +340,7 @@ class Senpy(object):
|
|||||||
logger.info(msg)
|
logger.info(msg)
|
||||||
success = True
|
success = True
|
||||||
self._set_active(plugin, success)
|
self._set_active(plugin, success)
|
||||||
|
return success
|
||||||
|
|
||||||
def activate_plugin(self, plugin_name, sync=True):
|
def activate_plugin(self, plugin_name, sync=True):
|
||||||
plugin_name = plugin_name.lower()
|
plugin_name = plugin_name.lower()
|
||||||
@@ -361,8 +351,8 @@ class Senpy(object):
|
|||||||
|
|
||||||
logger.info("Activating plugin: {}".format(plugin.name))
|
logger.info("Activating plugin: {}".format(plugin.name))
|
||||||
|
|
||||||
if sync or 'async' in plugin and not plugin.async:
|
if sync or not getattr(plugin, 'async', True):
|
||||||
self._activate(plugin)
|
return self._activate(plugin)
|
||||||
else:
|
else:
|
||||||
th = Thread(target=partial(self._activate, plugin))
|
th = Thread(target=partial(self._activate, plugin))
|
||||||
th.start()
|
th.start()
|
||||||
@@ -384,7 +374,7 @@ class Senpy(object):
|
|||||||
|
|
||||||
self._set_active(plugin, False)
|
self._set_active(plugin, False)
|
||||||
|
|
||||||
if sync or 'async' in plugin and not plugin.async:
|
if sync or not getattr(plugin, 'async', True):
|
||||||
self._deactivate(plugin)
|
self._deactivate(plugin)
|
||||||
else:
|
else:
|
||||||
th = Thread(target=partial(self._deactivate, plugin))
|
th = Thread(target=partial(self._deactivate, plugin))
|
||||||
|
26
senpy/gsitk_compat.py
Normal file
26
senpy/gsitk_compat.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
MSG = 'GSITK is not (properly) installed.'
|
||||||
|
IMPORTMSG = '{} Some functions will be unavailable.'.format(MSG)
|
||||||
|
RUNMSG = '{} Install it to use this function.'.format(MSG)
|
||||||
|
|
||||||
|
|
||||||
|
def raise_exception(*args, **kwargs):
|
||||||
|
raise Exception(RUNMSG)
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
from gsitk.datasets.datasets import DatasetManager
|
||||||
|
from gsitk.evaluation.evaluation import Evaluation as Eval
|
||||||
|
from sklearn.pipeline import Pipeline
|
||||||
|
import pkg_resources
|
||||||
|
GSITK_VERSION = pkg_resources.get_distribution("gsitk").version.split()
|
||||||
|
GSITK_AVAILABLE = GSITK_VERSION > (0, 1, 9, 1) # Earlier versions have a bug
|
||||||
|
modules = locals()
|
||||||
|
except ImportError:
|
||||||
|
logger.warning(IMPORTMSG)
|
||||||
|
GSITK_AVAILABLE = False
|
||||||
|
GSITK_VERSION = ()
|
||||||
|
DatasetManager = Eval = Pipeline = raise_exception
|
@@ -138,7 +138,7 @@ class BaseModel(with_metaclass(BaseMeta, CustomDict)):
|
|||||||
@property
|
@property
|
||||||
def id(self):
|
def id(self):
|
||||||
if '@id' not in self:
|
if '@id' not in self:
|
||||||
self['@id'] = ':{}_{}'.format(type(self).__name__, time.time())
|
self['@id'] = '_:{}_{}'.format(type(self).__name__, time.time())
|
||||||
return self['@id']
|
return self['@id']
|
||||||
|
|
||||||
@id.setter
|
@id.setter
|
||||||
@@ -146,7 +146,7 @@ class BaseModel(with_metaclass(BaseMeta, CustomDict)):
|
|||||||
self['@id'] = value
|
self['@id'] = value
|
||||||
|
|
||||||
def flask(self,
|
def flask(self,
|
||||||
in_headers=True,
|
in_headers=False,
|
||||||
headers=None,
|
headers=None,
|
||||||
outformat='json-ld',
|
outformat='json-ld',
|
||||||
**kwargs):
|
**kwargs):
|
||||||
@@ -176,20 +176,22 @@ class BaseModel(with_metaclass(BaseMeta, CustomDict)):
|
|||||||
|
|
||||||
def serialize(self, format='json-ld', with_mime=False, **kwargs):
|
def serialize(self, format='json-ld', with_mime=False, **kwargs):
|
||||||
js = self.jsonld(**kwargs)
|
js = self.jsonld(**kwargs)
|
||||||
|
content = json.dumps(js, indent=2, sort_keys=True)
|
||||||
if format == 'json-ld':
|
if format == 'json-ld':
|
||||||
content = json.dumps(js, indent=2, sort_keys=True)
|
|
||||||
mimetype = "application/json"
|
mimetype = "application/json"
|
||||||
elif format in ['turtle', ]:
|
elif format in ['turtle', 'ntriples']:
|
||||||
logger.debug(js)
|
logger.debug(js)
|
||||||
content = json.dumps(js, indent=2, sort_keys=True)
|
base = kwargs.get('prefix')
|
||||||
g = Graph().parse(
|
g = Graph().parse(
|
||||||
data=content,
|
data=content,
|
||||||
format='json-ld',
|
format='json-ld',
|
||||||
base=kwargs.get('prefix'),
|
base=base,
|
||||||
context=self._context)
|
context=[self._context,
|
||||||
|
{'@base': base}])
|
||||||
logger.debug(
|
logger.debug(
|
||||||
'Parsing with prefix: {}'.format(kwargs.get('prefix')))
|
'Parsing with prefix: {}'.format(kwargs.get('prefix')))
|
||||||
content = g.serialize(format='turtle').decode('utf-8')
|
content = g.serialize(format=format,
|
||||||
|
base=base).decode('utf-8')
|
||||||
mimetype = 'text/{}'.format(format)
|
mimetype = 'text/{}'.format(format)
|
||||||
else:
|
else:
|
||||||
raise Error('Unknown outformat: {}'.format(format))
|
raise Error('Unknown outformat: {}'.format(format))
|
||||||
@@ -205,25 +207,21 @@ class BaseModel(with_metaclass(BaseMeta, CustomDict)):
|
|||||||
expanded=False):
|
expanded=False):
|
||||||
|
|
||||||
result = self.serializable()
|
result = self.serializable()
|
||||||
if context_uri or with_context:
|
|
||||||
result['@context'] = context_uri or self._context
|
|
||||||
|
|
||||||
# result = jsonld.compact(result,
|
|
||||||
# self._context,
|
|
||||||
# options={
|
|
||||||
# 'base': prefix,
|
|
||||||
# 'expandContext': self._context,
|
|
||||||
# 'senpy': prefix
|
|
||||||
# })
|
|
||||||
if expanded:
|
if expanded:
|
||||||
result = jsonld.expand(
|
result = jsonld.expand(
|
||||||
result, options={'base': prefix,
|
result, options={'base': prefix,
|
||||||
'expandContext': self._context})
|
'expandContext': self._context})[0]
|
||||||
if not with_context:
|
if not with_context:
|
||||||
try:
|
try:
|
||||||
del result['@context']
|
del result['@context']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
elif context_uri:
|
||||||
|
result['@context'] = context_uri
|
||||||
|
else:
|
||||||
|
result['@context'] = self._context
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def validate(self, obj=None):
|
def validate(self, obj=None):
|
||||||
|
@@ -3,6 +3,7 @@ standard_library.install_aliases()
|
|||||||
|
|
||||||
|
|
||||||
from future.utils import with_metaclass
|
from future.utils import with_metaclass
|
||||||
|
from functools import partial
|
||||||
|
|
||||||
import os.path
|
import os.path
|
||||||
import os
|
import os
|
||||||
@@ -22,18 +23,12 @@ import nltk
|
|||||||
|
|
||||||
from .. import models, utils
|
from .. import models, utils
|
||||||
from .. import api
|
from .. import api
|
||||||
|
from .. import gsitk_compat
|
||||||
|
from .. import testing
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
try:
|
|
||||||
from gsitk.evaluation.evaluation import Evaluation as Eval
|
|
||||||
from sklearn.pipeline import Pipeline
|
|
||||||
GSITK_AVAILABLE = True
|
|
||||||
except ImportError:
|
|
||||||
logger.warn('GSITK is not installed. Some functions will be unavailable.')
|
|
||||||
GSITK_AVAILABLE = False
|
|
||||||
|
|
||||||
|
|
||||||
class PluginMeta(models.BaseMeta):
|
class PluginMeta(models.BaseMeta):
|
||||||
_classes = {}
|
_classes = {}
|
||||||
@@ -92,7 +87,7 @@ class Plugin(with_metaclass(PluginMeta, models.Plugin)):
|
|||||||
if info:
|
if info:
|
||||||
self.update(info)
|
self.update(info)
|
||||||
self.validate()
|
self.validate()
|
||||||
self.id = 'plugins/{}_{}'.format(self['name'], self['version'])
|
self.id = 'endpoint:plugins/{}_{}'.format(self['name'], self['version'])
|
||||||
self.is_activated = False
|
self.is_activated = False
|
||||||
self._lock = threading.Lock()
|
self._lock = threading.Lock()
|
||||||
self._directory = os.path.abspath(os.path.dirname(inspect.getfile(self.__class__)))
|
self._directory = os.path.abspath(os.path.dirname(inspect.getfile(self.__class__)))
|
||||||
@@ -149,14 +144,23 @@ class Plugin(with_metaclass(PluginMeta, models.Plugin)):
|
|||||||
self.log.warn('Test case failed:\n{}'.format(pprint.pformat(case)))
|
self.log.warn('Test case failed:\n{}'.format(pprint.pformat(case)))
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def test_case(self, case):
|
def test_case(self, case, mock=testing.MOCK_REQUESTS):
|
||||||
entry = models.Entry(case['entry'])
|
entry = models.Entry(case['entry'])
|
||||||
given_parameters = case.get('params', case.get('parameters', {}))
|
given_parameters = case.get('params', case.get('parameters', {}))
|
||||||
expected = case.get('expected', None)
|
expected = case.get('expected', None)
|
||||||
should_fail = case.get('should_fail', False)
|
should_fail = case.get('should_fail', False)
|
||||||
|
responses = case.get('responses', [])
|
||||||
|
|
||||||
try:
|
try:
|
||||||
params = api.parse_params(given_parameters, self.extra_params)
|
params = api.parse_params(given_parameters, self.extra_params)
|
||||||
res = list(self.analyse_entries([entry, ], params))
|
|
||||||
|
method = partial(self.analyse_entries, [entry, ], params)
|
||||||
|
|
||||||
|
if mock:
|
||||||
|
res = list(method())
|
||||||
|
else:
|
||||||
|
with testing.patch_all_requests(responses):
|
||||||
|
res = list(method())
|
||||||
|
|
||||||
if not isinstance(expected, list):
|
if not isinstance(expected, list):
|
||||||
expected = [expected]
|
expected = [expected]
|
||||||
@@ -332,7 +336,7 @@ class Box(AnalysisPlugin):
|
|||||||
return self.transform(X)
|
return self.transform(X)
|
||||||
|
|
||||||
def as_pipe(self):
|
def as_pipe(self):
|
||||||
pipe = Pipeline([('plugin', self)])
|
pipe = gsitk_compat.Pipeline([('plugin', self)])
|
||||||
pipe.name = self.name
|
pipe.name = self.name
|
||||||
return pipe
|
return pipe
|
||||||
|
|
||||||
@@ -530,7 +534,7 @@ def find_plugins(folders):
|
|||||||
yield fpath
|
yield fpath
|
||||||
|
|
||||||
|
|
||||||
def from_path(fpath, **kwargs):
|
def from_path(fpath, install_on_fail=False, **kwargs):
|
||||||
logger.debug("Loading plugin from {}".format(fpath))
|
logger.debug("Loading plugin from {}".format(fpath))
|
||||||
if fpath.endswith('.py'):
|
if fpath.endswith('.py'):
|
||||||
# We asume root is the dir of the file, and module is the name of the file
|
# We asume root is the dir of the file, and module is the name of the file
|
||||||
@@ -540,7 +544,7 @@ def from_path(fpath, **kwargs):
|
|||||||
yield instance
|
yield instance
|
||||||
else:
|
else:
|
||||||
info = parse_plugin_info(fpath)
|
info = parse_plugin_info(fpath)
|
||||||
yield from_info(info, **kwargs)
|
yield from_info(info, install_on_fail=install_on_fail, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def from_folder(folders, loader=from_path, **kwargs):
|
def from_folder(folders, loader=from_path, **kwargs):
|
||||||
@@ -551,7 +555,7 @@ def from_folder(folders, loader=from_path, **kwargs):
|
|||||||
return plugins
|
return plugins
|
||||||
|
|
||||||
|
|
||||||
def from_info(info, root=None, **kwargs):
|
def from_info(info, root=None, install_on_fail=True, **kwargs):
|
||||||
if any(x not in info for x in ('module',)):
|
if any(x not in info for x in ('module',)):
|
||||||
raise ValueError('Plugin info is not valid: {}'.format(info))
|
raise ValueError('Plugin info is not valid: {}'.format(info))
|
||||||
module = info["module"]
|
module = info["module"]
|
||||||
@@ -559,7 +563,12 @@ def from_info(info, root=None, **kwargs):
|
|||||||
if not root and '_path' in info:
|
if not root and '_path' in info:
|
||||||
root = os.path.dirname(info['_path'])
|
root = os.path.dirname(info['_path'])
|
||||||
|
|
||||||
return one_from_module(module, root=root, info=info, **kwargs)
|
fun = partial(one_from_module, module, root=root, info=info, **kwargs)
|
||||||
|
try:
|
||||||
|
return fun()
|
||||||
|
except (ImportError, LookupError):
|
||||||
|
install_deps(info)
|
||||||
|
return fun()
|
||||||
|
|
||||||
|
|
||||||
def parse_plugin_info(fpath):
|
def parse_plugin_info(fpath):
|
||||||
@@ -606,17 +615,9 @@ def _instances_in_module(module):
|
|||||||
yield obj
|
yield obj
|
||||||
|
|
||||||
|
|
||||||
def _from_module_name(module, root, info=None, install=True, **kwargs):
|
def _from_module_name(module, root, info=None, **kwargs):
|
||||||
try:
|
|
||||||
module = load_module(module, root)
|
|
||||||
except (ImportError, LookupError):
|
|
||||||
if not install or not info:
|
|
||||||
raise
|
|
||||||
install_deps(info)
|
|
||||||
module = load_module(module, root)
|
module = load_module(module, root)
|
||||||
for plugin in _from_loaded_module(module=module, root=root, info=info, **kwargs):
|
for plugin in _from_loaded_module(module=module, root=root, info=info, **kwargs):
|
||||||
if install:
|
|
||||||
install_deps(plugin)
|
|
||||||
yield plugin
|
yield plugin
|
||||||
|
|
||||||
|
|
||||||
@@ -628,10 +629,7 @@ def _from_loaded_module(module, info=None, **kwargs):
|
|||||||
|
|
||||||
|
|
||||||
def evaluate(plugins, datasets, **kwargs):
|
def evaluate(plugins, datasets, **kwargs):
|
||||||
if not GSITK_AVAILABLE:
|
ev = gsitk_compat.Eval(tuples=None,
|
||||||
raise Exception('GSITK is not available. Install it to use this function.')
|
|
||||||
|
|
||||||
ev = Eval(tuples=None,
|
|
||||||
datasets=datasets,
|
datasets=datasets,
|
||||||
pipelines=[plugin.as_pipe() for plugin in plugins])
|
pipelines=[plugin.as_pipe() for plugin in plugins])
|
||||||
ev.evaluate()
|
ev.evaluate()
|
||||||
|
@@ -4,6 +4,8 @@ import json
|
|||||||
from senpy.plugins import SentimentPlugin
|
from senpy.plugins import SentimentPlugin
|
||||||
from senpy.models import Sentiment
|
from senpy.models import Sentiment
|
||||||
|
|
||||||
|
ENDPOINT = 'http://www.sentiment140.com/api/bulkClassifyJson'
|
||||||
|
|
||||||
|
|
||||||
class Sentiment140(SentimentPlugin):
|
class Sentiment140(SentimentPlugin):
|
||||||
'''Connects to the sentiment140 free API: http://sentiment140.com'''
|
'''Connects to the sentiment140 free API: http://sentiment140.com'''
|
||||||
@@ -26,7 +28,7 @@ class Sentiment140(SentimentPlugin):
|
|||||||
|
|
||||||
def analyse_entry(self, entry, params):
|
def analyse_entry(self, entry, params):
|
||||||
lang = params["language"]
|
lang = params["language"]
|
||||||
res = requests.post("http://www.sentiment140.com/api/bulkClassifyJson",
|
res = requests.post(ENDPOINT,
|
||||||
json.dumps({
|
json.dumps({
|
||||||
"language": lang,
|
"language": lang,
|
||||||
"data": [{
|
"data": [{
|
||||||
@@ -52,18 +54,6 @@ class Sentiment140(SentimentPlugin):
|
|||||||
entry.language = lang
|
entry.language = lang
|
||||||
yield entry
|
yield entry
|
||||||
|
|
||||||
def test(self, *args, **kwargs):
|
|
||||||
'''
|
|
||||||
To avoid calling the sentiment140 API, we will mock the results
|
|
||||||
from requests.
|
|
||||||
'''
|
|
||||||
from senpy.testing import patch_requests
|
|
||||||
expected = {"data": [{"polarity": 4}]}
|
|
||||||
with patch_requests(expected) as (request, response):
|
|
||||||
super(Sentiment140, self).test(*args, **kwargs)
|
|
||||||
assert request.called
|
|
||||||
assert response.json.called
|
|
||||||
|
|
||||||
test_cases = [
|
test_cases = [
|
||||||
{
|
{
|
||||||
'entry': {
|
'entry': {
|
||||||
@@ -77,6 +67,9 @@ class Sentiment140(SentimentPlugin):
|
|||||||
'marl:hasPolarity': 'marl:Positive',
|
'marl:hasPolarity': 'marl:Positive',
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
},
|
||||||
|
'responses': [{'url': ENDPOINT,
|
||||||
|
'method': 'POST',
|
||||||
|
'json': {'data': [{'polarity': 4}]}}]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@@ -413,7 +413,7 @@ function evaluate_JSON(){
|
|||||||
url += "?algo="+plugin+"&dataset="+datasets
|
url += "?algo="+plugin+"&dataset="+datasets
|
||||||
|
|
||||||
$('#doevaluate').attr("disabled", true);
|
$('#doevaluate').attr("disabled", true);
|
||||||
$.ajax({type: "GET", url: url, dataType: 'json'}).done(function(resp) {
|
$.ajax({type: "GET", url: url, dataType: 'json'}).always(function(resp) {
|
||||||
$('#doevaluate').attr("disabled", false);
|
$('#doevaluate').attr("disabled", false);
|
||||||
response = resp.responseText;
|
response = resp.responseText;
|
||||||
|
|
||||||
|
@@ -1,36 +1,31 @@
|
|||||||
try:
|
|
||||||
from unittest.mock import patch, MagicMock
|
|
||||||
except ImportError:
|
|
||||||
from mock import patch, MagicMock
|
|
||||||
|
|
||||||
from past.builtins import basestring
|
from past.builtins import basestring
|
||||||
|
|
||||||
|
import os
|
||||||
import json
|
import responses as requestmock
|
||||||
from contextlib import contextmanager
|
|
||||||
|
|
||||||
from .models import BaseModel
|
from .models import BaseModel
|
||||||
|
|
||||||
|
|
||||||
@contextmanager
|
MOCK_REQUESTS = os.environ.get('MOCK_REQUESTS', '').lower() in ['no', 'false']
|
||||||
def patch_requests(value, code=200):
|
|
||||||
success = MagicMock()
|
|
||||||
if isinstance(value, BaseModel):
|
def patch_all_requests(responses):
|
||||||
value = value.jsonld()
|
|
||||||
if not isinstance(value, basestring):
|
patched = requestmock.RequestsMock()
|
||||||
data = json.dumps(value)
|
|
||||||
|
for response in responses or []:
|
||||||
|
args = response.copy()
|
||||||
|
if 'json' in args and isinstance(args['json'], BaseModel):
|
||||||
|
args['json'] = args['json'].jsonld()
|
||||||
|
args['method'] = getattr(requestmock, args.get('method', 'GET'))
|
||||||
|
patched.add(**args)
|
||||||
|
return patched
|
||||||
|
|
||||||
|
|
||||||
|
def patch_requests(url, response, method='GET', status=200):
|
||||||
|
args = {'url': url, 'method': method, 'status': status}
|
||||||
|
if isinstance(response, basestring):
|
||||||
|
args['body'] = response
|
||||||
else:
|
else:
|
||||||
data = value
|
args['json'] = response
|
||||||
|
return patch_all_requests([args])
|
||||||
success.json.return_value = value
|
|
||||||
|
|
||||||
success.status_code = code
|
|
||||||
success.content = data
|
|
||||||
success.text = data
|
|
||||||
|
|
||||||
method_mocker = MagicMock()
|
|
||||||
method_mocker.return_value = success
|
|
||||||
with patch.multiple('requests', request=method_mocker,
|
|
||||||
get=method_mocker, post=method_mocker):
|
|
||||||
yield method_mocker, success
|
|
||||||
assert method_mocker.called
|
|
||||||
|
@@ -80,7 +80,7 @@ def easy_test(plugin_list=None, debug=True):
|
|||||||
for plug in plugin_list:
|
for plug in plugin_list:
|
||||||
plug.test()
|
plug.test()
|
||||||
plug.log.info('My tests passed!')
|
plug.log.info('My tests passed!')
|
||||||
logger.info('All tests passed!')
|
logger.info('All tests passed for {} plugins!'.format(len(plugin_list)))
|
||||||
except Exception:
|
except Exception:
|
||||||
if not debug:
|
if not debug:
|
||||||
raise
|
raise
|
||||||
|
@@ -21,7 +21,6 @@ class BlueprintsTest(TestCase):
|
|||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
"""Set up only once, and re-use in every individual test"""
|
"""Set up only once, and re-use in every individual test"""
|
||||||
cls.app = Flask("test_extensions")
|
cls.app = Flask("test_extensions")
|
||||||
cls.app.debug = False
|
|
||||||
cls.client = cls.app.test_client()
|
cls.client = cls.app.test_client()
|
||||||
cls.senpy = Senpy(default_plugins=True)
|
cls.senpy = Senpy(default_plugins=True)
|
||||||
cls.senpy.init_app(cls.app)
|
cls.senpy.init_app(cls.app)
|
||||||
@@ -31,6 +30,9 @@ class BlueprintsTest(TestCase):
|
|||||||
cls.senpy.activate_plugin("DummyRequired", sync=True)
|
cls.senpy.activate_plugin("DummyRequired", sync=True)
|
||||||
cls.senpy.default_plugin = 'Dummy'
|
cls.senpy.default_plugin = 'Dummy'
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.app.config['TESTING'] = True # Tell Flask not to catch Exceptions
|
||||||
|
|
||||||
def assertCode(self, resp, code):
|
def assertCode(self, resp, code):
|
||||||
self.assertEqual(resp.status_code, code)
|
self.assertEqual(resp.status_code, code)
|
||||||
|
|
||||||
@@ -42,6 +44,7 @@ class BlueprintsTest(TestCase):
|
|||||||
"""
|
"""
|
||||||
Calling with no arguments should ask the user for more arguments
|
Calling with no arguments should ask the user for more arguments
|
||||||
"""
|
"""
|
||||||
|
self.app.config['TESTING'] = False # Errors are expected in this case
|
||||||
resp = self.client.get("/api/")
|
resp = self.client.get("/api/")
|
||||||
self.assertCode(resp, 400)
|
self.assertCode(resp, 400)
|
||||||
js = parse_resp(resp)
|
js = parse_resp(resp)
|
||||||
@@ -64,10 +67,24 @@ class BlueprintsTest(TestCase):
|
|||||||
logging.debug("Got response: %s", js)
|
logging.debug("Got response: %s", js)
|
||||||
assert "@context" in js
|
assert "@context" in js
|
||||||
assert "entries" in js
|
assert "entries" in js
|
||||||
|
assert len(js['analysis']) == 1
|
||||||
|
|
||||||
|
def test_analysis_post(self):
|
||||||
|
"""
|
||||||
|
The results for a POST request should be the same as for a GET request.
|
||||||
|
"""
|
||||||
|
resp = self.client.post("/api/", data={'i': 'My aloha mohame',
|
||||||
|
'with_parameters': True})
|
||||||
|
self.assertCode(resp, 200)
|
||||||
|
js = parse_resp(resp)
|
||||||
|
logging.debug("Got response: %s", js)
|
||||||
|
assert "@context" in js
|
||||||
|
assert "entries" in js
|
||||||
|
assert len(js['analysis']) == 1
|
||||||
|
|
||||||
def test_analysis_extra(self):
|
def test_analysis_extra(self):
|
||||||
"""
|
"""
|
||||||
Extra params that have a default should
|
Extra params that have a default should use it
|
||||||
"""
|
"""
|
||||||
resp = self.client.get("/api/?i=My aloha mohame&algo=Dummy&with_parameters=true")
|
resp = self.client.get("/api/?i=My aloha mohame&algo=Dummy&with_parameters=true")
|
||||||
self.assertCode(resp, 200)
|
self.assertCode(resp, 200)
|
||||||
@@ -81,7 +98,7 @@ class BlueprintsTest(TestCase):
|
|||||||
Extra params that have a required argument that does not
|
Extra params that have a required argument that does not
|
||||||
have a default should raise an error.
|
have a default should raise an error.
|
||||||
"""
|
"""
|
||||||
self.app.debug = False
|
self.app.config['TESTING'] = False # Errors are expected in this case
|
||||||
resp = self.client.get("/api/?i=My aloha mohame&algo=DummyRequired")
|
resp = self.client.get("/api/?i=My aloha mohame&algo=DummyRequired")
|
||||||
self.assertCode(resp, 400)
|
self.assertCode(resp, 400)
|
||||||
js = parse_resp(resp)
|
js = parse_resp(resp)
|
||||||
@@ -92,12 +109,50 @@ class BlueprintsTest(TestCase):
|
|||||||
resp = self.client.get("/api/?i=My aloha mohame&algo=DummyRequired&example=a")
|
resp = self.client.get("/api/?i=My aloha mohame&algo=DummyRequired&example=a")
|
||||||
self.assertCode(resp, 200)
|
self.assertCode(resp, 200)
|
||||||
|
|
||||||
|
def test_analysis_url(self):
|
||||||
|
"""
|
||||||
|
The algorithm can also be specified as part of the URL
|
||||||
|
"""
|
||||||
|
self.app.config['TESTING'] = False # Errors are expected in this case
|
||||||
|
resp = self.client.get("/api/DummyRequired?i=My aloha mohame")
|
||||||
|
self.assertCode(resp, 400)
|
||||||
|
js = parse_resp(resp)
|
||||||
|
logging.debug("Got response: %s", js)
|
||||||
|
assert isinstance(js, models.Error)
|
||||||
|
resp = self.client.get("/api/DummyRequired?i=My aloha mohame&example=notvalid")
|
||||||
|
self.assertCode(resp, 400)
|
||||||
|
resp = self.client.get("/api/DummyRequired?i=My aloha mohame&example=a")
|
||||||
|
self.assertCode(resp, 200)
|
||||||
|
|
||||||
|
def test_analysis_chain(self):
|
||||||
|
"""
|
||||||
|
More than one algorithm can be specified. Plugins will then be chained
|
||||||
|
"""
|
||||||
|
resp = self.client.get("/api/Dummy?i=My aloha mohame")
|
||||||
|
js = parse_resp(resp)
|
||||||
|
assert len(js['analysis']) == 1
|
||||||
|
assert js['entries'][0]['nif:isString'] == 'My aloha mohame'[::-1]
|
||||||
|
|
||||||
|
resp = self.client.get("/api/Dummy/Dummy?i=My aloha mohame")
|
||||||
|
# Calling dummy twice, should return the same string
|
||||||
|
self.assertCode(resp, 200)
|
||||||
|
js = parse_resp(resp)
|
||||||
|
assert len(js['analysis']) == 2
|
||||||
|
assert js['entries'][0]['nif:isString'] == 'My aloha mohame'
|
||||||
|
|
||||||
|
resp = self.client.get("/api/Dummy+Dummy?i=My aloha mohame")
|
||||||
|
# Same with pluses instead of slashes
|
||||||
|
self.assertCode(resp, 200)
|
||||||
|
js = parse_resp(resp)
|
||||||
|
assert len(js['analysis']) == 2
|
||||||
|
assert js['entries'][0]['nif:isString'] == 'My aloha mohame'
|
||||||
|
|
||||||
def test_error(self):
|
def test_error(self):
|
||||||
"""
|
"""
|
||||||
The dummy plugin returns an empty response,\
|
The dummy plugin returns an empty response,\
|
||||||
it should contain the context
|
it should contain the context
|
||||||
"""
|
"""
|
||||||
self.app.debug = False
|
self.app.config['TESTING'] = False # Errors are expected in this case
|
||||||
resp = self.client.get("/api/?i=My aloha mohame&algo=DOESNOTEXIST")
|
resp = self.client.get("/api/?i=My aloha mohame&algo=DOESNOTEXIST")
|
||||||
self.assertCode(resp, 404)
|
self.assertCode(resp, 404)
|
||||||
js = parse_resp(resp)
|
js = parse_resp(resp)
|
||||||
@@ -139,7 +194,7 @@ class BlueprintsTest(TestCase):
|
|||||||
js = parse_resp(resp)
|
js = parse_resp(resp)
|
||||||
logging.debug(js)
|
logging.debug(js)
|
||||||
assert "@id" in js
|
assert "@id" in js
|
||||||
assert js["@id"] == "plugins/Dummy_0.1"
|
assert js["@id"] == "endpoint:plugins/Dummy_0.1"
|
||||||
|
|
||||||
def test_default(self):
|
def test_default(self):
|
||||||
""" Show only one plugin"""
|
""" Show only one plugin"""
|
||||||
@@ -148,7 +203,7 @@ class BlueprintsTest(TestCase):
|
|||||||
js = parse_resp(resp)
|
js = parse_resp(resp)
|
||||||
logging.debug(js)
|
logging.debug(js)
|
||||||
assert "@id" in js
|
assert "@id" in js
|
||||||
assert js["@id"] == "plugins/Dummy_0.1"
|
assert js["@id"] == "endpoint:plugins/Dummy_0.1"
|
||||||
|
|
||||||
def test_context(self):
|
def test_context(self):
|
||||||
resp = self.client.get("/api/contexts/context.jsonld")
|
resp = self.client.get("/api/contexts/context.jsonld")
|
||||||
@@ -172,5 +227,6 @@ class BlueprintsTest(TestCase):
|
|||||||
assert "help" in js["valid_parameters"]
|
assert "help" in js["valid_parameters"]
|
||||||
|
|
||||||
def test_conversion(self):
|
def test_conversion(self):
|
||||||
|
self.app.config['TESTING'] = False # Errors are expected in this case
|
||||||
resp = self.client.get("/api/?input=hello&algo=emoRand&emotionModel=DOES NOT EXIST")
|
resp = self.client.get("/api/?input=hello&algo=emoRand&emotionModel=DOES NOT EXIST")
|
||||||
self.assertCode(resp, 404)
|
self.assertCode(resp, 404)
|
||||||
|
@@ -14,22 +14,28 @@ class ModelsTest(TestCase):
|
|||||||
def test_client(self):
|
def test_client(self):
|
||||||
endpoint = 'http://dummy/'
|
endpoint = 'http://dummy/'
|
||||||
client = Client(endpoint)
|
client = Client(endpoint)
|
||||||
with patch_requests(Results()) as (request, response):
|
with patch_requests('http://dummy/', Results()):
|
||||||
resp = client.analyse('hello')
|
resp = client.analyse('hello')
|
||||||
assert isinstance(resp, Results)
|
assert isinstance(resp, Results)
|
||||||
request.assert_called_with(
|
with patch_requests('http://dummy/', Error('Nothing')):
|
||||||
url=endpoint + '/', method='GET', params={'input': 'hello'})
|
|
||||||
with patch_requests(Error('Nothing')) as (request, response):
|
|
||||||
try:
|
try:
|
||||||
client.analyse(input='hello', algorithm='NONEXISTENT')
|
client.analyse(input='hello', algorithm='NONEXISTENT')
|
||||||
raise Exception('Exceptions should be raised. This is not golang')
|
raise Exception('Exceptions should be raised. This is not golang')
|
||||||
except Error:
|
except Error:
|
||||||
pass
|
pass
|
||||||
request.assert_called_with(
|
|
||||||
url=endpoint + '/',
|
def test_client_post(self):
|
||||||
method='GET',
|
endpoint = 'http://dummy/'
|
||||||
params={'input': 'hello',
|
client = Client(endpoint)
|
||||||
'algorithm': 'NONEXISTENT'})
|
with patch_requests('http://dummy/', Results()):
|
||||||
|
resp = client.analyse('hello')
|
||||||
|
assert isinstance(resp, Results)
|
||||||
|
with patch_requests('http://dummy/', Error('Nothing'), method='POST'):
|
||||||
|
try:
|
||||||
|
client.analyse(input='hello', method='POST', algorithm='NONEXISTENT')
|
||||||
|
raise Exception('Exceptions should be raised. This is not golang')
|
||||||
|
except Error:
|
||||||
|
pass
|
||||||
|
|
||||||
def test_plugins(self):
|
def test_plugins(self):
|
||||||
endpoint = 'http://dummy/'
|
endpoint = 'http://dummy/'
|
||||||
@@ -37,11 +43,8 @@ class ModelsTest(TestCase):
|
|||||||
plugins = Plugins()
|
plugins = Plugins()
|
||||||
p1 = AnalysisPlugin({'name': 'AnalysisP1', 'version': 0, 'description': 'No'})
|
p1 = AnalysisPlugin({'name': 'AnalysisP1', 'version': 0, 'description': 'No'})
|
||||||
plugins.plugins = [p1, ]
|
plugins.plugins = [p1, ]
|
||||||
with patch_requests(plugins) as (request, response):
|
with patch_requests('http://dummy/plugins', plugins):
|
||||||
response = client.plugins()
|
response = client.plugins()
|
||||||
assert isinstance(response, dict)
|
assert isinstance(response, dict)
|
||||||
assert len(response) == 1
|
assert len(response) == 1
|
||||||
assert "AnalysisP1" in response
|
assert "AnalysisP1" in response
|
||||||
request.assert_called_with(
|
|
||||||
url=endpoint + '/plugins', method='GET',
|
|
||||||
params={})
|
|
||||||
|
@@ -121,8 +121,8 @@ class ExtensionsTest(TestCase):
|
|||||||
# Leaf (defaultdict with __setattr__ and __getattr__.
|
# Leaf (defaultdict with __setattr__ and __getattr__.
|
||||||
r1 = analyse(self.senpy, algorithm="Dummy", input="tupni", output="tuptuo")
|
r1 = analyse(self.senpy, algorithm="Dummy", input="tupni", output="tuptuo")
|
||||||
r2 = analyse(self.senpy, input="tupni", output="tuptuo")
|
r2 = analyse(self.senpy, input="tupni", output="tuptuo")
|
||||||
assert r1.analysis[0] == "plugins/Dummy_0.1"
|
assert r1.analysis[0] == "endpoint:plugins/Dummy_0.1"
|
||||||
assert r2.analysis[0] == "plugins/Dummy_0.1"
|
assert r2.analysis[0] == "endpoint:plugins/Dummy_0.1"
|
||||||
assert r1.entries[0]['nif:isString'] == 'input'
|
assert r1.entries[0]['nif:isString'] == 'input'
|
||||||
|
|
||||||
def test_analyse_empty(self):
|
def test_analyse_empty(self):
|
||||||
@@ -156,8 +156,8 @@ class ExtensionsTest(TestCase):
|
|||||||
r2 = analyse(self.senpy,
|
r2 = analyse(self.senpy,
|
||||||
input="tupni",
|
input="tupni",
|
||||||
output="tuptuo")
|
output="tuptuo")
|
||||||
assert r1.analysis[0] == "plugins/Dummy_0.1"
|
assert r1.analysis[0] == "endpoint:plugins/Dummy_0.1"
|
||||||
assert r2.analysis[0] == "plugins/Dummy_0.1"
|
assert r2.analysis[0] == "endpoint:plugins/Dummy_0.1"
|
||||||
assert r1.entries[0]['nif:isString'] == 'input'
|
assert r1.entries[0]['nif:isString'] == 'input'
|
||||||
|
|
||||||
def test_analyse_error(self):
|
def test_analyse_error(self):
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
#!/bin/env python
|
#!/bin/env python
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
|
||||||
import pickle
|
import pickle
|
||||||
import shutil
|
import shutil
|
||||||
import tempfile
|
import tempfile
|
||||||
@@ -10,6 +9,7 @@ from unittest import TestCase, skipIf
|
|||||||
from senpy.models import Results, Entry, EmotionSet, Emotion, Plugins
|
from senpy.models import Results, Entry, EmotionSet, Emotion, Plugins
|
||||||
from senpy import plugins
|
from senpy import plugins
|
||||||
from senpy.plugins.conversion.emotion.centroids import CentroidConversion
|
from senpy.plugins.conversion.emotion.centroids import CentroidConversion
|
||||||
|
from senpy.gsitk_compat import GSITK_AVAILABLE
|
||||||
|
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
|
||||||
@@ -312,9 +312,7 @@ class PluginsTest(TestCase):
|
|||||||
res = c._backwards_conversion(e)
|
res = c._backwards_conversion(e)
|
||||||
assert res["onyx:hasEmotionCategory"] == "c2"
|
assert res["onyx:hasEmotionCategory"] == "c2"
|
||||||
|
|
||||||
@skipIf(sys.version_info < (3, 0),
|
def _test_evaluation(self):
|
||||||
reason="requires Python3")
|
|
||||||
def test_evaluation(self):
|
|
||||||
testdata = []
|
testdata = []
|
||||||
for i in range(50):
|
for i in range(50):
|
||||||
testdata.append(["good", 1])
|
testdata.append(["good", 1])
|
||||||
@@ -348,6 +346,16 @@ class PluginsTest(TestCase):
|
|||||||
smart_metrics = results[0].metrics[0]
|
smart_metrics = results[0].metrics[0]
|
||||||
assert abs(smart_metrics['accuracy'] - 1) < 0.01
|
assert abs(smart_metrics['accuracy'] - 1) < 0.01
|
||||||
|
|
||||||
|
@skipIf(not GSITK_AVAILABLE, "GSITK is not available")
|
||||||
|
def test_evaluation(self):
|
||||||
|
self._test_evaluation()
|
||||||
|
|
||||||
|
@skipIf(GSITK_AVAILABLE, "GSITK is available")
|
||||||
|
def test_evaluation_unavailable(self):
|
||||||
|
with self.assertRaises(Exception) as context:
|
||||||
|
self._test_evaluation()
|
||||||
|
self.assertTrue('GSITK ' in str(context.exception))
|
||||||
|
|
||||||
|
|
||||||
def make_mini_test(fpath):
|
def make_mini_test(fpath):
|
||||||
def mini_test(self):
|
def mini_test(self):
|
||||||
|
@@ -5,28 +5,29 @@ import json
|
|||||||
from senpy.testing import patch_requests
|
from senpy.testing import patch_requests
|
||||||
from senpy.models import Results
|
from senpy.models import Results
|
||||||
|
|
||||||
|
ENDPOINT = 'http://example.com'
|
||||||
|
|
||||||
|
|
||||||
class TestTest(TestCase):
|
class TestTest(TestCase):
|
||||||
def test_patch_text(self):
|
def test_patch_text(self):
|
||||||
with patch_requests('hello'):
|
with patch_requests(ENDPOINT, 'hello'):
|
||||||
r = requests.get('http://example.com')
|
r = requests.get(ENDPOINT)
|
||||||
assert r.text == 'hello'
|
assert r.text == 'hello'
|
||||||
assert r.content == 'hello'
|
|
||||||
|
|
||||||
def test_patch_json(self):
|
def test_patch_json(self):
|
||||||
r = Results()
|
r = Results()
|
||||||
with patch_requests(r):
|
with patch_requests(ENDPOINT, r):
|
||||||
res = requests.get('http://example.com')
|
res = requests.get(ENDPOINT)
|
||||||
assert res.content == json.dumps(r.jsonld())
|
assert res.text == json.dumps(r.jsonld())
|
||||||
js = res.json()
|
js = res.json()
|
||||||
assert js
|
assert js
|
||||||
assert js['@type'] == r['@type']
|
assert js['@type'] == r['@type']
|
||||||
|
|
||||||
def test_patch_dict(self):
|
def test_patch_dict(self):
|
||||||
r = {'nothing': 'new'}
|
r = {'nothing': 'new'}
|
||||||
with patch_requests(r):
|
with patch_requests(ENDPOINT, r):
|
||||||
res = requests.get('http://example.com')
|
res = requests.get(ENDPOINT)
|
||||||
assert res.content == json.dumps(r)
|
assert res.text == json.dumps(r)
|
||||||
js = res.json()
|
js = res.json()
|
||||||
assert js
|
assert js
|
||||||
assert js['nothing'] == 'new'
|
assert js['nothing'] == 'new'
|
||||||
|
Reference in New Issue
Block a user