Skip to content

Commit a2c82b5

Browse files
author
Michael Brewer
authored
feat(event-handler): Add AppSync handler decorator (#363)
* feat(event-handler): Add AppSync handler decorator * test(event-handler): Use pathlib * fix(tracer): Correct type hint for MyPy Closes #360 * docs(event-handler): Initial markdown docs * feat(event-handler): Add an implicit appsync handler * docs(event-handler): Add implicit handler example * docs(event-handler): Add full amplify example * chore(event-handler): Add more docs * chore(event-handler): Add current_event * refactor(event-handler): Remove include_event and incluce_context options * chore: bump ci * refactor(event-handler): Move to up to * chore(event-handler): Add debug logging to appsync resolver * docs(event-handler): Add more detailed example to docstring * chore(event-handler): Rename to _get_resolver
1 parent 1b5d46a commit a2c82b5

File tree

11 files changed

+416
-155
lines changed

11 files changed

+416
-155
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
"""
2+
Event handler decorators for common Lambda events
3+
"""
4+
5+
from .appsync import AppSyncResolver
6+
7+
__all__ = ["AppSyncResolver"]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import logging
2+
from typing import Any, Callable
3+
4+
from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent
5+
from aws_lambda_powertools.utilities.typing import LambdaContext
6+
7+
logger = logging.getLogger(__name__)
8+
9+
10+
class AppSyncResolver:
11+
"""
12+
AppSync resolver decorator
13+
14+
Example
15+
-------
16+
17+
**Sample usage**
18+
19+
from aws_lambda_powertools.event_handler import AppSyncResolver
20+
21+
app = AppSyncResolver()
22+
23+
@app.resolver(type_name="Query", field_name="listLocations")
24+
def list_locations(page: int = 0, size: int = 10) -> list:
25+
# Your logic to fetch locations with arguments passed in
26+
return [{"id": 100, "name": "Smooth Grooves"}]
27+
28+
@app.resolver(type_name="Merchant", field_name="extraInfo")
29+
def get_extra_info() -> dict:
30+
# Can use "app.current_event.source" to filter within the parent context
31+
account_type = app.current_event.source["accountType"]
32+
method = "BTC" if account_type == "NEW" else "USD"
33+
return {"preferredPaymentMethod": method}
34+
35+
@app.resolver(field_name="commonField")
36+
def common_field() -> str:
37+
# Would match all fieldNames matching 'commonField'
38+
return str(uuid.uuid4())
39+
"""
40+
41+
current_event: AppSyncResolverEvent
42+
lambda_context: LambdaContext
43+
44+
def __init__(self):
45+
self._resolvers: dict = {}
46+
47+
def resolver(self, type_name: str = "*", field_name: str = None):
48+
"""Registers the resolver for field_name
49+
50+
Parameters
51+
----------
52+
type_name : str
53+
Type name
54+
field_name : str
55+
Field name
56+
"""
57+
58+
def register_resolver(func):
59+
logger.debug(f"Adding resolver `{func.__name__}` for field `{type_name}.{field_name}`")
60+
self._resolvers[f"{type_name}.{field_name}"] = {"func": func}
61+
return func
62+
63+
return register_resolver
64+
65+
def resolve(self, event: dict, context: LambdaContext) -> Any:
66+
"""Resolve field_name
67+
68+
Parameters
69+
----------
70+
event : dict
71+
Lambda event
72+
context : LambdaContext
73+
Lambda context
74+
75+
Returns
76+
-------
77+
Any
78+
Returns the result of the resolver
79+
80+
Raises
81+
-------
82+
ValueError
83+
If we could not find a field resolver
84+
"""
85+
self.current_event = AppSyncResolverEvent(event)
86+
self.lambda_context = context
87+
resolver = self._get_resolver(self.current_event.type_name, self.current_event.field_name)
88+
return resolver(**self.current_event.arguments)
89+
90+
def _get_resolver(self, type_name: str, field_name: str) -> Callable:
91+
"""Get resolver for field_name
92+
93+
Parameters
94+
----------
95+
type_name : str
96+
Type name
97+
field_name : str
98+
Field name
99+
100+
Returns
101+
-------
102+
Callable
103+
callable function and configuration
104+
"""
105+
full_name = f"{type_name}.{field_name}"
106+
resolver = self._resolvers.get(full_name, self._resolvers.get(f"*.{field_name}"))
107+
if not resolver:
108+
raise ValueError(f"No resolver found for '{full_name}'")
109+
return resolver["func"]
110+
111+
def __call__(self, event, context) -> Any:
112+
"""Implicit lambda handler which internally calls `resolve`"""
113+
return self.resolve(event, context)

aws_lambda_powertools/utilities/data_classes/__init__.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1-
from aws_lambda_powertools.utilities.data_classes.appsync_resolver_event import AppSyncResolverEvent
1+
"""
2+
Event Source Data Classes utility provides classes self-describing Lambda event sources.
3+
"""
24

35
from .alb_event import ALBEvent
46
from .api_gateway_proxy_event import APIGatewayProxyEvent, APIGatewayProxyEventV2
7+
from .appsync_resolver_event import AppSyncResolverEvent
58
from .cloud_watch_logs_event import CloudWatchLogsEvent
69
from .connect_contact_flow_event import ConnectContactFlowEvent
710
from .dynamo_db_stream_event import DynamoDBStreamEvent

aws_lambda_powertools/utilities/data_classes/appsync/resolver_utils.py

-50
This file was deleted.

0 commit comments

Comments
 (0)