Skip to content

Commit ce80a90

Browse files
committed
feat(event-handler): Add AppSync handler decorator
1 parent 29497d4 commit ce80a90

File tree

8 files changed

+204
-107
lines changed

8 files changed

+204
-107
lines changed

aws_lambda_powertools/utilities/data_classes/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
from aws_lambda_powertools.utilities.data_classes.appsync_resolver_event import AppSyncResolverEvent
2-
31
from .alb_event import ALBEvent
42
from .api_gateway_proxy_event import APIGatewayProxyEvent, APIGatewayProxyEventV2
3+
from .appsync_resolver_event import AppSyncResolverEvent
54
from .cloud_watch_logs_event import CloudWatchLogsEvent
65
from .connect_contact_flow_event import ConnectContactFlowEvent
76
from .dynamo_db_stream_event import DynamoDBStreamEvent

aws_lambda_powertools/utilities/data_classes/appsync/resolver_utils.py

Lines changed: 0 additions & 50 deletions
This file was deleted.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from .appsync import AppSyncResolver
2+
3+
__all__ = ["AppSyncResolver"]
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
from typing import Any, Dict
2+
3+
from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent
4+
from aws_lambda_powertools.utilities.typing import LambdaContext
5+
6+
7+
class AppSyncResolver:
8+
"""
9+
AppSync resolver decorator
10+
11+
Example
12+
-------
13+
14+
**Sample usage**
15+
16+
from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent
17+
from aws_lambda_powertools.utilities.event_handler import AppSyncResolver
18+
19+
app = AppSyncResolver()
20+
21+
@app.resolver(type_name="Query", field_name="listLocations", include_event=True)
22+
def list_locations(event: AppSyncResolverEvent, page: int = 0, size: int = 10):
23+
# Your logic to fetch locations
24+
...
25+
26+
@app.resolver(type_name="Merchant", field_name="extraInfo", include_event=True)
27+
def get_extra_info(event: AppSyncResolverEvent):
28+
# Can use "event.source" to filter within the parent context
29+
...
30+
31+
@app.resolver(field_name="commonField")
32+
def common_field():
33+
# Would match all fieldNames matching 'commonField'
34+
...
35+
36+
def handle(event, context):
37+
app.resolve(event, context)
38+
39+
"""
40+
41+
def __init__(self):
42+
self._resolvers: dict = {}
43+
44+
def resolver(
45+
self,
46+
type_name: str = "*",
47+
field_name: str = None,
48+
include_event: bool = False,
49+
include_context: bool = False,
50+
**kwargs,
51+
):
52+
"""Registers the resolver for field_name
53+
54+
Parameters
55+
----------
56+
type_name : str
57+
Type name
58+
field_name : str
59+
Field name
60+
include_event: bool
61+
Whether to include the lambda event
62+
include_context: bool
63+
Whether to include the lambda context
64+
kwargs :
65+
Extra options via kwargs
66+
"""
67+
68+
def register_resolver(func):
69+
kwargs["include_event"] = include_event
70+
kwargs["include_context"] = include_context
71+
self._resolvers[f"{type_name}.{field_name}"] = {
72+
"func": func,
73+
"config": kwargs,
74+
}
75+
return func
76+
77+
return register_resolver
78+
79+
def resolve(self, _event: dict, context: LambdaContext) -> Any:
80+
"""Resolve field_name
81+
82+
Parameters
83+
----------
84+
_event : dict
85+
Lambda event
86+
context : LambdaContext
87+
Lambda context
88+
89+
Returns
90+
-------
91+
Any
92+
Returns the result of the resolver
93+
94+
Raises
95+
-------
96+
ValueError
97+
If we could not find a field resolver
98+
"""
99+
event = AppSyncResolverEvent(_event)
100+
resolver, config = self._resolver(event.type_name, event.field_name)
101+
kwargs = self._kwargs(event, context, config)
102+
return resolver(**kwargs)
103+
104+
def _resolver(self, type_name: str, field_name: str) -> tuple:
105+
"""Find resolver for field_name
106+
107+
Parameters
108+
----------
109+
type_name : str
110+
Type name
111+
field_name : str
112+
Field name
113+
114+
Returns
115+
-------
116+
tuple
117+
callable function and configuration
118+
"""
119+
full_name = f"{type_name}.{field_name}"
120+
resolver = self._resolvers.get(full_name, self._resolvers.get(f"*.{field_name}"))
121+
if not resolver:
122+
raise ValueError(f"No resolver found for '{full_name}'")
123+
return resolver["func"], resolver["config"]
124+
125+
@staticmethod
126+
def _kwargs(event: AppSyncResolverEvent, context: LambdaContext, config: dict) -> Dict[str, Any]:
127+
"""Get the keyword arguments
128+
Parameters
129+
----------
130+
event : AppSyncResolverEvent
131+
Lambda event
132+
context : LambdaContext
133+
Lambda context
134+
config : dict
135+
Configuration settings
136+
Returns
137+
-------
138+
dict
139+
Returns keyword arguments
140+
"""
141+
kwargs = {**event.arguments}
142+
if config.get("include_event", False):
143+
kwargs["event"] = event
144+
if config.get("include_context", False):
145+
kwargs["context"] = context
146+
return kwargs

docs/utilities/data_classes.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ title: Event Source Data Classes
33
description: Utility
44
---
55

6-
Event Source Data Classes utility provides classes self-describing Lambda event sources, including API decorators when
7-
applicable.
6+
Event Source Data Classes utility provides classes self-describing Lambda event sources.
87

