Skip to content

Commit a8b3f48

Browse files
committed
feat(event_sources): add support for Lambda Function URL events
1 parent bb1eda7 commit a8b3f48

File tree

6 files changed

+150
-8
lines changed

6 files changed

+150
-8
lines changed

aws_lambda_powertools/utilities/data_classes/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from .event_bridge_event import EventBridgeEvent
1313
from .event_source import event_source
1414
from .kinesis_stream_event import KinesisStreamEvent
15+
from .lambda_function_url_event import LambdaFunctionUrlEvent
1516
from .s3_event import S3Event
1617
from .ses_event import SESEvent
1718
from .sns_event import SNSEvent
@@ -28,6 +29,7 @@
2829
"DynamoDBStreamEvent",
2930
"EventBridgeEvent",
3031
"KinesisStreamEvent",
32+
"LambdaFunctionUrlEvent",
3133
"S3Event",
3234
"SESEvent",
3335
"SNSEvent",

aws_lambda_powertools/utilities/data_classes/api_gateway_proxy_event.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -127,19 +127,22 @@ def caller_id(self) -> Optional[str]:
127127
def cognito_amr(self) -> Optional[List[str]]:
128128
"""This represents how the user was authenticated.
129129
AMR stands for Authentication Methods References as per the openid spec"""
130-
return self["cognitoIdentity"].get("amr")
130+
cognito_identity = self["cognitoIdentity"] or {}
131+
return cognito_identity.get("amr")
131132

132133
@property
133134
def cognito_identity_id(self) -> Optional[str]:
134135
"""The Amazon Cognito identity ID of the caller making the request.
135136
Available only if the request was signed with Amazon Cognito credentials."""
136-
return self["cognitoIdentity"].get("identityId")
137+
cognito_identity = self.get("cognitoIdentity") or {}
138+
return cognito_identity.get("identityId")
137139

138140
@property
139141
def cognito_identity_pool_id(self) -> Optional[str]:
140142
"""The Amazon Cognito identity pool ID of the caller making the request.
141143
Available only if the request was signed with Amazon Cognito credentials."""
142-
return self["cognitoIdentity"].get("identityPoolId")
144+
cognito_identity = self.get("cognitoIdentity") or {}
145+
return cognito_identity.get("identityPoolId")
143146

144147
@property
145148
def principal_org_id(self) -> Optional[str]:
@@ -159,12 +162,14 @@ def user_id(self) -> Optional[str]:
159162

160163
class RequestContextV2Authorizer(DictWrapper):
161164
@property
162-
def jwt_claim(self) -> Dict[str, Any]:
163-
return self["jwt"]["claims"]
165+
def jwt_claim(self) -> Optional[Dict[str, Any]]:
166+
jwt = self.get("jwt") or {}
167+
return jwt.get("claims")
164168

165169
@property
166-
def jwt_scopes(self) -> List[str]:
167-
return self["jwt"]["scopes"]
170+
def jwt_scopes(self) -> Optional[List[str]]:
171+
jwt = self.get("jwt") or {}
172+
return jwt.get("scopes")
168173

169174
@property
170175
def get_lambda(self) -> Optional[Dict[str, Any]]:

aws_lambda_powertools/utilities/data_classes/common.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -395,5 +395,6 @@ def time_epoch(self) -> int:
395395
@property
396396
def authentication(self) -> Optional[RequestContextClientCert]:
397397
"""Optional when using mutual TLS authentication"""
398-
client_cert = self["requestContext"].get("authentication", {}).get("clientCert")
398+
authentication = self["requestContext"].get("authentication") or {}
399+
client_cert = authentication.get("clientCert")
399400
return None if client_cert is None else RequestContextClientCert(client_cert)

docs/utilities/data_classes.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ title: Event Source Data Classes
33
description: Utility
44
---
55

6+
<!-- markdownlint-disable MD043 -->
7+
68
Event Source Data Classes utility provides classes self-describing Lambda event sources.
79

