Skip to content

feat(event_handlers) Add support for returning dict, status-code tuples from resolvers #1853

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

Merged
merged 5 commits into from
Jan 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions aws_lambda_powertools/event_handler/api_gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -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),
)
Expand Down
7 changes: 6 additions & 1 deletion docs/core/event_handler/api_gateway.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
20 changes: 20 additions & 0 deletions examples/event_handler_rest/src/getting_started_return_tuple.py
Original file line number Diff line number Diff line change
@@ -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)
36 changes: 36 additions & 0 deletions tests/functional/event_handler/test_api_gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"