Skip to content

Commit 1f6ea86

Browse files
Adding data class
1 parent ec14a12 commit 1f6ea86

File tree

5 files changed

+191
-34
lines changed

5 files changed

+191
-34
lines changed

aws_lambda_powertools/utilities/data_classes/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from .api_gateway_proxy_event import APIGatewayProxyEvent, APIGatewayProxyEventV2
77
from .api_gateway_websocket_event import APIGatewayWebSocketEvent
88
from .appsync_resolver_event import AppSyncResolverEvent
9+
from .appsync_resolver_events_event import AppSyncResolverEventsEvent
910
from .aws_config_rule_event import AWSConfigRuleEvent
1011
from .bedrock_agent_event import BedrockAgentEvent
1112
from .cloud_watch_alarm_event import (
@@ -55,6 +56,7 @@
5556
"APIGatewayWebSocketEvent",
5657
"SecretsManagerEvent",
5758
"AppSyncResolverEvent",
59+
"AppSyncResolverEventsEvent",
5860
"ALBEvent",
5961
"BedrockAgentEvent",
6062
"CloudWatchAlarmData",

aws_lambda_powertools/utilities/data_classes/appsync_resolver_event.py

Lines changed: 45 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,50 @@ def get_identity_object(identity: dict | None) -> Any:
2626
return AppSyncIdentityIAM(identity)
2727

2828

29+
class AppSyncEventBase(DictWrapper):
30+
"""AppSync resolver event base to work with AppSync GraphQL + Events"""
31+
32+
@property
33+
def request_headers(self) -> dict[str, str]:
34+
"""Request headers"""
35+
return CaseInsensitiveDict(self["request"]["headers"])
36+
37+
@property
38+
def domain_name(self) -> str | None:
39+
"""The domain name when using custom domain"""
40+
return self["request"].get("domainName")
41+
42+
@property
43+
def prev_result(self) -> dict[str, Any] | None:
44+
"""It represents the result of whatever previous operation was executed in a pipeline resolver."""
45+
prev = self.get("prev")
46+
if not prev:
47+
return None
48+
return prev.get("result")
49+
50+
@property
51+
def stash(self) -> dict:
52+
"""The stash is a map that is made available inside each resolver and function mapping template.
53+
The same stash instance lives through a single resolver execution. This means that you can use the
54+
stash to pass arbitrary data across request and response mapping templates, and across functions in
55+
a pipeline resolver."""
56+
return self.get("stash") or {}
57+
58+
@property
59+
def identity(self) -> AppSyncIdentityIAM | AppSyncIdentityCognito | None:
60+
"""An object that contains information about the caller.
61+
62+
Depending on the type of identify found:
63+
64+
- API_KEY authorization - returns None
65+
- AWS_IAM authorization - returns AppSyncIdentityIAM
66+
- AMAZON_COGNITO_USER_POOLS authorization - returns AppSyncIdentityCognito
67+
- AWS_LAMBDA authorization - returns None - NEED TO TEST
68+
- OPENID_CONNECT authorization - returns None - NEED TO TEST
69+
"""
70+
return get_identity_object(self.get("identity"))
71+
72+
2973
class AppSyncIdentityIAM(DictWrapper):
3074
"""AWS_IAM authorization"""
3175

@@ -141,7 +185,7 @@ def selection_set_graphql(self) -> str | None:
141185
return self.get("selectionSetGraphQL")
142186

143187

144-
class AppSyncResolverEvent(DictWrapper):
188+
class AppSyncResolverEvent(AppSyncEventBase):
145189
"""AppSync resolver event
146190
147191
**NOTE:** AppSync Resolver Events can come in various shapes this data class
@@ -178,49 +222,16 @@ def arguments(self) -> dict[str, Any]:
178222
"""A map that contains all GraphQL arguments for this field."""
179223
return self["arguments"]
180224

181-
@property
182-
def identity(self) -> AppSyncIdentityIAM | AppSyncIdentityCognito | None:
183-
"""An object that contains information about the caller.
184-
185-
Depending on the type of identify found:
186-
187-
- API_KEY authorization - returns None
188-
- AWS_IAM authorization - returns AppSyncIdentityIAM
189-
- AMAZON_COGNITO_USER_POOLS authorization - returns AppSyncIdentityCognito
190-
"""
191-
return get_identity_object(self.get("identity"))
192-
193225
@property
194226
def source(self) -> dict[str, Any]:
195227
"""A map that contains the resolution of the parent field."""
196228
return self.get("source") or {}
197229

198-
@property
199-
def request_headers(self) -> dict[str, str]:
200-
"""Request headers"""
201-
return CaseInsensitiveDict(self["request"]["headers"])
202-
203-
@property
204-
def prev_result(self) -> dict[str, Any] | None:
205-
"""It represents the result of whatever previous operation was executed in a pipeline resolver."""
206-
prev = self.get("prev")
207-
if not prev:
208-
return None
209-
return prev.get("result")
210-
211230
@property
212231
def info(self) -> AppSyncResolverEventInfo:
213232
"""The info section contains information about the GraphQL request."""
214233
return self._info
215234

216-
@property
217-
def stash(self) -> dict:
218-
"""The stash is a map that is made available inside each resolver and function mapping template.
219-
The same stash instance lives through a single resolver execution. This means that you can use the
220-
stash to pass arbitrary data across request and response mapping templates, and across functions in
221-
a pipeline resolver."""
222-
return self.get("stash") or {}
223-
224235
@overload
225236
def get_header_value(
226237
self,
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
from __future__ import annotations
2+
3+
from typing import Any
4+
5+
from aws_lambda_powertools.utilities.data_classes.appsync_resolver_event import AppSyncEventBase
6+
from aws_lambda_powertools.utilities.data_classes.common import DictWrapper
7+
8+
9+
class AppSyncResolverEventsInfo(DictWrapper):
10+
11+
@property
12+
def channel(self) -> dict[str, Any]:
13+
"""Channel details including path and segments"""
14+
return self["channel"]
15+
16+
@property
17+
def channel_path(self) -> str:
18+
"""Provides direct access to the 'path' attribute within the 'channel' object."""
19+
return self["channel"]["path"]
20+
21+
@property
22+
def channel_segments(self) -> list[str]:
23+
"""Provides direct access to the 'segments' attribute within the 'channel' object."""
24+
return self["channel"]["segments"]
25+
26+
@property
27+
def channel_namespace(self) -> dict:
28+
"""Namespace configuration for the channel"""
29+
return self["channelNamespace"]
30+
31+
@property
32+
def operation(self) -> str:
33+
"""The operation being performed (e.g., PUBLISH, SUBSCRIBE)"""
34+
return self["operation"]
35+
36+
37+
class AppSyncResolverEventsEvent(AppSyncEventBase):
38+
"""AppSync resolver event events
39+
40+
Documentation:
41+
-------------
42+
- TBD
43+
"""
44+
45+
@property
46+
def events(self) -> list[dict[str, Any]]:
47+
"""The payload sent to Lambda"""
48+
return self.get("events") or [{}]
49+
50+
@property
51+
def out_errors(self) -> list:
52+
"""The outErrors property"""
53+
return self.get("outErrors") or []
54+
55+
@property
56+
def info(self) -> AppSyncResolverEventsInfo:
57+
"The info containing information about channel, namespace, and event"
58+
return AppSyncResolverEventsInfo(self["info"])

tests/events/appSyncEventsEvent.json

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
{
2+
"identity":"None",
3+
"result":"None",
4+
"request":{
5+
"headers": {
6+
"x-forwarded-for": "1.1.1.1, 2.2.2.2",
7+
"cloudfront-viewer-country": "US",
8+
"cloudfront-is-tablet-viewer": "false",
9+
"via": "2.0 xxxxxxxxxxxxxxxx.cloudfront.net (CloudFront)",
10+
"cloudfront-forwarded-proto": "https",
11+
"origin": "https://us-west-1.console.aws.amazon.com",
12+
"content-length": "217",
13+
"accept-language": "en-US,en;q=0.9",
14+
"host": "xxxxxxxxxxxxxxxx.appsync-api.us-west-1.amazonaws.com",
15+
"x-forwarded-proto": "https",
16+
"user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36",
17+
"accept": "*/*",
18+
"cloudfront-is-mobile-viewer": "false",
19+
"cloudfront-is-smarttv-viewer": "false",
20+
"accept-encoding": "gzip, deflate, br",
21+
"referer": "https://us-west-1.console.aws.amazon.com/appsync/home?region=us-west-1",
22+
"content-type": "application/json",
23+
"sec-fetch-mode": "cors",
24+
"x-amz-cf-id": "3aykhqlUwQeANU-HGY7E_guV5EkNeMMtwyOgiA==",
25+
"x-amzn-trace-id": "Root=1-5f512f51-fac632066c5e848ae714",
26+
"authorization": "eyJraWQiOiJScWFCSlJqYVJlM0hrSnBTUFpIcVRXazNOW...",
27+
"sec-fetch-dest": "empty",
28+
"x-amz-user-agent": "AWS-Console-AppSync/",
29+
"cloudfront-is-desktop-viewer": "true",
30+
"sec-fetch-site": "cross-site",
31+
"x-forwarded-port": "443"
32+
},
33+
"domainName":"None"
34+
},
35+
"info":{
36+
"channel":{
37+
"path":"/default/channel",
38+
"segments":[
39+
"default",
40+
"channel"
41+
]
42+
},
43+
"channelNamespace":{
44+
"name":"default"
45+
},
46+
"operation":"PUBLISH"
47+
},
48+
"error":"None",
49+
"prev":"None",
50+
"stash":{
51+
52+
},
53+
"outErrors":[
54+
55+
],
56+
"events":[
57+
{
58+
"payload":{
59+
"event_1":"data_1"
60+
},
61+
"id":"96bc1d46-5b01-40ae-9859-173a5bb3a46a"
62+
},
63+
{
64+
"payload":{
65+
"event_2":"data_2"
66+
},
67+
"id":"48cde766-afe9-4c2c-9708-2fed930efe56"
68+
}
69+
]
70+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEventsEvent
2+
from tests.functional.utils import load_event
3+
4+
5+
def test_appsync_resolver_event():
6+
raw_event = load_event("appSyncEventsEvent.json")
7+
parsed_event = AppSyncResolverEventsEvent(raw_event)
8+
9+
assert parsed_event.events == raw_event["events"]
10+
assert parsed_event.out_errors == raw_event["outErrors"]
11+
assert parsed_event.domain_name == raw_event["request"]["domainName"]
12+
assert parsed_event.info.channel == raw_event["info"]["channel"]
13+
assert parsed_event.info.channel_path == raw_event["info"]["channel"]["path"]
14+
assert parsed_event.info.channel_segments == raw_event["info"]["channel"]["segments"]
15+
assert parsed_event.info.channel_namespace == raw_event["info"]["channelNamespace"]
16+
assert parsed_event.info.operation == raw_event["info"]["operation"]

0 commit comments

Comments
 (0)