Skip to content

Commit 2b48848

Browse files
committed
Merge branch 'develop' into docs/mkdocstrings
* develop: chore(deps): bump boto3 from 1.18.0 to 1.18.1 (aws-powertools#528) fix(tracer): mypy generic to preserve decorated method signature (aws-powertools#529) fix(parser): Make ApiGateway version, authorizer fields optional (aws-powertools#532) fix(mypy): fixes to resolve no implicit optional errors (aws-powertools#521) chore(deps): bump boto3 from 1.17.110 to 1.18.0 (aws-powertools#527) feat(feat-toggle): New simple feature toggles rule engine (WIP) (aws-powertools#494) chore(deps-dev): bump mkdocs-material from 7.1.9 to 7.1.10 (aws-powertools#522) chore(deps): bump boto3 from 1.17.102 to 1.17.110 (aws-powertools#523) chore(deps-dev): bump isort from 5.9.1 to 5.9.2 (aws-powertools#514) feat(mypy): add mypy support to makefile (aws-powertools#508) feat(api-gateway): add debug mode (aws-powertools#507)
2 parents 07e1ed4 + 018a91b commit 2b48848

38 files changed

+1857
-370
lines changed

Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,3 +83,6 @@ release: pr
8383
changelog:
8484
@echo "[+] Pre-generating CHANGELOG for tag: $$(git describe --abbrev=0 --tag)"
8585
docker run -v "${PWD}":/workdir quay.io/git-chglog/git-chglog $$(git describe --abbrev=0 --tag).. > TMP_CHANGELOG.md
86+
87+
mypy:
88+
poetry run mypy aws_lambda_powertools

aws_lambda_powertools/event_handler/api_gateway.py

Lines changed: 89 additions & 39 deletions
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"]
@@ -119,7 +126,11 @@ class Response:
119126
"""Response data class that provides greater control over what is returned from the proxy event"""
120127

121128
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,
123134
):
124135
"""
125136
@@ -160,7 +171,7 @@ def __init__(
160171
class ResponseBuilder:
161172
"""Internally used Response builder"""
162173

163-
def __init__(self, response: Response, route: Route = None):
174+
def __init__(self, response: Response, route: Optional[Route] = None):
164175
self.response = response
165176
self.route = route
166177

@@ -192,7 +203,7 @@ def _route(self, event: BaseProxyEvent, cors: Optional[CORSConfig]):
192203
if self.route.compress and "gzip" in (event.get_header_value("accept-encoding", "") or ""):
193204
self._compress()
194205

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]:
196207
"""Build the full response dict to be returned by the lambda"""
197208
self._route(event, cors)
198209

@@ -240,22 +251,33 @@ def lambda_handler(event, context):
240251
current_event: BaseProxyEvent
241252
lambda_context: LambdaContext
242253

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+
):
244260
"""
245261
Parameters
246262
----------
247263
proxy_type: ProxyEventType
248264
Proxy request type, defaults to API Gateway V1
249265
cors: CORSConfig
250266
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
251270
"""
252271
self._proxy_type = proxy_type
253272
self._routes: List[Route] = []
254273
self._cors = cors
255274
self._cors_enabled: bool = cors is not None
256275
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+
)
257279

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):
259281
"""Get route decorator with GET `method`
260282
261283
Examples
@@ -280,7 +302,7 @@ def lambda_handler(event, context):
280302
"""
281303
return self.route(rule, "GET", cors, compress, cache_control)
282304

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):
284306
"""Post route decorator with POST `method`
285307
286308
Examples
@@ -306,7 +328,7 @@ def lambda_handler(event, context):
306328
"""
307329
return self.route(rule, "POST", cors, compress, cache_control)
308330

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):
310332
"""Put route decorator with PUT `method`
311333
312334
Examples
@@ -332,7 +354,9 @@ def lambda_handler(event, context):
332354
"""
333355
return self.route(rule, "PUT", cors, compress, cache_control)
334356

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+
):
336360
"""Delete route decorator with DELETE `method`
337361
338362
Examples
@@ -357,7 +381,9 @@ def lambda_handler(event, context):
357381
"""
358382
return self.route(rule, "DELETE", cors, compress, cache_control)
359383

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+
):
361387
"""Patch route decorator with PATCH `method`
362388
363389
Examples
@@ -385,7 +411,14 @@ def lambda_handler(event, context):
385411
"""
386412
return self.route(rule, "PATCH", cors, compress, cache_control)
387413

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+
):
389422
"""Route decorator includes parameter `method`"""
390423

391424
def register_resolver(func: Callable):
@@ -416,6 +449,8 @@ def resolve(self, event, context) -> Dict[str, Any]:
416449
dict
417450
Returns the dict response
418451
"""
452+
if self._debug:
453+
print(self._json_dump(event))
419454
self.current_event = self._to_proxy_event(event)
420455
self.lambda_context = context
421456
return self._resolve().build(self.current_event, self._cors)
@@ -489,6 +524,19 @@ def _call_route(self, route: Route, args: Dict[str, str]) -> ResponseBuilder:
489524
),
490525
route,
491526
)
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
492540

