3
3
import warnings
4
4
from typing import Any , Callable , Dict , List , Optional , Type , Union
5
5
6
- from aws_lambda_powertools .event_handler .graphql_appsync .exceptions import ResolverNotFoundError
6
+ from aws_lambda_powertools .event_handler .graphql_appsync .exceptions import InvalidBatchResponse , ResolverNotFoundError
7
7
from aws_lambda_powertools .event_handler .graphql_appsync .router import Router
8
8
from aws_lambda_powertools .utilities .data_classes import AppSyncResolverEvent
9
9
from aws_lambda_powertools .utilities .typing import LambdaContext
@@ -168,7 +168,12 @@ def _call_single_resolver(self, event: dict, data_model: Type[AppSyncResolverEve
168
168
raise ValueError (f"No resolver found for '{ self .current_event .type_name } .{ self .current_event .field_name } '" )
169
169
return resolver ["func" ](** self .current_event .arguments )
170
170
171
- def _call_sync_batch_resolver (self , resolver : Callable , raise_on_error : bool = False ) -> List [Any ]:
171
+ def _call_sync_batch_resolver (
172
+ self ,
173
+ resolver : Callable ,
174
+ raise_on_error : bool = False ,
175
+ aggregate : bool = True ,
176
+ ) -> List [Any ]:
172
177
"""
173
178
Calls a synchronous batch resolver function for each event in the current batch.
174
179
@@ -179,6 +184,10 @@ def _call_sync_batch_resolver(self, resolver: Callable, raise_on_error: bool = F
179
184
raise_on_error: bool
180
185
A flag indicating whether to raise an error when processing batches
181
186
with failed items. Defaults to False, which means errors are handled without raising exceptions.
187
+ aggregate: bool
188
+ A flag indicating whether the batch items should be processed at once or individually.
189
+ If True (default), the batch resolver will process all items in the batch as a single event.
190
+ If False, the batch resolver will process each item in the batch individually.
182
191
183
192
Returns
184
193
-------
@@ -188,6 +197,17 @@ def _call_sync_batch_resolver(self, resolver: Callable, raise_on_error: bool = F
188
197
189
198
logger .debug (f"Graceful error handling flag { raise_on_error = } " )
190
199
200
+ # Checks whether the entire batch should be processed at once
201
+ if aggregate :
202
+ # Process the entire batch
203
+ response = resolver (event = self .current_batch_event )
204
+
205
+ if not isinstance (response , List ):
206
+ raise InvalidBatchResponse ("The response must be a List when using batch resolvers" )
207
+
208
+ return response
209
+
210
+ # Non aggregated events, so we call this event list x times
191
211
# Stop on first exception we encounter
192
212
if raise_on_error :
193
213
return [
@@ -206,7 +226,12 @@ def _call_sync_batch_resolver(self, resolver: Callable, raise_on_error: bool = F
206
226
207
227
return results
208
228
209
- async def _call_async_batch_resolver (self , resolver : Callable , raise_on_error : bool = False ) -> List [Any ]:
229
+ async def _call_async_batch_resolver (
230
+ self ,
231
+ resolver : Callable ,
232
+ raise_on_error : bool = False ,
233
+ aggregate : bool = True ,
234
+ ) -> List [Any ]:
210
235
"""
211
236
Asynchronously call a batch resolver for each event in the current batch.
212
237
@@ -217,6 +242,10 @@ async def _call_async_batch_resolver(self, resolver: Callable, raise_on_error: b
217
242
raise_on_error: bool
218
243
A flag indicating whether to raise an error when processing batches
219
244
with failed items. Defaults to False, which means errors are handled without raising exceptions.
245
+ aggregate: bool
246
+ A flag indicating whether the batch items should be processed at once or individually.
247
+ If True (default), the batch resolver will process all items in the batch as a single event.
248
+ If False, the batch resolver will process each item in the batch individually.
220
249
221
250
Returns
222
251
-------
@@ -225,7 +254,17 @@ async def _call_async_batch_resolver(self, resolver: Callable, raise_on_error: b
225
254
"""
226
255
227
256
logger .debug (f"Graceful error handling flag { raise_on_error = } " )
228
- response = []
257
+
258
+ response : List = []
259
+
260
+ # Checks whether the entire batch should be processed at once
261
+ if aggregate :
262
+ # Process the entire batch
263
+ response .extend (await asyncio .gather (resolver (event = self .current_batch_event )))
264
+ if not isinstance (response [0 ], List ):
265
+ raise InvalidBatchResponse ("The response must be a List when using batch resolvers" )
266
+
267
+ return response [0 ]
229
268
230
269
# Prime coroutines
231
270
tasks = [resolver (event = e , ** e .arguments ) for e in self .current_batch_event ]
@@ -286,14 +325,19 @@ def _call_batch_resolver(self, event: List[dict], data_model: Type[AppSyncResolv
286
325
287
326
if resolver :
288
327
logger .debug (f"Found sync resolver. { resolver = } , { field_name = } " )
289
- return self ._call_sync_batch_resolver (resolver = resolver ["func" ], raise_on_error = resolver ["raise_on_error" ])
328
+ return self ._call_sync_batch_resolver (
329
+ resolver = resolver ["func" ],
330
+ raise_on_error = resolver ["raise_on_error" ],
331
+ aggregate = resolver ["aggregate" ],
332
+ )
290
333
291
334
if async_resolver :
292
335
logger .debug (f"Found async resolver. { resolver = } , { field_name = } " )
293
336
return asyncio .run (
294
337
self ._call_async_batch_resolver (
295
338
resolver = async_resolver ["func" ],
296
339
raise_on_error = async_resolver ["raise_on_error" ],
340
+ aggregate = async_resolver ["aggregate" ],
297
341
),
298
342
)
299
343
@@ -371,6 +415,7 @@ def batch_resolver(
371
415
type_name : str = "*" ,
372
416
field_name : Optional [str ] = None ,
373
417
raise_on_error : bool = False ,
418
+ aggregate : bool = True ,
374
419
) -> Callable :
375
420
"""Registers batch resolver function for GraphQL type and field name.
376
421
@@ -385,6 +430,10 @@ def batch_resolver(
385
430
GraphQL field e.g., getTodo, createTodo, by default None
386
431
raise_on_error : bool, optional
387
432
Whether to fail entire batch upon error, or handle errors gracefully (None), by default False
433
+ aggregate: bool
434
+ A flag indicating whether the batch items should be processed at once or individually.
435
+ If True (default), the batch resolver will process all items in the batch as a single event.
436
+ If False, the batch resolver will process each item in the batch individually.
388
437
389
438
Returns
390
439
-------
@@ -395,16 +444,19 @@ def batch_resolver(
395
444
field_name = field_name ,
396
445
type_name = type_name ,
397
446
raise_on_error = raise_on_error ,
447
+ aggregate = aggregate ,
398
448
)
399
449
400
450
def async_batch_resolver (
401
451
self ,
402
452
type_name : str = "*" ,
403
453
field_name : Optional [str ] = None ,
404
454
raise_on_error : bool = False ,
455
+ aggregate : bool = True ,
405
456
) -> Callable :
406
457
return self ._async_batch_resolver_registry .register (
407
458
field_name = field_name ,
408
459
type_name = type_name ,
409
460
raise_on_error = raise_on_error ,
461
+ aggregate = aggregate ,
410
462
)
0 commit comments