Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions aws_lambda_powertools/utilities/data_classes/ses_event.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,35 +20,35 @@ def value(self) -> str:

class SESMailCommonHeaders(DictWrapper):
@property
def return_path(self) -> str:
def return_path(self) -> str | None:
"""The values in the Return-Path header of the email."""
return self["returnPath"]
return self.get("returnPath")

@property
def get_from(self) -> list[str]:
"""The values in the From header of the email."""
# Note: this name conflicts with existing python builtins
return self["from"]
return self.get("from") or []

@property
def date(self) -> str:
def date(self) -> str | None:
"""The date and time when Amazon SES received the message."""
return self["date"]
return self.get("date")

@property
def to(self) -> list[str]:
"""The values in the To header of the email."""
return self["to"]
return self.get("to") or []

@property
def message_id(self) -> str:
def message_id(self) -> str | None:
"""The ID of the original message."""
return str(self["messageId"])
return self.get("messageId")

@property
def subject(self) -> str:
def subject(self) -> str | None:
"""The value of the Subject header for the email."""
return str(self["subject"])
return self.get("subject")

@property
def cc(self) -> list[str]:
Expand Down Expand Up @@ -148,10 +148,10 @@ def function_arn(self) -> str:
return self["functionArn"]

@property
def invocation_type(self) -> str:
def invocation_type(self) -> str | None:
"""String that contains the invocation type of the Lambda function. Possible values are RequestResponse
and Event. Present only for the Lambda action type."""
return self["invocationType"]
return self.get("invocationType")


class SESReceipt(DictWrapper):
Expand Down
15 changes: 8 additions & 7 deletions aws_lambda_powertools/utilities/parser/models/ses.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class SesReceiptVerdict(BaseModel):

class SesReceiptAction(BaseModel):
type: Literal["Lambda"] # noqa A003,VNE003
invocationType: Literal["Event"]
invocationType: Literal["Event", "RequestResponse"] | None = None
functionArn: str


Expand All @@ -32,8 +32,9 @@ class SesMailHeaders(BaseModel):


class SesMailCommonHeaders(BaseModel):
header_from: list[str] = Field(..., alias="from")
to: list[str]
# SES sends each common header only when the email has that header
header_from: list[str] | None = Field(None, alias="from")
to: list[str] | None = None
cc: list[str] | None = None
bcc: list[str] | None = None
sender: list[str] | None = None
Expand All @@ -42,10 +43,10 @@ class SesMailCommonHeaders(BaseModel):
validation_alias=AliasChoices("replyTo", "reply-to", "reply_to"),
serialization_alias="replyTo",
)
returnPath: str
messageId: str
date: str
subject: str
returnPath: str | None = None
messageId: str | None = None
date: str | None = None
subject: str | None = None


class SesMail(BaseModel):
Expand Down
21 changes: 21 additions & 0 deletions tests/unit/data_classes/required_dependencies/test_ses_event.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,24 @@ def test_ses_trigger_event():
assert parsed_event.record.raw_event == raw_event["Records"][0]
assert parsed_event.mail.raw_event == raw_event["Records"][0]["ses"]["mail"]
assert parsed_event.receipt.raw_event == raw_event["Records"][0]["ses"]["receipt"]


def test_ses_trigger_event_without_common_headers_or_invocation_type():
# GIVEN an SES event for an email without any common headers and a Lambda action without an invocation type
raw_event = load_event("sesEvent.json")
raw_event["Records"][0]["ses"]["mail"]["commonHeaders"] = {}
del raw_event["Records"][0]["ses"]["receipt"]["action"]["invocationType"]

# WHEN reading the common headers and the action
parsed_event = SESEvent(raw_event)
common_headers = parsed_event.mail.common_headers
action = parsed_event.receipt.action

# THEN the missing headers are empty instead of raising KeyError
assert common_headers.return_path is None
assert common_headers.get_from == []
assert common_headers.date is None
assert common_headers.to == []
assert common_headers.message_id is None
assert common_headers.subject is None
assert action.invocation_type is None
44 changes: 44 additions & 0 deletions tests/unit/parser/_pydantic/test_ses.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import pytest

from aws_lambda_powertools.utilities.parser.models import SesModel, SesRecordModel
from tests.functional.utils import load_event

Expand Down Expand Up @@ -117,3 +119,45 @@ def test_ses_trigger_event_with_optional_common_headers():
assert common_headers.bcc == ["bcc@example.com"]
assert common_headers.sender == ["sender@example.com"]
assert common_headers.reply_to == ["reply@example.com"]


@pytest.mark.parametrize("invocation_type", ["Event", "RequestResponse"])
def test_ses_trigger_event_with_invocation_type(invocation_type):
# GIVEN an SES event from a Lambda action with either documented invocation type
raw_event = load_event("sesEvent.json")
raw_event["Records"][0]["ses"]["receipt"]["action"]["invocationType"] = invocation_type

# WHEN parsing the event
action = SesModel(**raw_event).Records[0].ses.receipt.action

# THEN the invocation type is kept
assert action.invocationType == invocation_type


def test_ses_trigger_event_without_invocation_type():
# GIVEN an SES event whose Lambda action has no invocation type
raw_event = load_event("sesEvent.json")
del raw_event["Records"][0]["ses"]["receipt"]["action"]["invocationType"]

# WHEN parsing the event
action = SesModel(**raw_event).Records[0].ses.receipt.action

# THEN the invocation type is None
assert action.invocationType is None


def test_ses_trigger_event_without_common_headers():
# GIVEN an SES event for an email without any common headers (SES only sends the ones the email has)
raw_event = load_event("sesEvent.json")
raw_event["Records"][0]["ses"]["mail"]["commonHeaders"] = {}

# WHEN parsing the event
common_headers = SesModel(**raw_event).Records[0].ses.mail.commonHeaders

# THEN every common header is None
assert common_headers.header_from is None
assert common_headers.to is None
assert common_headers.returnPath is None
assert common_headers.messageId is None
assert common_headers.date is None
assert common_headers.subject is None