aws-lambda-powertools 3.0.0__py3-none-any.whl → 3.0.0.dev2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aws_lambda_powertools/event_handler/api_gateway.py +54 -227
- aws_lambda_powertools/event_handler/appsync.py +97 -343
- aws_lambda_powertools/event_handler/bedrock_agent.py +0 -121
- aws_lambda_powertools/event_handler/exceptions.py +1 -1
- aws_lambda_powertools/event_handler/middlewares/openapi_validation.py +1 -1
- aws_lambda_powertools/event_handler/openapi/compat.py +7 -4
- aws_lambda_powertools/event_handler/openapi/constants.py +4 -0
- aws_lambda_powertools/event_handler/openapi/encoders.py +70 -94
- aws_lambda_powertools/event_handler/openapi/exceptions.py +0 -12
- aws_lambda_powertools/event_handler/openapi/models.py +9 -41
- aws_lambda_powertools/event_handler/openapi/swagger_ui/html.py +4 -1
- aws_lambda_powertools/event_handler/openapi/swagger_ui/oauth2.py +1 -1
- aws_lambda_powertools/event_handler/openapi/types.py +7 -8
- aws_lambda_powertools/event_handler/util.py +2 -55
- aws_lambda_powertools/logging/utils.py +5 -5
- aws_lambda_powertools/metrics/provider/cloudwatch_emf/metric_properties.py +0 -1
- aws_lambda_powertools/shared/cookies.py +2 -2
- aws_lambda_powertools/shared/dynamodb_deserializer.py +0 -13
- aws_lambda_powertools/shared/version.py +1 -1
- aws_lambda_powertools/tracing/tracer.py +2 -4
- aws_lambda_powertools/utilities/batch/base.py +4 -13
- aws_lambda_powertools/utilities/data_classes/api_gateway_authorizer_event.py +1 -101
- aws_lambda_powertools/utilities/data_classes/appsync_resolver_event.py +1 -57
- aws_lambda_powertools/utilities/data_classes/cognito_user_pool_event.py +1 -260
- aws_lambda_powertools/utilities/data_classes/common.py +1 -114
- aws_lambda_powertools/utilities/data_classes/kafka_event.py +2 -2
- aws_lambda_powertools/utilities/data_classes/shared_functions.py +0 -141
- aws_lambda_powertools/utilities/data_classes/vpc_lattice.py +3 -8
- aws_lambda_powertools/utilities/data_masking/base.py +4 -4
- aws_lambda_powertools/utilities/feature_flags/appconfig.py +2 -21
- aws_lambda_powertools/utilities/idempotency/idempotency.py +2 -22
- aws_lambda_powertools/utilities/idempotency/persistence/base.py +1 -4
- aws_lambda_powertools/utilities/idempotency/persistence/datarecord.py +0 -21
- aws_lambda_powertools/utilities/parameters/secrets.py +1 -1
- aws_lambda_powertools/utilities/parser/models/__init__.py +0 -6
- aws_lambda_powertools/utilities/parser/models/apigw.py +1 -12
- aws_lambda_powertools/utilities/parser/models/apigwv2.py +1 -7
- aws_lambda_powertools/utilities/validation/base.py +2 -27
- aws_lambda_powertools/utilities/validation/validator.py +4 -49
- {aws_lambda_powertools-3.0.0.dist-info → aws_lambda_powertools-3.0.0.dev2.dist-info}/METADATA +2 -12
- {aws_lambda_powertools-3.0.0.dist-info → aws_lambda_powertools-3.0.0.dev2.dist-info}/RECORD +43 -48
- aws_lambda_powertools/event_handler/graphql_appsync/__init__.py +0 -0
- aws_lambda_powertools/event_handler/graphql_appsync/_registry.py +0 -78
- aws_lambda_powertools/event_handler/graphql_appsync/base.py +0 -160
- aws_lambda_powertools/event_handler/graphql_appsync/exceptions.py +0 -10
- aws_lambda_powertools/event_handler/graphql_appsync/router.py +0 -62
- {aws_lambda_powertools-3.0.0.dist-info → aws_lambda_powertools-3.0.0.dev2.dist-info}/LICENSE +0 -0
- {aws_lambda_powertools-3.0.0.dist-info → aws_lambda_powertools-3.0.0.dev2.dist-info}/WHEEL +0 -0
|
@@ -1,80 +1,100 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import asyncio
|
|
4
3
|
import logging
|
|
5
|
-
import
|
|
6
|
-
from typing import TYPE_CHECKING, Any, Callable
|
|
4
|
+
from typing import TYPE_CHECKING, Any, Callable, TypeVar
|
|
7
5
|
|
|
8
|
-
from aws_lambda_powertools.event_handler.graphql_appsync.exceptions import InvalidBatchResponse, ResolverNotFoundError
|
|
9
|
-
from aws_lambda_powertools.event_handler.graphql_appsync.router import Router
|
|
10
6
|
from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent
|
|
11
7
|
|
|
12
8
|
if TYPE_CHECKING:
|
|
13
9
|
from aws_lambda_powertools.utilities.typing import LambdaContext
|
|
14
10
|
|
|
15
|
-
from aws_lambda_powertools.warnings import PowertoolsUserWarning
|
|
16
|
-
|
|
17
11
|
logger = logging.getLogger(__name__)
|
|
18
12
|
|
|
13
|
+
AppSyncResolverEventT = TypeVar("AppSyncResolverEventT", bound=AppSyncResolverEvent)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class BaseRouter:
|
|
17
|
+
current_event: AppSyncResolverEventT # type: ignore[valid-type]
|
|
18
|
+
lambda_context: LambdaContext
|
|
19
|
+
context: dict
|
|
20
|
+
|
|
21
|
+
def __init__(self):
|
|
22
|
+
self._resolvers: dict = {}
|
|
19
23
|
|
|
20
|
-
|
|
24
|
+
def resolver(self, type_name: str = "*", field_name: str | None = None):
|
|
25
|
+
"""Registers the resolver for field_name
|
|
26
|
+
|
|
27
|
+
Parameters
|
|
28
|
+
----------
|
|
29
|
+
type_name : str
|
|
30
|
+
Type name
|
|
31
|
+
field_name : str
|
|
32
|
+
Field name
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def register_resolver(func):
|
|
36
|
+
logger.debug(f"Adding resolver `{func.__name__}` for field `{type_name}.{field_name}`")
|
|
37
|
+
self._resolvers[f"{type_name}.{field_name}"] = {"func": func}
|
|
38
|
+
return func
|
|
39
|
+
|
|
40
|
+
return register_resolver
|
|
41
|
+
|
|
42
|
+
def append_context(self, **additional_context):
|
|
43
|
+
"""Append key=value data as routing context"""
|
|
44
|
+
self.context.update(**additional_context)
|
|
45
|
+
|
|
46
|
+
def clear_context(self):
|
|
47
|
+
"""Resets routing context"""
|
|
48
|
+
self.context.clear()
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class AppSyncResolver(BaseRouter):
|
|
21
52
|
"""
|
|
22
|
-
AppSync
|
|
53
|
+
AppSync resolver decorator
|
|
23
54
|
|
|
24
55
|
Example
|
|
25
56
|
-------
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
57
|
+
|
|
58
|
+
**Sample usage**
|
|
59
|
+
|
|
60
|
+
from aws_lambda_powertools.event_handler import AppSyncResolver
|
|
61
|
+
|
|
62
|
+
app = AppSyncResolver()
|
|
63
|
+
|
|
64
|
+
@app.resolver(type_name="Query", field_name="listLocations")
|
|
65
|
+
def list_locations(page: int = 0, size: int = 10) -> list:
|
|
66
|
+
# Your logic to fetch locations with arguments passed in
|
|
67
|
+
return [{"id": 100, "name": "Smooth Grooves"}]
|
|
68
|
+
|
|
69
|
+
@app.resolver(type_name="Merchant", field_name="extraInfo")
|
|
70
|
+
def get_extra_info() -> dict:
|
|
71
|
+
# Can use "app.current_event.source" to filter within the parent context
|
|
72
|
+
account_type = app.current_event.source["accountType"]
|
|
73
|
+
method = "BTC" if account_type == "NEW" else "USD"
|
|
74
|
+
return {"preferredPaymentMethod": method}
|
|
75
|
+
|
|
76
|
+
@app.resolver(field_name="commonField")
|
|
77
|
+
def common_field() -> str:
|
|
78
|
+
# Would match all fieldNames matching 'commonField'
|
|
79
|
+
return str(uuid.uuid4())
|
|
48
80
|
"""
|
|
49
81
|
|
|
50
82
|
def __init__(self):
|
|
51
|
-
"""
|
|
52
|
-
Initialize a new instance of the AppSyncResolver.
|
|
53
|
-
"""
|
|
54
83
|
super().__init__()
|
|
55
84
|
self.context = {} # early init as customers might add context before event resolution
|
|
56
85
|
|
|
57
|
-
def __call__(
|
|
58
|
-
self,
|
|
59
|
-
event: dict,
|
|
60
|
-
context: LambdaContext,
|
|
61
|
-
data_model: type[AppSyncResolverEvent] = AppSyncResolverEvent,
|
|
62
|
-
) -> Any:
|
|
63
|
-
"""Implicit lambda handler which internally calls `resolve`"""
|
|
64
|
-
return self.resolve(event, context, data_model)
|
|
65
|
-
|
|
66
86
|
def resolve(
|
|
67
87
|
self,
|
|
68
|
-
event: dict
|
|
88
|
+
event: dict,
|
|
69
89
|
context: LambdaContext,
|
|
70
90
|
data_model: type[AppSyncResolverEvent] = AppSyncResolverEvent,
|
|
71
91
|
) -> Any:
|
|
72
|
-
"""
|
|
92
|
+
"""Resolve field_name
|
|
73
93
|
|
|
74
94
|
Parameters
|
|
75
95
|
----------
|
|
76
|
-
event : dict
|
|
77
|
-
Lambda event
|
|
96
|
+
event : dict
|
|
97
|
+
Lambda event
|
|
78
98
|
context : LambdaContext
|
|
79
99
|
Lambda context
|
|
80
100
|
data_model:
|
|
@@ -138,214 +158,45 @@ class AppSyncResolver(Router):
|
|
|
138
158
|
ValueError
|
|
139
159
|
If we could not find a field resolver
|
|
140
160
|
"""
|
|
161
|
+
# Maintenance: revisit generics/overload to fix [attr-defined] in mypy usage
|
|
162
|
+
BaseRouter.current_event = data_model(event)
|
|
163
|
+
BaseRouter.lambda_context = context
|
|
141
164
|
|
|
142
|
-
self.
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
if isinstance(event, list):
|
|
146
|
-
Router.current_batch_event = [data_model(e) for e in event]
|
|
147
|
-
response = self._call_batch_resolver(event=event, data_model=data_model)
|
|
148
|
-
else:
|
|
149
|
-
Router.current_event = data_model(event)
|
|
150
|
-
response = self._call_single_resolver(event=event, data_model=data_model)
|
|
151
|
-
|
|
165
|
+
resolver = self._get_resolver(BaseRouter.current_event.type_name, BaseRouter.current_event.field_name)
|
|
166
|
+
response = resolver(**BaseRouter.current_event.arguments)
|
|
152
167
|
self.clear_context()
|
|
153
168
|
|
|
154
169
|
return response
|
|
155
170
|
|
|
156
|
-
def
|
|
157
|
-
"""
|
|
171
|
+
def _get_resolver(self, type_name: str, field_name: str) -> Callable:
|
|
172
|
+
"""Get resolver for field_name
|
|
158
173
|
|
|
159
174
|
Parameters
|
|
160
175
|
----------
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
"""
|
|
166
|
-
|
|
167
|
-
logger.debug("Processing direct resolver event")
|
|
168
|
-
|
|
169
|
-
self.current_event = data_model(event)
|
|
170
|
-
resolver = self._resolver_registry.find_resolver(self.current_event.type_name, self.current_event.field_name)
|
|
171
|
-
if not resolver:
|
|
172
|
-
raise ValueError(f"No resolver found for '{self.current_event.type_name}.{self.current_event.field_name}'")
|
|
173
|
-
return resolver["func"](**self.current_event.arguments)
|
|
174
|
-
|
|
175
|
-
def _call_sync_batch_resolver(
|
|
176
|
-
self,
|
|
177
|
-
resolver: Callable,
|
|
178
|
-
raise_on_error: bool = False,
|
|
179
|
-
aggregate: bool = True,
|
|
180
|
-
) -> list[Any]:
|
|
181
|
-
"""
|
|
182
|
-
Calls a synchronous batch resolver function for each event in the current batch.
|
|
183
|
-
|
|
184
|
-
Parameters
|
|
185
|
-
----------
|
|
186
|
-
resolver: Callable
|
|
187
|
-
The callable function to resolve events.
|
|
188
|
-
raise_on_error: bool
|
|
189
|
-
A flag indicating whether to raise an error when processing batches
|
|
190
|
-
with failed items. Defaults to False, which means errors are handled without raising exceptions.
|
|
191
|
-
aggregate: bool
|
|
192
|
-
A flag indicating whether the batch items should be processed at once or individually.
|
|
193
|
-
If True (default), the batch resolver will process all items in the batch as a single event.
|
|
194
|
-
If False, the batch resolver will process each item in the batch individually.
|
|
176
|
+
type_name : str
|
|
177
|
+
Type name
|
|
178
|
+
field_name : str
|
|
179
|
+
Field name
|
|
195
180
|
|
|
196
181
|
Returns
|
|
197
182
|
-------
|
|
198
|
-
|
|
199
|
-
|
|
183
|
+
Callable
|
|
184
|
+
callable function and configuration
|
|
200
185
|
"""
|
|
186
|
+
full_name = f"{type_name}.{field_name}"
|
|
187
|
+
resolver = self._resolvers.get(full_name, self._resolvers.get(f"*.{field_name}"))
|
|
188
|
+
if not resolver:
|
|
189
|
+
raise ValueError(f"No resolver found for '{full_name}'")
|
|
190
|
+
return resolver["func"]
|
|
201
191
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
# Checks whether the entire batch should be processed at once
|
|
205
|
-
if aggregate:
|
|
206
|
-
# Process the entire batch
|
|
207
|
-
response = resolver(event=self.current_batch_event)
|
|
208
|
-
|
|
209
|
-
if not isinstance(response, list):
|
|
210
|
-
raise InvalidBatchResponse("The response must be a List when using batch resolvers")
|
|
211
|
-
|
|
212
|
-
return response
|
|
213
|
-
|
|
214
|
-
# Non aggregated events, so we call this event list x times
|
|
215
|
-
# Stop on first exception we encounter
|
|
216
|
-
if raise_on_error:
|
|
217
|
-
return [
|
|
218
|
-
resolver(event=appconfig_event, **appconfig_event.arguments)
|
|
219
|
-
for appconfig_event in self.current_batch_event
|
|
220
|
-
]
|
|
221
|
-
|
|
222
|
-
# By default, we gracefully append `None` for any records that failed processing
|
|
223
|
-
results = []
|
|
224
|
-
for idx, event in enumerate(self.current_batch_event):
|
|
225
|
-
try:
|
|
226
|
-
results.append(resolver(event=event, **event.arguments))
|
|
227
|
-
except Exception:
|
|
228
|
-
logger.debug(f"Failed to process event number {idx} from field '{event.info.field_name}'")
|
|
229
|
-
results.append(None)
|
|
230
|
-
|
|
231
|
-
return results
|
|
232
|
-
|
|
233
|
-
async def _call_async_batch_resolver(
|
|
192
|
+
def __call__(
|
|
234
193
|
self,
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
) ->
|
|
239
|
-
"""
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
Parameters
|
|
243
|
-
----------
|
|
244
|
-
resolver: Callable
|
|
245
|
-
The asynchronous resolver function.
|
|
246
|
-
raise_on_error: bool
|
|
247
|
-
A flag indicating whether to raise an error when processing batches
|
|
248
|
-
with failed items. Defaults to False, which means errors are handled without raising exceptions.
|
|
249
|
-
aggregate: bool
|
|
250
|
-
A flag indicating whether the batch items should be processed at once or individually.
|
|
251
|
-
If True (default), the batch resolver will process all items in the batch as a single event.
|
|
252
|
-
If False, the batch resolver will process each item in the batch individually.
|
|
253
|
-
|
|
254
|
-
Returns
|
|
255
|
-
-------
|
|
256
|
-
list[Any]
|
|
257
|
-
A list of results corresponding to the resolved events.
|
|
258
|
-
"""
|
|
259
|
-
|
|
260
|
-
logger.debug(f"Graceful error handling flag {raise_on_error=}")
|
|
261
|
-
|
|
262
|
-
# Checks whether the entire batch should be processed at once
|
|
263
|
-
if aggregate:
|
|
264
|
-
# Process the entire batch
|
|
265
|
-
ret = await resolver(event=self.current_batch_event)
|
|
266
|
-
if not isinstance(ret, list):
|
|
267
|
-
raise InvalidBatchResponse("The response must be a List when using batch resolvers")
|
|
268
|
-
|
|
269
|
-
return ret
|
|
270
|
-
|
|
271
|
-
response: list = []
|
|
272
|
-
|
|
273
|
-
# Prime coroutines
|
|
274
|
-
tasks = [resolver(event=e, **e.arguments) for e in self.current_batch_event]
|
|
275
|
-
|
|
276
|
-
# Aggregate results or raise at first error
|
|
277
|
-
if raise_on_error:
|
|
278
|
-
response.extend(await asyncio.gather(*tasks))
|
|
279
|
-
return response
|
|
280
|
-
|
|
281
|
-
# Aggregate results and exceptions, then filter them out
|
|
282
|
-
# Use `None` upon exception for graceful error handling at GraphQL engine level
|
|
283
|
-
#
|
|
284
|
-
# NOTE: asyncio.gather(return_exceptions=True) catches and includes exceptions in the results
|
|
285
|
-
# this will become useful when we support exception handling in AppSync resolver
|
|
286
|
-
results = await asyncio.gather(*tasks, return_exceptions=True)
|
|
287
|
-
response.extend(None if isinstance(ret, Exception) else ret for ret in results)
|
|
288
|
-
|
|
289
|
-
return response
|
|
290
|
-
|
|
291
|
-
def _call_batch_resolver(self, event: list[dict], data_model: type[AppSyncResolverEvent]) -> list[Any]:
|
|
292
|
-
"""Call batch event resolver for sync and async methods
|
|
293
|
-
|
|
294
|
-
Parameters
|
|
295
|
-
----------
|
|
296
|
-
event : list[dict]
|
|
297
|
-
Batch event
|
|
298
|
-
data_model : type[AppSyncResolverEvent]
|
|
299
|
-
Data_model to decode AppSync event, by default AppSyncResolverEvent or a subclass
|
|
300
|
-
|
|
301
|
-
Returns
|
|
302
|
-
-------
|
|
303
|
-
list[Any]
|
|
304
|
-
Results of the resolver execution.
|
|
305
|
-
|
|
306
|
-
Raises
|
|
307
|
-
------
|
|
308
|
-
InconsistentPayloadError:
|
|
309
|
-
When all events in the batch do not have the same fieldName.
|
|
310
|
-
|
|
311
|
-
ResolverNotFoundError:
|
|
312
|
-
When no resolver is found for the specified type and field.
|
|
313
|
-
"""
|
|
314
|
-
logger.debug("Processing batch resolver event")
|
|
315
|
-
|
|
316
|
-
self.current_batch_event = [data_model(e) for e in event]
|
|
317
|
-
type_name, field_name = self.current_batch_event[0].type_name, self.current_batch_event[0].field_name
|
|
318
|
-
|
|
319
|
-
resolver = self._batch_resolver_registry.find_resolver(type_name, field_name)
|
|
320
|
-
async_resolver = self._async_batch_resolver_registry.find_resolver(type_name, field_name)
|
|
321
|
-
|
|
322
|
-
if resolver and async_resolver:
|
|
323
|
-
warnings.warn(
|
|
324
|
-
f"Both synchronous and asynchronous resolvers found for the same event and field."
|
|
325
|
-
f"The synchronous resolver takes precedence. Executing: {resolver['func'].__name__}",
|
|
326
|
-
stacklevel=2,
|
|
327
|
-
category=PowertoolsUserWarning,
|
|
328
|
-
)
|
|
329
|
-
|
|
330
|
-
if resolver:
|
|
331
|
-
logger.debug(f"Found sync resolver. {resolver=}, {field_name=}")
|
|
332
|
-
return self._call_sync_batch_resolver(
|
|
333
|
-
resolver=resolver["func"],
|
|
334
|
-
raise_on_error=resolver["raise_on_error"],
|
|
335
|
-
aggregate=resolver["aggregate"],
|
|
336
|
-
)
|
|
337
|
-
|
|
338
|
-
if async_resolver:
|
|
339
|
-
logger.debug(f"Found async resolver. {resolver=}, {field_name=}")
|
|
340
|
-
return asyncio.run(
|
|
341
|
-
self._call_async_batch_resolver(
|
|
342
|
-
resolver=async_resolver["func"],
|
|
343
|
-
raise_on_error=async_resolver["raise_on_error"],
|
|
344
|
-
aggregate=async_resolver["aggregate"],
|
|
345
|
-
),
|
|
346
|
-
)
|
|
347
|
-
|
|
348
|
-
raise ResolverNotFoundError(f"No resolver found for '{type_name}.{field_name}'")
|
|
194
|
+
event: dict,
|
|
195
|
+
context: LambdaContext,
|
|
196
|
+
data_model: type[AppSyncResolverEvent] = AppSyncResolverEvent,
|
|
197
|
+
) -> Any:
|
|
198
|
+
"""Implicit lambda handler which internally calls `resolve`"""
|
|
199
|
+
return self.resolve(event, context, data_model)
|
|
349
200
|
|
|
350
201
|
def include_router(self, router: Router) -> None:
|
|
351
202
|
"""Adds all resolvers defined in a router
|
|
@@ -355,112 +206,15 @@ class AppSyncResolver(Router):
|
|
|
355
206
|
router : Router
|
|
356
207
|
A router containing a dict of field resolvers
|
|
357
208
|
"""
|
|
358
|
-
|
|
359
209
|
# Merge app and router context
|
|
360
|
-
logger.debug("Merging router and app context")
|
|
361
210
|
self.context.update(**router.context)
|
|
362
|
-
|
|
363
211
|
# use pointer to allow context clearance after event is processed e.g., resolve(evt, ctx)
|
|
364
212
|
router.context = self.context
|
|
365
213
|
|
|
366
|
-
|
|
367
|
-
self._resolver_registry.merge(router._resolver_registry)
|
|
368
|
-
self._batch_resolver_registry.merge(router._batch_resolver_registry)
|
|
369
|
-
self._async_batch_resolver_registry.merge(router._async_batch_resolver_registry)
|
|
214
|
+
self._resolvers.update(router._resolvers)
|
|
370
215
|
|
|
371
|
-
def resolver(self, type_name: str = "*", field_name: str | None = None) -> Callable:
|
|
372
|
-
"""Registers direct resolver function for GraphQL type and field name.
|
|
373
|
-
|
|
374
|
-
Parameters
|
|
375
|
-
----------
|
|
376
|
-
type_name : str, optional
|
|
377
|
-
GraphQL type e.g., Query, Mutation, by default "*" meaning any
|
|
378
|
-
field_name : Optional[str], optional
|
|
379
|
-
GraphQL field e.g., getTodo, createTodo, by default None
|
|
380
216
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
Example
|
|
387
|
-
-------
|
|
388
|
-
|
|
389
|
-
```python
|
|
390
|
-
from aws_lambda_powertools.event_handler import AppSyncResolver
|
|
391
|
-
|
|
392
|
-
from typing import TypedDict
|
|
393
|
-
|
|
394
|
-
app = AppSyncResolver()
|
|
395
|
-
|
|
396
|
-
class Todo(TypedDict, total=False):
|
|
397
|
-
id: str
|
|
398
|
-
userId: str
|
|
399
|
-
title: str
|
|
400
|
-
completed: bool
|
|
401
|
-
|
|
402
|
-
# resolve any GraphQL `getTodo` queries
|
|
403
|
-
# arguments are injected as function arguments as-is
|
|
404
|
-
@app.resolver(type_name="Query", field_name="getTodo")
|
|
405
|
-
def get_todo(id: str = "", status: str = "open") -> Todo:
|
|
406
|
-
todos: Response = requests.get(f"https://jsonplaceholder.typicode.com/todos/{id}")
|
|
407
|
-
todos.raise_for_status()
|
|
408
|
-
|
|
409
|
-
return todos.json()
|
|
410
|
-
|
|
411
|
-
def lambda_handler(event, context):
|
|
412
|
-
return app.resolve(event, context)
|
|
413
|
-
```
|
|
414
|
-
"""
|
|
415
|
-
return self._resolver_registry.register(field_name=field_name, type_name=type_name)
|
|
416
|
-
|
|
417
|
-
def batch_resolver(
|
|
418
|
-
self,
|
|
419
|
-
type_name: str = "*",
|
|
420
|
-
field_name: str | None = None,
|
|
421
|
-
raise_on_error: bool = False,
|
|
422
|
-
aggregate: bool = True,
|
|
423
|
-
) -> Callable:
|
|
424
|
-
"""Registers batch resolver function for GraphQL type and field name.
|
|
425
|
-
|
|
426
|
-
By default, we handle errors gracefully by returning `None`. If you want
|
|
427
|
-
to short-circuit and fail the entire batch use `raise_on_error=True`.
|
|
428
|
-
|
|
429
|
-
Parameters
|
|
430
|
-
----------
|
|
431
|
-
type_name : str, optional
|
|
432
|
-
GraphQL type e.g., Query, Mutation, by default "*" meaning any
|
|
433
|
-
field_name : Optional[str], optional
|
|
434
|
-
GraphQL field e.g., getTodo, createTodo, by default None
|
|
435
|
-
raise_on_error : bool, optional
|
|
436
|
-
Whether to fail entire batch upon error, or handle errors gracefully (None), by default False
|
|
437
|
-
aggregate: bool
|
|
438
|
-
A flag indicating whether the batch items should be processed at once or individually.
|
|
439
|
-
If True (default), the batch resolver will process all items in the batch as a single event.
|
|
440
|
-
If False, the batch resolver will process each item in the batch individually.
|
|
441
|
-
|
|
442
|
-
Returns
|
|
443
|
-
-------
|
|
444
|
-
Callable
|
|
445
|
-
Registered resolver
|
|
446
|
-
"""
|
|
447
|
-
return self._batch_resolver_registry.register(
|
|
448
|
-
field_name=field_name,
|
|
449
|
-
type_name=type_name,
|
|
450
|
-
raise_on_error=raise_on_error,
|
|
451
|
-
aggregate=aggregate,
|
|
452
|
-
)
|
|
453
|
-
|
|
454
|
-
def async_batch_resolver(
|
|
455
|
-
self,
|
|
456
|
-
type_name: str = "*",
|
|
457
|
-
field_name: str | None = None,
|
|
458
|
-
raise_on_error: bool = False,
|
|
459
|
-
aggregate: bool = True,
|
|
460
|
-
) -> Callable:
|
|
461
|
-
return self._async_batch_resolver_registry.register(
|
|
462
|
-
field_name=field_name,
|
|
463
|
-
type_name=type_name,
|
|
464
|
-
raise_on_error=raise_on_error,
|
|
465
|
-
aggregate=aggregate,
|
|
466
|
-
)
|
|
217
|
+
class Router(BaseRouter):
|
|
218
|
+
def __init__(self):
|
|
219
|
+
super().__init__()
|
|
220
|
+
self.context = {} # early init as customers might add context before event resolution
|