-
Notifications
You must be signed in to change notification settings - Fork 463
Description
Expected Behaviour
When building an API using the APIGatewayRestResolver, I should be able to reuse common response dictionary objects across multiple routes. The generated OpenAPI schema should accurately reflect the unique return type annotation for each function, even when they share the same response configuration dictionary object.
For example, given these two routes:
| Route | Return Type | Expected OpenAPI Schema Response Model |
|---|---|---|
/exams |
Response[list[ExamSummary]] |
A list of ExamSummary objects |
/exams/<exam_id> |
Response[ExamConfig] |
A single ExamConfig object |
Each route should display its own correct return type in the OpenAPI schema.
Current Behaviour
The framework leverages response dictionaries by reference (since multiple routes share the same dictionary object) and mutates them when injecting the return type schema.
As a result, the OpenAPI schema generated is incorrect due to a schema bleed: the return type from one of the routes persists in the shared dictionary and is then erroneously used for other routes.
In my specific local tests with the code snippet below, it appears the schema for /exams/<exam_id> is being injected first (or whichever route is processed first by the OpenAPI generator):
- The
/examsendpoint incorrectly shows a response model forExamConfiginstead of the expectedlist[ExamSummary]type. - The
/exams/<exam_id>endpoint correctly shows a response model forExamConfig.
Code snippet
class Responses:
"""Pre-configured OpenAPI response schemas."""
# Base responses
OK = {200: OpenAPIResponse(description="Successful operation")}
#...
# Common combinations
STANDARD_ERRORS = {**NOT_FOUND, **CONFLICT, **VALIDATION_ERROR, **SERVER_ERROR}
@classmethod
def combine(cls, *response_dicts: dict[int, OpenAPIResponse]) -> dict[int, OpenAPIResponse]:
"""Combine multiple response dictionaries."""
result = {}
for response_dict in response_dicts:
result.update(response_dict)
return result
@app.get(
"/exams",
tags=["Exams"],
responses=Responses.combine(Responses.OK, Responses.STANDARD_ERRORS),
)
def list_exams() -> Response[list[ExamSummary]]:
"""Lists all available exams."""
# ... returns list[ExamSummary] but the OpenAPI says it returns ExamConfig
@app.get(
"/exams/<exam_id>",
tags=["Exams"],
responses=Responses.combine(Responses.OK, Responses.STANDARD_ERRORS), # Reusing the shared Responses.OK dictionary
)
def get_exam_config(exam_id: str) -> Response[ExamConfig]:
"""Get the configuration for a specific exam"""
# ... returns ExamConfigPossible Solution
The issue appears to be in the _get_openapi_path method in api_gateway.py, where the response dictionary is mutated directly without creating a copy first.
Suggested fix - perform a deep copy before mutation:
# Add the response to the OpenAPI operation
if self.responses:
for status_code in list(self.responses):
# Current implementation (mutates the shared dictionary):
# response = self.responses[status_code]
# Proposed fix - deep copy to prevent mutation of the source object:
response = copy.deepcopy(self.responses[status_code])
# Case 1: there is not 'content' key
if "content" not in response:
# This mutation would now only affect the local copy
response["content"] = {
_DEFAULT_CONTENT_TYPE: self._openapi_operation_return(...)
}Steps to Reproduce
- Create a shared response dictionary in a
Responsesclass (e.g.,Responses.OK) - Define multiple routes that reuse the same response dictionary via
Responses.combine() - Use different return type annotations for each route (e.g.,
Response[list[ExamSummary]]vsResponse[ExamConfig]) - Generate the OpenAPI schema
- Inspect the response models in the OpenAPI schema - observe that one route's return type incorrectly appears in another route's schema
Powertools for AWS Lambda (Python) version
latest
AWS Lambda function runtime
3.13
Packaging format used
PyPi
Debugging logs
Metadata
Metadata
Assignees
Labels
Type
Projects
Status