blueprints.py 7.06 KB
Newer Older
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
1 2
#!/usr/bin/python
# -*- coding: utf-8 -*-
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
3 4
# Copyright 2014 J. Fernando Sánchez Rada - Grupo de Sistemas Inteligentes
# DIT, UPM
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
5
#
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
6 7 8
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
9
#
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
10
# http://www.apache.org/licenses/LICENSE-2.0
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
11
#
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
12
# Unless required by applicable law or agreed to in writing, software
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
13 14 15 16
#    distributed under the License is distributed on an "AS IS" BASIS,
#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#    See the License for the specific language governing permissions and
#    limitations under the License.
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
17 18 19
"""
Blueprints for Senpy
"""
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
20
from flask import (Blueprint, request, current_app, render_template, url_for,
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
21
                   jsonify, redirect)
22
from .models import Error, Response, Help, Plugins, read_schema, dump_schema, Datasets
23
from . import api
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
24
from .version import __version__
25
from functools import wraps
26

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
27
import logging
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
28
import traceback
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
29
import json
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
30
import base64
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
31

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
32 33
logger = logging.getLogger(__name__)

34
api_blueprint = Blueprint("api", __name__)
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
35
demo_blueprint = Blueprint("demo", __name__, template_folder='templates')
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
36
ns_blueprint = Blueprint("ns", __name__)
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
37

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
38 39 40 41 42 43 44 45 46 47 48 49 50
_mimetypes_r = {'json-ld': ['application/ld+json'],
                'turtle': ['text/turtle'],
                'text': ['text/plain']}

MIMETYPES = {}

for k, vs in _mimetypes_r.items():
    for v in vs:
        MIMETYPES[v] = k

DEFAULT_MIMETYPE = 'application/ld+json'
DEFAULT_FORMAT = 'json-ld'

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
51

52
def get_params(req):
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
53
    if req.method == 'POST':
54
        indict = req.form.to_dict(flat=True)
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
55
    elif req.method == 'GET':
56
        indict = req.args.to_dict(flat=True)
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
57
    else:
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
58
        raise Error(message="Invalid data")
59
    return indict
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
60

61

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
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.form.params().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


86
@demo_blueprint.route('/')
Ignacio Corcuera's avatar
Ignacio Corcuera committed
87
def index():
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
88 89 90 91 92 93
    ev = str(get_params(request).get('evaluation', False))
    evaluation_enabled = ev.lower() not in ['false', 'no', 'none']

    return render_template("index.html",
                           evaluation=evaluation_enabled,
                           version=__version__)
Ignacio Corcuera's avatar
Ignacio Corcuera committed
94

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
95

96 97
@api_blueprint.route('/contexts/<entity>.jsonld')
def context(entity="context"):
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
98 99
    context = Response._context
    context['@vocab'] = url_for('ns.index', _external=True)
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
100
    context['endpoint'] = url_for('api.api_root', _external=True)
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
101 102 103
    return jsonify({"@context": context})


J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
104 105 106 107 108 109 110 111
@api_blueprint.route('/d/<code>')
def decode(code):
    try:
        return redirect(decoded_url(code))
    except Exception:
        return Error('invalid URL').flask()


J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
112 113
@ns_blueprint.route('/')  # noqa: F811
def index():
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
114 115
    context = Response._context.copy()
    context['endpoint'] = url_for('api.api_root', _external=True)
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
116
    return jsonify({"@context": context})
Ignacio Corcuera's avatar
Ignacio Corcuera committed
117

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
118

119 120
@api_blueprint.route('/schemas/<schema>')
def schema(schema="definitions"):
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
121
    try:
122 123 124
        return dump_schema(read_schema(schema))
    except Exception as ex:  # Should be FileNotFoundError, but it's missing from py2
        return Error(message="Schema not found: {}".format(ex), status=404).flask()
125

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
126