810
## Key Features
@@ -73,6 +75,7 @@ Event Source | Data_class
7375
[DynamoDB streams](#dynamodb-streams) | `DynamoDBStreamEvent`, `DynamoDBRecordEventName`
7476
[EventBridge](#eventbridge) | `EventBridgeEvent`
7577
[Kinesis Data Stream](#kinesis-streams) | `KinesisStreamEvent`
78+
[Lambda Function URL](#lambda-function-url) | `LambdaFunctionUrlEvent`
7679
[Rabbit MQ](#rabbit-mq) | `RabbitMQEvent`
7780
[S3](#s3) | `S3Event`
7881
[S3 Object Lambda](#s3-object-lambda) | `S3ObjectLambdaEvent`
@@ -837,6 +840,18 @@ or plain text, depending on the original payload.
837840
do_something_with(data)
838841
```
839842

843+
### Lambda Function URL
844+
845+
=== "app.py"
846+
847+
```python
848+
from aws_lambda_powertools.utilities.data_classes import event_source, LambdaFunctoinUrlEvent
849+
850+
@event_source(data_class=LambdaFunctionUrlEvent)
851+
def lambda_handler(event: LambdaFunctionUrlEvent, context):
852+
do_something_with(event.body)
853+
```
854+
840855
### Rabbit MQ
841856

842857
It is used for [Rabbit MQ payloads](https://docs.aws.amazon.com/lambda/latest/dg/with-mq.html){target="_blank"}, also see
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
{
2+
"version": "2.0",
3+
"routeKey": "$default",
4+
"rawPath": "/my/path",
5+
"rawQueryString": "parameter1=value1&parameter1=value2&parameter2=value",
6+
"cookies": [
7+
"cookie1",
8+
"cookie2"
9+
],
10+
"headers": {
11+
"header1": "value1",
12+
"header2": "value1,value2"
13+
},
14+
"queryStringParameters": {
15+
"parameter1": "value1,value2",
16+
"parameter2": "value"
17+
},
18+
"requestContext": {
19+
"accountId": "123456789012",
20+
"apiId": "<urlid>",
21+
"authentication": null,
22+
"authorizer": {
23+
"iam": {
24+
"accessKey": "AKIA...",
25+
"accountId": "111122223333",
26+
"callerId": "AIDA...",
27+
"cognitoIdentity": null,
28+
"principalOrgId": null,
29+
"userArn": "arn:aws:iam::111122223333:user/example-user",
30+
"userId": "AIDA..."
31+
}
32+
},
33+
"domainName": "<url-id>.lambda-url.us-west-2.on.aws",
34+
"domainPrefix": "<url-id>",
35+
"http": {
36+
"method": "POST",
37+
"path": "/my/path",
38+
"protocol": "HTTP/1.1",
39+
"sourceIp": "123.123.123.123",
40+
"userAgent": "agent"
41+
},
42+
"requestId": "id",
43+
"routeKey": "$default",
44+
"stage": "$default",
45+
"time": "12/Mar/2020:19:03:58 +0000",
46+
"timeEpoch": 1583348638390
47+
},
48+
"body": "Hello from client!",
49+
"pathParameters": null,
50+
"isBase64Encoded": false,
51+
"stageVariables": null
52+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
from aws_lambda_powertools.utilities.data_classes import LambdaFunctionUrlEvent
2+
from tests.functional.utils import load_event
3+
4+
5+
def test_lambda_function_url_event():
6+
event = LambdaFunctionUrlEvent(load_event("lambdaFunctionUrlEvent.json"))
7+
8+
assert event.version == "2.0"
9+
assert event.route_key == "$default"
10+
11+
assert event.path == "/my/path"
12+
assert event.raw_query_string == "parameter1=value1&parameter1=value2&parameter2=value"
13+
14+
cookies = event.cookies
15+
assert len(cookies) == 2
16+
assert cookies[0] == "cookie1"
17+
18+
headers = event.headers
19+
assert len(headers) == 2
20+
21+
query_string_parameters = event.query_string_parameters
22+
assert len(query_string_parameters) == 2
23+
assert query_string_parameters.get("parameter2") == "value"
24+
25+
assert event.is_base64_encoded is False
26+
assert event.body == "Hello from client!"
27+
assert event.decoded_body == event.body
28+
assert event.path_parameters is None
29+
assert event.stage_variables is None
30+
assert event.http_method == "POST"
31+
32+
request_context = event.request_context
33+
34+
assert request_context.account_id == "123456789012"
35+
assert request_context.api_id is not None
36+
assert request_context.domain_name == "<url-id>.lambda-url.us-west-2.on.aws"
37+
assert request_context.domain_prefix == "<url-id>"
38+
assert request_context.request_id == "id"
39+
assert request_context.route_key == "$default"
40+
assert request_context.stage == "$default"
41+
assert request_context.time is not None
42+
assert request_context.time_epoch == 1583348638390
43+
assert request_context.authentication is None
44+
45+
http = request_context.http
46+
assert http.method == "POST"
47+
assert http.path == "/my/path"
48+
assert http.protocol == "HTTP/1.1"
49+
assert http.source_ip == "123.123.123.123"
50+
assert http.user_agent == "agent"
51+
52+
authorizer = request_context.authorizer
53+
assert authorizer is not None
54+
assert authorizer.jwt_claim is None
55+
assert authorizer.jwt_scopes is None
56+
assert authorizer.get_lambda is None
57+
58+
iam = authorizer.iam
59+
assert iam is not None
60+
assert iam.access_key is not None
61+
assert iam.account_id == "111122223333"
62+
assert iam.caller_id is not None
63+
assert iam.cognito_identity_id is None
64+
assert iam.cognito_identity_pool_id is None
65+
assert iam.principal_org_id is None
66+
assert iam.user_id is not None
67+
assert iam.user_arn == "arn:aws:iam::111122223333:user/example-user"

0 commit comments

Comments
 (0)