1
1
import base64
2
2
import json
3
3
import logging
4
+ import os
4
5
import re
6
+ import traceback
5
7
import zlib
6
8
from enum import Enum
7
9
from http import HTTPStatus
8
10
from typing import Any , Callable , Dict , List , Optional , Set , Union
9
11
10
12
from aws_lambda_powertools .event_handler import content_types
11
13
from aws_lambda_powertools .event_handler .exceptions import ServiceError
14
+ from aws_lambda_powertools .shared import constants
15
+ from aws_lambda_powertools .shared .functions import resolve_truthy_env_var_choice
12
16
from aws_lambda_powertools .shared .json_encoder import Encoder
13
17
from aws_lambda_powertools .utilities .data_classes import ALBEvent , APIGatewayProxyEvent , APIGatewayProxyEventV2
14
18
from aws_lambda_powertools .utilities .data_classes .common import BaseProxyEvent
@@ -28,43 +32,46 @@ class ProxyEventType(Enum):
28
32
class CORSConfig (object ):
29
33
"""CORS Config
30
34
31
-
32
35
Examples
33
36
--------
34
37
35
38
Simple cors example using the default permissive cors, not this should only be used during early prototyping
36
39
37
- from aws_lambda_powertools.event_handler.api_gateway import ApiGatewayResolver
40
+ ```python
41
+ from aws_lambda_powertools.event_handler.api_gateway import ApiGatewayResolver
38
42
39
- app = ApiGatewayResolver()
43
+ app = ApiGatewayResolver()
40
44
41
- @app.get("/my/path", cors=True)
42
- def with_cors():
43
- return {"message": "Foo"}
45
+ @app.get("/my/path", cors=True)
46
+ def with_cors():
47
+ return {"message": "Foo"}
48
+ ```
44
49
45
50
Using a custom CORSConfig where `with_cors` used the custom provided CORSConfig and `without_cors`
46
51
do not include any cors headers.
47
52
48
- from aws_lambda_powertools.event_handler.api_gateway import (
49
- ApiGatewayResolver, CORSConfig
50
- )
51
-
52
- cors_config = CORSConfig(
53
- allow_origin="https://wwww.example.com/",
54
- expose_headers=["x-exposed-response-header"],
55
- allow_headers=["x-custom-request-header"],
56
- max_age=100,
57
- allow_credentials=True,
58
- )
59
- app = ApiGatewayResolver(cors=cors_config)
60
-
61
- @app.get("/my/path")
62
- def with_cors():
63
- return {"message": "Foo"}
53
+ ```python
54
+ from aws_lambda_powertools.event_handler.api_gateway import (
55
+ ApiGatewayResolver, CORSConfig
56
+ )
57
+
58
+ cors_config = CORSConfig(
59
+ allow_origin="https://wwww.example.com/",
60
+ expose_headers=["x-exposed-response-header"],
61
+ allow_headers=["x-custom-request-header"],
62
+ max_age=100,
63
+ allow_credentials=True,
64
+ )
65
+ app = ApiGatewayResolver(cors=cors_config)
66
+
67
+ @app.get("/my/path")
68
+ def with_cors():
69
+ return {"message": "Foo"}
64
70
65
- @app.get("/another-one", cors=False)
66
- def without_cors():
67
- return {"message": "Foo"}
71
+ @app.get("/another-one", cors=False)
72
+ def without_cors():
73
+ return {"message": "Foo"}
74
+ ```
68
75
"""
69
76
70
77
_REQUIRED_HEADERS = ["Authorization" , "Content-Type" , "X-Amz-Date" , "X-Api-Key" , "X-Amz-Security-Token" ]
@@ -119,7 +126,11 @@ class Response:
119
126
"""Response data class that provides greater control over what is returned from the proxy event"""
120
127
121
128
def __init__ (
122
- self , status_code : int , content_type : Optional [str ], body : Union [str , bytes , None ], headers : Dict = None
129
+ self ,
130
+ status_code : int ,
131
+ content_type : Optional [str ],
132
+ body : Union [str , bytes , None ],
133
+ headers : Optional [Dict ] = None ,
123
134
):
124
135
"""
125
136
@@ -160,7 +171,7 @@ def __init__(
160
171
class ResponseBuilder :
161
172
"""Internally used Response builder"""
162
173
163
- def __init__ (self , response : Response , route : Route = None ):
174
+ def __init__ (self , response : Response , route : Optional [ Route ] = None ):
164
175
self .response = response
165
176
self .route = route
166
177
@@ -192,7 +203,7 @@ def _route(self, event: BaseProxyEvent, cors: Optional[CORSConfig]):
192
203
if self .route .compress and "gzip" in (event .get_header_value ("accept-encoding" , "" ) or "" ):
193
204
self ._compress ()
194
205
195
- def build (self , event : BaseProxyEvent , cors : CORSConfig = None ) -> Dict [str , Any ]:
206
+ def build (self , event : BaseProxyEvent , cors : Optional [ CORSConfig ] = None ) -> Dict [str , Any ]:
196
207
"""Build the full response dict to be returned by the lambda"""
197
208
self ._route (event , cors )
198
209
@@ -240,22 +251,33 @@ def lambda_handler(event, context):
240
251
current_event : BaseProxyEvent
241
252
lambda_context : LambdaContext
242
253
243
- def __init__ (self , proxy_type : Enum = ProxyEventType .APIGatewayProxyEvent , cors : CORSConfig = None ):
254
+ def __init__ (
255
+ self ,
256
+ proxy_type : Enum = ProxyEventType .APIGatewayProxyEvent ,
257
+ cors : Optional [CORSConfig ] = None ,
258
+ debug : Optional [bool ] = None ,
259
+ ):
244
260
"""
245
261
Parameters
246
262
----------
247
263
proxy_type: ProxyEventType
248
264
Proxy request type, defaults to API Gateway V1
249
265
cors: CORSConfig
250
266
Optionally configure and enabled CORS. Not each route will need to have to cors=True
267
+ debug: Optional[bool]
268
+ Enables debug mode, by default False. Can be also be enabled by "POWERTOOLS_EVENT_HANDLER_DEBUG"
269
+ environment variable
251
270
"""
252
271
self ._proxy_type = proxy_type
253
272
self ._routes : List [Route ] = []
254
273
self ._cors = cors
255
274
self ._cors_enabled : bool = cors is not None
256
275
self ._cors_methods : Set [str ] = {"OPTIONS" }
276
+ self ._debug = resolve_truthy_env_var_choice (
277
+ env = os .getenv (constants .EVENT_HANDLER_DEBUG_ENV , "false" ), choice = debug
278
+ )
257
279
258
- def get (self , rule : str , cors : bool = None , compress : bool = False , cache_control : str = None ):
280
+ def get (self , rule : str , cors : Optional [ bool ] = None , compress : bool = False , cache_control : Optional [ str ] = None ):
259
281
"""Get route decorator with GET `method`
260
282
261
283
Examples
@@ -280,7 +302,7 @@ def lambda_handler(event, context):
280
302
"""
281
303
return self .route (rule , "GET" , cors , compress , cache_control )
282
304
283
- def post (self , rule : str , cors : bool = None , compress : bool = False , cache_control : str = None ):
305
+ def post (self , rule : str , cors : Optional [ bool ] = None , compress : bool = False , cache_control : Optional [ str ] = None ):
284
306
"""Post route decorator with POST `method`
285
307
286
308
Examples
@@ -306,7 +328,7 @@ def lambda_handler(event, context):
306
328
"""
307
329
return self .route (rule , "POST" , cors , compress , cache_control )
308
330
309
- def put (self , rule : str , cors : bool = None , compress : bool = False , cache_control : str = None ):
331
+ def put (self , rule : str , cors : Optional [ bool ] = None , compress : bool = False , cache_control : Optional [ str ] = None ):
310
332
"""Put route decorator with PUT `method`
311
333
312
334
Examples
@@ -332,7 +354,9 @@ def lambda_handler(event, context):
332
354
"""
333
355
return self .route (rule , "PUT" , cors , compress , cache_control )
334
356
335
- def delete (self , rule : str , cors : bool = None , compress : bool = False , cache_control : str = None ):
357
+ def delete (
358
+ self , rule : str , cors : Optional [bool ] = None , compress : bool = False , cache_control : Optional [str ] = None
359
+ ):
336
360
"""Delete route decorator with DELETE `method`
337
361
338
362
Examples
@@ -357,7 +381,9 @@ def lambda_handler(event, context):
357
381
"""
358
382
return self .route (rule , "DELETE" , cors , compress , cache_control )
359
383
360
- def patch (self , rule : str , cors : bool = None , compress : bool = False , cache_control : str = None ):
384
+ def patch (
385
+ self , rule : str , cors : Optional [bool ] = None , compress : bool = False , cache_control : Optional [str ] = None
386
+ ):
361
387
"""Patch route decorator with PATCH `method`
362
388
363
389
Examples
@@ -385,7 +411,14 @@ def lambda_handler(event, context):
385
411
"""
386
412
return self .route (rule , "PATCH" , cors , compress , cache_control )
387
413
388
- def route (self , rule : str , method : str , cors : bool = None , compress : bool = False , cache_control : str = None ):
414
+ def route (
415
+ self ,
416
+ rule : str ,
417
+ method : str ,
418
+ cors : Optional [bool ] = None ,
419
+ compress : bool = False ,
420
+ cache_control : Optional [str ] = None ,
421
+ ):
389
422
"""Route decorator includes parameter `method`"""
390
423
391
424
def register_resolver (func : Callable ):
@@ -416,6 +449,8 @@ def resolve(self, event, context) -> Dict[str, Any]:
416
449
dict
417
450
Returns the dict response
418
451
"""
452
+ if self ._debug :
453
+ print (self ._json_dump (event ))
419
454
self .current_event = self ._to_proxy_event (event )
420
455
self .lambda_context = context
421
456
return self ._resolve ().build (self .current_event , self ._cors )
@@ -489,6 +524,19 @@ def _call_route(self, route: Route, args: Dict[str, str]) -> ResponseBuilder:
489
524
),
490
525
route ,
491
526
)
527
+ except Exception :
528
+ if self ._debug :
529
+ # If the user has turned on debug mode,
530
+ # we'll let the original exception propagate so
531
+ # they get more information about what went wrong.
532
+ return ResponseBuilder (
533
+ Response (
534
+ status_code = 500 ,
535
+ content_type = content_types .TEXT_PLAIN ,
536
+ body = "" .join (traceback .format_exc ()),
537
+ )
538
+ )
539
+ raise
492
540
493
541
def _to_response (self , result : Union [Dict , Response ]) -> Response :
494
542
"""Convert the route's result to a Response
@@ -509,7 +557,9 @@ def _to_response(self, result: Union[Dict, Response]) -> Response:
509
557
body = self ._json_dump (result ),
510
558
)
511
559
512
- @staticmethod
513
- def _json_dump (obj : Any ) -> str :
514
- """Does a concise json serialization"""
515
- return json .dumps (obj , separators = ("," , ":" ), cls = Encoder )
560
+ def _json_dump (self , obj : Any ) -> str :
561
+ """Does a concise json serialization or pretty print when in debug mode"""
562
+ if self ._debug :
563
+ return json .dumps (obj , indent = 4 , cls = Encoder )
564
+ else :
565
+ return json .dumps (obj , separators = ("," , ":" ), cls = Encoder )
0 commit comments