blueprints.py 7.53 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
WIP    
J. Fernando Sánchez committed
21
                   jsonify, redirect)
22
from .models import Error, Response, Help, Plugins, read_schema, dump_schema, Datasets
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
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 json
J. Fernando Sánchez's avatar
WIP    
J. Fernando Sánchez committed
29
import base64
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
30

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

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

J. Fernando Sánchez's avatar
WIP    
J. Fernando Sánchez committed
37
38
_mimetypes_r = {'json-ld': ['application/ld+json'],
                'turtle': ['text/turtle'],
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
39
                'ntriples': ['application/n-triples'],
J. Fernando Sánchez's avatar
WIP    
J. Fernando Sánchez committed
40
41
42
43
44
45
                'text': ['text/plain']}

MIMETYPES = {}

for k, vs in _mimetypes_r.items():
    for v in vs:
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
46
47
48
49
        if v in MIMETYPES:
            raise Exception('MIMETYPE {} specified for two formats: {} and {}'.format(v,
                                                                                      v,
                                                                                      MIMETYPES[v]))
J. Fernando Sánchez's avatar
WIP    
J. Fernando Sánchez committed
50
51
52
53
54
        MIMETYPES[v] = k

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

J. Fernando Sánchez's avatar
YAPFed    
J. Fernando Sánchez committed
55

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

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

J. Fernando Sánchez's avatar
WIP    
J. Fernando Sánchez committed
66
67
68
69
70
71
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:
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
72
            hash(frozenset(tuple(request.parameters.items())))
J. Fernando Sánchez's avatar
WIP    
J. Fernando Sánchez committed
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
            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


J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
90
@demo_blueprint.route('/')
Ignacio Corcuera's avatar
Ignacio Corcuera committed
91
def index():
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
92
93
94
95
96
97
    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
98

J. Fernando Sánchez's avatar
YAPFed    
J. Fernando Sánchez committed
99

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


J. Fernando Sánchez's avatar
WIP    
J. Fernando Sánchez committed
108
109
110
111
112
113
114
115
@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
116
117
@ns_blueprint.route('/')  # noqa: F811
def index():
J. Fernando Sánchez's avatar
WIP    
J. Fernando Sánchez committed
118
119
    context = Response._context.copy()
    context['endpoint'] = url_for('api.api_root', _external=True)
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
120
    return jsonify({"@context": context})
Ignacio Corcuera's avatar
Ignacio Corcuera committed
121

J. Fernando Sánchez's avatar
YAPFed    
J. Fernando Sánchez committed
122

123
124
@api_blueprint.route('/schemas/<schema>')
def schema(schema="definitions"):
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
125
    try:
126
127
128
        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()
129

J. Fernando Sánchez's avatar
YAPFed    
J. Fernando Sánchez committed
130

131
def basic_api(f):
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
132
133
134
    default_params = {
        'inHeaders': False,
        'expanded-jsonld': False,
J. Fernando Sánchez's avatar
WIP    
J. Fernando Sánchez committed
135
        'outformat': None,
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
136
137
138
        'with_parameters': True,
    }

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

146
        try:
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
147
148
149
            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
150
            else:
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
151
                request.parameters = params
152
            response = f(*args, **kwargs)
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
153
        except (Exception) as ex:
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
154
            if current_app.debug or current_app.config['TESTING']:
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
155
                raise
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
156
            if not isinstance(ex, Error):
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
157
                msg = "{}".format(ex)
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
158
159
160
                ex = Error(message=msg, status=500)
            response = ex
            response.parameters = raw_params
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
161
            logger.exception(ex)
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
162

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

J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
166
        logger.info('Response: {}'.format(response))
J. Fernando Sánchez's avatar
WIP    
J. Fernando Sánchez committed
167
168
169
170
171
172
173
        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
YAPFed    
J. Fernando Sánchez committed
174
        return response.flask(
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
175
            in_headers=params['inHeaders'],
J. Fernando Sánchez's avatar
YAPFed    
J. Fernando Sánchez committed
176
            headers=headers,
J. Fernando Sánchez's avatar
WIP    
J. Fernando Sánchez committed
177
            prefix=params.get('prefix', encoded_url()),
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
178
179
180
            context_uri=url_for('api.context',
                                entity=type(response).__name__,
                                _external=True),
J. Fernando Sánchez's avatar
WIP    
J. Fernando Sánchez committed
181
            outformat=outformat,
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
182
            expanded=params['expanded-jsonld'])
J. Fernando Sánchez's avatar
YAPFed    
J. Fernando Sánchez committed
183

184
    return decorated_function
J. Fernando Sánchez's avatar
YAPFed    
J. Fernando Sánchez committed
185
186


187
188
@api_blueprint.route('/', defaults={'plugin': None}, methods=['POST', 'GET'])
@api_blueprint.route('/<path:plugin>', methods=['POST', 'GET'])
189
@basic_api
190
def api_root(plugin):
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
191
192
    if request.parameters['help']:
        dic = dict(api.API_PARAMS, **api.NIF_PARAMS)
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
193
        response = Help(valid_parameters=dic)
194
        return response
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
195
    req = api.parse_call(request.parameters)
196
197
198
    if plugin:
        plugin = plugin.replace('+', '/')
        plugin = plugin.split('/')
199
        req.parameters['algorithm'] = tuple(plugin)
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
200
    return current_app.senpy.analyse(req)
201

202

203
204
205
206
207
208
209
210
211
212
213
@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
214

215

216
217
@api_blueprint.route('/plugins/', methods=['POST', 'GET'])
@basic_api
218
219
def plugins():
    sp = current_app.senpy
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
220
221
    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
222
223
    plugins = list(sp.plugins(plugin_type=ptype))
    dic = Plugins(plugins=plugins)
224
    return dic
J. Fernando Sánchez's avatar
YAPFed    
J. Fernando Sánchez committed
225
226


227
228
@api_blueprint.route('/plugins/<plugin>/', methods=['POST', 'GET'])
@basic_api
229
def plugin(plugin):
230
    sp = current_app.senpy
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
231
    return sp.get_plugin(plugin)
232
233


234
@api_blueprint.route('/datasets/', methods=['POST', 'GET'])
235
236
237
238
@basic_api
def datasets():
    sp = current_app.senpy
    datasets = sp.datasets
239
240
    dic = Datasets(datasets=list(datasets.values()))
    return dic