Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit e8d4859

Browse files
anafalcaoFalcaoleandrodamascena
authoredNov 7, 2024··
docs(parser): change parser documentation (#5262)
* changed parser documentation * changed parser documentation * changed parser documentation * changed parser documentation * change the code from the doc to examples folder * add line highlights and fix sub header typo * fix typos, pydantic install, and add built in envelope example * changed the example from built in env to models * fix validationerror import * Changing highlights + import * Changing highlights + import * Changing highlights + import * Addressing Andrea's feedback --------- Signed-off-by: Ana Falcão <[email protected]> Co-authored-by: Falcao <[email protected]> Co-authored-by: Leandro Damascena <[email protected]>
1 parent 4a4bf17 commit e8d4859

24 files changed

+582
-519
lines changed
 

‎.github/workflows/quality_check.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ on:
2020
paths:
2121
- "aws_lambda_powertools/**"
2222
- "tests/**"
23+
- "examples/**"
2324
- "pyproject.toml"
2425
- "poetry.lock"
2526
- "mypy.ini"
@@ -30,6 +31,7 @@ on:
3031
paths:
3132
- "aws_lambda_powertools/**"
3233
- "tests/**"
34+
- "examples/**"
3335
- "pyproject.toml"
3436
- "poetry.lock"
3537
- "mypy.ini"

‎docs/utilities/parser.md

Lines changed: 144 additions & 411 deletions
Large diffs are not rendered by default.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"version": "0",
3+
"id": "12345678-1234-1234-1234-123456789012",
4+
"detail-type": "Order Placed",
5+
"source": "com.mycompany.orders",
6+
"account": "123456789012",
7+
"time": "2023-05-03T12:00:00Z",
8+
"region": "us-west-2",
9+
"resources": [],
10+
"detail": {
11+
"order_id": "ORD-12345",
12+
"amount": 99.99,
13+
"customer_id": "CUST-6789"
14+
}
15+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import json
2+
from typing import Any, Dict, Optional, Type, TypeVar, Union
3+
4+
from pydantic import BaseModel
5+
6+
from aws_lambda_powertools.utilities.parser import BaseEnvelope, event_parser
7+
from aws_lambda_powertools.utilities.parser.models import EventBridgeModel
8+
from aws_lambda_powertools.utilities.typing import LambdaContext
9+
10+
Model = TypeVar("Model", bound=BaseModel)
11+
12+
13+
class EventBridgeEnvelope(BaseEnvelope):
14+
def parse(self, data: Optional[Union[Dict[str, Any], Any]], model: Type[Model]) -> Optional[Model]:
15+
if data is None:
16+
return None
17+
18+
parsed_envelope = EventBridgeModel.model_validate(data)
19+
return self._parse(data=parsed_envelope.detail, model=model)
20+
21+
22+
class OrderDetail(BaseModel):
23+
order_id: str
24+
amount: float
25+
customer_id: str
26+
27+
28+
@event_parser(model=OrderDetail, envelope=EventBridgeEnvelope)
29+
def lambda_handler(event: OrderDetail, context: LambdaContext):
30+
try:
31+
# Process the order
32+
print(f"Processing order {event.order_id} for customer {event.customer_id}")
33+
print(f"Order amount: ${event.amount:.2f}")
34+
35+
# Your business logic here
36+
# For example, you might save the order to a database or trigger a payment process
37+
38+
return {
39+
"statusCode": 200,
40+
"body": json.dumps(
41+
{
42+
"message": f"Order {event.order_id} processed successfully",
43+
"order_id": event.order_id,
44+
"amount": event.amount,
45+
"customer_id": event.customer_id,
46+
},
47+
),
48+
}
49+
except Exception as e:
50+
print(f"Error processing order: {str(e)}")
51+
return {"statusCode": 500, "body": json.dumps({"error": "Internal server error"})}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from pydantic import Field, ValidationError
2+
3+
from aws_lambda_powertools.utilities.parser import parse
4+
from aws_lambda_powertools.utilities.parser.models import EventBridgeModel
5+
6+
7+
# Define a custom EventBridge model by extending the built-in EventBridgeModel
8+
class MyCustomEventBridgeModel(EventBridgeModel):
9+
detail_type: str = Field(alias="detail-type")
10+
source: str
11+
detail: dict
12+
13+
14+
def lambda_handler(event: dict, context):
15+
try:
16+
# Manually parse the incoming event into the custom model
17+
parsed_event: MyCustomEventBridgeModel = parse(model=MyCustomEventBridgeModel, event=event)
18+
19+
return {"statusCode": 200, "body": f"Event from {parsed_event.source}, type: {parsed_event.detail_type}"}
20+
except ValidationError as e:
21+
return {"statusCode": 400, "body": f"Validation error: {str(e)}"}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"version": "0",
3+
"id": "abcd-1234-efgh-5678",
4+
"detail-type": "order.created",
5+
"source": "my.order.service",
6+
"account": "123456789012",
7+
"time": "2023-09-10T12:00:00Z",
8+
"region": "us-west-2",
9+
"resources": [],
10+
"detail": {
11+
"orderId": "O-12345",
12+
"amount": 100.0
13+
}
14+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"version": "0",
3+
"id": "6a7e8feb-b491-4cf7-a9f1-bf3703467718",
4+
"detail-type": "CustomerSignedUp",
5+
"source": "CustomerService",
6+
"account": "111122223333",
7+
"time": "2020-10-22T18:43:48Z",
8+
"region": "us-west-1",
9+
"resources": [
10+
"some_additional_"
11+
],
12+
"detail": {
13+
"username": "universe",
14+
"parentid_1": "12345",
15+
"parentid_2": "6789"
16+
}
17+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from pydantic import BaseModel
2+
3+
from aws_lambda_powertools.utilities.parser import envelopes, event_parser
4+
from aws_lambda_powertools.utilities.typing import LambdaContext
5+
6+
7+
class UserModel(BaseModel):
8+
username: str
9+
parentid_1: str
10+
parentid_2: str
11+
12+
13+
@event_parser(model=UserModel, envelope=envelopes.EventBridgeEnvelope)
14+
def lambda_handler(event: UserModel, context: LambdaContext):
15+
if event.parentid_1 != event.parentid_2:
16+
return {"statusCode": 400, "body": "Parent ids do not match"}
17+
18+
# If parentids match, proceed with user registration
19+
20+
return {"statusCode": 200, "body": f"User {event.username} registered successfully"}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"id": "12345",
3+
"name": "Jane Doe"
4+
}

