Skip to content

Commit 22a6925

Browse files
heitorlessasthulb
andauthored
docs(idempotency): use tab navigation, improves custom serializer example, and additional explanations (aws-powertools#3067)
* fix(parameters): make cache aware of single vs multiple calls Signed-off-by: heitorlessa <[email protected]> * chore: cleanup, add test for single and nested Signed-off-by: heitorlessa <[email protected]> * docs: improves navigation, custom example, explanations Signed-off-by: heitorlessa <[email protected]> --------- Signed-off-by: heitorlessa <[email protected]> Co-authored-by: Simon Thulbourn <[email protected]>
1 parent 41b4529 commit 22a6925

6 files changed

+70
-47
lines changed

docs/utilities/idempotency.md

+45-21
Original file line numberDiff line numberDiff line change
@@ -154,43 +154,67 @@ When using `idempotent_function`, you must tell us which keyword parameter in yo
154154

155155
#### Output serialization
156156

157-
The default return of the `idempotent_function` decorator is a JSON object, but you can customize the function's return type by utilizing the `output_serializer` parameter. The output serializer supports any JSON serializable data, **Python Dataclasses** and **Pydantic Models**.
157+
By default, `idempotent_function` serializes, stores, and returns your annotated function's result as a JSON object. You can change this behavior using `output_serializer` parameter.
158+
159+
The output serializer supports any JSON serializable data, **Python Dataclasses** and **Pydantic Models**.
158160

159161
!!! info "When using the `output_serializer` parameter, the data will continue to be stored in DynamoDB as a JSON object."
160162

161-
Working with Pydantic Models:
163+
=== "Pydantic"
162164

163-
=== "Explicitly passing the Pydantic model type"
165+
You can use `PydanticSerializer` to automatically serialize what's retrieved from the persistent storage based on the return type annotated.
164166

165-
```python hl_lines="6 24 25 32 35 44"
166-
--8<-- "examples/idempotency/src/working_with_pydantic_explicitly_output_serializer.py"
167-
```
168-
=== "Deducing the Pydantic model type from the return type annotation"
167+
=== "Inferring via the return type"
169168

170-
```python hl_lines="6 24 25 32 36 45"
171-
--8<-- "examples/idempotency/src/working_with_pydantic_deduced_output_serializer.py"
172-
```
169+
```python hl_lines="6 24 25 32 36 45"
170+
--8<-- "examples/idempotency/src/working_with_pydantic_deduced_output_serializer.py"
171+
```
173172

174-
Working with Python Dataclasses:
173+
1. We'll use `OrderOutput` to instantiate a new object using the data retrieved from persistent storage as input. <br><br> This ensures the return of the function is not impacted when `@idempotent_function` is used.
175174

176-
=== "Explicitly passing the model type"
175+
=== "Explicit model type"
177176

178-
```python hl_lines="8 27-29 36 39 48"
179-
--8<-- "examples/idempotency/src/working_with_dataclass_explicitly_output_serializer.py"
180-
```
177+
Alternatively, you can provide an explicit model as an input to `PydanticSerializer`.
181178

182-
=== "Deducing the model type from the return type annotation"
179+
```python hl_lines="6 24 25 32 35 44"
180+
--8<-- "examples/idempotency/src/working_with_pydantic_explicitly_output_serializer.py"
181+
```
183182

184-
```python hl_lines="8 27-29 36 40 49"
185-
--8<-- "examples/idempotency/src/working_with_dataclass_deduced_output_serializer.py"
186-
```
183+
=== "Dataclasses"
184+
185+
You can use `DataclassSerializer` to automatically serialize what's retrieved from the persistent storage based on the return type annotated.
186+
187+
=== "Inferring via the return type"
188+
189+
```python hl_lines="8 27-29 36 40 49"
190+
--8<-- "examples/idempotency/src/working_with_dataclass_deduced_output_serializer.py"
191+
```
187192

188-
=== "Using A Custom Type (Dataclasses)"
193+
1. We'll use `OrderOutput` to instantiate a new object using the data retrieved from persistent storage as input. <br><br> This ensures the return of the function is not impacted when `@idempotent_function` is used.
189194

190-
```python hl_lines="9 33 37 41-44 51 54"
195+
=== "Explicit model type"
196+
197+
Alternatively, you can provide an explicit model as an input to `DataclassSerializer`.
198+
199+
```python hl_lines="8 27-29 36 39 48"
200+
--8<-- "examples/idempotency/src/working_with_dataclass_explicitly_output_serializer.py"
201+
```
202+
203+
=== "Any type"
204+
205+
You can use `CustomDictSerializer` to have full control over the serialization process for any type. It expects two functions:
206+
207+
* **to_dict**. Function to convert any type to a JSON serializable dictionary before it saves into the persistent storage.
208+
* **from_dict**. Function to convert from a dictionary retrieved from persistent storage and serialize in its original form.
209+
210+
```python hl_lines="8 32 36 40 50 53"
191211
--8<-- "examples/idempotency/src/working_with_idempotent_function_custom_output_serializer.py"
192212
```
193213

214+
1. This function does the following <br><br>**1**. Receives the return from `process_order`<br> **2**. Converts to dictionary before it can be saved into the persistent storage.
215+
2. This function does the following <br><br>**1**. Receives the dictionary saved into the persistent storage <br>**1** Serializes to `OrderOutput` before `@idempotent` returns back to the caller.
216+
3. This serializer receives both functions so it knows who to call when to serialize to and from dictionary.
217+
194218
#### Batch integration
195219

196220
You can can easily integrate with [Batch utility](batch.md){target="_blank"} via context manager. This ensures that you process each record in an idempotent manner, and guard against a [Lambda timeout](#lambda-timeouts) idempotent situation.

examples/idempotency/src/working_with_dataclass_deduced_output_serializer.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ class OrderOutput:
3535
persistence_store=dynamodb,
3636
output_serializer=DataclassSerializer,
3737
)
38-
# order output is deduced from return type
39-
def deduced_order_output_serializer(order: Order) -> OrderOutput:
38+
# order output is inferred from return type
39+
def process_order(order: Order) -> OrderOutput: # (1)!
4040
return OrderOutput(order_id=order.order_id)
4141

4242

@@ -46,4 +46,4 @@ def lambda_handler(event: dict, context: LambdaContext):
4646
order = Order(item=order_item, order_id=1)
4747

4848
# `order` parameter must be called as a keyword argument to work
49-
deduced_order_output_serializer(order=order)
49+
process_order(order=order)

examples/idempotency/src/working_with_dataclass_explicitly_output_serializer.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ class OrderOutput:
3535
persistence_store=dynamodb,
3636
output_serializer=DataclassSerializer(model=OrderOutput),
3737
)
38-
def explicit_order_output_serializer(order: Order):
38+
def process_order(order: Order):
3939
return OrderOutput(order_id=order.order_id)
4040

4141

@@ -45,4 +45,4 @@ def lambda_handler(event: dict, context: LambdaContext):
4545
order = Order(item=order_item, order_id=1)
4646

4747
# `order` parameter must be called as a keyword argument to work
48-
explicit_order_output_serializer(order=order)
48+
process_order(order=order)

examples/idempotency/src/working_with_idempotent_function_custom_output_serializer.py

+15-16
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
from dataclasses import asdict, dataclass
2-
from typing import Any, Dict
1+
from typing import Dict, Type
32

43
from aws_lambda_powertools.utilities.idempotency import (
54
DynamoDBPersistenceLayer,
@@ -13,34 +12,34 @@
1312
config = IdempotencyConfig(event_key_jmespath="order_id") # see Choosing a payload subset section
1413

1514

16-
@dataclass
1715
class OrderItem:
18-
sku: str
19-
description: str
16+
def __init__(self, sku: str, description: str):
17+
self.sku = sku
18+
self.description = description
2019

2120

22-
@dataclass
2321
class Order:
24-
item: OrderItem
25-
order_id: int
22+
def __init__(self, item: OrderItem, order_id: int):
23+
self.item = item
24+
self.order_id = order_id
2625

2726

28-
@dataclass
2927
class OrderOutput:
30-
order_id: int
28+
def __init__(self, order_id: int):
29+
self.order_id = order_id
3130

3231

33-
def custom_to_dict(x: Any) -> Dict:
34-
return asdict(x)
32+
def order_to_dict(x: Type[OrderOutput]) -> Dict: # (1)!
33+
return x.__dict__
3534

3635

37-
def custom_from_dict(x: Dict) -> Any:
36+
def dict_to_order(x: Dict) -> OrderOutput: # (2)!
3837
return OrderOutput(**x)
3938

4039

41-
order_output_serializer = CustomDictSerializer(
42-
to_dict=custom_to_dict,
43-
from_dict=custom_from_dict,
40+
order_output_serializer = CustomDictSerializer( # (3)!
41+
to_dict=order_to_dict,
42+
from_dict=dict_to_order,
4443
)
4544

4645

examples/idempotency/src/working_with_pydantic_deduced_output_serializer.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ class OrderOutput(BaseModel):
3131
persistence_store=dynamodb,
3232
output_serializer=PydanticSerializer,
3333
)
34-
# order output is deduced from return type
35-
def deduced_order_output_serializer(order: Order) -> OrderOutput:
34+
# order output is inferred from return type
35+
def process_order(order: Order) -> OrderOutput: # (1)!
3636
return OrderOutput(order_id=order.order_id)
3737

3838

@@ -42,4 +42,4 @@ def lambda_handler(event: dict, context: LambdaContext):
4242
order = Order(item=order_item, order_id=1)
4343

4444
# `order` parameter must be called as a keyword argument to work
45-
deduced_order_output_serializer(order=order)
45+
process_order(order=order)

examples/idempotency/src/working_with_pydantic_explicitly_output_serializer.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ class OrderOutput(BaseModel):
3131
persistence_store=dynamodb,
3232
output_serializer=PydanticSerializer(model=OrderOutput),
3333
)
34-
def explicit_order_output_serializer(order: Order):
34+
def process_order(order: Order):
3535
return OrderOutput(order_id=order.order_id)
3636

3737

@@ -41,4 +41,4 @@ def lambda_handler(event: dict, context: LambdaContext):
4141
order = Order(item=order_item, order_id=1)
4242

4343
# `order` parameter must be called as a keyword argument to work
44-
explicit_order_output_serializer(order=order)
44+
process_order(order=order)

0 commit comments

Comments
 (0)