Skip to content

Commit 793a097

Browse files
committed
feat(event-handler): add documentation and example for Field discriminator support
1 parent 89fb5f8 commit 793a097

File tree

3 files changed

+65
-2
lines changed

3 files changed

+65
-2
lines changed

aws_lambda_powertools/event_handler/openapi/compat.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
from collections import deque
55
from collections.abc import Mapping, Sequence
6+
from copy import copy
67

78
# MAINTENANCE: remove when deprecating Pydantic v1. Mypy doesn't handle two different code paths that import different
89
# versions of a module, so we need to ignore errors here.
@@ -187,8 +188,6 @@ def model_rebuild(model: type[BaseModel]) -> None:
187188

188189
def copy_field_info(*, field_info: FieldInfo, annotation: Any) -> FieldInfo:
189190
# Create a shallow copy of the field_info to preserve its type and all attributes
190-
from copy import copy
191-
192191
new_field = copy(field_info)
193192
# Update only the annotation to the new one
194193
new_field.annotation = annotation

docs/core/event_handler/api_gateway.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,23 @@ You can use the `Form` type to tell the Event Handler that a parameter expects f
568568
--8<-- "examples/event_handler_rest/src/working_with_form_data.py"
569569
```
570570

571+
#### Discriminated unions
572+
573+
!!! info "You must set `enable_validation=True` to use discriminated unions via type annotation."
574+
575+
You can use Pydantic's `Field(discriminator="...")` with union types to create discriminated unions (also known as tagged unions). This allows the Event Handler to automatically determine which model to use based on a discriminator field in the request body.
576+
577+
In the following example, we define two action types (`FooAction` and `BarAction`) that share a common discriminator field `action`. The Event Handler will automatically parse the request body and instantiate the correct model based on the `action` field value:
578+
579+
```python hl_lines="3 4 8 31 36" title="discriminated_unions.py"
580+
--8<-- "examples/event_handler_rest/src/discriminated_unions.py"
581+
```
582+
583+
1. `Field(discriminator="action")` tells Pydantic to use the `action` field to determine which model to instantiate
584+
2. `Body()` annotation tells the Event Handler to parse the request body using the discriminated union
585+
586+
When you send a request with `{"action": "foo", "foo_data": "example"}`, the Event Handler will automatically create a `FooAction` instance. Similarly, `{"action": "bar", "bar_data": 42}` will create a `BarAction` instance.
587+
571588
#### Supported types for response serialization
572589

573590
With data validation enabled, we natively support serializing the following data types to JSON:
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
from typing import Literal, Union
2+
3+
from pydantic import BaseModel, Field
4+
from typing_extensions import Annotated
5+
6+
from aws_lambda_powertools import Logger, Tracer
7+
from aws_lambda_powertools.event_handler import APIGatewayRestResolver
8+
from aws_lambda_powertools.event_handler.openapi.params import Body
9+
from aws_lambda_powertools.logging import correlation_paths
10+
from aws_lambda_powertools.utilities.typing import LambdaContext
11+
12+
tracer = Tracer()
13+
logger = Logger()
14+
app = APIGatewayRestResolver(enable_validation=True)
15+
16+
17+
class FooAction(BaseModel):
18+
"""Action type for foo operations."""
19+
20+
action: Literal["foo"] = "foo"
21+
foo_data: str
22+
23+
24+
class BarAction(BaseModel):
25+
"""Action type for bar operations."""
26+
27+
action: Literal["bar"] = "bar"
28+
bar_data: int
29+
30+
31+
ActionType = Annotated[Union[FooAction, BarAction], Field(discriminator="action")] # (1)!
32+
33+
34+
@app.post("/actions")
35+
@tracer.capture_method
36+
def handle_action(action: Annotated[ActionType, Body(description="Action to perform")]): # (2)!
37+
"""Handle different action types using discriminated unions."""
38+
if isinstance(action, FooAction):
39+
return {"message": f"Handling foo action with data: {action.foo_data}"}
40+
elif isinstance(action, BarAction):
41+
return {"message": f"Handling bar action with data: {action.bar_data}"}
42+
43+
44+
@logger.inject_lambda_context(correlation_id_path=correlation_paths.API_GATEWAY_HTTP)
45+
@tracer.capture_lambda_handler
46+
def lambda_handler(event: dict, context: LambdaContext) -> dict:
47+
return app.resolve(event, context)

0 commit comments

Comments
 (0)