Skip to content

Commit

Permalink
Merge branch 'develop' into docs/mkdocstrings
Browse files Browse the repository at this point in the history
* 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)
  • Loading branch information
heitorlessa committed Jul 17, 2021
2 parents 07e1ed4 + 018a91b commit 2b48848
Show file tree
Hide file tree
Showing 38 changed files with 1,857 additions and 370 deletions.
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,6 @@ release: pr
changelog:
@echo "[+] Pre-generating CHANGELOG for tag: $$(git describe --abbrev=0 --tag)"
docker run -v "${PWD}":/workdir quay.io/git-chglog/git-chglog $$(git describe --abbrev=0 --tag).. > TMP_CHANGELOG.md

mypy:
poetry run mypy aws_lambda_powertools
128 changes: 89 additions & 39 deletions aws_lambda_powertools/event_handler/api_gateway.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import base64
import json
import logging
import os
import re
import traceback
import zlib
from enum import Enum
from http import HTTPStatus
from typing import Any, Callable, Dict, List, Optional, Set, Union

from aws_lambda_powertools.event_handler import content_types
from aws_lambda_powertools.event_handler.exceptions import ServiceError
from aws_lambda_powertools.shared import constants
from aws_lambda_powertools.shared.functions import resolve_truthy_env_var_choice
from aws_lambda_powertools.shared.json_encoder import Encoder
from aws_lambda_powertools.utilities.data_classes import ALBEvent, APIGatewayProxyEvent, APIGatewayProxyEventV2
from aws_lambda_powertools.utilities.data_classes.common import BaseProxyEvent
Expand All @@ -28,43 +32,46 @@ class ProxyEventType(Enum):
class CORSConfig(object):
"""CORS Config
Examples
--------
Simple cors example using the default permissive cors, not this should only be used during early prototyping
from aws_lambda_powertools.event_handler.api_gateway import ApiGatewayResolver
```python
from aws_lambda_powertools.event_handler.api_gateway import ApiGatewayResolver
app = ApiGatewayResolver()
app = ApiGatewayResolver()
@app.get("/my/path", cors=True)
def with_cors():
return {"message": "Foo"}
@app.get("/my/path", cors=True)
def with_cors():
return {"message": "Foo"}
```
Using a custom CORSConfig where `with_cors` used the custom provided CORSConfig and `without_cors`
do not include any cors headers.
from aws_lambda_powertools.event_handler.api_gateway import (
ApiGatewayResolver, CORSConfig
)
cors_config = CORSConfig(
allow_origin="https://wwww.example.com/",
expose_headers=["x-exposed-response-header"],
allow_headers=["x-custom-request-header"],
max_age=100,
allow_credentials=True,
)
app = ApiGatewayResolver(cors=cors_config)
@app.get("/my/path")
def with_cors():
return {"message": "Foo"}
```python
from aws_lambda_powertools.event_handler.api_gateway import (
ApiGatewayResolver, CORSConfig
)
cors_config = CORSConfig(
allow_origin="https://wwww.example.com/",
expose_headers=["x-exposed-response-header"],
allow_headers=["x-custom-request-header"],
max_age=100,
allow_credentials=True,
)
app = ApiGatewayResolver(cors=cors_config)
@app.get("/my/path")
def with_cors():
return {"message": "Foo"}
@app.get("/another-one", cors=False)
def without_cors():
return {"message": "Foo"}
@app.get("/another-one", cors=False)
def without_cors():
return {"message": "Foo"}
```
"""

_REQUIRED_HEADERS = ["Authorization", "Content-Type", "X-Amz-Date", "X-Api-Key", "X-Amz-Security-Token"]
Expand Down Expand Up @@ -119,7 +126,11 @@ class Response:
"""Response data class that provides greater control over what is returned from the proxy event"""

