Include Swagger UI
This commit is contained in:
parent
5c6708cddd
commit
b2824251ae
17 changed files with 433 additions and 47 deletions
|
|
@ -4,6 +4,7 @@ import time
|
||||||
import atexit
|
import atexit
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import pkg_resources
|
||||||
|
|
||||||
from flask import Flask, jsonify, send_file
|
from flask import Flask, jsonify, send_file
|
||||||
|
|
||||||
|
|
@ -59,8 +60,9 @@ else:
|
||||||
app, labthing = create_app(
|
app, labthing = create_app(
|
||||||
__name__,
|
__name__,
|
||||||
prefix="/api/v2",
|
prefix="/api/v2",
|
||||||
description="Test LabThing-based API for OpenFlexure Microscope",
|
|
||||||
title=f"OpenFlexure Microscope {api_microscope.name}",
|
title=f"OpenFlexure Microscope {api_microscope.name}",
|
||||||
|
description="Test LabThing-based API for OpenFlexure Microscope",
|
||||||
|
version=pkg_resources.get_distribution("openflexure_microscope").version,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Use custom JSON encoder
|
# Use custom JSON encoder
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ from .spec import view2path
|
||||||
|
|
||||||
from .views.extensions import ExtensionList
|
from .views.extensions import ExtensionList
|
||||||
from .views.tasks import TaskList, TaskResource
|
from .views.tasks import TaskList, TaskResource
|
||||||
|
from .views.docs import docs_blueprint, APISpecResource, W3CThingDescriptionResource
|
||||||
|
|
||||||
from openflexure_microscope.common.labthings_core.utilities import get_docstring
|
from openflexure_microscope.common.labthings_core.utilities import get_docstring
|
||||||
|
|
||||||
|
|
@ -106,11 +107,12 @@ class LabThing(object):
|
||||||
# Add root representation
|
# Add root representation
|
||||||
self.app.add_url_rule(self._complete_url("/", ""), "rootrep", self.rootrep)
|
self.app.add_url_rule(self._complete_url("/", ""), "rootrep", self.rootrep)
|
||||||
# Add thing description
|
# Add thing description
|
||||||
self.app.add_url_rule(self._complete_url("/td", ""), "td", self.td)
|
# self.app.add_url_rule(self._complete_url("/td", ""), "td", self.td)
|
||||||
# Add swagger spec
|
# Add swagger spec
|
||||||
self.app.add_url_rule(
|
# self.app.add_url_rule(
|
||||||
self._complete_url("/swagger", ""), "swagger", self.swagger
|
# self._complete_url("/swagger", ""), "swagger", self.swagger
|
||||||
)
|
# )
|
||||||
|
self.app.register_blueprint(docs_blueprint, url_prefix=self.url_prefix)
|
||||||
|
|
||||||
# Add extension overview
|
# Add extension overview
|
||||||
self.add_resource(
|
self.add_resource(
|
||||||
|
|
@ -270,35 +272,6 @@ class LabThing(object):
|
||||||
return endpoint in self.endpoints
|
return endpoint in self.endpoints
|
||||||
|
|
||||||
### Description
|
### Description
|
||||||
|
|
||||||
def td(self):
|
|
||||||
"""
|
|
||||||
W3C-style Thing Description
|
|
||||||
"""
|
|
||||||
props = {}
|
|
||||||
for key, prop in self.properties.items():
|
|
||||||
props[key] = {}
|
|
||||||
props[key]["title"] = prop.__name__
|
|
||||||
props[key]["description"] = get_docstring(prop)
|
|
||||||
props[key]["links"] = [{"href": self.url_for(prop, _external=True)}]
|
|
||||||
|
|
||||||
actions = {}
|
|
||||||
for key, prop in self.actions.items():
|
|
||||||
actions[key] = {}
|
|
||||||
actions[key]["title"] = prop.__name__
|
|
||||||
actions[key]["description"] = get_docstring(prop)
|
|
||||||
actions[key]["links"] = [{"href": self.url_for(prop, _external=True)}]
|
|
||||||
|
|
||||||
td = {
|
|
||||||
"id": url_for("td", _external=True),
|
|
||||||
"title": self.title,
|
|
||||||
"description": self.description,
|
|
||||||
"properties": props,
|
|
||||||
"actions": actions,
|
|
||||||
}
|
|
||||||
|
|
||||||
return jsonify(td)
|
|
||||||
|
|
||||||
def rootrep(self):
|
def rootrep(self):
|
||||||
"""
|
"""
|
||||||
Root representation
|
Root representation
|
||||||
|
|
@ -311,14 +284,13 @@ class LabThing(object):
|
||||||
"description": self.description,
|
"description": self.description,
|
||||||
"links": {
|
"links": {
|
||||||
"thingDescription": {
|
"thingDescription": {
|
||||||
"href": url_for("td", _external=True),
|
"href": url_for("labthings_docs.w3c_td", _external=True),
|
||||||
"description": get_docstring(self.td),
|
"description": get_docstring(W3CThingDescriptionResource),
|
||||||
"methods": ["GET"],
|
"methods": ["GET"],
|
||||||
},
|
},
|
||||||
"swagger": {
|
"swaggerUI": {
|
||||||
"href": url_for("swagger", _external=True),
|
"href": url_for("labthings_docs.swagger_ui", _external=True),
|
||||||
"description": get_docstring(self.swagger),
|
**description_from_view(APISpecResource),
|
||||||
"methods": ["GET"],
|
|
||||||
},
|
},
|
||||||
"extensions": {
|
"extensions": {
|
||||||
"href": self.url_for(ExtensionList, _external=True),
|
"href": self.url_for(ExtensionList, _external=True),
|
||||||
|
|
@ -332,9 +304,3 @@ class LabThing(object):
|
||||||
}
|
}
|
||||||
|
|
||||||
return jsonify(rr)
|
return jsonify(rr)
|
||||||
|
|
||||||
def swagger(self):
|
|
||||||
"""
|
|
||||||
OpenAPI v3 documentation
|
|
||||||
"""
|
|
||||||
return jsonify(self.spec.to_dict())
|
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ def create_app(
|
||||||
|
|
||||||
# Create a LabThing
|
# Create a LabThing
|
||||||
labthing = LabThing(
|
labthing = LabThing(
|
||||||
app, prefix=prefix, title=title, description=description, version=version
|
app, prefix=prefix, title=title, description=description, version=str(version)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Store references to added-in handlers
|
# Store references to added-in handlers
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
from flask import abort, url_for, jsonify, render_template, Blueprint
|
||||||
|
|
||||||
|
from openflexure_microscope.common.labthings_core.utilities import get_docstring
|
||||||
|
|
||||||
|
from ...resource import Resource
|
||||||
|
from ...find import current_labthing
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
class APISpecResource(Resource):
|
||||||
|
"""
|
||||||
|
OpenAPI v3 documentation
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get(self):
|
||||||
|
"""
|
||||||
|
OpenAPI v3 documentation
|
||||||
|
"""
|
||||||
|
return jsonify(current_labthing().spec.to_dict())
|
||||||
|
|
||||||
|
|
||||||
|
class SwaggerUIResource(Resource):
|
||||||
|
"""
|
||||||
|
Swagger UI documentation
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get(self):
|
||||||
|
return render_template("swagger-ui.html")
|
||||||
|
|
||||||
|
|
||||||
|
class W3CThingDescriptionResource(Resource):
|
||||||
|
"""
|
||||||
|
W3C-style Thing Description
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get(self):
|
||||||
|
props = {}
|
||||||
|
for key, prop in current_labthing().properties.items():
|
||||||
|
props[key] = {}
|
||||||
|
props[key]["title"] = prop.__name__
|
||||||
|
props[key]["description"] = get_docstring(prop)
|
||||||
|
props[key]["links"] = [
|
||||||
|
{"href": current_labthing().url_for(prop, _external=True)}
|
||||||
|
]
|
||||||
|
|
||||||
|
actions = {}
|
||||||
|
for key, prop in current_labthing().actions.items():
|
||||||
|
actions[key] = {}
|
||||||
|
actions[key]["title"] = prop.__name__
|
||||||
|
actions[key]["description"] = get_docstring(prop)
|
||||||
|
actions[key]["links"] = [
|
||||||
|
{"href": current_labthing().url_for(prop, _external=True)}
|
||||||
|
]
|
||||||
|
|
||||||
|
td = {
|
||||||
|
"id": url_for("labthings_docs.w3c_td", _external=True),
|
||||||
|
"title": current_labthing().title,
|
||||||
|
"description": current_labthing().description,
|
||||||
|
"properties": props,
|
||||||
|
"actions": actions,
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsonify(td)
|
||||||
|
|
||||||
|
|
||||||
|
docs_blueprint = Blueprint(
|
||||||
|
"labthings_docs", __name__, static_folder="./static", template_folder="./templates"
|
||||||
|
)
|
||||||
|
|
||||||
|
docs_blueprint.add_url_rule(
|
||||||
|
"/swagger", view_func=APISpecResource.as_view("swagger_json")
|
||||||
|
)
|
||||||
|
docs_blueprint.add_url_rule(
|
||||||
|
"/swagger-ui", view_func=SwaggerUIResource.as_view("swagger_ui")
|
||||||
|
)
|
||||||
|
docs_blueprint.add_url_rule(
|
||||||
|
"/td", view_func=W3CThingDescriptionResource.as_view("w3c_td")
|
||||||
|
)
|
||||||
BIN
openflexure_microscope/common/flask_labthings/views/docs/static/favicon-16x16.png
vendored
Normal file
BIN
openflexure_microscope/common/flask_labthings/views/docs/static/favicon-16x16.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 665 B |
BIN
openflexure_microscope/common/flask_labthings/views/docs/static/favicon-32x32.png
vendored
Normal file
BIN
openflexure_microscope/common/flask_labthings/views/docs/static/favicon-32x32.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 628 B |
60
openflexure_microscope/common/flask_labthings/views/docs/static/index.html
vendored
Normal file
60
openflexure_microscope/common/flask_labthings/views/docs/static/index.html
vendored
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
<!-- HTML for static distribution bundle build -->
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Swagger UI</title>
|
||||||
|
<link rel="stylesheet" type="text/css" href="./swagger-ui.css" >
|
||||||
|
<link rel="icon" type="image/png" href="./favicon-32x32.png" sizes="32x32" />
|
||||||
|
<link rel="icon" type="image/png" href="./favicon-16x16.png" sizes="16x16" />
|
||||||
|
<style>
|
||||||
|
html
|
||||||
|
{
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow: -moz-scrollbars-vertical;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
*,
|
||||||
|
*:before,
|
||||||
|
*:after
|
||||||
|
{
|
||||||
|
box-sizing: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
body
|
||||||
|
{
|
||||||
|
margin:0;
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="swagger-ui"></div>
|
||||||
|
|
||||||
|
<script src="./swagger-ui-bundle.js"> </script>
|
||||||
|
<script src="./swagger-ui-standalone-preset.js"> </script>
|
||||||
|
<script>
|
||||||
|
window.onload = function() {
|
||||||
|
// Begin Swagger UI call region
|
||||||
|
const ui = SwaggerUIBundle({
|
||||||
|
url: "https://petstore.swagger.io/v2/swagger.json",
|
||||||
|
dom_id: '#swagger-ui',
|
||||||
|
deepLinking: true,
|
||||||
|
presets: [
|
||||||
|
SwaggerUIBundle.presets.apis,
|
||||||
|
SwaggerUIStandalonePreset
|
||||||
|
],
|
||||||
|
plugins: [
|
||||||
|
SwaggerUIBundle.plugins.DownloadUrl
|
||||||
|
],
|
||||||
|
layout: "StandaloneLayout"
|
||||||
|
})
|
||||||
|
// End Swagger UI call region
|
||||||
|
|
||||||
|
window.ui = ui
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
68
openflexure_microscope/common/flask_labthings/views/docs/static/oauth2-redirect.html
vendored
Normal file
68
openflexure_microscope/common/flask_labthings/views/docs/static/oauth2-redirect.html
vendored
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en-US">
|
||||||
|
<title>Swagger UI: OAuth2 Redirect</title>
|
||||||
|
<body onload="run()">
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
<script>
|
||||||
|
'use strict';
|
||||||
|
function run () {
|
||||||
|
var oauth2 = window.opener.swaggerUIRedirectOauth2;
|
||||||
|
var sentState = oauth2.state;
|
||||||
|
var redirectUrl = oauth2.redirectUrl;
|
||||||
|
var isValid, qp, arr;
|
||||||
|
|
||||||
|
if (/code|token|error/.test(window.location.hash)) {
|
||||||
|
qp = window.location.hash.substring(1);
|
||||||
|
} else {
|
||||||
|
qp = location.search.substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
arr = qp.split("&")
|
||||||
|
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';})
|
||||||
|
qp = qp ? JSON.parse('{' + arr.join() + '}',
|
||||||
|
function (key, value) {
|
||||||
|
return key === "" ? value : decodeURIComponent(value)
|
||||||
|
}
|
||||||
|
) : {}
|
||||||
|
|
||||||
|
isValid = qp.state === sentState
|
||||||
|
|
||||||
|
if ((
|
||||||
|
oauth2.auth.schema.get("flow") === "accessCode"||
|
||||||
|
oauth2.auth.schema.get("flow") === "authorizationCode"
|
||||||
|
) && !oauth2.auth.code) {
|
||||||
|
if (!isValid) {
|
||||||
|
oauth2.errCb({
|
||||||
|
authId: oauth2.auth.name,
|
||||||
|
source: "auth",
|
||||||
|
level: "warning",
|
||||||
|
message: "Authorization may be unsafe, passed state was changed in server Passed state wasn't returned from auth server"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (qp.code) {
|
||||||
|
delete oauth2.state;
|
||||||
|
oauth2.auth.code = qp.code;
|
||||||
|
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
|
||||||
|
} else {
|
||||||
|
let oauthErrorMsg
|
||||||
|
if (qp.error) {
|
||||||
|
oauthErrorMsg = "["+qp.error+"]: " +
|
||||||
|
(qp.error_description ? qp.error_description+ ". " : "no accessCode received from the server. ") +
|
||||||
|
(qp.error_uri ? "More info: "+qp.error_uri : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
oauth2.errCb({
|
||||||
|
authId: oauth2.auth.name,
|
||||||
|
source: "auth",
|
||||||
|
level: "error",
|
||||||
|
message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid, redirectUrl: redirectUrl});
|
||||||
|
}
|
||||||
|
window.close();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
134
openflexure_microscope/common/flask_labthings/views/docs/static/swagger-ui-bundle.js
vendored
Normal file
134
openflexure_microscope/common/flask_labthings/views/docs/static/swagger-ui-bundle.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
openflexure_microscope/common/flask_labthings/views/docs/static/swagger-ui-bundle.js.map
vendored
Normal file
1
openflexure_microscope/common/flask_labthings/views/docs/static/swagger-ui-bundle.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
22
openflexure_microscope/common/flask_labthings/views/docs/static/swagger-ui-standalone-preset.js
vendored
Normal file
22
openflexure_microscope/common/flask_labthings/views/docs/static/swagger-ui-standalone-preset.js
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
4
openflexure_microscope/common/flask_labthings/views/docs/static/swagger-ui.css
vendored
Normal file
4
openflexure_microscope/common/flask_labthings/views/docs/static/swagger-ui.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
openflexure_microscope/common/flask_labthings/views/docs/static/swagger-ui.css.map
vendored
Normal file
1
openflexure_microscope/common/flask_labthings/views/docs/static/swagger-ui.css.map
vendored
Normal file
File diff suppressed because one or more lines are too long
9
openflexure_microscope/common/flask_labthings/views/docs/static/swagger-ui.js
vendored
Normal file
9
openflexure_microscope/common/flask_labthings/views/docs/static/swagger-ui.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
openflexure_microscope/common/flask_labthings/views/docs/static/swagger-ui.js.map
vendored
Normal file
1
openflexure_microscope/common/flask_labthings/views/docs/static/swagger-ui.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
38
openflexure_microscope/common/flask_labthings/views/docs/templates/swagger-ui.html
vendored
Normal file
38
openflexure_microscope/common/flask_labthings/views/docs/templates/swagger-ui.html
vendored
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Swagger UI</title>
|
||||||
|
<link rel="icon" type="image/png" href="{{ url_for('labthings_docs.static', filename='favicon-32x32.png') }}"
|
||||||
|
sizes="32x32" />
|
||||||
|
<link rel="icon" type="image/png" href="{{ url_for('labthings_docs.static', filename='favicon-16x16.png') }}"
|
||||||
|
sizes="16x16" />
|
||||||
|
<link href="{{ url_for('labthings_docs.static', filename='swagger-ui.css') }}" rel="stylesheet" type="text/css" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body class="swagger-section">
|
||||||
|
<div id="message-bar" class="swagger-ui-wrap" data-sw-translate> </div>
|
||||||
|
<div id="swagger-ui-container" class="swagger-ui-wrap"></div>
|
||||||
|
<script src="{{ url_for('labthings_docs.static', filename='swagger-ui-bundle.js') }}" type="text/javascript"></script>
|
||||||
|
<script src="{{ url_for('labthings_docs.static', filename='swagger-ui-standalone-preset.js') }}"
|
||||||
|
type="text/javascript"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
var ui = SwaggerUIBundle({
|
||||||
|
url: "{{ url_for('labthings_docs.swagger_json') }}",
|
||||||
|
dom_id: '#swagger-ui-container',
|
||||||
|
deepLinking: true,
|
||||||
|
presets: [
|
||||||
|
SwaggerUIBundle.presets.apis,
|
||||||
|
SwaggerUIStandalonePreset
|
||||||
|
],
|
||||||
|
plugins: [
|
||||||
|
SwaggerUIBundle.plugins.DownloadUrl
|
||||||
|
],
|
||||||
|
layout: "BaseLayout"
|
||||||
|
})
|
||||||
|
window.ui = ui
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue