11import  base64 
22import  json 
3+ import  logging 
34import  re 
45import  zlib 
56from  enum  import  Enum 
1011from  aws_lambda_powertools .utilities .data_classes .common  import  BaseProxyEvent 
1112from  aws_lambda_powertools .utilities .typing  import  LambdaContext 
1213
14+ logger  =  logging .getLogger (__name__ )
15+ 
1316
1417class  ProxyEventType (Enum ):
1518    """An enumerations of the supported proxy event types.""" 
@@ -28,47 +31,47 @@ class CORSConfig(object):
2831
2932    Simple cors example using the default permissive cors, not this should only be used during early prototyping 
3033
31-         >>>  from aws_lambda_powertools.event_handler.api_gateway import ApiGatewayResolver 
32-         >>> 
33-         >>>  app = ApiGatewayResolver() 
34-         >>> 
35-         >>>  @app.get("/my/path", cors=True) 
36-         >>>  def with_cors(): 
37-         >>>       return {"message": "Foo"} 
34+         from aws_lambda_powertools.event_handler.api_gateway import ApiGatewayResolver 
35+ 
36+         app = ApiGatewayResolver() 
37+ 
38+         @app.get("/my/path", cors=True) 
39+         def with_cors(): 
40+             return {"message": "Foo"} 
3841
3942    Using a custom CORSConfig where `with_cors` used the custom provided CORSConfig and `without_cors` 
4043    do not include any cors headers. 
4144
42-         >>>  from aws_lambda_powertools.event_handler.api_gateway import ( 
43-         >>>     ApiGatewayResolver, CORSConfig 
44-         >>>  ) 
45-         >>> 
46-         >>>  cors_config = CORSConfig( 
47-         >>>     allow_origin="https://wwww.example.com/", 
48-         >>>     expose_headers=["x-exposed-response-header"], 
49-         >>>     allow_headers=["x-custom-request-header"], 
50-         >>>     max_age=100, 
51-         >>>     allow_credentials=True, 
52-         >>>  ) 
53-         >>>  app = ApiGatewayResolver(cors=cors_config) 
54-         >>> 
55-         >>>  @app.get("/my/path", cors=True) 
56-         >>>  def with_cors(): 
57-         >>>       return {"message": "Foo"} 
58-         >>> 
59-         >>>  @app.get("/another-one") 
60-         >>>  def without_cors(): 
61-         >>>      return {"message": "Foo"} 
45+         from aws_lambda_powertools.event_handler.api_gateway import ( 
46+             ApiGatewayResolver, CORSConfig 
47+         ) 
48+ 
49+         cors_config = CORSConfig( 
50+             allow_origin="https://wwww.example.com/", 
51+             expose_headers=["x-exposed-response-header"], 
52+             allow_headers=["x-custom-request-header"], 
53+             max_age=100, 
54+             allow_credentials=True, 
55+         ) 
56+         app = ApiGatewayResolver(cors=cors_config) 
57+ 
58+         @app.get("/my/path", cors=True) 
59+         def with_cors(): 
60+             return {"message": "Foo"} 
61+ 
62+         @app.get("/another-one") 
63+         def without_cors(): 
64+             return {"message": "Foo"} 
6265    """ 
6366
6467    _REQUIRED_HEADERS  =  ["Authorization" , "Content-Type" , "X-Amz-Date" , "X-Api-Key" , "X-Amz-Security-Token" ]
6568
6669    def  __init__ (
6770        self ,
6871        allow_origin : str  =  "*" ,
69-         allow_headers : List [str ] =  None ,
70-         expose_headers : List [str ] =  None ,
71-         max_age : int  =  None ,
72+         allow_headers : Optional [ List [str ] ] =  None ,
73+         expose_headers : Optional [ List [str ] ] =  None ,
74+         max_age : Optional [ int ]  =  None ,
7275        allow_credentials : bool  =  False ,
7376    ):
7477        """ 
@@ -77,13 +80,13 @@ def __init__(
7780        allow_origin: str 
7881            The value of the `Access-Control-Allow-Origin` to send in the response. Defaults to "*", but should 
7982            only be used during development. 
80-         allow_headers: str 
83+         allow_headers: Optional[List[ str]]  
8184            The list of additional allowed headers. This list is added to list of 
8285            built in allowed headers: `Authorization`, `Content-Type`, `X-Amz-Date`, 
8386            `X-Api-Key`, `X-Amz-Security-Token`. 
84-         expose_headers: str 
87+         expose_headers: Optional[List[ str]]  
8588            A list of values to return for the Access-Control-Expose-Headers 
86-         max_age: int 
89+         max_age: Optional[ int]  
8790            The value for the `Access-Control-Max-Age` 
8891        allow_credentials: bool 
8992            A boolean value that sets the value of `Access-Control-Allow-Credentials` 
@@ -170,6 +173,7 @@ def _compress(self):
170173        """Compress the response body, but only if `Accept-Encoding` headers includes gzip.""" 
171174        self .response .headers ["Content-Encoding" ] =  "gzip" 
172175        if  isinstance (self .response .body , str ):
176+             logger .debug ("Converting string response to bytes before compressing it" )
173177            self .response .body  =  bytes (self .response .body , "utf-8" )
174178        gzip  =  zlib .compressobj (9 , zlib .DEFLATED , zlib .MAX_WBITS  |  16 )
175179        self .response .body  =  gzip .compress (self .response .body ) +  gzip .flush ()
@@ -190,6 +194,7 @@ def build(self, event: BaseProxyEvent, cors: CORSConfig = None) -> Dict[str, Any
190194        self ._route (event , cors )
191195
192196        if  isinstance (self .response .body , bytes ):
197+             logger .debug ("Encoding bytes response with base64" )
193198            self .response .base64_encoded  =  True 
194199            self .response .body  =  base64 .b64encode (self .response .body ).decode ()
195200        return  {
@@ -207,27 +212,26 @@ class ApiGatewayResolver:
207212    -------- 
208213    Simple example with a custom lambda handler using the Tracer capture_lambda_handler decorator 
209214
210-         >>> from aws_lambda_powertools import Tracer 
211-         >>> from aws_lambda_powertools.event_handler.api_gateway import ( 
212-         >>>    ApiGatewayResolver 
213-         >>> ) 
214-         >>> 
215-         >>> tracer = Tracer() 
216-         >>> app = ApiGatewayResolver() 
217-         >>> 
218-         >>> @app.get("/get-call") 
219-         >>> def simple_get(): 
220-         >>>      return {"message": "Foo"} 
221-         >>> 
222-         >>> @app.post("/post-call") 
223-         >>> def simple_post(): 
224-         >>>     post_data: dict = app.current_event.json_body 
225-         >>>     return {"message": post_data["value"]} 
226-         >>> 
227-         >>> @tracer.capture_lambda_handler 
228-         >>> def lambda_handler(event, context): 
229-         >>>    return app.resolve(event, context) 
215+     ```python 
216+     from aws_lambda_powertools import Tracer 
217+     from aws_lambda_powertools.event_handler.api_gateway import ApiGatewayResolver 
218+ 
219+     tracer = Tracer() 
220+     app = ApiGatewayResolver() 
230221
222+     @app.get("/get-call") 
223+     def simple_get(): 
224+         return {"message": "Foo"} 
225+ 
226+     @app.post("/post-call") 
227+     def simple_post(): 
228+         post_data: dict = app.current_event.json_body 
229+         return {"message": post_data["value"]} 
230+ 
231+     @tracer.capture_lambda_handler 
232+     def lambda_handler(event, context): 
233+         return app.resolve(event, context) 
234+     ``` 
231235    """ 
232236
233237    current_event : BaseProxyEvent 
@@ -247,32 +251,144 @@ def __init__(self, proxy_type: Enum = ProxyEventType.APIGatewayProxyEvent, cors:
247251        self ._cors  =  cors 
248252        self ._cors_methods : Set [str ] =  {"OPTIONS" }
249253
250-     def  get (self , rule : str , cors : bool  =  False , compress : bool  =  False , cache_control : str  =  None ):
251-         """Get route decorator with GET `method`""" 
254+     def  get (self , rule : str , cors : bool  =  True , compress : bool  =  False , cache_control : str  =  None ):
255+         """Get route decorator with GET `method` 
256+ 
257+         Examples 
258+         -------- 
259+         Simple example with a custom lambda handler using the Tracer capture_lambda_handler decorator 
260+ 
261+         ```python 
262+         from aws_lambda_powertools import Tracer 
263+         from aws_lambda_powertools.event_handler.api_gateway import ApiGatewayResolver 
264+ 
265+         tracer = Tracer() 
266+         app = ApiGatewayResolver() 
267+ 
268+         @app.get("/get-call") 
269+         def simple_get(): 
270+             return {"message": "Foo"} 
271+ 
272+         @tracer.capture_lambda_handler 
273+         def lambda_handler(event, context): 
274+             return app.resolve(event, context) 
275+         ``` 
276+         """ 
252277        return  self .route (rule , "GET" , cors , compress , cache_control )
253278
254-     def  post (self , rule : str , cors : bool  =  False , compress : bool  =  False , cache_control : str  =  None ):
255-         """Post route decorator with POST `method`""" 
279+     def  post (self , rule : str , cors : bool  =  True , compress : bool  =  False , cache_control : str  =  None ):
280+         """Post route decorator with POST `method` 
281+ 
282+         Examples 
283+         -------- 
284+         Simple example with a custom lambda handler using the Tracer capture_lambda_handler decorator 
285+ 
286+         ```python 
287+         from aws_lambda_powertools import Tracer 
288+         from aws_lambda_powertools.event_handler.api_gateway import ApiGatewayResolver 
289+ 
290+         tracer = Tracer() 
291+         app = ApiGatewayResolver() 
292+ 
293+         @app.post("/post-call") 
294+         def simple_post(): 
295+             post_data: dict = app.current_event.json_body 
296+             return {"message": post_data["value"]} 
297+ 
298+         @tracer.capture_lambda_handler 
299+         def lambda_handler(event, context): 
300+             return app.resolve(event, context) 
301+         ``` 
302+         """ 
256303        return  self .route (rule , "POST" , cors , compress , cache_control )
257304
258-     def  put (self , rule : str , cors : bool  =  False , compress : bool  =  False , cache_control : str  =  None ):
259-         """Put route decorator with PUT `method`""" 
305+     def  put (self , rule : str , cors : bool  =  True , compress : bool  =  False , cache_control : str  =  None ):
306+         """Put route decorator with PUT `method` 
307+ 
308+         Examples 
309+         -------- 
310+         Simple example with a custom lambda handler using the Tracer capture_lambda_handler decorator 
311+ 
312+         ```python 
313+         from aws_lambda_powertools import Tracer 
314+         from aws_lambda_powertools.event_handler.api_gateway import ApiGatewayResolver 
315+ 
316+         tracer = Tracer() 
317+         app = ApiGatewayResolver() 
318+ 
319+         @app.put("/put-call") 
320+         def simple_post(): 
321+             put_data: dict = app.current_event.json_body 
322+             return {"message": put_data["value"]} 
323+ 
324+         @tracer.capture_lambda_handler 
325+         def lambda_handler(event, context): 
326+             return app.resolve(event, context) 
327+         ``` 
328+         """ 
260329        return  self .route (rule , "PUT" , cors , compress , cache_control )
261330
262-     def  delete (self , rule : str , cors : bool  =  False , compress : bool  =  False , cache_control : str  =  None ):
263-         """Delete route decorator with DELETE `method`""" 
331+     def  delete (self , rule : str , cors : bool  =  True , compress : bool  =  False , cache_control : str  =  None ):
332+         """Delete route decorator with DELETE `method` 
333+ 
334+         Examples 
335+         -------- 
336+         Simple example with a custom lambda handler using the Tracer capture_lambda_handler decorator 
337+ 
338+         ```python 
339+         from aws_lambda_powertools import Tracer 
340+         from aws_lambda_powertools.event_handler.api_gateway import ApiGatewayResolver 
341+ 
342+         tracer = Tracer() 
343+         app = ApiGatewayResolver() 
344+ 
345+         @app.delete("/delete-call") 
346+         def simple_delete(): 
347+             return {"message": "deleted"} 
348+ 
349+         @tracer.capture_lambda_handler 
350+         def lambda_handler(event, context): 
351+             return app.resolve(event, context) 
352+         ``` 
353+         """ 
264354        return  self .route (rule , "DELETE" , cors , compress , cache_control )
265355
266-     def  patch (self , rule : str , cors : bool  =  False , compress : bool  =  False , cache_control : str  =  None ):
267-         """Patch route decorator with PATCH `method`""" 
356+     def  patch (self , rule : str , cors : bool  =  True , compress : bool  =  False , cache_control : str  =  None ):
357+         """Patch route decorator with PATCH `method` 
358+ 
359+         Examples 
360+         -------- 
361+         Simple example with a custom lambda handler using the Tracer capture_lambda_handler decorator 
362+ 
363+         ```python 
364+         from aws_lambda_powertools import Tracer 
365+         from aws_lambda_powertools.event_handler.api_gateway import ApiGatewayResolver 
366+ 
367+         tracer = Tracer() 
368+         app = ApiGatewayResolver() 
369+ 
370+         @app.patch("/patch-call") 
371+         def simple_patch(): 
372+             patch_data: dict = app.current_event.json_body 
373+             patch_data["value"] = patched 
374+ 
375+             return {"message": patch_data} 
376+ 
377+         @tracer.capture_lambda_handler 
378+         def lambda_handler(event, context): 
379+             return app.resolve(event, context) 
380+         ``` 
381+         """ 
268382        return  self .route (rule , "PATCH" , cors , compress , cache_control )
269383
270-     def  route (self , rule : str , method : str , cors : bool  =  False , compress : bool  =  False , cache_control : str  =  None ):
384+     def  route (self , rule : str , method : str , cors : bool  =  True , compress : bool  =  False , cache_control : str  =  None ):
271385        """Route decorator includes parameter `method`""" 
272386
273387        def  register_resolver (func : Callable ):
388+             logger .debug (f"Adding route using rule { rule } { method .upper ()}  )
274389            self ._routes .append (Route (method , self ._compile_regex (rule ), func , cors , compress , cache_control ))
275390            if  cors :
391+                 logger .debug (f"Registering method { method .upper ()}  )
276392                self ._cors_methods .add (method .upper ())
277393            return  func 
278394
@@ -308,9 +424,12 @@ def _compile_regex(rule: str):
308424    def  _to_proxy_event (self , event : Dict ) ->  BaseProxyEvent :
309425        """Convert the event dict to the corresponding data class""" 
310426        if  self ._proxy_type  ==  ProxyEventType .APIGatewayProxyEvent :
427+             logger .debug ("Converting event to API Gateway REST API contract" )
311428            return  APIGatewayProxyEvent (event )
312429        if  self ._proxy_type  ==  ProxyEventType .APIGatewayProxyEventV2 :
430+             logger .debug ("Converting event to API Gateway HTTP API contract" )
313431            return  APIGatewayProxyEventV2 (event )
432+         logger .debug ("Converting event to ALB contract" )
314433        return  ALBEvent (event )
315434
316435    def  _resolve (self ) ->  ResponseBuilder :
@@ -322,17 +441,21 @@ def _resolve(self) -> ResponseBuilder:
322441                continue 
323442            match : Optional [re .Match ] =  route .rule .match (path )
324443            if  match :
444+                 logger .debug ("Found a registered route. Calling function" )
325445                return  self ._call_route (route , match .groupdict ())
326446
447+         logger .debug (f"No match found for path { path } { method }  )
327448        return  self ._not_found (method )
328449
329450    def  _not_found (self , method : str ) ->  ResponseBuilder :
330451        """Called when no matching route was found and includes support for the cors preflight response""" 
331452        headers  =  {}
332453        if  self ._cors :
454+             logger .debug ("CORS is enabled, updating headers." )
333455            headers .update (self ._cors .to_dict ())
334456
335-             if  method  ==  "OPTIONS" :  # Preflight 
457+             if  method  ==  "OPTIONS" :  # Pre-flight 
458+                 logger .debug ("Pre-flight request detected. Returning CORS with null response" )
336459                headers ["Access-Control-Allow-Methods" ] =  "," .join (sorted (self ._cors_methods ))
337460                return  ResponseBuilder (Response (status_code = 204 , content_type = None , headers = headers , body = None ))
338461
@@ -361,11 +484,10 @@ def _to_response(result: Union[Dict, Response]) -> Response:
361484        """ 
362485        if  isinstance (result , Response ):
363486            return  result 
364-         elif  isinstance (result , dict ):
365-             return  Response (
366-                 status_code = 200 ,
367-                 content_type = "application/json" ,
368-                 body = json .dumps (result , separators = ("," , ":" ), cls = Encoder ),
369-             )
370-         else :  # Tuple[int, str, Union[bytes, str]] 
371-             return  Response (* result )
487+ 
488+         logger .debug ("Simple response detected, serializing return before constructing final response" )
489+         return  Response (
490+             status_code = 200 ,
491+             content_type = "application/json" ,
492+             body = json .dumps (result , separators = ("," , ":" ), cls = Encoder ),
493+         )
0 commit comments