Skip to content

RFC: Auto-generate OpenAPI docs from routes #2421

Closed
@rubenfonseca

Description

@rubenfonseca

Is this related to an existing feature request or issue?

#1236

Which Powertools for AWS Lambda (Python) utility does this relate to?

Event Handler - REST API

Summary

This RFC proposes the implementation of an automatic OpenAPI documentation generation feature for the AWS Lambda Powertools for Python's Event Handler - REST API utility.

The goal is to save developers time and effort by automating the generation of OpenAPI documentation based on the defined routes. The proposal suggests integrating this functionality into the existing utility, allowing developers to easily incorporate it into their applications.

Additionally, an optional Swagger UI endpoint would be exposed to provide a user-friendly interface for accessing the API documentation.

The scope of this proposal focuses solely on the auto-generation of OpenAPI docs from routes, excluding middleware support and authorization aspects. Some potential challenges discussed include handling dependencies, performance considerations, and deciding the level of exposure of the underlying apispec library.

Use case

The proposal aims to address the need for automatic generation of OpenAPI documentation based on the routes defined in the AWS Lambda Powertools for Python's Event Handler - REST API utility.

By automating the generation of OpenAPI docs, developers can save time and effort in maintaining the documentation manually. It ensures that the docs stay up to date with the application's routes, making the process efficient and hassle-free.

This automation optimizes developers' productivity and enhances the overall quality of the documentation, serving as a reliable reference for internal teams and external stakeholders.

With this integration of automation, developers can focus more on innovation and coding, knowing that their OpenAPI docs are effortlessly maintained and reflect the current state of the application.

Task list

Proposal (updated 11/07/2023)

The design proposes integrating a functionality within the AWS Lambda Powertools for Python's Event Handler - REST API utility that auto-generates OpenAPI documentation based on the defined routes. The implementation should be intuitive and accessible for developers familiar with the project, allowing them to easily incorporate this feature into their applications.

We took inspiration from FastAPI and their excelent first class support for generationg OpenAPI specs. The implementation will need to introspect named parameters to add it to the documentation.

Additionally, we want to optionally expose a Swagger UI endpoint with the API documentation.

import json

from aws_lambda_powertools.event_handler import ApiGatewayResolver
from aws_lambda_powertools.event_handler.openapi import (
    Example,
    MediaType,
    OpenAPIInfo,
    Operation,
    Parameter,
    ParameterLocation,
    Response,
    Responses,
)

app = ApiGatewayResolver(generate_openapi=True, openapi_info=OpenAPIInfo(
    title="This is my API",
    version="0.0.1b",
))


@app.get(rule="/viewer",
         operation=Operation(
             operationId="geViewerDetailsv1",
             summary="Viewer user information",
             description="Returns information about the current signed-in user",
             tags=["users"],
             parameters=[
                 Parameter(name="include_details",
                           location=ParameterLocation.QUERY,
                           required=False,
                           schema={
                               "type": "boolean",
                           }),
             ],
             responses=Responses(codes={
                 200: Response(description="Success",
                               content={
                                   "application/json": MediaType(
                                       schema={"$ref": "#/components/schemas/User"},
                                       examples={
                                           "application/json": Example(
                                               value=json.dumps({"id": 1, "name": "John Smith"}),
                                           ),
                                       },
                                   ),
                               }),
             }),
         ))
def viewer(event: dict):
    print(event)


print(app._spec.to_yaml())

Results in

paths:
  /viewer:
    get:
      tags:
      - users
      summary: Viewer user information
      description: Returns information about the current signed-in user
      operationId: geViewerDetailsv1
      parameters:
      - name: include_details
        in: query
        required: false
        schema:
          type: boolean
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
              examples:
                application/json:
                  value: '{"id": 1, "name": "John Smith"}'
    summary: Viewer user information
info:
  title: This is my API
  version: 0.0.1b
openapi: 3.0.2

TODO:

  • Explore way of defining global components (on the example, the User schema isn't defined in the spec)
  • Make sure that the schema mechanism can be easily extended in the future (e.g: Pydantic models)
  • Explore auto generation of parameters based on path parameters
  • Create an example of serving the Swagger API

Enriching the OpenAPI data

OpenAPI is a rich spec that allows a lot of detail to be added. Currently, there's only a very limited amount of information we can derive automatically:

  • HTTP method
  • path
  • parameters
  • eventually CORS configuration

In order to allow the user to extend the specification with additional metadata, we can consider different options:

Expose the apispec instance on the resolver's decorator (abandoned)

app = ApiGatewayResolver(generate_openapi=True)

app.spec.title = "Swagger Petstore"
app.spec.version = "1.0.0"
app.spec.path("/hello", operations={...})

@app.get("/hello")
def hello():
    print("world")

Create a DTO for the OpenAPI properties (accepted)

app = ApiGatewayResolver(generate_openapi=True, openapi_properties={
  "title": "Swagger Petstore",
  "version": "1.0.0",
  "openapi_version": "3.0.2"
})

@app.get("/hello", spec={
    "operation": {
        "summary": "This is a hello world endpoint",
        ...
    }
})

In this case we could of course improve the DX by creating utility classes instead of making the user have to define arbitrary dictionaries.

Extend through documentation (abandoned for now)

This would follow the apispec-frameworks model, where the documentation for the handler would get parsed and data would be extracted from it. Example from Flask:

class GistApi(MethodView):
        '''Gist API.
        ---
        x-extension: metadata
        '''
        def get(self):
           '''Gist view
           ---
           responses:
               200:
                   schema:
                       $ref: '#/definitions/Gist'
           '''
           pass

        def post(self):
           pass

Serving a Swagger UI

We could add a new parameter to the resolver enabling a new GET /api-docs route that would serve a Swagger UI.

app = ApiGatewayResolver(generate_openapi=True, enable_swagger=True, swagger_path="/api-docs")

This would still require configuring the mapping in the API Gateway (or equivalent).

To implement this, check how chalice-spec implements it, by loading the swagger ui from their CDN, and passing the API spec into the page as a JSON file.

Out of scope

The scope of this proposal does not include adding middleware support, such as incorporating authorization into the Swagger UI. While authorization is an important aspect of API documentation, it falls outside the scope of this specific RFC. The focus is solely on the auto-generation of OpenAPI docs from routes.

Additionally, we won't support any type of model validation for this RFC. This means we won't be able to attach Pydantic models to the API and have Powertools inspect and generate the appropriate OpenAPI sections for it.

Potential challenges

  • Include apispec as a dependency or an optional dependency: the current version of apispec weighs around 29.6Kb. Should we just import the dependency, or mark it as optinal?
  • Performance problems: should we opt-out from generating the API spec during a production workload? How to identify that we are running in production mode?
  • How much of apispec to expose: should we just "escape-hatch" the apispec and let the customer use its API to enrich the API spec, or should we create a thin wrapper around it, allowing us to switch implementations in the future?

Acknowledgment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

Status

Shipped

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions