A Python serverless Swagger UI for Amazon API Gateway

Temps de lecture : 4 minutes

A few days ago, one of my colleagues was complaining about the lack of ready-to-use Swagger UI for Amazon Api Gateway. He had found a working Node.js version, but the project was written using Node.js 14. I suggested to migrate it to Python 3.11, and he argues that it will take too much time. Consequently, I proposed the following deal: if I can create Swagger UI for Amazon API Gateway in Python in less than one week, then he must pass the AWS Certified Security – Specialty certification the next year, otherwise, I will have the obligation to pass this certification on my side the next month.

Spoiler: he is now actively studying the AWS Certified Security – Specialty certification. Here is my implementation.

Implementation

The project is heavily based on the Terraform sample provided by Hendrik Hagen with a Node.js implementation. I will not explain here the architecture and the Terraform source code here because it is almost the same, the architecture diagram is also absolutely identical: https://www.tecracer.com/blog/img/2023/03/serverless-swagger-ui-architecture.png.

Checkout the blog post from Hendrik Hagen to get the Terraform implementation details.

I will dig a bit into the Python implementation, to give you the necessary level of information to customize it on your side.

Flask

First of all, the project must be able to serve Swagger UI HTML/CSS files directly from API Gateway. The Node.js implementation relied on the Express library for it, in Python we will use Flask.

Flask is a powerful micro web framework written in Python. It runs without any database and is really simple to extend. We can consequently bundle it into an AWS Lambda Function to have it serving Swagger ui HTML files. Additionally, somebody already proposed an implementation of SwaggerUI served by Flask, we will use it for our implementation.

The code is really straightforward, it contains a Flask blueprint declaring where the static files should be served, injects some elements into the index.html for CSS/Javascript urls, and that’s all:

from flask import Flask
from flask_swagger_ui import get_swaggerui_blueprint

app = Flask(__name__)


SWAGGER_URL = '/api-docs'
API_URL = 'http://petstore.swagger.io/v2/swagger.json'

swaggerui_blueprint = get_swaggerui_blueprint(
    SWAGGER_URL,
    API_URL,
    config={}
)

app.register_blueprint(swaggerui_blueprint)

app.run()

awsgi

Running a Flask application in an AWS Lambda Function behind an Amazon API Gateway cannot work out-of-the-box, because Flask is usually associated with a WSGI server distributing requests from the Internet to it. We must consequently create a bridge between the event object provided to AWS Lambda by Amazon API Gateway and the Flask runtime.

One of the must famous project running Python server-side stuff in AWS Lambda is Zappa. However, Zappa is more than a library: it is also a framework allowing to package the source code, deploy the API, manage Amazon API Gateway stages. All these features are great if you are bulding a serverless application, but I was looking for a more lightweight solution to have my SwaggerUI ready to be bundled in many project with a few code change, and creating a dependency with Zappa did not appear as a good idea.

Fortunately, somebody have written a really simple WSGI integration for Flask, which is not *really* a WSGI server, but passes the required information from Amazon API Gateway event to Flask params in less than 200 lines of code!

Let’s glue it all together:

import awsgi
from flask import Flask
from flask_swagger_ui import get_swaggerui_blueprint

app = Flask(__name__)

SWAGGER_URL = '/api-docs'
API_URL = 'http://petstore.swagger.io/v2/swagger.json'

swaggerui_blueprint = get_swaggerui_blueprint(
    SWAGGER_URL,
    API_URL,
    config={}
)

app.register_blueprint(swaggerui_blueprint)

def lambda_handler(event, context):
    return awsgi.response(app, event, context, base64_content_types={"image/png"})

Generated Swagger JSON from Amazon API Gateway definition

We have almost a working version forcing my colleague pass the AWS Certified Security – Specialty certification, but currently, the swagger exposed is not the one from the deployed Amazon API Gateway. Let’s export it and inject the URL in the Flask Swagger UI params:

import awsgi
import boto3
from flask import Flask, request
from flask_swagger_ui import get_swaggerui_blueprint

app = Flask(__name__)

SWAGGER_URL = '/api-docs'
API_URL = 'swagger.json'

swaggerui_blueprint = get_swaggerui_blueprint(
    SWAGGER_URL,
    API_URL,
    config={}
)

@swaggerui_blueprint.route("/swagger.json")
def expose_swagger_definition():

    api_identifier = request.environ["awsgi.event"]["requestContext"]["apiId"]
    api_stage = request.environ["awsgi.event"]["requestContext"]["stage"]

    export = boto3_api_client.get_export(
        restApiId=api_identifier,
        stageName=api_stage,
        exportType="swagger",
        accepts="application/json",
    )

    return export["body"].read().decode('utf-8')

app.register_blueprint(swaggerui_blueprint)

def lambda_handler(event, context):
    return awsgi.response(app, event, context, base64_content_types={"image/png"})

And finally, it works, with (almost) no code written by myself:

Source code bundle

The complete source code explained in this article is available in the GitHub repository: https://github.com/npellegrin/serverless-swagger-ui

Special thanks

Special thanks to Hendrik Hagen for writing the blog post containing the Node.js version of this software. And many thanks to sveint for writing the flask-swagger-ui project, and hats off to Matthew Wedgwood for writing the awsgi project.

Commentaires :

A lire également sur le sujet :