diff --git a/docs/core/event_handler/api_gateway.md b/docs/core/event_handler/api_gateway.md index 2b7ef205227..618d055fcfa 100644 --- a/docs/core/event_handler/api_gateway.md +++ b/docs/core/event_handler/api_gateway.md @@ -554,6 +554,38 @@ With data validation enabled, we natively support serializing the following data ???+ info "See [custom serializer section](#custom-serializer) for bringing your own." Otherwise, we will raise `SerializationError` for any unsupported types _e.g., SQLAlchemy models_. +#### Pydantic serialization behavior + +!!! warning "Important: Pydantic models are serialized using aliases by default" + When `enable_validation=True` is set, **all Pydantic models in responses are automatically serialized using `by_alias=True`**. This differs from Pydantic's default behavior (`by_alias=False`). + +When you return Pydantic models from your handlers, Event Handler will serialize them using field aliases defined in your model configuration: + +=== "pydantic_alias_serialization.py" + + ```python hl_lines="1 2 11 20" + --8<-- "examples/event_handler_rest/src/pydantic_alias_serialization.py" + ``` + + 1. The `alias_generator=to_snake` converts camelCase field names to snake_case aliases + 2. `firstName` becomes `first_name` and lastName` becomes `last_name` in the JSON response + +=== "pydantic_alias_serialization_output.json" + + ```json hl_lines="3" + --8<-- "examples/event_handler_rest/src/pydantic_alias_serialization_output.json" + ``` + +##### Serializing using field name + +If you need to serialize using field names instead of aliases, you can dump the model: + +=== "pydantic_field_name_serialization.py" + + ```python hl_lines="11 19 20" + --8<-- "examples/event_handler_rest/src/pydantic_field_name_serialization.py" + ``` + ### Accessing request details Event Handler integrates with [Event Source Data Classes utilities](../../utilities/data_classes.md){target="_blank"}, and it exposes their respective resolver request details and convenient methods under `app.current_event`. diff --git a/examples/event_handler_rest/src/pydantic_alias_serialization.py b/examples/event_handler_rest/src/pydantic_alias_serialization.py new file mode 100644 index 00000000000..6b682dbe5ed --- /dev/null +++ b/examples/event_handler_rest/src/pydantic_alias_serialization.py @@ -0,0 +1,24 @@ +from pydantic import BaseModel, ConfigDict +from pydantic.alias_generators import to_snake + +from aws_lambda_powertools.event_handler import APIGatewayRestResolver +from aws_lambda_powertools.utilities.typing import LambdaContext + +app = APIGatewayRestResolver(enable_validation=True) + + +class UserResponse(BaseModel): + model_config = ConfigDict(alias_generator=to_snake, populate_by_name=True) # (1)! + + firstName: str # Will be serialized as "first_name" + lastName: str # Will be serialized as "last_name" + + +@app.get("/user") +def get_user() -> UserResponse: + return UserResponse(firstName="John", lastName="Doe") # (2)! + # Response will be: {"first_name": "John", "last_name": "Doe"} + + +def lambda_handler(event: dict, context: LambdaContext) -> dict: + return app.resolve(event, context) diff --git a/examples/event_handler_rest/src/pydantic_alias_serialization_output.json b/examples/event_handler_rest/src/pydantic_alias_serialization_output.json new file mode 100644 index 00000000000..f7438fa952d --- /dev/null +++ b/examples/event_handler_rest/src/pydantic_alias_serialization_output.json @@ -0,0 +1,9 @@ +{ + "statusCode": 200, + "body": "{\"first_name\":\"John\",\"last_name\":\"Doe\"}", + "headers": { + "Content-Type": "application/json" + }, + "multiValueHeaders": {}, + "isBase64Encoded": false +} diff --git a/examples/event_handler_rest/src/pydantic_field_name_serialization.py b/examples/event_handler_rest/src/pydantic_field_name_serialization.py new file mode 100644 index 00000000000..dbea7cbcdfc --- /dev/null +++ b/examples/event_handler_rest/src/pydantic_field_name_serialization.py @@ -0,0 +1,24 @@ +from pydantic import BaseModel, ConfigDict +from pydantic.alias_generators import to_snake + +from aws_lambda_powertools.event_handler import APIGatewayRestResolver +from aws_lambda_powertools.utilities.typing import LambdaContext + +app = APIGatewayRestResolver(enable_validation=True) + + +class UserResponse(BaseModel): + model_config = ConfigDict(alias_generator=to_snake, populate_by_name=True) + + firstName: str + lastName: str + + +@app.get("/user") +def get_user_manual() -> dict: + user = UserResponse(firstName="John", lastName="Doe") + return user.model_dump(by_alias=False) # Returns dict, not UserResponse + + +def lambda_handler(event: dict, context: LambdaContext) -> dict: + return app.resolve(event, context)