1
+ from abc import ABC , abstractmethod
1
2
import logging
2
3
from itertools import groupby
3
- from typing import Any , Callable , List , Optional , Type , Union
4
+ from typing import Any , Callable , Dict , List , Optional , Type , Union
4
5
5
6
from aws_lambda_powertools .utilities .data_classes import AppSyncResolverEvent
6
7
from aws_lambda_powertools .utilities .typing import LambdaContext
10
11
11
12
class RouterContext :
12
13
def __init__ (self ):
13
- super ().__init__ ()
14
- self .context = {}
14
+ self ._context = {}
15
15
16
- def append_context (self , ** additional_context ):
16
+ @property
17
+ def context (self ) -> Dict [str , Any ]:
18
+ return self ._context
19
+
20
+ @context .setter
21
+ def context (self , additional_context : Dict [str , Any ]) -> None :
17
22
"""Append key=value data as routing context"""
18
- self .context .update (** additional_context )
23
+ self ._context .update (** additional_context )
19
24
20
- def clear_context (self ):
25
+ @context .deleter
26
+ def context (self ):
21
27
"""Resets routing context"""
22
- self .context .clear ()
28
+ self ._context .clear ()
29
+
30
+
31
+ class IResolverRegistry (ABC ):
32
+ @abstractmethod
33
+ def resolver (self , type_name : str = "*" , field_name : Optional [str ] = None ) -> Callable :
34
+ ...
23
35
36
+ @abstractmethod
37
+ def find_resolver (self , type_name : str , field_name : str ) -> Callable :
38
+ ...
24
39
25
- class ResolverRegistry :
40
+
41
+ class ResolverRegistry (IResolverRegistry ):
26
42
def __init__ (self ):
27
- super ().__init__ ()
28
- self ._resolvers : dict = {}
29
- self ._batch_resolvers : dict = {}
43
+ self ._resolvers : Dict [str , Dict [str , Any ]] = {}
44
+
45
+ @property
46
+ def resolvers (self ) -> Dict [str , Dict [str , Any ]]:
47
+ return self ._resolvers
48
+
49
+ @resolvers .setter
50
+ def resolvers (self , resolvers : dict ) -> None :
51
+ self ._resolvers .update (resolvers )
30
52
31
53
def resolver (self , type_name : str = "*" , field_name : Optional [str ] = None ):
32
54
"""Registers the resolver for field_name
@@ -46,26 +68,15 @@ def register(func):
46
68
47
69
return register
48
70
49
- def batch_resolver (self , type_name : str = "*" , field_name : Optional [str ] = None ):
50
- """Registers the resolver for field_name
51
-
52
- Parameters
53
- ----------
54
- type_name : str
55
- Type name
56
- field_name : str
57
- Field name
58
- """
59
-
60
- def register (func ):
61
- logger .debug (f"Adding batch resolver `{ func .__name__ } ` for field `{ type_name } .{ field_name } `" )
62
- self ._batch_resolvers [f"{ type_name } .{ field_name } " ] = {"func" : func }
63
- return func
64
-
65
- return register
71
+ def find_resolver (self , type_name : str , field_name : str ) -> Callable :
72
+ full_name = f"{ type_name } .{ field_name } "
73
+ resolver = self ._resolvers .get (full_name , self ._resolvers .get (f"*.{ field_name } " ))
74
+ if not resolver :
75
+ raise ValueError (f"No resolver found for '{ full_name } '" )
76
+ return resolver ["func" ]
66
77
67
78
68
- class AppSyncResolver ( ResolverRegistry , RouterContext ) :
79
+ class AppSyncResolver :
69
80
"""
70
81
AppSync resolver decorator
71
82
@@ -97,17 +108,20 @@ def common_field() -> str:
97
108
"""
98
109
99
110
def __init__ (self ):
100
- super ().__init__ ()
111
+ self ._resolver_registry : IResolverRegistry = ResolverRegistry ()
112
+ self ._batch_resolver_registry : IResolverRegistry = ResolverRegistry ()
113
+ self ._router_context : RouterContext = RouterContext ()
101
114
self .current_batch_event : List [AppSyncResolverEvent ] = []
102
115
self .current_event : Optional [AppSyncResolverEvent ] = None
116
+ self .lambda_context : Optional [LambdaContext ] = None
103
117
104
118
def resolve (
105
119
self ,
106
- event : Union [dict , List [dict ]],
120
+ event : Union [Dict [ str , Any ], List [Dict [ str , Any ] ]],
107
121
context : LambdaContext ,
108
122
data_model : Type [AppSyncResolverEvent ] = AppSyncResolverEvent ,
109
123
) -> Any :
110
- """Resolve field_name
124
+ """Resolve field_name in single event or in a batch event
111
125
112
126
Parameters
113
127
----------
@@ -180,17 +194,17 @@ def lambda_handler(event, context):
180
194
self .lambda_context = context
181
195
182
196
response = (
183
- self ._call_batch_resolver (event , data_model )
197
+ self ._call_batch_resolver (event = event , data_model = data_model )
184
198
if isinstance (event , list )
185
- else self ._call_resolver (event , data_model )
199
+ else self ._call_single_resolver (event = event , data_model = data_model )
186
200
)
187
- self .clear_context ()
201
+ del self ._router_context . context
188
202
189
203
return response
190
204
191
- def _call_resolver (self , event : dict , data_model : Type [AppSyncResolverEvent ]) -> Any :
205
+ def _call_single_resolver (self , event : dict , data_model : Type [AppSyncResolverEvent ]) -> Any :
192
206
self .current_event = data_model (event )
193
- resolver = self ._get_resolver (self .current_event .type_name , self .current_event .field_name )
207
+ resolver = self ._resolver_registry . find_resolver (self .current_event .type_name , self .current_event .field_name )
194
208
return resolver (** self .current_event .arguments )
195
209
196
210
def _call_batch_resolver (self , event : List [dict ], data_model : Type [AppSyncResolverEvent ]) -> List [Any ]:
@@ -202,54 +216,12 @@ def _call_batch_resolver(self, event: List[dict], data_model: Type[AppSyncResolv
202
216
ValueError ("batch with different field names. It shouldn't happen!" )
203
217
204
218
self .current_batch_event = [data_model (event ) for event in event_groups [0 ]["events" ]]
205
- resolver = self ._get_batch_resolver (
219
+ resolver = self ._batch_resolver_registry . find_resolver (
206
220
self .current_batch_event [0 ].type_name , self .current_batch_event [0 ].field_name
207
221
)
208
222
209
223
return [resolver (event = appconfig_event ) for appconfig_event in self .current_batch_event ]
210
224
211
- def _get_resolver (self , type_name : str , field_name : str ) -> Callable :
212
- """Get resolver for field_name
213
-
214
- Parameters
215
- ----------
216
- type_name : str
217
- Type name
218
- field_name : str
219
- Field name
220
-
221
- Returns
222
- -------
223
- Callable
224
- callable function and configuration
225
- """
226
- full_name = f"{ type_name } .{ field_name } "
227
- resolver = self ._resolvers .get (full_name , self ._resolvers .get (f"*.{ field_name } " ))
228
- if not resolver :
229
- raise ValueError (f"No resolver found for '{ full_name } '" )
230
- return resolver ["func" ]
231
-
232
- def _get_batch_resolver (self , type_name : str , field_name : str ) -> Callable :
233
- """Get resolver for field_name
234
-
235
- Parameters
236
- ----------
237
- type_name : str
238
- Type name
239
- field_name : str
240
- Field name
241
-
242
- Returns
243
- -------
244
- Callable
245
- callable function and configuration
246
- """
247
- full_name = f"{ type_name } .{ field_name } "
248
- resolver = self ._batch_resolvers .get (full_name , self ._batch_resolvers .get (f"*.{ field_name } " ))
249
- if not resolver :
250
- raise ValueError (f"No batch resolver found for '{ full_name } '" )
251
- return resolver ["func" ]
252
-
253
225
def __call__ (
254
226
self ,
255
227
event : Union [dict , List [dict ]],
@@ -267,14 +239,38 @@ def include_router(self, router: "Router") -> None:
267
239
router : Router
268
240
A router containing a dict of field resolvers
269
241
"""
242
+
270
243
# Merge app and router context
271
- self .context . update ( ** router .context )
244
+ self ._router_context . context = router ._router_context . context
272
245
# use pointer to allow context clearance after event is processed e.g., resolve(evt, ctx)
273
- router .context = self .context
246
+ router ._router_context . _context = self . _router_context .context
274
247
275
- self ._resolvers .update (router ._resolvers )
248
+ self ._resolver_registry .resolvers = router ._resolver_registry .resolvers
249
+ self ._batch_resolver_registry .resolvers = router ._batch_resolver_registry .resolvers
276
250
251
+ # Interfaces
252
+ def resolver (self , type_name : str = "*" , field_name : Optional [str ] = None ):
253
+ return self ._resolver_registry .resolver (field_name = field_name , type_name = type_name )
277
254
278
- class Router (RouterContext , ResolverRegistry ):
255
+ def batch_resolver (self , type_name : str = "*" , field_name : Optional [str ] = None ):
256
+ return self ._batch_resolver_registry .resolver (field_name = field_name , type_name = type_name )
257
+
258
+ def append_context (self , ** additional_context ) -> None :
259
+ self ._router_context .context = additional_context
260
+
261
+
262
+ class Router :
279
263
def __init__ (self ):
280
- super ().__init__ ()
264
+ self ._resolver_registry = ResolverRegistry ()
265
+ self ._batch_resolver_registry = ResolverRegistry ()
266
+ self ._router_context = RouterContext ()
267
+
268
+ # Interfaces
269
+ def resolver (self , type_name : str = "*" , field_name : Optional [str ] = None ):
270
+ return self ._resolver_registry .resolver (field_name = field_name , type_name = type_name )
271
+
272
+ def batch_resolver (self , type_name : str = "*" , field_name : Optional [str ] = None ):
273
+ return self ._batch_resolver_registry .resolver (field_name = field_name , type_name = type_name )
274
+
275
+ def append_context (self , ** additional_context ) -> None :
276
+ self ._router_context .context = additional_context
0 commit comments