127
def basic_api(f):
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
128 129 130
    default_params = {
        'inHeaders': False,
        'expanded-jsonld': False,
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
131
        'outformat': None,
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
132 133 134
        'with_parameters': True,
    }

135 136
    @wraps(f)
    def decorated_function(*args, **kwargs):
137
        raw_params = get_params(request)
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
138
        logger.info('Getting request: {}'.format(raw_params))
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
139
        headers = {'X-ORIGINAL-PARAMS': json.dumps(raw_params)}
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
140
        params = default_params
141

142
        try:
143 144 145
            params = api.parse_params(raw_params, api.WEB_PARAMS, api.API_PARAMS)
            if hasattr(request, 'parameters'):
                request.parameters.update(params)
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
146
            else:
147
                request.parameters = params
148
            response = f(*args, **kwargs)
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
149
        except (Exception) as ex:
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
150 151
            if current_app.debug:
                raise
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
152 153 154 155 156 157 158 159
            if not isinstance(ex, Error):
                msg = "{}:\n\t{}".format(ex,
                                         traceback.format_exc())
                ex = Error(message=msg, status=500)
            logger.exception('Error returning analysis result')
            response = ex
            response.parameters = raw_params
            logger.error(ex)
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
160

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
161 162
        if 'parameters' in response and not params['with_parameters']:
            del response.parameters
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
163

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
164
        logger.info('Response: {}'.format(response))
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
165 166 167 168 169 170 171
        mime = request.accept_mimetypes\
                      .best_match(MIMETYPES.keys(),
                                  DEFAULT_MIMETYPE)

        mimeformat = MIMETYPES.get(mime, DEFAULT_FORMAT)
        outformat = params['outformat'] or mimeformat

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
172
        return response.flask(
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
173
            in_headers=params['inHeaders'],
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
174
            headers=headers,
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
175
            prefix=params.get('prefix', encoded_url()),
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
176 177 178
            context_uri=url_for('api.context',
                                entity=type(response).__name__,
                                _external=True),
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
179
            outformat=outformat,
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
180
            expanded=params['expanded-jsonld'])
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
181

182
    return decorated_function
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
183 184


185 186
@api_blueprint.route('/', methods=['POST', 'GET'])
@basic_api
187 188 189
def api_root():
    if request.parameters['help']:
        dic = dict(api.API_PARAMS, **api.NIF_PARAMS)
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
190
        response = Help(valid_parameters=dic)
191
        return response
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
192 193
    req = api.parse_call(request.parameters)
    return current_app.senpy.analyse(req)
194

195

196 197 198 199 200 201 202 203 204 205 206
@api_blueprint.route('/evaluate/', methods=['POST', 'GET'])
@basic_api
def evaluate():
    if request.parameters['help']:
        dic = dict(api.EVAL_PARAMS)
        response = Help(parameters=dic)
        return response
    else:
        params = api.parse_params(request.parameters, api.EVAL_PARAMS)
        response = current_app.senpy.evaluate(params)
        return response
207

208

209 210
@api_blueprint.route('/plugins/', methods=['POST', 'GET'])
@basic_api
211 212
def plugins():
    sp = current_app.senpy
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
213 214
    params = api.parse_params(request.parameters, api.PLUGINS_PARAMS)
    ptype = params.get('plugin_type')
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
215 216
    plugins = list(sp.plugins(plugin_type=ptype))
    dic = Plugins(plugins=plugins)
217
    return dic
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
218 219


220 221
@api_blueprint.route('/plugins/<plugin>/', methods=['POST', 'GET'])
@basic_api
222
def plugin(plugin=None):
223
    sp = current_app.senpy
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
224
    return sp.get_plugin(plugin)
225 226


227
@api_blueprint.route('/datasets/', methods=['POST', 'GET'])
228 229 230 231
@basic_api
def datasets():
    sp = current_app.senpy
    datasets = sp.datasets
232 233
    dic = Datasets(datasets=list(datasets.values()))
    return dic