def __init__(
self, status_code: int, content_type: Optional[str], body: Union[str, bytes, None], headers: Dict = None
self,
status_code: int,
content_type: Optional[str],
body: Union[str, bytes, None],
headers: Optional[Dict] = None,
):
"""
Expand Down Expand Up @@ -160,7 +171,7 @@ def __init__(
class ResponseBuilder:
"""Internally used Response builder"""

def __init__(self, response: Response, route: Route = None):
def __init__(self, response: Response, route: Optional[Route] = None):
self.response = response
self.route = route

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

def build(self, event: BaseProxyEvent, cors: CORSConfig = None) -> Dict[str, Any]:
def build(self, event: BaseProxyEvent, cors: Optional[CORSConfig] = None) -> Dict[str, Any]:
"""Build the full response dict to be returned by the lambda"""
self._route(event, cors)

Expand Down Expand Up @@ -240,22 +251,33 @@ def lambda_handler(event, context):
current_event: BaseProxyEvent
lambda_context: LambdaContext

def __init__(self, proxy_type: Enum = ProxyEventType.APIGatewayProxyEvent, cors: CORSConfig = None):
def __init__(
self,
proxy_type: Enum = ProxyEventType.APIGatewayProxyEvent,
cors: Optional[CORSConfig] = None,
debug: Optional[bool] = None,
):
"""
Parameters
----------
proxy_type: ProxyEventType
Proxy request type, defaults to API Gateway V1
cors: CORSConfig
Optionally configure and enabled CORS. Not each route will need to have to cors=True
debug: Optional[bool]
Enables debug mode, by default False. Can be also be enabled by "POWERTOOLS_EVENT_HANDLER_DEBUG"
environment variable
"""
self._proxy_type = proxy_type
self._routes: List[Route] = []
self._cors = cors
self._cors_enabled: bool = cors is not None
self._cors_methods: Set[str] = {"OPTIONS"}
self._debug = resolve_truthy_env_var_choice(
env=os.getenv(constants.EVENT_HANDLER_DEBUG_ENV, "false"), choice=debug
)

def get(self, rule: str, cors: bool = None, compress: bool = False, cache_control: str = None):
def get(self, rule: str, cors: Optional[bool] = None, compress: bool = False, cache_control: Optional[str] = None):
"""Get route decorator with GET `method`
Examples
Expand All @@ -280,7 +302,7 @@ def lambda_handler(event, context):
"""
return self.route(rule, "GET", cors, compress, cache_control)

def post(self, rule: str, cors: bool = None, compress: bool = False, cache_control: str = None):
def post(self, rule: str, cors: Optional[bool] = None, compress: bool = False, cache_control: Optional[str] = None):
"""Post route decorator with POST `method`
Examples
Expand All @@ -306,7 +328,7 @@ def lambda_handler(event, context):
"""
return self.route(rule, "POST", cors, compress, cache_control)

def put(self, rule: str, cors: bool = None, compress: bool = False, cache_control: str = None):
def put(self, rule: str, cors: Optional[bool] = None, compress: bool = False, cache_control: Optional[str] = None):
"""Put route decorator with PUT `method`
Examples
Expand All @@ -332,7 +354,9 @@ def lambda_handler(event, context):
"""
return self.route(rule, "PUT", cors, compress, cache_control)

def delete(self, rule: str, cors: bool = None, compress: bool = False, cache_control: str = None):
def delete(
self, rule: str, cors: Optional[bool] = None, compress: bool = False, cache_control: Optional[str] = None
):
"""Delete route decorator with DELETE `method`
Examples
Expand All @@ -357,7 +381,9 @@ def lambda_handler(event, context):
"""
return self.route(rule, "DELETE", cors, compress, cache_control)

def patch(self, rule: str, cors: bool = None, compress: bool = False, cache_control: str = None):
def patch(
self, rule: str, cors: Optional[bool] = None, compress: bool = False, cache_control: Optional[str] = None
):
"""Patch route decorator with PATCH `method`
Examples
Expand Down Expand Up @@ -385,7 +411,14 @@ def lambda_handler(event, context):
"""
return self.route(rule, "PATCH", cors, compress, cache_control)

def route(self, rule: str, method: str, cors: bool = None, compress: bool = False, cache_control: str = None):
def route(
self,
rule: str,
method: str,
cors: Optional[bool] = None,
compress: bool = False,
cache_control: Optional[str] = None,
):
"""Route decorator includes parameter `method`"""

def register_resolver(func: Callable):
Expand Down Expand Up @@ -416,6 +449,8 @@ def resolve(self, event, context) -> Dict[str, Any]:
dict
Returns the dict response
"""
if self._debug:
print(self._json_dump(event))
self.current_event = self._to_proxy_event(event)
self.lambda_context = context
return self._resolve().build(self.current_event, self._cors)
Expand Down Expand Up @@ -489,6 +524,19 @@ def _call_route(self, route: Route, args: Dict[str, str]) -> ResponseBuilder:
),
route,
)
except Exception:
if self._debug:
# If the user has turned on debug mode,
# we'll let the original exception propagate so
# they get more information about what went wrong.
return ResponseBuilder(
Response(
status_code=500,
content_type=content_types.TEXT_PLAIN,
body="".join(traceback.format_exc()),
)
)
raise

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

