app.py 3.61 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Copyright 2018 Markus Scheidgen
#
# 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
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# 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.

15
16
17
18
"""
All APIs are served by one Flask app (:py:mod:`nomad.api.app`) under different paths.
"""

19
from flask import Flask, jsonify
20
from flask_restplus import Api
21
from flask_cors import CORS
22
from werkzeug.exceptions import HTTPException
Markus Scheidgen's avatar
Markus Scheidgen committed
23
from werkzeug.wsgi import DispatcherMiddleware
24
import os.path
25
import inspect
26

27
from nomad import config, utils
28
29

base_path = config.services.api_base_path
30
""" Provides the root path of the nomad APIs. """
31
32
33

app = Flask(
    __name__,
34
    static_url_path='/docs',
35
    static_folder=os.path.abspath(os.path.join(os.path.dirname(__file__), '../../docs/.build/html')))
36
37
""" The Flask app that serves all APIs. """

38
app.config.setdefault('APPLICATION_ROOT', base_path)
39
40
41
app.config.setdefault('RESTPLUS_MASK_HEADER', False)
app.config.setdefault('RESTPLUS_MASK_SWAGGER', False)

42

Markus Scheidgen's avatar
Markus Scheidgen committed
43
44
45
46
47
48
49
50
51
52
53
def api_base_path_response(env, resp):
    resp(b'200 OK', [(b'Content-Type', b'text/plain')])
    return [
        ('Development nomad api server. Api is served under %s/.' %
            config.services.api_base_path).encode('utf-8')]


app.wsgi_app = DispatcherMiddleware(
    api_base_path_response, {config.services.api_base_path: app.wsgi_app})


54
55
CORS(app)

56
api = Api(
57
    app, version='1.0', title='nomad@FAIRDI API',
58
59
    description='Official API for nomad@FAIRDI services.',
    validate=True)
60
""" Provides the flask restplust api instance """
61
62


63
@app.errorhandler(Exception)
Markus Scheidgen's avatar
Markus Scheidgen committed
64
@api.errorhandler
65
def handle(error: Exception):
66
67
    status_code = getattr(error, 'code', 500)
    name = getattr(error, 'name', 'Internal Server Error')
68
    description = getattr(error, 'description', 'No description available')
69
70
71
72
73
74
75
    data = dict(
        code=status_code,
        name=name,
        description=description)
    data.update(getattr(error, 'data', []))
    response = jsonify(data)
    response.status_code = status_code
76
77
    if status_code == 500:
        utils.get_logger(__name__).error('internal server error', exc_info=error)
78
    return response
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97


def with_logger(func):
    """
    Decorator for endpoint implementations that provides a pre configured logger and
    automatically logs errors on all 500 responses.
    """
    signature = inspect.signature(func)
    has_logger = 'logger' in signature.parameters
    wrapper_signature = signature.replace(parameters=tuple(
        param for param in signature.parameters.values()
        if param.name != 'logger'
    ))

    def wrapper(*args, **kwargs):
        if has_logger:
            args = inspect.getcallargs(wrapper, *args, **kwargs)
            logger_args = {
                k: v for k, v in args.items()
98
                if k in ['upload_id', 'calc_id']}
99
100
101
102
103
104
105
106
107
108
109
110
111
112
            logger = utils.get_logger(__name__, **logger_args)
            args.update(logger=logger)
        try:
            return func(**args)
        except HTTPException as e:
            if getattr(e, 'code', None) == 500:
                logger.error('Internal server error', exc_info=e)
            raise e
        except Exception as e:
            logger.error('Internal server error', exc_info=e)
            raise e

    wrapper.__signature__ = wrapper_signature
    return wrapper