/
apispec.py
101 lines (85 loc) · 3.99 KB
/
apispec.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
#
# Copyright 2020 - Swiss Data Science Center (SDSC)
# A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and
# Eidgenössische Technische Hochschule Zürich (ETHZ).
#
# 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.
"""Renku service apispec views."""
from apispec import APISpec, yaml_utils
from apispec_oneofschema import MarshmallowPlugin
from apispec_webframeworks.flask import FlaskPlugin
from flask import Blueprint, current_app, jsonify
from flask.views import MethodView
from renku.ui.service.config import (
API_VERSION,
OIDC_URL,
OPENAPI_VERSION,
SERVICE_API_BASE_PATH,
SERVICE_NAME,
SERVICE_PREFIX,
)
apispec_blueprint = Blueprint("apispec", __name__, url_prefix=SERVICE_PREFIX)
# security schemes
oidc_scheme = {"type": "openIdConnect", "openIdConnectUrl": OIDC_URL}
jwt_scheme = {"type": "apiKey", "name": "Renku-User", "in": "header"}
gitlab_token_scheme = {"type": "apiKey", "name": "Authorization", "in": "header"}
TOP_LEVEL_DESCRIPTION = """
This is the API specification of the renku core service.
## Responses
Loosely following the JSON-RPC 2.0 Specification, the methods all return with
HTTP code 200 and include a [response
object](https://www.jsonrpc.org/specification#response_object) may contain
either a `result` or an `error` object. If the call succeeds, the returned
`result` follows the schema documented in the individual methods. In the case of
an error, the [`error`
object](https://www.jsonrpc.org/specification#error_object), contains a code and
two messages describing the nature of the error. One targets the user, while the
other targets the developers. Instead of following the [standard JSON-RPC
response codes](https://www.jsonrpc.org/specification#error_object), we define
our own application-specific codes. Those can be found in the definition of the
error service classes.
"""
class MultiURLFlaskPlugin(FlaskPlugin):
"""FlaskPlugin extension that supports multiple URLs per endpoint."""
def path_helper(self, path, operations, *, view, app=None, **kwargs):
"""Path helper that allows passing a Flask view function."""
rule = self._rule_for_view(view, app=app)
operations.update(yaml_utils.load_operations_from_docstring(view.__doc__))
if hasattr(view, "view_class") and issubclass(view.view_class, MethodView):
for method in view.methods:
if method in rule.methods:
method_name = method.lower()
method = getattr(view.view_class, method_name)
operations[method_name] = yaml_utils.load_yaml_from_docstring(method.__doc__)
return self.flaskpath2openapi(path)
spec = APISpec(
title=SERVICE_NAME,
openapi_version=OPENAPI_VERSION,
version=API_VERSION,
plugins=[MultiURLFlaskPlugin(), MarshmallowPlugin()],
servers=[{"url": SERVICE_API_BASE_PATH}, {"url": f"/ui-server{SERVICE_API_BASE_PATH}"}],
security=[{"oidc": []}, {"JWT": [], "gitlab-token": []}],
info={"description": TOP_LEVEL_DESCRIPTION},
)
spec.components.security_scheme("oidc", oidc_scheme)
spec.components.security_scheme("jwt", jwt_scheme)
spec.components.security_scheme("gitlab-token", gitlab_token_scheme)
@apispec_blueprint.route("/spec.json")
def openapi():
"""Return the OpenAPI spec for this service."""
return jsonify(get_apispec(current_app).to_dict())
def get_apispec(app):
"""Return the apispec."""
for rule in current_app.url_map.iter_rules():
spec.path(path=rule.rule, view=app.view_functions[rule.endpoint])
return spec