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 } and method { 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 ()} to Allow Methods in CORS" )
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 } and method { 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