connexion icon indicating copy to clipboard operation
connexion copied to clipboard

ReDoc support

Open rgreinho opened this issue 5 years ago • 8 comments

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

rgreinho avatar Nov 12 '18 03:11 rgreinho

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.

dtkav avatar Nov 12 '18 18:11 dtkav

Also, I don't think #719 is related at all - that's targeted at adding/changing middleware, not adding extra routes.

dtkav avatar Nov 12 '18 18:11 dtkav

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?

rgreinho avatar Nov 12 '18 23:11 rgreinho

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 .

dtkav avatar Nov 13 '18 01:11 dtkav

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.

ChristianMaehler avatar Apr 10 '19 14:04 ChristianMaehler

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

g0di avatar Sep 20 '19 09:09 g0di

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.

dtilchin avatar May 05 '20 06:05 dtilchin

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.

DenisCasselle avatar Jan 05 '24 18:01 DenisCasselle