diff --git a/aws_lambda_powertools/event_handler/api_gateway.py b/aws_lambda_powertools/event_handler/api_gateway.py index 8ced47f81e2..7b4001c7265 100644 --- a/aws_lambda_powertools/event_handler/api_gateway.py +++ b/aws_lambda_powertools/event_handler/api_gateway.py @@ -728,21 +728,26 @@ def _call_exception_handler(self, exp: Exception, route: Route) -> Optional[Resp return None - def _to_response(self, result: Union[Dict, Response]) -> Response: + def _to_response(self, result: Union[Dict, Tuple, Response]) -> Response: """Convert the route's result to a Response - 2 main result types are supported: + 3 main result types are supported: - Dict[str, Any]: Rest api response with just the Dict to json stringify and content-type is set to application/json + - Tuple[dict, int]: Same dict handling as above but with the option of including a status code - Response: returned as is, and allows for more flexibility """ + status_code = HTTPStatus.OK if isinstance(result, Response): return result + elif isinstance(result, tuple) and len(result) == 2: + # Unpack result dict and status code from tuple + result, status_code = result logger.debug("Simple response detected, serializing return before constructing final response") return Response( - status_code=200, + status_code=status_code, content_type=content_types.APPLICATION_JSON, body=self._json_dump(result), ) diff --git a/docs/core/event_handler/api_gateway.md b/docs/core/event_handler/api_gateway.md index cee848c24c3..802b6112e04 100644 --- a/docs/core/event_handler/api_gateway.md +++ b/docs/core/event_handler/api_gateway.md @@ -45,7 +45,12 @@ A resolver will handle request resolution, including [one or more routers](#spli For resolvers, we provide: `APIGatewayRestResolver`, `APIGatewayHttpResolver`, `ALBResolver`, and `LambdaFunctionUrlResolver`. From here on, we will default to `APIGatewayRestResolver` across examples. ???+ info "Auto-serialization" - We serialize `Dict` responses as JSON, trim whitespace for compact responses, and set content-type to `application/json`. + We serialize `Dict` responses as JSON, trim whitespace for compact responses, set content-type to `application/json`, and + return a 200 OK HTTP status. You can optionally set a different HTTP status code as the second argument of the tuple: + + ```python hl_lines="15 16" + --8<-- "examples/event_handler_rest/src/getting_started_return_tuple.py" + ``` #### API Gateway REST API diff --git a/examples/event_handler_rest/src/getting_started_return_tuple.py b/examples/event_handler_rest/src/getting_started_return_tuple.py new file mode 100644 index 00000000000..1c26970c1c1 --- /dev/null +++ b/examples/event_handler_rest/src/getting_started_return_tuple.py @@ -0,0 +1,20 @@ +import requests +from requests import Response + +from aws_lambda_powertools.event_handler import ALBResolver +from aws_lambda_powertools.utilities.typing import LambdaContext + +app = ALBResolver() + + +@app.post("/todo") +def create_todo(): + data: dict = app.current_event.json_body + todo: Response = requests.post("https://jsonplaceholder.typicode.com/todos", data=data) + + # Returns the created todo object, with a HTTP 201 Created status + return {"todo": todo.json()}, 201 + + +def lambda_handler(event: dict, context: LambdaContext) -> dict: + return app.resolve(event, context) diff --git a/tests/functional/event_handler/test_api_gateway.py b/tests/functional/event_handler/test_api_gateway.py index cf560fbcc34..ad9f834dbb2 100644 --- a/tests/functional/event_handler/test_api_gateway.py +++ b/tests/functional/event_handler/test_api_gateway.py @@ -1579,3 +1579,39 @@ def get_lambda() -> Response: # AND set the current_event type as APIGatewayProxyEvent assert result["statusCode"] == 200 assert result2["statusCode"] == 200 + + +def test_dict_response(): + # GIVEN a dict is returned + app = ApiGatewayResolver() + + @app.get("/lambda") + def get_message(): + return {"message": "success"} + + # WHEN calling handler + response = app({"httpMethod": "GET", "path": "/lambda"}, None) + + # THEN the body is correctly formatted, the status code is 200 and the content type is json + assert response["statusCode"] == 200 + assert response["multiValueHeaders"]["Content-Type"] == [content_types.APPLICATION_JSON] + response_body = json.loads(response["body"]) + assert response_body["message"] == "success" + + +def test_dict_response_with_status_code(): + # GIVEN a dict is returned with a status code + app = ApiGatewayResolver() + + @app.get("/lambda") + def get_message(): + return {"message": "success"}, 201 + + # WHEN calling handler + response = app({"httpMethod": "GET", "path": "/lambda"}, None) + + # THEN the body is correctly formatted, the status code is 201 and the content type is json + assert response["statusCode"] == 201 + assert response["multiValueHeaders"]["Content-Type"] == [content_types.APPLICATION_JSON] + response_body = json.loads(response["body"]) + assert response_body["message"] == "success"