Connexion: ReDoc support

Created on 12 Nov 2018  Â·  7Comments  Â·  Source: zalando/connexion

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

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
enhancement

Most helpful comment

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.

All 7 comments

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 notifications@github.com
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.

Was this page helpful?
0 / 5 - 0 ratings