Skip to content

Commit

Permalink
Merge branch 'develop' into whardier-patch-appsync-resolve-current-ev…
Browse files Browse the repository at this point in the history
…ent-subclass

* develop:
  fix(api-gateway): non-greedy route pattern regex (aws-powertools#533)
  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)
  • Loading branch information
heitorlessa committed Jul 19, 2021
2 parents 4531757 + 89e8151 commit 2637bd1
Show file tree
Hide file tree
Showing 22 changed files with 369 additions and 108 deletions.
73 changes: 59 additions & 14 deletions aws_lambda_powertools/event_handler/api_gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@

logger = logging.getLogger(__name__)

_DYNAMIC_ROUTE_PATTERN = r"(<\w+>)"
_NAMED_GROUP_BOUNDARY_PATTERN = r"(?P\1\\w+\\b)"


class ProxyEventType(Enum):
"""An enumerations of the supported proxy event types."""
Expand Down Expand Up @@ -126,7 +129,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 @@ -167,7 +174,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 @@ -199,7 +206,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 @@ -250,7 +257,7 @@ def lambda_handler(event, context):
def __init__(
self,
proxy_type: Enum = ProxyEventType.APIGatewayProxyEvent,
cors: CORSConfig = None,
cors: Optional[CORSConfig] = None,
debug: Optional[bool] = None,
):
"""
Expand All @@ -270,10 +277,10 @@ def __init__(
self._cors_enabled: bool = cors is not None
self._cors_methods: Set[str] = {"OPTIONS"}
self._debug = resolve_truthy_env_var_choice(
choice=debug, env=os.getenv(constants.EVENT_HANDLER_DEBUG_ENV, "false")
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 @@ -298,7 +305,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 @@ -324,7 +331,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 @@ -350,7 +357,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 @@ -375,7 +384,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 @@ -403,7 +414,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 @@ -445,8 +463,35 @@ def __call__(self, event, context) -> Any:

@staticmethod
def _compile_regex(rule: str):
"""Precompile regex pattern"""
rule_regex: str = re.sub(r"(<\w+>)", r"(?P\1.+)", rule)
"""Precompile regex pattern
Logic
-----
1. Find any dynamic routes defined as <pattern>
e.g. @app.get("/accounts/<account_id>")
2. Create a new regex by substituting every dynamic route found as a named group (?P<group>),
and match whole words only (word boundary) instead of a greedy match
non-greedy example with word boundary
rule: '/accounts/<account_id>'
regex: r'/accounts/(?P<account_id>\\w+\\b)'
value: /accounts/123/some_other_path
account_id: 123
greedy example without word boundary
regex: r'/accounts/(?P<account_id>.+)'
value: /accounts/123/some_other_path
account_id: 123/some_other_path
3. Compiles a regex and include start (^) and end ($) in between for an exact match
NOTE: See #520 for context
"""
rule_regex: str = re.sub(_DYNAMIC_ROUTE_PATTERN, _NAMED_GROUP_BOUNDARY_PATTERN, rule)
return re.compile("^{}$".format(rule_regex))

def _to_proxy_event(self, event: Dict) -> BaseProxyEvent:
Expand All @@ -470,7 +515,7 @@ def _resolve(self) -> ResponseBuilder:
match: Optional[re.Match] = route.rule.match(path)
if match:
logger.debug("Found a registered route. Calling function")
return self._call_route(route, match.groupdict())
return self._call_route(route, match.groupdict()) # pass fn args

logger.debug(f"No match found for path {path} and method {method}")
return self._not_found(method)
Expand Down
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
22 changes: 12 additions & 10 deletions aws_lambda_powertools/logging/logger.py
Original file line number Diff line number Diff line change
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: IO[str] = 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 @@ -421,7 +421,9 @@ def _get_caller_filename():


def set_package_logger(
level: Union[str, int] = logging.DEBUG, stream: IO[str] = 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
16 changes: 9 additions & 7 deletions aws_lambda_powertools/metrics/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import os
from collections import defaultdict
from enum import Enum
from typing import Any, Dict, List, Union
from typing import Any, Dict, List, Optional, Union

from ..shared import constants
from ..shared.functions import resolve_env_var_choice
Expand Down Expand Up @@ -76,11 +76,11 @@ class MetricManager:

def __init__(
self,
metric_set: Dict[str, Any] = None,
dimension_set: Dict = None,
namespace: str = None,
metadata_set: Dict[str, Any] = None,
service: str = None,
metric_set: Optional[Dict[str, Any]] = None,
dimension_set: Optional[Dict] = None,
namespace: Optional[str] = None,
metadata_set: Optional[Dict[str, Any]] = None,
service: Optional[str] = None,
):
self.metric_set = metric_set if metric_set is not None else {}
self.dimension_set = dimension_set if dimension_set is not None else {}
Expand Down Expand Up @@ -136,7 +136,9 @@ def add_metric(self, name: str, unit: Union[MetricUnit, str], value: float):
# since we could have more than 100 metrics
self.metric_set.clear()

def serialize_metric_set(self, metrics: Dict = None, dimensions: Dict = None, metadata: Dict = None) -> Dict:
def serialize_metric_set(
self, metrics: Optional[Dict] = None, dimensions: Optional[Dict] = None, metadata: Optional[Dict] = None
) -> Dict:
"""Serializes metric and dimensions set
Parameters
Expand Down
2 changes: 1 addition & 1 deletion aws_lambda_powertools/metrics/metric.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ def add_metric(self, name: str, unit: Union[MetricUnit, str], value: float):


