Skip to content

Commit 1eaa85d

Browse files
barreeeirooRan Isenberg
and
Ran Isenberg
authored
fix(parser): S3Model Object Deleted omits size and eTag attr (#1638)
Co-authored-by: Ran Isenberg <[email protected]>
1 parent 4bee8cb commit 1eaa85d

File tree

4 files changed

+109
-5
lines changed

4 files changed

+109
-5
lines changed

aws_lambda_powertools/utilities/parser/models/s3.py

+12-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from datetime import datetime
22
from typing import List, Optional
33

4-
from pydantic import BaseModel
4+
from pydantic import BaseModel, root_validator
55
from pydantic.fields import Field
66
from pydantic.networks import IPvAnyNetwork
77
from pydantic.types import NonNegativeFloat
@@ -43,8 +43,8 @@ class S3Bucket(BaseModel):
4343

4444
class S3Object(BaseModel):
4545
key: str
46-
size: NonNegativeFloat
47-
eTag: str
46+
size: Optional[NonNegativeFloat]
47+
eTag: Optional[str]
4848
sequencer: str
4949
versionId: Optional[str]
5050

@@ -68,6 +68,15 @@ class S3RecordModel(BaseModel):
6868
s3: S3Message
6969
glacierEventData: Optional[S3EventRecordGlacierEventData]
7070

71+
@root_validator
72+
def validate_s3_object(cls, values):
73+
event_name = values.get("eventName")
74+
s3_object = values.get("s3").object
75+
if "ObjectRemoved" not in event_name:
76+
if s3_object.size is None or s3_object.eTag is None:
77+
raise ValueError("S3Object.size and S3Object.eTag are required for non-ObjectRemoved events")
78+
return values
79+
7180

7281
class S3Model(BaseModel):
7382
Records: List[S3RecordModel]

examples/event_sources/src/kinesis_firehose_delivery_stream.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import base64
22
import json
33

4-
from aws_lambda_powertools.utilities.data_classes import KinesisFirehoseEvent, event_source
4+
from aws_lambda_powertools.utilities.data_classes import (
5+
KinesisFirehoseEvent,
6+
event_source,
7+
)
58
from aws_lambda_powertools.utilities.typing import LambdaContext
69

710

tests/events/s3EventDeleteObject.json

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"Records": [
3+
{
4+
"eventVersion": "2.1",
5+
"eventSource": "aws:s3",
6+
"awsRegion": "us-east-2",
7+
"eventTime": "2019-09-03T19:37:27.192Z",
8+
"eventName": "ObjectRemoved:Delete",
9+
"userIdentity": {
10+
"principalId": "AWS:AIDAINPONIXQXHT3IKHL2"
11+
},
12+
"requestParameters": {
13+
"sourceIPAddress": "205.255.255.255"
14+
},
15+
"responseElements": {
16+
"x-amz-request-id": "D82B88E5F771F645",
17+
"x-amz-id-2": "vlR7PnpV2Ce81l0PRw6jlUpck7Jo5ZsQjryTjKlc5aLWGVHPZLj5NeC6qMa0emYBDXOo6QBU0Wo="
18+
},
19+
"s3": {
20+
"s3SchemaVersion": "1.0",
21+
"configurationId": "828aa6fc-f7b5-4305-8584-487c791949c1",
22+
"bucket": {
23+
"name": "lambda-artifacts-deafc19498e3f2df",
24+
"ownerIdentity": {
25+
"principalId": "A3I5XTEXAMAI3E"
26+
},
27+
"arn": "arn:aws:s3:::lambda-artifacts-deafc19498e3f2df"
28+
},
29+
"object": {
30+
"key": "b21b84d653bb07b05b1e6b33684dc11b",
31+
"sequencer": "0C0F6F405D6ED209E1"
32+
}
33+
}
34+
}
35+
]
36+
}

tests/functional/parser/test_s3.py

+57-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
from aws_lambda_powertools.utilities.parser import event_parser, parse
1+
import pytest
2+
3+
from aws_lambda_powertools.utilities.parser import ValidationError, event_parser, parse
24
from aws_lambda_powertools.utilities.parser.models import S3Model, S3RecordModel
35
from aws_lambda_powertools.utilities.typing import LambdaContext
46
from tests.functional.utils import load_event
@@ -93,3 +95,57 @@ def test_s3_empty_object():
9395
event_dict = load_event("s3Event.json")
9496
event_dict["Records"][0]["s3"]["object"]["size"] = 0
9597
parse(event=event_dict, model=S3Model)
98+
99+
100+
def test_s3_none_object_size_failed_validation():
101+
event_dict = load_event("s3Event.json")
102+
event_dict["Records"][0]["s3"]["object"]["size"] = None
103+
with pytest.raises(ValidationError):
104+
parse(event=event_dict, model=S3Model)
105+
106+
107+
def test_s3_none_etag_value_failed_validation():
108+
event_dict = load_event("s3Event.json")
109+
event_dict["Records"][0]["s3"]["object"]["eTag"] = None
110+
with pytest.raises(ValidationError):
111+
parse(event=event_dict, model=S3Model)
112+
113+
114+
@event_parser(model=S3Model)
115+
def handle_s3_delete_object(event: S3Model, _: LambdaContext):
116+
records = list(event.Records)
117+
assert len(records) == 1
118+
record: S3RecordModel = records[0]
119+
assert record.eventVersion == "2.1"
120+
assert record.eventSource == "aws:s3"
121+
assert record.awsRegion == "us-east-2"
122+
convert_time = int(round(record.eventTime.timestamp() * 1000))
123+
assert convert_time == 1567539447192
124+
assert record.eventName == "ObjectRemoved:Delete"
125+
user_identity = record.userIdentity
126+
assert user_identity.principalId == "AWS:AIDAINPONIXQXHT3IKHL2"
127+
request_parameters = record.requestParameters
128+
assert str(request_parameters.sourceIPAddress) == "205.255.255.255/32"
129+
assert record.responseElements.x_amz_request_id == "D82B88E5F771F645"
130+
assert (
131+
record.responseElements.x_amz_id_2
132+
== "vlR7PnpV2Ce81l0PRw6jlUpck7Jo5ZsQjryTjKlc5aLWGVHPZLj5NeC6qMa0emYBDXOo6QBU0Wo="
133+
)
134+
s3 = record.s3
135+
assert s3.s3SchemaVersion == "1.0"
136+
assert s3.configurationId == "828aa6fc-f7b5-4305-8584-487c791949c1"
137+
bucket = s3.bucket
138+
assert bucket.name == "lambda-artifacts-deafc19498e3f2df"
139+
assert bucket.ownerIdentity.principalId == "A3I5XTEXAMAI3E"
140+
assert bucket.arn == "arn:aws:s3:::lambda-artifacts-deafc19498e3f2df"
141+
assert s3.object.key == "b21b84d653bb07b05b1e6b33684dc11b"
142+
assert s3.object.size is None
143+
assert s3.object.eTag is None
144+
assert s3.object.versionId is None
145+
assert s3.object.sequencer == "0C0F6F405D6ED209E1"
146+
assert record.glacierEventData is None
147+
148+
149+
def test_s3_trigger_event_delete_object():
150+
event_dict = load_event("s3EventDeleteObject.json")
151+
handle_s3_delete_object(event_dict, LambdaContext())

0 commit comments

Comments
 (0)