-
Notifications
You must be signed in to change notification settings - Fork 421
RFC: Auto-generate OpenAPI docs from routes #2421
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Added a section about Swagger UI |
Hey @rubenfonseca Why model schemas are out of scope of this rfc ? |
That's a great idea and would be so powerful. |
You can use an environment variable to generate/or not the api spec. Perhaps even supply a cli command to generate it and during runtime the decorator/open api code will not affect production code. |
That's cool! SwaggerUI facilitates the debugging. We use something like that in FastAPI with Mangum and REALLY facilitates development when Lambda is behind a API Gateway through proxy. Maybe we can use |
@darnley we use the same approach to be able to debug and work locally. |
I would prefer to autogenerate it at runtime on the fly and keep the same spec for the whole lifetime of the lambda. At least as a first option.
|
Hi @rubenfonseca! Impressive RFC, dude! Clear purpose and valuable addition to Powertools. Following my considerations. 1 - Extending the specification with additional metadata 2 - Serving a Swagger UI
I really like that because we don't burden the customer with additional needs to handle static assets.
I'm thinking here that we don't have a workaround for this, if the customer doesn't have a route defined (or ANY) in the APIGW there's nothing to do. We must be very careful in communicating this in the documentation. 3 - Dependencies
This is actually 29.6kb if you just use the 4 - Development environment
I agree with @darnley, this can make life easier when running in development environments like SAM. I'm eager to see this implementation and learn from your code, Ruben! |
I agree with this comment. I'd like to see the generation away from the handler – I think a few people will eventually forget to remove it and generate the OpenAPI spec in their actual functions or perhaps more frustrating to customers, forget to add it and wonder why it's not generating. |
Hi everyone, I've updated the RFC with an example of the final DX that we are working on. Can you please take a look at it? There is still a couple of things to resolve, but I'll keep you posted with the updates. |
Nice, looking very good! |
@ran-isenberg direct integration with pydantic models would make this RFC / PR too big, but I'm happy to look at it as soon as this one is merged. I just want to make sure that we will be able to plug in any schema generator, and not be tied to Pydantic only. |
makes sense. |
Hi @rubenfonseca, thanks for the RFC. We have been thinking about how to better document all of our APIs. I have a lot of thoughts, but they're not quite fully baked:
I don't expect you to address any of these in this RFC. Just wanted to post some ideas. Thanks for an amazing library. ❤️ |
To help me decide I’d like to understand better one of the tradeoffs. Option 3 seems to offer the best of both worlds, but I’m not sure I’m seeing the full implication of the cons. In which cases would I want to customize the handler for this specific route, besides adding middlewares to it? |
must we include /swagger to the API? |
I like the second and third option. They look cleaner and I see some similarity with implementations in other frameworks/languages. |
Hi @rubenfonseca, Thanks for the proposal. I'm worried about the use case where the swagger endpoint needs a different authorization model from the actual API endpoints. For example, let's say we want swagger for an API protected by a Lambda Authorizer that validates user-based Bearer JWTs. Do we really want to require a end-user Bearer token for the Swagger endpoint? That might be onerous for document automation. Now, this ☝🏽 may be a corner case. So, ultimately, my vote is to make the common case easy and the hard cases possible. Option 1 is a bit too much on the user and I'm having trouble thinking of customization the user would need outside of middleware. Also, to my point above, I think any real customization the user will need to do will live outside the I vote for a combination of Option 3 with making the underlying generation code available as public methods in a utility module in case a user needs to expose the swagger via a separate lambda outside |
It would be very helpful when we could export the json/yaml somehow. I would prefer solution 1. Btw. is json the preferred format or yaml? Is it configurable? |
+1 for json/yaml export. I dont care for the /swagger at all. |
As a datapoint, I too will be exporting this for consumption by a documentation tool. |
@dreamorosi, @Tankanow, @wurstnase even if we go with Option 3, you can still write Option 1 yourself manually and do whatever you want with the handler, including different authorization, etc. We will have both an @ran-isenberg no, that's just a default ("/swagger"), but you could customize it, of course. @darnley thank you! Thank you everyone for your great feedback! We will try to move forward with Option 3 while still giving you the flexibility to build your own logic and/or export the raw OpenAPI model for consumption/processing elsewhere. |
Currently there are only |
Hi @wurstnase! When you create a POST endpoint we are already adding app.pyfrom dataclasses import dataclass, field
import split_route
import requests
from requests import Response
from pydantic import BaseModel, Field
from aws_lambda_powertools import Logger
from aws_lambda_powertools.event_handler import APIGatewayRestResolver
from aws_lambda_powertools.logging import correlation_paths
from aws_lambda_powertools.utilities.typing import LambdaContext
logger = Logger()
app = APIGatewayRestResolver(enable_validation=True)
#app.include_router(split_route.router)
@dataclass
class Todo:
id: int
completed: bool
title: str = field(default_factory="PT")
@app.post("/hello")
def post_todo(todo: Todo):
logger.info("Processing todo", todo_title=todo.title)
return {"todos": todo}
# You can continue to use other utilities just as before
@logger.inject_lambda_context(correlation_id_path=correlation_paths.API_GATEWAY_REST)
def lambda_handler(event: dict, context: LambdaContext) -> dict:
print(app.get_openapi_json_schema(title="My API", version="2.5.0", description="Powertools OpenAPI"))
return app.resolve(event, context) OpenAPI Schema{
"openapi": "3.1.0",
"info": {
"title": "My API",
"description": "Powertools OpenAPI",
"version": "2.5.0"
},
"servers": [
{
"url": "/"
}
],
"paths": {
"/hello": {
"post": {
"summary": "POST /hello",
"operationId": "get_todos_hello_post",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Todo"
}
}
},
"required": true
},
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
}
},
"components": {
"schemas": {
"HTTPValidationError": {
"properties": {
"detail": {
"items": {
"$ref": "#/components/schemas/ValidationError"
},
"type": "array",
"title": "Detail"
}
},
"type": "object",
"title": "HTTPValidationError"
},
"Todo": {
"properties": {
"id": {
"type": "integer",
"title": "Id"
},
"completed": {
"type": "boolean",
"title": "Completed"
},
"title": {
"type": "string",
"title": "Title"
}
},
"type": "object",
"required": [
"id",
"completed"
],
"title": "Todo"
},
"ValidationError": {
"properties": {
"loc": {
"items": {
"anyOf": [
{
"type": "string"
},
{
"type": "integer"
}
]
},
"type": "array",
"title": "Location"
},
"msg": {
"type": "string",
"title": "Message"
},
"type": {
"type": "string",
"title": "Error Type"
}
},
"type": "object",
"required": [
"loc",
"msg",
"type"
],
"title": "ValidationError"
}
}
}
}
|
Thanks for the example. @app.post("/hello")
def get_todos(leo: Todo):
... The body parsing as argument is a new feature? |
Yes, we have added this support and will release it when we release OpenAPI! For existing users there is no change, but if someone wants to use the body as an argument they must enable the from aws_lambda_powertools.event_handler import APIGatewayRestResolver
app = APIGatewayRestResolver(enable_validation=True) and then @app.post("/hello")
def get_todos(todo: Todo):
print(todo.id)
return {"todo": todo} |
Hi, how about documenting the headers? I'm creating a webhook handler which needs a |
@tlinhart thank you for this valuable feedback! This is something that we have in our head. However, the PR is getting already too big to handle, so we will initially merge without support for Headers. BUT I don't think it will require a lot of effort to add it ! Thank you again for raising this up! I'll make sure I open a new issue for this this one |
Awesome, thanks! |
|
Hey everyone! We are excited to let you know that we have completed the first step to add support for OpenAPI Spec + SwaggerUI. After intense months of work, we have finally added support and plan to release this feature in the next version! We are writing the documentation to explain how to use this new feature. This first version brings several functionalities, such as: 1 - Integration with Pydantic We know that we have things to improve, we have to develop a CLI to make your lives easier. Still, this first version is a big step in the project and fulfills our main objective: improving the development experience in Lambda environments. Thank you for all your support, collaboration, and contributions. 🚀 🥇 |
This is now released under 2.26.1 version! |
issue to document this new feature as it requires a new subsection only for OpenAPI: #3324 |
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.
Results in
TODO:
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:
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)Create a DTO for the OpenAPI properties (accepted)
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: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.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
Acknowledgment
The text was updated successfully, but these errors were encountered: