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
9
+ from http import HTTPStatus
7
10
from typing import Any , Callable , Dict , List , Optional , Set , Union
8
11
12
+ from aws_lambda_powertools .event_handler import content_types
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
9
16
from aws_lambda_powertools .shared .json_encoder import Encoder
10
17
from aws_lambda_powertools .utilities .data_classes import ALBEvent , APIGatewayProxyEvent , APIGatewayProxyEventV2
11
18
from aws_lambda_powertools .utilities .data_classes .common import BaseProxyEvent
@@ -25,43 +32,46 @@ class ProxyEventType(Enum):
25
32
class CORSConfig (object ):
26
33
"""CORS Config
27
34
28
-
29
35
Examples
30
36
--------
31
37
32
38
Simple cors example using the default permissive cors, not this should only be used during early prototyping
33
39
34
- from aws_lambda_powertools.event_handler.api_gateway import ApiGatewayResolver
40
+ ```python
41
+ from aws_lambda_powertools.event_handler.api_gateway import ApiGatewayResolver
35
42
36
- app = ApiGatewayResolver()
43
+ app = ApiGatewayResolver()
37
44
38
- @app.get("/my/path", cors=True)
39
- def with_cors():
40
- return {"message": "Foo"}
45
+ @app.get("/my/path", cors=True)
46
+ def with_cors():
47
+ return {"message": "Foo"}
48
+ ```
41
49
42
50
Using a custom CORSConfig where `with_cors` used the custom provided CORSConfig and `without_cors`
43
51
do not include any cors headers.
44
52
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")
59
- def with_cors():
60
- 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"}
61
70
62
- @app.get("/another-one", cors=False)
63
- def without_cors():
64
- return {"message": "Foo"}
71
+ @app.get("/another-one", cors=False)
72
+ def without_cors():
73
+ return {"message": "Foo"}
74
+ ```
65
75
"""
66
76
67
77
_REQUIRED_HEADERS = ["Authorization" , "Content-Type" , "X-Amz-Date" , "X-Api-Key" , "X-Amz-Security-Token" ]
@@ -237,20 +247,31 @@ def lambda_handler(event, context):
237
247
current_event : BaseProxyEvent
238
248
lambda_context : LambdaContext
239
249
240
- def __init__ (self , proxy_type : Enum = ProxyEventType .APIGatewayProxyEvent , cors : CORSConfig = None ):
250
+ def __init__ (
251
+ self ,
252
+ proxy_type : Enum = ProxyEventType .APIGatewayProxyEvent ,
253
+ cors : CORSConfig = None ,
254
+ debug : Optional [bool ] = None ,
255
+ ):
241
256
"""
242
257
Parameters
243
258
----------
244
259
proxy_type: ProxyEventType
245
260
Proxy request type, defaults to API Gateway V1
246
261
cors: CORSConfig
247
262
Optionally configure and enabled CORS. Not each route will need to have to cors=True
263
+ debug: Optional[bool]
264
+ Enables debug mode, by default False. Can be also be enabled by "POWERTOOLS_EVENT_HANDLER_DEBUG"
265
+ environment variable
248
266
"""
249
267
self ._proxy_type = proxy_type
250
268
self ._routes : List [Route ] = []
251
269
self ._cors = cors
252
270
self ._cors_enabled : bool = cors is not None
253
271
self ._cors_methods : Set [str ] = {"OPTIONS" }
272
+ self ._debug = resolve_truthy_env_var_choice (
273
+ choice = debug , env = os .getenv (constants .EVENT_HANDLER_DEBUG_ENV , "false" )
274
+ )
254
275
255
276
def get (self , rule : str , cors : bool = None , compress : bool = False , cache_control : str = None ):
256
277
"""Get route decorator with GET `method`
@@ -413,6 +434,8 @@ def resolve(self, event, context) -> Dict[str, Any]:
413
434
dict
414
435
Returns the dict response
415
436
"""
437
+ if self ._debug :
438
+ print (self ._json_dump (event ))
416
439
self .current_event = self ._to_proxy_event (event )
417
440
self .lambda_context = context
418
441
return self ._resolve ().build (self .current_event , self ._cors )
@@ -466,19 +489,41 @@ def _not_found(self, method: str) -> ResponseBuilder:
466
489
467
490
return ResponseBuilder (
468
491
Response (
469
- status_code = 404 ,
470
- content_type = "application/json" ,
492
+ status_code = HTTPStatus . NOT_FOUND . value ,
493
+ content_type = content_types . APPLICATION_JSON ,
471
494
headers = headers ,
472
- body = json . dumps ({ "message" : "Not found" }),
495
+ body = self . _json_dump ({ "statusCode" : HTTPStatus . NOT_FOUND . value , "message" : "Not found" }),
473
496
)
474
497
)
475
498
476
499
def _call_route (self , route : Route , args : Dict [str , str ]) -> ResponseBuilder :
477
500
"""Actually call the matching route with any provided keyword arguments."""
478
- return ResponseBuilder (self ._to_response (route .func (** args )), route )
479
-
480
- @staticmethod
481
- def _to_response (result : Union [Dict , Response ]) -> Response :
501
+ try :
502
+ return ResponseBuilder (self ._to_response (route .func (** args )), route )
503
+ except ServiceError as e :
504
+ return ResponseBuilder (
505
+ Response (
506
+ status_code = e .status_code ,
507
+ content_type = content_types .APPLICATION_JSON ,
508
+ body = self ._json_dump ({"statusCode" : e .status_code , "message" : e .msg }),
509
+ ),
510
+ route ,
511
+ )
512
+ except Exception :
513
+ if self ._debug :
514
+ # If the user has turned on debug mode,
515
+ # we'll let the original exception propagate so
516
+ # they get more information about what went wrong.
517
+ return ResponseBuilder (
518
+ Response (
519
+ status_code = 500 ,
520
+ content_type = content_types .TEXT_PLAIN ,
521
+ body = "" .join (traceback .format_exc ()),
522
+ )
523
+ )
524
+ raise
525
+
526
+ def _to_response (self , result : Union [Dict , Response ]) -> Response :
482
527
"""Convert the route's result to a Response
483
528
484
529
2 main result types are supported:
@@ -493,6 +538,13 @@ def _to_response(result: Union[Dict, Response]) -> Response:
493
538
logger .debug ("Simple response detected, serializing return before constructing final response" )
494
539
return Response (
495
540
status_code = 200 ,
496
- content_type = "application/json" ,
497
- body = json . dumps (result , separators = ( "," , ":" ), cls = Encoder ),
541
+ content_type = content_types . APPLICATION_JSON ,
542
+ body = self . _json_dump (result ),
498
543
)
544
+
545
+ def _json_dump (self , obj : Any ) -> str :
546
+ """Does a concise json serialization or pretty print when in debug mode"""
547
+ if self ._debug :
548
+ return json .dumps (obj , indent = 4 , cls = Encoder )
549
+ else :
550
+ return json .dumps (obj , separators = ("," , ":" ), cls = Encoder )
0 commit comments