Skip to content

Commit fbb2ee3

Browse files
author
Ran Isenberg
committed
feat: Add Ses lambda event support to Parser utility #213
1 parent 66edf65 commit fbb2ee3

File tree

4 files changed

+124
-1
lines changed

4 files changed

+124
-1
lines changed

aws_lambda_powertools/utilities/parser/models/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from .dynamodb import DynamoDBStreamChangedRecordModel, DynamoDBStreamModel, DynamoDBStreamRecordModel
22
from .event_bridge import EventBridgeModel
3+
from .ses import SesModel, SesRecordModel
34
from .sns import SnsModel, SnsNotificationModel, SnsRecordModel
45
from .sqs import SqsModel, SqsRecordModel
56

@@ -8,6 +9,8 @@
89
"EventBridgeModel",
910
"DynamoDBStreamChangedRecordModel",
1011
"DynamoDBStreamRecordModel",
12+
"SesModel",
13+
"SesRecordModel",
1114
"SnsModel",
1215
"SnsNotificationModel",
1316
"SnsRecordModel",
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
from datetime import datetime
2+
from typing import List, Optional
3+
4+
from pydantic import BaseModel, Field
5+
from pydantic.networks import EmailStr
6+
from pydantic.types import PositiveInt
7+
from typing_extensions import Literal
8+
9+
10+
class SesReciptVerdict(BaseModel):
11+
status: Literal["PASS", "FAIL", "GRAY", "PROCESSING_FAILED"]
12+
13+
14+
class SesReciptAction(BaseModel):
15+
type: Literal["Lambda"] # noqa A003,VNE003
16+
invocationType: Literal["Event"]
17+
functionArn: str
18+
19+
20+
class SesReceipt(BaseModel):
21+
timestamp: datetime
22+
processingTimeMillis: PositiveInt
23+
recipients: List[EmailStr]
24+
spamVerdict: SesReciptVerdict
25+
virusVerdict: SesReciptVerdict
26+
spfVerdict: SesReciptVerdict
27+
dmarcVerdict: SesReciptVerdict
28+
action: SesReciptAction
29+
30+
31+
class SesMailHeaders(BaseModel):
32+
name: str
33+
value: str
34+
35+
36+
class SesMailCommonHeaders(BaseModel):
37+
header_from: List[str] = Field(None, alias="from")
38+
to: List[str]
39+
cc: Optional[List[str]]
40+
bcc: Optional[List[str]]
41+
sender: Optional[List[str]]
42+
reply_to: Optional[List[str]] = Field(None, alias="reply-to")
43+
returnPath: EmailStr
44+
messageId: str
45+
date: str
46+
subject: str
47+
48+
49+
class SesMail(BaseModel):
50+
timestamp: datetime
51+
source: EmailStr
52+
messageId: str
53+
destination: List[EmailStr]
54+
headersTruncated: bool
55+
headers: List[SesMailHeaders]
56+
commonHeaders: SesMailCommonHeaders
57+
58+
59+
class SesMessage(BaseModel):
60+
mail: SesMail
61+
receipt: SesReceipt
62+
63+
64+
class SesRecordModel(BaseModel):
65+
eventSource: Literal["aws:ses"]
66+
eventVersion: str
67+
ses: SesMessage
68+
69+
70+
class SesModel(BaseModel):
71+
Records: List[SesRecordModel]

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ flake8-bugbear = "^20.1.4"
5151

5252

5353
[tool.poetry.extras]
54-
pydantic = ["pydantic", "typing_extensions"]
54+
pydantic = ["pydantic", "typing_extensions", "pydantic[email]"]
5555

5656
[tool.coverage.run]
5757
source = ["aws_lambda_powertools"]

tests/functional/parser/test_ses.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
from aws_lambda_powertools.utilities.parser import event_parser
2+
from aws_lambda_powertools.utilities.parser.models import SesModel, SesRecordModel
3+
from aws_lambda_powertools.utilities.typing import LambdaContext
4+
from tests.functional.parser.utils import load_event
5+
6+
7+
@event_parser(model=SesModel)
8+
def handle_ses(event: SesModel, _: LambdaContext):
9+
expected_address = "johndoe@example.com"
10+
records = event.Records
11+
record: SesRecordModel = records[0]
12+
assert record.eventSource == "aws:ses"
13+
assert record.eventVersion == "1.0"
14+
mail = record.ses.mail
15+
convert_time = int(round(mail.timestamp.timestamp() * 1000))
16+
assert convert_time == 0
17+
assert mail.source == "janedoe@example.com"
18+
assert mail.messageId == "o3vrnil0e2ic28tr"
19+
assert mail.destination == [expected_address]
20+
assert mail.headersTruncated is False
21+
headers = list(mail.headers)
22+
assert len(headers) == 10
23+
assert headers[0].name == "Return-Path"
24+
assert headers[0].value == "<janedoe@example.com>"
25+
common_headers = mail.commonHeaders
26+
assert common_headers.returnPath == "janedoe@example.com"
27+
assert common_headers.header_from == ["Jane Doe <janedoe@example.com>"]
28+
assert common_headers.date == "Wed, 7 Oct 2015 12:34:56 -0700"
29+
assert common_headers.to == [expected_address]
30+
assert common_headers.messageId == "<0123456789example.com>"
31+
assert common_headers.subject == "Test Subject"
32+
receipt = record.ses.receipt
33+
convert_time = int(round(receipt.timestamp.timestamp() * 1000))
34+
assert convert_time == 0
35+
assert receipt.processingTimeMillis == 574
36+
assert receipt.recipients == [expected_address]
37+
assert receipt.spamVerdict.status == "PASS"
38+
assert receipt.virusVerdict.status == "PASS"
39+
assert receipt.spfVerdict.status == "PASS"
40+
assert receipt.dmarcVerdict.status == "PASS"
41+
action = receipt.action
42+
assert action.type == "Lambda"
43+
assert action.functionArn == "arn:aws:lambda:us-west-2:012345678912:function:Example"
44+
assert action.invocationType == "Event"
45+
46+
47+
def test_ses_trigger_event():
48+
event_dict = load_event("sesEvent.json")
49+
handle_ses(event_dict, LambdaContext())

0 commit comments

Comments
 (0)