@staticmethod
def _json_dump(obj: Any) -> str:
"""Does a concise json serialization"""
return json.dumps(obj, separators=(",", ":"), cls=Encoder)
def _json_dump(self, obj: Any) -> str:
"""Does a concise json serialization or pretty print when in debug mode"""
if self._debug:
return json.dumps(obj, indent=4, cls=Encoder)
else:
return json.dumps(obj, separators=(",", ":"), cls=Encoder)
4 changes: 2 additions & 2 deletions aws_lambda_powertools/event_handler/appsync.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import logging
from typing import Any, Callable
from typing import Any, Callable, Optional

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

def resolver(self, type_name: str = "*", field_name: str = None):
def resolver(self, type_name: str = "*", field_name: Optional[str] = None):
"""Registers the resolver for field_name
Parameters
Expand Down
3 changes: 2 additions & 1 deletion aws_lambda_powertools/event_handler/content_types.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# use mimetypes library to be certain, e.g., mimetypes.types_map[".json"]

APPLICATION_JSON = "application/json"
PLAIN_TEXT = "text/plain"
TEXT_PLAIN = "text/plain"
TEXT_HTML = "text/html"
4 changes: 2 additions & 2 deletions aws_lambda_powertools/logging/formatter.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ def __init__(
json_serializer: Optional[Callable[[Dict], str]] = None,
json_deserializer: Optional[Callable[[Dict], str]] = None,
json_default: Optional[Callable[[Any], Any]] = None,
datefmt: str = None,
log_record_order: List[str] = None,
datefmt: Optional[str] = None,
log_record_order: Optional[List[str]] = None,
utc: bool = False,
**kwargs
):
Expand Down
28 changes: 15 additions & 13 deletions aws_lambda_powertools/logging/logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import os
import random
import sys
from typing import Any, Callable, Dict, Iterable, Optional, TypeVar, Union
from typing import IO, Any, Callable, Dict, Iterable, Optional, TypeVar, Union

import jmespath

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

def __init__(
self,
service: str = None,
level: Union[str, int] = None,
service: Optional[str] = None,
level: Union[str, int, None] = None,
child: bool = False,
sampling_rate: float = None,
stream: sys.stdout = None,
sampling_rate: Optional[float] = None,
stream: Optional[IO[str]] = None,
logger_formatter: Optional[PowertoolsFormatter] = None,
logger_handler: Optional[logging.Handler] = None,
**kwargs,
Expand Down Expand Up @@ -261,10 +261,10 @@ def _configure_sampling(self):

def inject_lambda_context(
self,
lambda_handler: Callable[[Dict, Any], Any] = None,
log_event: bool = None,
correlation_id_path: str = None,
clear_state: bool = False,
lambda_handler: Optional[Callable[[Dict, Any], Any]] = None,
log_event: Optional[bool] = None,
correlation_id_path: Optional[str] = None,
clear_state: Optional[bool] = False,
):
"""Decorator to capture Lambda contextual info and inject into logger
Expand Down Expand Up @@ -324,7 +324,7 @@ def handler(event, context):
)

log_event = resolve_truthy_env_var_choice(
choice=log_event, env=os.getenv(constants.LOGGER_LOG_EVENT_ENV, "false")
env=os.getenv(constants.LOGGER_LOG_EVENT_ENV, "false"), choice=log_event
)

@functools.wraps(lambda_handler)
Expand Down Expand Up @@ -363,7 +363,7 @@ def registered_handler(self) -> logging.Handler:
@property
def registered_formatter(self) -> Optional[PowertoolsFormatter]:
"""Convenience property to access logger formatter"""
return self.registered_handler.formatter
return self.registered_handler.formatter # type: ignore

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

def set_correlation_id(self, value: str):
Expand Down Expand Up @@ -421,7 +421,9 @@ def _get_caller_filename():


def set_package_logger(
level: Union[str, int] = logging.DEBUG, stream: sys.stdout = None, formatter: logging.Formatter = None
level: Union[str, int] = logging.DEBUG,
stream: Optional[IO[str]] = None,
formatter: Optional[logging.Formatter] = None,
):
"""Set an additional stream handler, formatter, and log level for aws_lambda_powertools package logger.
Expand Down
Loading

0 comments on commit 2b48848

Please sign in to comment.