‎examples/parser/src/extending_built_in_models_with_json_mypy.py

Lines changed: 0 additions & 21 deletions
This file was deleted.

‎examples/parser/src/extending_built_in_models_with_json_validator.py

Lines changed: 0 additions & 27 deletions
This file was deleted.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
from pydantic import BaseModel, field_validator
2+
3+
from aws_lambda_powertools.utilities.parser import parse
4+
from aws_lambda_powertools.utilities.typing import LambdaContext
5+
6+
7+
class HelloWorldModel(BaseModel):
8+
message: str
9+
10+
@field_validator("message")
11+
def is_hello_world(cls, v):
12+
if v != "hello world":
13+
raise ValueError("Message must be hello world!")
14+
return v
15+
16+
17+
def lambda_handler(event: dict, context: LambdaContext):
18+
try:
19+
parsed_event = parse(model=HelloWorldModel, event=event)
20+
return {"statusCode": 200, "body": f"Received message: {parsed_event.message}"}
21+
except ValueError as e:
22+
return {"statusCode": 400, "body": str(e)}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
from aws_lambda_powertools.utilities.parser import BaseModel, field_validator, parse
2+
from aws_lambda_powertools.utilities.typing import LambdaContext
3+
4+
5+
class HelloWorldModel(BaseModel):
6+
message: str
7+
sender: str
8+
9+
@field_validator("*")
10+
def has_whitespace(cls, v):
11+
if " " not in v:
12+
raise ValueError("Must have whitespace...")
13+
return v
14+
15+
16+
def lambda_handler(event: dict, context: LambdaContext):
17+
try:
18+
parsed_event = parse(model=HelloWorldModel, event=event)
19+
return {
20+
"statusCode": 200,
21+
"body": f"Received message: {parsed_event.message}",
22+
}
23+
except ValueError as e:
24+
return {
25+
"statusCode": 400,
26+
"body": str(e),
27+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from pydantic import BaseModel
2+
3+
from aws_lambda_powertools.utilities.parser import event_parser
4+
5+
6+
class MyEvent(BaseModel):
7+
id: int
8+
name: str
9+
10+
11+
@event_parser(model=MyEvent)
12+
def lambda_handler(event: MyEvent, context):
13+
# if your model is valid, you can return
14+
return {"statusCode": 200, "body": f"Hello {event.name}, your ID is {event.id}"}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"body": "{\"order_id\": 12345, \"reason\": \"Changed my mind\"}"
3+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from pydantic import BaseModel, model_validator
2+
3+
from aws_lambda_powertools.utilities.parser import parse
4+
from aws_lambda_powertools.utilities.typing import LambdaContext
5+
6+
7+
class UserModel(BaseModel):
8+
username: str
9+
parentid_1: str
10+
parentid_2: str
11+
12+
@model_validator(mode="after") # (1)!
13+
def check_parents_match(cls, values):
14+
pi1, pi2 = values.get("parentid_1"), values.get("parentid_2")
15+
if pi1 is not None and pi2 is not None and pi1 != pi2:
16+
raise ValueError("Parent ids do not match")
17+
return values
18+
19+
20+
def lambda_handler(event: dict, context: LambdaContext):
21+
try:
22+
parsed_event = parse(model=UserModel, event=event)
23+
return {
24+
"statusCode": 200,
25+
"body": f"Received parent id from: {parsed_event.username}",
26+
}
27+
except ValueError as e:
28+
return {
29+
"statusCode": 400,
30+
"body": str(e),
31+
}

‎examples/parser/src/multiple_model_parsing.py

Lines changed: 0 additions & 33 deletions
This file was deleted.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from pydantic import BaseModel, ValidationError
2+
3+
from aws_lambda_powertools.utilities.parser import parse
4+
5+
6+
# Define a Pydantic model for the expected structure of the input
7+
class MyEvent(BaseModel):
8+
id: int
9+
name: str
10+
11+
12+
def lambda_handler(event: dict, context):
13+
try:
14+
# Manually parse the incoming event into MyEvent model
15+
parsed_event: MyEvent = parse(model=MyEvent, event=event)
16+
return {"statusCode": 200, "body": f"Hello {parsed_event.name}, your ID is {parsed_event.id}"}
17+
except ValidationError as e:
18+
# Catch validation errors and return a 400 response
19+
return {"statusCode": 400, "body": f"Validation error: {str(e)}"}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
from pydantic import BaseModel
2+
3+
from aws_lambda_powertools.logging import Logger
4+
from aws_lambda_powertools.utilities.parser import parse
5+
from aws_lambda_powertools.utilities.typing import LambdaContext
6+
7+
logger = Logger()
8+
9+
10+
class UserModel(BaseModel):
11+
username: str
12+
parentid_1: str
13+
parentid_2: str
14+
15+
16+
def validate_user(event):
17+
try:
18+
user = parse(model=UserModel, event=event)
19+
return {"statusCode": 200, "body": user.model_dump_json()}
20+
except Exception as e:
21+
logger.exception("Validation error")
22+
return {"statusCode": 400, "body": str(e)}
23+
24+
25+
@logger.inject_lambda_context
26+
def lambda_handler(event: dict, context: LambdaContext) -> dict:
27+
logger.info("Received event", extra={"event": event})
28+
29+
result = validate_user(event)
30+
31+
if result["statusCode"] == 200:
32+
user = UserModel.model_validate_json(result["body"])
33+
logger.info("User validated successfully", extra={"username": user.username})
34+
35+
# Example of serialization
36+
user_dict = user.model_dump()
37+
user_json = user.model_dump_json()
38+
39+
logger.debug("User serializations", extra={"dict": user_dict, "json": user_json})
40+
41+
return result
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"Records": [
3+
{
4+
"messageId": "059f36b4-87a3-44ab-83d2-661975830a7d",
5+
"receiptHandle": "AQEBwJnKyrHigUMZj6rYigCgxlaS3SLy0a...",
6+
"body": "Test message hello!",
7+
"attributes": {
8+
"ApproximateReceiveCount": "1",
9+
"SentTimestamp": "1545082649183",
10+
"SenderId": "AIDAIENQZJOLO23YVJ4VO",
11+
"ApproximateFirstReceiveTimestamp": "1545082649185"
12+
},
13+
"messageAttributes": {
14+
"testAttr": {
15+
"stringValue": "100",
16+
"binaryValue": "base64Str",
17+
"dataType": "Number"
18+
}
19+
},
20+
"md5OfBody": "e4e68fb7bd0e697a0ae8f1bb342846b3",
21+
"eventSource": "aws:sqs",
22+
"eventSourceARN": "arn:aws:sqs:us-east-2:123456789012:my-queue",
23+
"awsRegion": "us-east-2"
24+
}
25+
]
26+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from aws_lambda_powertools.utilities.parser import parse
2+
from aws_lambda_powertools.utilities.parser.models import SqsModel
3+
from aws_lambda_powertools.utilities.typing import LambdaContext
4+
5+
6+
def lambda_handler(event: dict, context: LambdaContext) -> list:
7+
parsed_event = parse(model=SqsModel, event=event)
8+
9+
results = []
10+
for record in parsed_event.Records:
11+
results.append(
12+
{
13+
"message_id": record.messageId,
14+
"body": record.body,
15+
},
16+
)
17+
return results
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
from __future__ import annotations
2+
3+
from typing import TYPE_CHECKING, Any
4+
5+
from pydantic import BaseModel, Json
6+
7+
from aws_lambda_powertools.utilities.parser import BaseEnvelope, event_parser
8+
from aws_lambda_powertools.utilities.parser.functions import (
9+
_parse_and_validate_event,
10+
_retrieve_or_set_model_from_cache,
11+
)
12+
from aws_lambda_powertools.utilities.typing import LambdaContext
13+
14+
if TYPE_CHECKING:
15+
from aws_lambda_powertools.utilities.parser.types import T
16+
17+
18+
class CancelOrder(BaseModel):
19+
order_id: int
20+
reason: str
21+
22+
23+
class CancelOrderModel(BaseModel):
24+
body: Json[CancelOrder]
25+
26+
27+
class CustomEnvelope(BaseEnvelope):
28+
def parse(self, data: dict[str, Any] | Any | None, model: type[T]):
29+
adapter = _retrieve_or_set_model_from_cache(model=model)
30+
return _parse_and_validate_event(data=data, adapter=adapter)
31+
32+
33+
@event_parser(model=CancelOrderModel, envelope=CustomEnvelope)
34+
def lambda_handler(event: CancelOrderModel, context: LambdaContext):
35+
cancel_order: CancelOrder = event.body
36+
37+
assert cancel_order.order_id is not None
38+
39+
# Process the cancel order request
40+
print(f"Cancelling order {cancel_order.order_id} for reason: {cancel_order.reason}")
41+
42+
return {
43+
"statusCode": 200,
44+
"body": f"Order {cancel_order.order_id} cancelled successfully",
45+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
from __future__ import annotations
2+
3+
import json
4+
from typing import TYPE_CHECKING, Any
5+
6+
from aws_lambda_powertools.utilities.parser import BaseEnvelope, BaseModel, event_parser
7+
from aws_lambda_powertools.utilities.parser.functions import (
8+
_parse_and_validate_event,
9+
_retrieve_or_set_model_from_cache,
10+
)
11+
from aws_lambda_powertools.utilities.typing import LambdaContext
12+
from aws_lambda_powertools.utilities.validation import validator
13+
14+
if TYPE_CHECKING:
15+
from aws_lambda_powertools.utilities.parser.types import T
16+
17+
18+
class CancelOrder(BaseModel):
19+
order_id: int
20+
reason: str
21+
22+
23+
class CancelOrderModel(BaseModel):
24+
body: CancelOrder
25+
26+
@validator("body", pre=True)
27+
def transform_body_to_dict(cls, value):
28+
return json.loads(value) if isinstance(value, str) else value
29+
30+
31+
class CustomEnvelope(BaseEnvelope):
32+
def parse(self, data: dict[str, Any] | Any | None, model: type[T]):
33+
adapter = _retrieve_or_set_model_from_cache(model=model)
34+
return _parse_and_validate_event(data=data, adapter=adapter)
35+
36+
37+
@event_parser(model=CancelOrderModel, envelope=CustomEnvelope)
38+
def lambda_handler(event: CancelOrderModel, context: LambdaContext):
39+
cancel_order: CancelOrder = event.body
40+
41+
assert cancel_order.order_id is not None
42+
43+
# Process the cancel order request
44+
print(f"Cancelling order {cancel_order.order_id} for reason: {cancel_order.reason}")
45+
46+
return {
47+
"statusCode": 200,
48+
"body": json.dumps({"message": f"Order {cancel_order.order_id} cancelled successfully"}),
49+
}

‎examples/parser/src/using_the_model_from_event.py

Lines changed: 0 additions & 27 deletions
This file was deleted.

0 commit comments

Comments
 (0)
Please sign in to comment.