98
## Key Features
109

tests/functional/event_handler/__init__.py

Whitespace-only changes.

tests/functional/appsync/test_appsync_resolver_utils.py renamed to tests/functional/event_handler/test_appsync.py

Lines changed: 1 addition & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,12 @@
11
import asyncio
2-
import datetime
32
import json
43
import os
54
import sys
65

76
import pytest
87

98
from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent
10-
from aws_lambda_powertools.utilities.data_classes.appsync.resolver_utils import AppSyncResolver
11-
from aws_lambda_powertools.utilities.data_classes.appsync.scalar_types_utils import (
12-
_formatted_time,
13-
aws_date,
14-
aws_datetime,
15-
aws_time,
16-
aws_timestamp,
17-
make_id,
18-
)
9+
from aws_lambda_powertools.utilities.event_handler import AppSyncResolver
1910
from aws_lambda_powertools.utilities.typing import LambdaContext
2011

2112

@@ -189,46 +180,3 @@ async def get_async():
189180

190181
# THEN
191182
assert asyncio.run(result) == "value"
192-
193-
194-
def test_make_id():
195-
uuid: str = make_id()
196-
assert isinstance(uuid, str)
197-
assert len(uuid) == 36
198-
199-
200-
def test_aws_date_utc():
201-
date_str = aws_date()
202-
assert isinstance(date_str, str)
203-
assert datetime.datetime.strptime(date_str, "%Y-%m-%dZ")
204-
205-
206-
def test_aws_time_utc():
207-
time_str = aws_time()
208-
assert isinstance(time_str, str)
209-
assert datetime.datetime.strptime(time_str, "%H:%M:%SZ")
210-
211-
212-
def test_aws_datetime_utc():
213-
datetime_str = aws_datetime()
214-
assert isinstance(datetime_str, str)
215-
assert datetime.datetime.strptime(datetime_str, "%Y-%m-%dT%H:%M:%SZ")
216-
217-
218-
def test_aws_timestamp():
219-
timestamp = aws_timestamp()
220-
assert isinstance(timestamp, int)
221-
222-
223-
def test_format_time_positive():
224-
now = datetime.datetime(2022, 1, 22)
225-
datetime_str = _formatted_time(now, "%Y-%m-%d", 8)
226-
assert isinstance(datetime_str, str)
227-
assert datetime_str == "2022-01-22+08:00:00"
228-
229-
230-
def test_format_time_negative():
231-
now = datetime.datetime(2022, 1, 22, 14, 22, 33)
232-
datetime_str = _formatted_time(now, "%H:%M:%S", -12)
233-
assert isinstance(datetime_str, str)
234-
assert datetime_str == "02:22:33-12:00:00"

tests/functional/test_lambda_trigger_events.py renamed to tests/functional/test_data_classes.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import base64
2+
import datetime
23
import json
34
import os
45
from secrets import compare_digest
@@ -17,6 +18,14 @@
1718
SNSEvent,
1819
SQSEvent,
1920
)
21+
from aws_lambda_powertools.utilities.data_classes.appsync.scalar_types_utils import (
22+
_formatted_time,
23+
aws_date,
24+
aws_datetime,
25+
aws_time,
26+
aws_timestamp,
27+
make_id,
28+
)
2029
from aws_lambda_powertools.utilities.data_classes.appsync_resolver_event import (
2130
AppSyncIdentityCognito,
2231
AppSyncIdentityIAM,
@@ -1059,3 +1068,46 @@ def test_s3_object_event_temp_credentials():
10591068
assert session_attributes is not None
10601069
assert session_attributes.mfa_authenticated == session_context["attributes"]["mfaAuthenticated"]
10611070
assert session_attributes.creation_date == session_context["attributes"]["creationDate"]
1071+
1072+
1073+
def test_make_id():
1074+
uuid: str = make_id()
1075+
assert isinstance(uuid, str)
1076+
assert len(uuid) == 36
1077+
1078+
1079+
def test_aws_date_utc():
1080+
date_str = aws_date()
1081+
assert isinstance(date_str, str)
1082+
assert datetime.datetime.strptime(date_str, "%Y-%m-%dZ")
1083+
1084+
1085+
def test_aws_time_utc():
1086+
time_str = aws_time()
1087+
assert isinstance(time_str, str)
1088+
assert datetime.datetime.strptime(time_str, "%H:%M:%SZ")
1089+
1090+
1091+
def test_aws_datetime_utc():
1092+
datetime_str = aws_datetime()
1093+
assert isinstance(datetime_str, str)
1094+
assert datetime.datetime.strptime(datetime_str, "%Y-%m-%dT%H:%M:%SZ")
1095+
1096+
1097+
def test_aws_timestamp():
1098+
timestamp = aws_timestamp()
1099+
assert isinstance(timestamp, int)
1100+
1101+
1102+
def test_format_time_positive():
1103+
now = datetime.datetime(2022, 1, 22)
1104+
datetime_str = _formatted_time(now, "%Y-%m-%d", 8)
1105+
assert isinstance(datetime_str, str)
1106+
assert datetime_str == "2022-01-22+08:00:00"
1107+
1108+
1109+
def test_format_time_negative():
1110+
now = datetime.datetime(2022, 1, 22, 14, 22, 33)
1111+
datetime_str = _formatted_time(now, "%H:%M:%S", -12)
1112+
assert isinstance(datetime_str, str)
1113+
assert datetime_str == "02:22:33-12:00:00"

0 commit comments

Comments
 (0)