@contextmanager
def single_metric(name: str, unit: MetricUnit, value: float, namespace: str = None):
def single_metric(name: str, unit: MetricUnit, value: float, namespace: Optional[str] = None):
"""Context manager to simplify creation of a single metric
Example
Expand Down
6 changes: 3 additions & 3 deletions aws_lambda_powertools/metrics/metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ def lambda_handler():
_metadata: Dict[str, Any] = {}
_default_dimensions: Dict[str, Any] = {}

def __init__(self, service: str = None, namespace: str = None):
def __init__(self, service: Optional[str] = None, namespace: Optional[str] = None):
self.metric_set = self._metrics
self.service = service
self.namespace: Optional[str] = namespace
Expand Down Expand Up @@ -125,10 +125,10 @@ def clear_metrics(self):

def log_metrics(
self,
lambda_handler: Callable[[Any, Any], Any] = None,
lambda_handler: Optional[Callable[[Any, Any], Any]] = None,
capture_cold_start_metric: bool = False,
raise_on_empty_metrics: bool = False,
default_dimensions: Dict[str, str] = None,
default_dimensions: Optional[Dict[str, str]] = None,
):
"""Decorator to serialize and publish metrics at the end of a function execution.
Expand Down
8 changes: 4 additions & 4 deletions aws_lambda_powertools/middleware_factory/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import inspect
import logging
import os
from typing import Callable
from typing import Callable, Optional

from ..shared import constants
from ..shared.functions import resolve_truthy_env_var_choice
Expand All @@ -12,7 +12,7 @@
logger = logging.getLogger(__name__)


def lambda_handler_decorator(decorator: Callable = None, trace_execution: bool = None):
def lambda_handler_decorator(decorator: Optional[Callable] = None, trace_execution: Optional[bool] = None):
"""Decorator factory for decorating Lambda handlers.
You can use lambda_handler_decorator to create your own middlewares,
Expand Down Expand Up @@ -106,11 +106,11 @@ def lambda_handler(event, context):
return functools.partial(lambda_handler_decorator, trace_execution=trace_execution)

trace_execution = resolve_truthy_env_var_choice(
choice=trace_execution, env=os.getenv(constants.MIDDLEWARE_FACTORY_TRACE_ENV, "false")
env=os.getenv(constants.MIDDLEWARE_FACTORY_TRACE_ENV, "false"), choice=trace_execution
)

@functools.wraps(decorator)
def final_decorator(func: Callable = None, **kwargs):
def final_decorator(func: Optional[Callable] = None, **kwargs):
# If called with kwargs return new func with kwargs
if func is None:
return functools.partial(final_decorator, **kwargs)
Expand Down
4 changes: 2 additions & 2 deletions aws_lambda_powertools/shared/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
from typing import Any, Optional, Union


def resolve_truthy_env_var_choice(env: Any, choice: bool = None) -> bool:
def resolve_truthy_env_var_choice(env: str, choice: Optional[bool] = None) -> bool:
"""Pick explicit choice over truthy env value, if available, otherwise return truthy env value
NOTE: Environment variable should be resolved by the caller.
Parameters
----------
env : Any
env : str
environment variable actual value
choice : bool
explicit choice
Expand Down
8 changes: 4 additions & 4 deletions aws_lambda_powertools/tracing/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
import numbers
import traceback
from contextlib import contextmanager
from typing import Any, AsyncContextManager, ContextManager, List, NoReturn, Set, Union
from typing import Any, AsyncContextManager, ContextManager, List, NoReturn, Optional, Set, Union


class BaseProvider(abc.ABC):
@abc.abstractmethod
@abc.abstractmethod # type: ignore
@contextmanager
def in_subsegment(self, name=None, **kwargs) -> ContextManager:
"""Return a subsegment context manger.
Expand All @@ -19,7 +19,7 @@ def in_subsegment(self, name=None, **kwargs) -> ContextManager:
Optional parameters to be propagated to segment
"""

@abc.abstractmethod
@abc.abstractmethod # type: ignore
@contextmanager
def in_subsegment_async(self, name=None, **kwargs) -> AsyncContextManager:
"""Return a subsegment async context manger.
Expand Down Expand Up @@ -81,7 +81,7 @@ class BaseSegment(abc.ABC):
"""Holds common properties and methods on segment and subsegment."""

@abc.abstractmethod
def close(self, end_time: int = None):
def close(self, end_time: Optional[int] = None):
"""Close the trace entity by setting `end_time`
and flip the in progress flag to False.
Expand Down
Loading

0 comments on commit 2637bd1

Please sign in to comment.