connexion
connexion copied to clipboard
ReDoc support
Description
As a developer I want to be able to use ReDoc to browse my OpenAPI specfication.
Expected behaviour
Implement a plugin (or similar) to be able to browse to {base_url}/redoc
to view the specification with ReDoc.
Actual behaviour
This behaviour does not currently exist.
Steps to reproduce
I read the issue #719 and the associated PR, but I was not sure how to use this information, so here is the way I implemented it.
Pre-requisites
- I started from the OpenAPI3 HelloWorld example.
- I installed
flask-cors
- I added the
connexion_redoc.py
file (see bellow)
The code
# connexion_redoc.py
"""Add a route to ReDoc."""
from flask import render_template_string
REDOC_TEMPLATE = '''
<!DOCTYPE html>
<html>
<head>
<title>ReDoc</title>
<!-- needed for adaptive design -->
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
<!--
ReDoc doesn't change outer page styles
-->
<style>
body {
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<redoc spec-url="{{spec_url}}"></redoc>
<script src="https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js"> </script>
</body>
</html>
'''
def redoc_view(spec_url):
"""
Render OpenAPI specification using ReDoc.
:param str spec_url: URL to the JSON specification file
"""
return render_template_string(REDOC_TEMPLATE, spec_url=spec_url)
def add_redoc_route(app, spec_url):
"""
Add a '/redoc' route pointing to de ReDoc page.
:param FlaskAPP app: the connexion application
:param str spec_url: URL to the JSON specification file
"""
app.app.add_url_rule('/redoc', 'redoc', redoc_view, defaults={'spec_url': spec_url})
#hello.py
#!/usr/bin/env python3
import connexion
from flask_cors import CORS
from connexion_redoc import add_redoc_route
def post_greeting(name: str) -> str:
return 'Hello {name}'.format(name=name)
if __name__ == '__main__':
app = connexion.FlaskApp(__name__, port=9090, specification_dir='openapi/')
app.add_api('helloworld-api.yaml', arguments={'title': 'Hello World Example'})
spec_url = "http://0.0.0.0:9090/v1.0/openapi.json"
add_redoc_route(app, spec_url)
CORS(app.app)
app.run()
Questions regarding the implementation
- I had to hardcode
spec_url
, what would be the proper way to implement it? - What would be the proper way to make this feature available to the
connexion
community?
Additional info:
Output of the commands:
-
python --version
:Python 3.7.0
-
pip show connexion | grep "^Version\:"
:Version: 2.0.1
We've removed vendored swagger-ui from connexion and into a separate package that is optionally installed (swagger-ui-bundle).
AFAICT ReDoc falls into a similar bucket. I doubt we'd want to vendor it, so it would probably also make sense for it to live as a separate package, and be installable with pip extras.
Also, I don't think #719 is related at all - that's targeted at adding/changing middleware, not adding extra routes.
Ok, I'll try to reproduce the same thing as swagger-ui.
Can I get an answer to my other question please?
I had to hardcode spec_url, what would be the proper way to implement it?
yeah, look into how I did it with swagger-ui. the HTML is a Jinja template that gets the spec url passed in
On Mon, Nov 12, 2018, 3:03 PM Rémy Greinhofer [email protected] wrote:
Ok, I'll try to reproduce the same thing as swagger-ui.
Can I get an answer to my other question please?
I had to hardcode spec_url, what would be the proper way to implement it?
— You are receiving this because you commented.
Reply to this email directly, view it on GitHub https://github.com/zalando/connexion/issues/774#issuecomment-438060840, or mute the thread https://github.com/notifications/unsubscribe-auth/AAlPSSrYnCDVyJTOqVIEVHeDp37QDR7Bks5uuf5OgaJpZM4YY4LI .
I tried it with the current implementation of connexion and a workaround.
I created a staticfile.yaml that looks like this
openapi: 3.0.0
servers: []
info:
version: 0.1.0
title: Static file yaml
description: This is a API specification for the static files
tags:
- name: Static
description: Deliver static files like redoc documentation
externalDocs:
description: idea/inspiration found here
url: https://github.com/zalando/connexion/issues/441
paths:
/:
get:
operationId: api.static_redoc
description: HTML file with rendered OpenAPI reference.
tags:
- Static
responses:
'200':
description: Successfully loaded html page
content:
text/html:
schema:
type: string
Then I added this "api" to the connexion app by this part:
import connexion
app = connexion.FlaskApp(__name__, specification_dir='../specifications/')
app.add_api('myactualspec.yaml', base_path='/myactualapi')
app.add_api('staticfile.yaml', base_path='/redoc')
app.run(port=8080)
if __name__ == "__main__":
app.run()
And implemented this handler in the api.py
def static_redoc():
print("Deliver redoc")
return flask.send_file('redoc.htm')
Of course, you have to provide a redoc.htm (I used the standard template) that refers to the spec with this line
<redoc spec-url='/myactualapi/openapi.json'></redoc>
which is available through the actual API
Works fine. Maybe this is useful as a workaround for you.
For people interested in using ReDoc instead of swagger UI I found a super simple solution.
In my python project package I simply added a templates
folder including a single file named index.j2
containing the redoc html
<!DOCTYPE html>
<html>
<head>
<title>ReDoc</title>
<!-- needed for adaptive design -->
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
<!--
ReDoc doesn't change outer page styles
-->
<style>
body {
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<redoc spec-url='{{ openapi_spec_url }}'></redoc>
<script src="https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js"> </script>
</body>
</html>
When I access my generated API documentation at /ui
it then show ReDoc.
That's it!
Please note that if you installed connexion without the [swagger-ui]
extension you will have to add the swagger_path
option to your connexion app like:
app = FlaskApp(__name__, options={"swagger_path": "./"})
Of course you may notice that this prevent you from being able to serve SwaggerUI and ReDoc at the same time on different paths.
Tested with connexion 2.3.0
Here's a pretty simple way I figured out to serve ReDoc easily, without making SwaggerUI unavailable.
app = connexion.FlaskApp('ThisHereApp')
api = app.add_api('myspec.yml')
bp = flask.Blueprint('docs', __name__, url_prefix = api.base_path,
template_folder = os.path.dirname(__file__))
# Copied from FlaskApi; it'd be nice to grab the spec path from the instance somehow
specPath = api.base_path + api.options.openapi_spec_path
serveRedoc = lambda: flask.render_template('redoc.j2', openapi_spec_url = specPath)
bp.add_url_rule('/docs/', __name__, serveRedoc)
app.app.register_blueprint(bp)
redoc.j2
is identical to the template in @hiboo 's (quite useful!) comment above. The code assumes that the template file is in the same directory as this app code lives.
As mentioned in https://github.com/zalando/connexion/issues/441, it would be great to have an example of serving static content - or ReDoc specifically, even - in the documentation. It wasn't obvious to me for a while that you could add a blueprint that does this so readily.
Here's a pretty simple way I figured out to serve ReDoc easily, without making SwaggerUI unavailable.
app = connexion.FlaskApp('ThisHereApp') api = app.add_api('myspec.yml') bp = flask.Blueprint('docs', __name__, url_prefix = api.base_path, template_folder = os.path.dirname(__file__)) # Copied from FlaskApi; it'd be nice to grab the spec path from the instance somehow specPath = api.base_path + api.options.openapi_spec_path serveRedoc = lambda: flask.render_template('redoc.j2', openapi_spec_url = specPath) bp.add_url_rule('/docs/', __name__, serveRedoc) app.app.register_blueprint(bp)
redoc.j2
is identical to the template in @hiboo 's (quite useful!) comment above. The code assumes that the template file is in the same directory as this app code lives.As mentioned in #441, it would be great to have an example of serving static content - or ReDoc specifically, even - in the documentation. It wasn't obvious to me for a while that you could add a blueprint that does this so readily.
I have trouble making this work since upgrading to V3. Has anyone found a solution? The first issue I encountered is that the add_api
method no longer returns the API. Even if I figure out a workaround for that, the routes from my spec aren't being added to my API correctly.