493541
def _to_response(self, result: Union[Dict, Response]) -> Response:
494542
"""Convert the route's result to a Response
@@ -509,7 +557,9 @@ def _to_response(self, result: Union[Dict, Response]) -> Response:
509557
body=self._json_dump(result),
510558
)
511559

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)

aws_lambda_powertools/event_handler/appsync.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import logging
2-
from typing import Any, Callable
2+
from typing import Any, Callable, Optional
33

44
from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent
55
from aws_lambda_powertools.utilities.typing import LambdaContext
@@ -44,7 +44,7 @@ def common_field() -> str:
4444
def __init__(self):
4545
self._resolvers: dict = {}
4646

47-
def resolver(self, type_name: str = "*", field_name: str = None):
47+
def resolver(self, type_name: str = "*", field_name: Optional[str] = None):
4848
"""Registers the resolver for field_name
4949
5050
Parameters
Lines changed: 2 additions & 1 deletion
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/logging/formatter.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,8 @@ def __init__(
6060
json_serializer: Optional[Callable[[Dict], str]] = None,
6161
json_deserializer: Optional[Callable[[Dict], str]] = None,
6262
json_default: Optional[Callable[[Any], Any]] = None,
63-
datefmt: str = None,
64-
log_record_order: List[str] = None,
63+
datefmt: Optional[str] = None,
64+
log_record_order: Optional[List[str]] = None,
6565
utc: bool = False,
6666
**kwargs
6767
):

aws_lambda_powertools/logging/logger.py

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import os
55
import random
66
import sys
7-
from typing import Any, Callable, Dict, Iterable, Optional, TypeVar, Union
7+
from typing import IO, Any, Callable, Dict, Iterable, Optional, TypeVar, Union
88

99
import jmespath
1010

@@ -167,11 +167,11 @@ class Logger(logging.Logger): # lgtm [py/missing-call-to-init]
167167

168168
def __init__(
169169
self,
170-
service: str = None,
171-
level: Union[str, int] = None,
170+
service: Optional[str] = None,
171+
level: Union[str, int, None] = None,
172172
child: bool = False,
173-
sampling_rate: float = None,
174-
stream: sys.stdout = None,
173+
sampling_rate: Optional[float] = None,
174+
stream: Optional[IO[str]] = None,
175175
logger_formatter: Optional[PowertoolsFormatter] = None,
176176
logger_handler: Optional[logging.Handler] = None,
177177
**kwargs,
@@ -261,10 +261,10 @@ def _configure_sampling(self):
261261

262262
def inject_lambda_context(
263263
self,
264-
lambda_handler: Callable[[Dict, Any], Any] = None,
265-
log_event: bool = None,
266-
correlation_id_path: str = None,
267-
clear_state: bool = False,
264+
lambda_handler: Optional[Callable[[Dict, Any], Any]] = None,
265+
log_event: Optional[bool] = None,
266+
correlation_id_path: Optional[str] = None,
267+
clear_state: Optional[bool] = False,
268268
):
269269
"""Decorator to capture Lambda contextual info and inject into logger
270270
@@ -324,7 +324,7 @@ def handler(event, context):
324324
)
325325

326326
log_event = resolve_truthy_env_var_choice(
327-
choice=log_event, env=os.getenv(constants.LOGGER_LOG_EVENT_ENV, "false")
327+
env=os.getenv(constants.LOGGER_LOG_EVENT_ENV, "false"), choice=log_event
328328
)
329329

330330
@functools.wraps(lambda_handler)
@@ -363,7 +363,7 @@ def registered_handler(self) -> logging.Handler:
363363
@property
364364
def registered_formatter(self) -> Optional[PowertoolsFormatter]:
365365
"""Convenience property to access logger formatter"""
366-
return self.registered_handler.formatter
366+
return self.registered_handler.formatter # type: ignore
367367

368368
def structure_logs(self, append: bool = False, **keys):
369369
"""Sets logging formatting to JSON.
@@ -384,7 +384,7 @@ def structure_logs(self, append: bool = False, **keys):
384384
self.append_keys(**keys)
385385
else:
386386
log_keys = {**self._default_log_keys, **keys}
387-
formatter = self.logger_formatter or LambdaPowertoolsFormatter(**log_keys)
387+
formatter = self.logger_formatter or LambdaPowertoolsFormatter(**log_keys) # type: ignore
388388
self.registered_handler.setFormatter(formatter)
389389

390390
def set_correlation_id(self, value: str):
@@ -421,7 +421,9 @@ def _get_caller_filename():
421421

422422

423423
def set_package_logger(
424-
level: Union[str, int] = logging.DEBUG, stream: sys.stdout = None, formatter: logging.Formatter = None
424+
level: Union[str, int] = logging.DEBUG,
425+
stream: Optional[IO[str]] = None,
426+
formatter: Optional[logging.Formatter] = None,
425427
):
426428
"""Set an additional stream handler, formatter, and log level for aws_lambda_powertools package logger.
427429

0 commit comments

Comments
 (0)