Skip to content

Commit 43b828e

Browse files
Michael Brewerheitorlessa
Michael Brewer
andauthored
feat(api-gateway): add debug mode (#507)
Co-authored-by: Heitor Lessa <[email protected]>
1 parent f61b02f commit 43b828e

File tree

5 files changed

+172
-53
lines changed

5 files changed

+172
-53
lines changed

aws_lambda_powertools/event_handler/api_gateway.py

+65-30
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
import base64
22
import json
33
import logging
4+
import os
45
import re
6+
import traceback
57
import zlib
68
from enum import Enum
79
from http import HTTPStatus
810
from typing import Any, Callable, Dict, List, Optional, Set, Union
911

1012
from aws_lambda_powertools.event_handler import content_types
1113
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
1216
from aws_lambda_powertools.shared.json_encoder import Encoder
1317
from aws_lambda_powertools.utilities.data_classes import ALBEvent, APIGatewayProxyEvent, APIGatewayProxyEventV2
1418
from aws_lambda_powertools.utilities.data_classes.common import BaseProxyEvent
@@ -28,43 +32,46 @@ class ProxyEventType(Enum):
2832
class CORSConfig(object):
2933
"""CORS Config
3034
31-
3235
Examples
3336
--------
3437
3538
Simple cors example using the default permissive cors, not this should only be used during early prototyping
3639
37-
from aws_lambda_powertools.event_handler.api_gateway import ApiGatewayResolver
40+
```python
41+
from aws_lambda_powertools.event_handler.api_gateway import ApiGatewayResolver
3842
39-
app = ApiGatewayResolver()
43+
app = ApiGatewayResolver()
4044
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+
```
4449
4550
Using a custom CORSConfig where `with_cors` used the custom provided CORSConfig and `without_cors`
4651
do not include any cors headers.
4752
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"}
6470
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+
```
6875
"""
6976

7077
_REQUIRED_HEADERS = ["Authorization", "Content-Type", "X-Amz-Date", "X-Api-Key", "X-Amz-Security-Token"]
@@ -240,20 +247,31 @@ def lambda_handler(event, context):
240247
current_event: BaseProxyEvent
241248
lambda_context: LambdaContext
242249

243-
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+
):
244256
"""
245257
Parameters
246258
----------
247259
proxy_type: ProxyEventType
248260
Proxy request type, defaults to API Gateway V1
249261
cors: CORSConfig
250262
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
251266
"""
252267
self._proxy_type = proxy_type
253268
self._routes: List[Route] = []
254269
self._cors = cors
255270
self._cors_enabled: bool = cors is not None
256271
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+
)
257275

258276
def get(self, rule: str, cors: bool = None, compress: bool = False, cache_control: str = None):
259277
"""Get route decorator with GET `method`
@@ -416,6 +434,8 @@ def resolve(self, event, context) -> Dict[str, Any]:
416434
dict
417435
Returns the dict response
418436
"""
437+
if self._debug:
438+
print(self._json_dump(event))
419439
self.current_event = self._to_proxy_event(event)
420440
self.lambda_context = context
421441
return self._resolve().build(self.current_event, self._cors)
@@ -489,6 +509,19 @@ def _call_route(self, route: Route, args: Dict[str, str]) -> ResponseBuilder:
489509
),
490510
route,
491511
)
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
492525

493526
def _to_response(self, result: Union[Dict, Response]) -> Response:
494527
"""Convert the route's result to a Response
@@ -509,7 +542,9 @@ def _to_response(self, result: Union[Dict, Response]) -> Response:
509542
body=self._json_dump(result),
510543
)
511544

512-
@staticmethod
513-
def _json_dump(obj: Any) -> str:
514-
"""Does a concise json serialization"""
515-
return json.dumps(obj, separators=(",", ":"), cls=Encoder)
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)
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# use mimetypes library to be certain, e.g., mimetypes.types_map[".json"]
22

33
APPLICATION_JSON = "application/json"
4-
PLAIN_TEXT = "text/plain"
4+
TEXT_PLAIN = "text/plain"
5+
TEXT_HTML = "text/html"

aws_lambda_powertools/shared/constants.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,12 @@
1010

1111
METRICS_NAMESPACE_ENV: str = "POWERTOOLS_METRICS_NAMESPACE"
1212

13+
EVENT_HANDLER_DEBUG_ENV: str = "POWERTOOLS_EVENT_HANDLER_DEBUG"
14+
1315
SAM_LOCAL_ENV: str = "AWS_SAM_LOCAL"
1416
CHALICE_LOCAL_ENV: str = "AWS_CHALICE_CLI_MODE"
1517
SERVICE_NAME_ENV: str = "POWERTOOLS_SERVICE_NAME"
1618
XRAY_TRACE_ID_ENV: str = "_X_AMZN_TRACE_ID"
1719

18-
19-
XRAY_SDK_MODULE = "aws_xray_sdk"
20-
XRAY_SDK_CORE_MODULE = "aws_xray_sdk.core"
20+
XRAY_SDK_MODULE: str = "aws_xray_sdk"
21+
XRAY_SDK_CORE_MODULE: str = "aws_xray_sdk.core"

0 commit comments

Comments
 (0)