1
1
import base64
2
2
import json
3
+ import logging
3
4
import re
4
5
import zlib
5
6
from enum import Enum
10
11
from aws_lambda_powertools .utilities .data_classes .common import BaseProxyEvent
11
12
from aws_lambda_powertools .utilities .typing import LambdaContext
12
13
14
+ logger = logging .getLogger (__name__ )
15
+
13
16
14
17
class ProxyEventType (Enum ):
15
18
"""An enumerations of the supported proxy event types."""
@@ -28,47 +31,47 @@ class CORSConfig(object):
28
31
29
32
Simple cors example using the default permissive cors, not this should only be used during early prototyping
30
33
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"}
38
41
39
42
Using a custom CORSConfig where `with_cors` used the custom provided CORSConfig and `without_cors`
40
43
do not include any cors headers.
41
44
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"}
62
65
"""
63
66
64
67
_REQUIRED_HEADERS = ["Authorization" , "Content-Type" , "X-Amz-Date" , "X-Api-Key" , "X-Amz-Security-Token" ]
65
68
66
69
def __init__ (
67
70
self ,
68
71
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 ,
72
75
allow_credentials : bool = False ,
73
76
):
74
77
"""
@@ -77,13 +80,13 @@ def __init__(
77
80
allow_origin: str
78
81
The value of the `Access-Control-Allow-Origin` to send in the response. Defaults to "*", but should
79
82
only be used during development.
80
- allow_headers: str
83
+ allow_headers: Optional[List[ str]]
81
84
The list of additional allowed headers. This list is added to list of
82
85
built in allowed headers: `Authorization`, `Content-Type`, `X-Amz-Date`,
83
86
`X-Api-Key`, `X-Amz-Security-Token`.
84
- expose_headers: str
87
+ expose_headers: Optional[List[ str]]
85
88
A list of values to return for the Access-Control-Expose-Headers
86
- max_age: int
89
+ max_age: Optional[ int]
87
90
The value for the `Access-Control-Max-Age`
88
91
allow_credentials: bool
89
92
A boolean value that sets the value of `Access-Control-Allow-Credentials`
@@ -170,6 +173,7 @@ def _compress(self):
170
173
"""Compress the response body, but only if `Accept-Encoding` headers includes gzip."""
171
174
self .response .headers ["Content-Encoding" ] = "gzip"
172
175
if isinstance (self .response .body , str ):
176
+ logger .debug ("Converting string response to bytes before compressing it" )
173
177
self .response .body = bytes (self .response .body , "utf-8" )
174
178
gzip = zlib .compressobj (9 , zlib .DEFLATED , zlib .MAX_WBITS | 16 )
175
179
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
190
194
self ._route (event , cors )
191
195
192
196
if isinstance (self .response .body , bytes ):
197
+ logger .debug ("Encoding bytes response with base64" )
193
198
self .response .base64_encoded = True
194
199
self .response .body = base64 .b64encode (self .response .body ).decode ()
195
200
return {
@@ -207,27 +212,26 @@ class ApiGatewayResolver:
207
212
--------
208
213
Simple example with a custom lambda handler using the Tracer capture_lambda_handler decorator
209
214
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()
230
221
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
+ ```
231
235
"""
232
236
233
237
current_event : BaseProxyEvent
@@ -247,32 +251,144 @@ def __init__(self, proxy_type: Enum = ProxyEventType.APIGatewayProxyEvent, cors:
247
251
self ._cors = cors
248
252
self ._cors_methods : Set [str ] = {"OPTIONS" }
249
253
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
+ """
252
277
return self .route (rule , "GET" , cors , compress , cache_control )
253
278
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
+ """
256
303
return self .route (rule , "POST" , cors , compress , cache_control )
257
304
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
+ """
260
329
return self .route (rule , "PUT" , cors , compress , cache_control )
261
330
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
+ """
264
354
return self .route (rule , "DELETE" , cors , compress , cache_control )
265
355
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
+ """
268
382
return self .route (rule , "PATCH" , cors , compress , cache_control )
269
383
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 ):
271
385
"""Route decorator includes parameter `method`"""
272
386
273
387
def register_resolver (func : Callable ):
388
+ logger .debug (f"Adding route using rule { rule } and method { method .upper ()} " )
274
389
self ._routes .append (Route (method , self ._compile_regex (rule ), func , cors , compress , cache_control ))
275
390
if cors :
391
+ logger .debug (f"Registering method { method .upper ()} to Allow Methods in CORS" )
276
392
self ._cors_methods .add (method .upper ())
277
393
return func
278
394
@@ -308,9 +424,12 @@ def _compile_regex(rule: str):
308
424
def _to_proxy_event (self , event : Dict ) -> BaseProxyEvent :
309
425
"""Convert the event dict to the corresponding data class"""
310
426
if self ._proxy_type == ProxyEventType .APIGatewayProxyEvent :
427
+ logger .debug ("Converting event to API Gateway REST API contract" )
311
428
return APIGatewayProxyEvent (event )
312
429
if self ._proxy_type == ProxyEventType .APIGatewayProxyEventV2 :
430
+ logger .debug ("Converting event to API Gateway HTTP API contract" )
313
431
return APIGatewayProxyEventV2 (event )
432
+ logger .debug ("Converting event to ALB contract" )
314
433
return ALBEvent (event )
315
434
316
435
def _resolve (self ) -> ResponseBuilder :
@@ -322,17 +441,21 @@ def _resolve(self) -> ResponseBuilder:
322
441
continue
323
442
match : Optional [re .Match ] = route .rule .match (path )
324
443
if match :
444
+ logger .debug ("Found a registered route. Calling function" )
325
445
return self ._call_route (route , match .groupdict ())
326
446
447
+ logger .debug (f"No match found for path { path } and method { method } " )
327
448
return self ._not_found (method )
328
449
329
450
def _not_found (self , method : str ) -> ResponseBuilder :
330
451
"""Called when no matching route was found and includes support for the cors preflight response"""
331
452
headers = {}
332
453
if self ._cors :
454
+ logger .debug ("CORS is enabled, updating headers." )
333
455
headers .update (self ._cors .to_dict ())
334
456
335
- if method == "OPTIONS" : # Preflight
457
+ if method == "OPTIONS" : # Pre-flight
458
+ logger .debug ("Pre-flight request detected. Returning CORS with null response" )
336
459
headers ["Access-Control-Allow-Methods" ] = "," .join (sorted (self ._cors_methods ))
337
460
return ResponseBuilder (Response (status_code = 204 , content_type = None , headers = headers , body = None ))
338
461
@@ -361,11 +484,10 @@ def _to_response(result: Union[Dict, Response]) -> Response:
361
484
"""
362
485
if isinstance (result , Response ):
363
486
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