Skip to content

Commit 2637bd1

Browse files
committed
Merge branch 'develop' into whardier-patch-appsync-resolve-current-event-subclass
* develop: fix(api-gateway): non-greedy route pattern regex (#533) chore(deps): bump boto3 from 1.18.0 to 1.18.1 (#528) fix(tracer): mypy generic to preserve decorated method signature (#529) fix(parser): Make ApiGateway version, authorizer fields optional (#532) fix(mypy): fixes to resolve no implicit optional errors (#521) chore(deps): bump boto3 from 1.17.110 to 1.18.0 (#527)
2 parents 4531757 + 89e8151 commit 2637bd1

File tree

22 files changed

+369
-108
lines changed

22 files changed

+369
-108
lines changed

aws_lambda_powertools/event_handler/api_gateway.py

+59-14
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020

2121
logger = logging.getLogger(__name__)
2222

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

2427
class ProxyEventType(Enum):
2528
"""An enumerations of the supported proxy event types."""
@@ -126,7 +129,11 @@ class Response:
126129
"""Response data class that provides greater control over what is returned from the proxy event"""
127130

128131
def __init__(
129-
self, status_code: int, content_type: Optional[str], body: Union[str, bytes, None], headers: Dict = None
132+
self,
133+
status_code: int,
134+
content_type: Optional[str],
135+
body: Union[str, bytes, None],
136+
headers: Optional[Dict] = None,
130137
):
131138
"""
132139
@@ -167,7 +174,7 @@ def __init__(
167174
class ResponseBuilder:
168175
"""Internally used Response builder"""
169176

170-
def __init__(self, response: Response, route: Route = None):
177+
def __init__(self, response: Response, route: Optional[Route] = None):
171178
self.response = response
172179
self.route = route
173180

@@ -199,7 +206,7 @@ def _route(self, event: BaseProxyEvent, cors: Optional[CORSConfig]):
199206
if self.route.compress and "gzip" in (event.get_header_value("accept-encoding", "") or ""):
200207
self._compress()
201208

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

@@ -250,7 +257,7 @@ def lambda_handler(event, context):
250257
def __init__(
251258
self,
252259
proxy_type: Enum = ProxyEventType.APIGatewayProxyEvent,
253-
cors: CORSConfig = None,
260+
cors: Optional[CORSConfig] = None,
254261
debug: Optional[bool] = None,
255262
):
256263
"""
@@ -270,10 +277,10 @@ def __init__(
270277
self._cors_enabled: bool = cors is not None
271278
self._cors_methods: Set[str] = {"OPTIONS"}
272279
self._debug = resolve_truthy_env_var_choice(
273-
choice=debug, env=os.getenv(constants.EVENT_HANDLER_DEBUG_ENV, "false")
280+
env=os.getenv(constants.EVENT_HANDLER_DEBUG_ENV, "false"), choice=debug
274281
)
275282

276-
def get(self, rule: str, cors: bool = None, compress: bool = False, cache_control: str = None):
283+
def get(self, rule: str, cors: Optional[bool] = None, compress: bool = False, cache_control: Optional[str] = None):
277284
"""Get route decorator with GET `method`
278285
279286
Examples
@@ -298,7 +305,7 @@ def lambda_handler(event, context):
298305
"""
299306
return self.route(rule, "GET", cors, compress, cache_control)
300307

301-
def post(self, rule: str, cors: bool = None, compress: bool = False, cache_control: str = None):
308+
def post(self, rule: str, cors: Optional[bool] = None, compress: bool = False, cache_control: Optional[str] = None):
302309
"""Post route decorator with POST `method`
303310
304311
Examples
@@ -324,7 +331,7 @@ def lambda_handler(event, context):
324331
"""
325332
return self.route(rule, "POST", cors, compress, cache_control)
326333

327-
def put(self, rule: str, cors: bool = None, compress: bool = False, cache_control: str = None):
334+
def put(self, rule: str, cors: Optional[bool] = None, compress: bool = False, cache_control: Optional[str] = None):
328335
"""Put route decorator with PUT `method`
329336
330337
Examples
@@ -350,7 +357,9 @@ def lambda_handler(event, context):
350357
"""
351358
return self.route(rule, "PUT", cors, compress, cache_control)
352359

353-
def delete(self, rule: str, cors: bool = None, compress: bool = False, cache_control: str = None):
360+
def delete(
361+
self, rule: str, cors: Optional[bool] = None, compress: bool = False, cache_control: Optional[str] = None
362+
):
354363
"""Delete route decorator with DELETE `method`
355364
356365
Examples
@@ -375,7 +384,9 @@ def lambda_handler(event, context):
375384
"""
376385
return self.route(rule, "DELETE", cors, compress, cache_control)
377386

378-
def patch(self, rule: str, cors: bool = None, compress: bool = False, cache_control: str = None):
387+
def patch(
388+
self, rule: str, cors: Optional[bool] = None, compress: bool = False, cache_control: Optional[str] = None
389+
):
379390
"""Patch route decorator with PATCH `method`
380391
381392
Examples
@@ -403,7 +414,14 @@ def lambda_handler(event, context):
403414
"""
404415
return self.route(rule, "PATCH", cors, compress, cache_control)
405416

406-
def route(self, rule: str, method: str, cors: bool = None, compress: bool = False, cache_control: str = None):
417+
def route(
418+
self,
419+
rule: str,
420+
method: str,
421+
cors: Optional[bool] = None,
422+
compress: bool = False,
423+
cache_control: Optional[str] = None,
424+
):
407425
"""Route decorator includes parameter `method`"""
408426

409427
def register_resolver(func: Callable):
@@ -445,8 +463,35 @@ def __call__(self, event, context) -> Any:
445463

446464
@staticmethod
447465
def _compile_regex(rule: str):
448-
"""Precompile regex pattern"""
449-
rule_regex: str = re.sub(r"(<\w+>)", r"(?P\1.+)", rule)
466+
"""Precompile regex pattern
467+
468+
Logic
469+
-----
470+
471+
1. Find any dynamic routes defined as <pattern>
472+
e.g. @app.get("/accounts/<account_id>")
473+
2. Create a new regex by substituting every dynamic route found as a named group (?P<group>),
474+
and match whole words only (word boundary) instead of a greedy match
475+
476+
non-greedy example with word boundary
477+
478+
rule: '/accounts/<account_id>'
479+
regex: r'/accounts/(?P<account_id>\\w+\\b)'
480+
481+
value: /accounts/123/some_other_path
482+
account_id: 123
483+
484+
greedy example without word boundary
485+
486+
regex: r'/accounts/(?P<account_id>.+)'
487+
488+
value: /accounts/123/some_other_path
489+
account_id: 123/some_other_path
490+
3. Compiles a regex and include start (^) and end ($) in between for an exact match
491+
492+
NOTE: See #520 for context
493+
"""
494+
rule_regex: str = re.sub(_DYNAMIC_ROUTE_PATTERN, _NAMED_GROUP_BOUNDARY_PATTERN, rule)
450495
return re.compile("^{}$".format(rule_regex))
451496

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

475520
logger.debug(f"No match found for path {path} and method {method}")
476521
return self._not_found(method)

aws_lambda_powertools/logging/formatter.py

+2-2
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

+12-10
Original file line numberDiff line numberDiff line change
@@ -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: IO[str] = 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)
@@ -421,7 +421,9 @@ def _get_caller_filename():
421421

422422

423423
def set_package_logger(
424-
level: Union[str, int] = logging.DEBUG, stream: IO[str] = 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

aws_lambda_powertools/metrics/base.py

+9-7
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import os
66
from collections import defaultdict
77
from enum import Enum
8-
from typing import Any, Dict, List, Union
8+
from typing import Any, Dict, List, Optional, Union
99

1010
from ..shared import constants
1111
from ..shared.functions import resolve_env_var_choice
@@ -76,11 +76,11 @@ class MetricManager:
7676

7777
def __init__(
7878
self,
79-
metric_set: Dict[str, Any] = None,
80-
dimension_set: Dict = None,
81-
namespace: str = None,
82-
metadata_set: Dict[str, Any] = None,
83-
service: str = None,
79+
metric_set: Optional[Dict[str, Any]] = None,
80+
dimension_set: Optional[Dict] = None,
81+
namespace: Optional[str] = None,
82+
metadata_set: Optional[Dict[str, Any]] = None,
83+
service: Optional[str] = None,
8484
):
8585
self.metric_set = metric_set if metric_set is not None else {}
8686
self.dimension_set = dimension_set if dimension_set is not None else {}
@@ -136,7 +136,9 @@ def add_metric(self, name: str, unit: Union[MetricUnit, str], value: float):
136136
# since we could have more than 100 metrics
137137
self.metric_set.clear()
138138

139-
def serialize_metric_set(self, metrics: Dict = None, dimensions: Dict = None, metadata: Dict = None) -> Dict:
139+
def serialize_metric_set(
140+
self, metrics: Optional[Dict] = None, dimensions: Optional[Dict] = None, metadata: Optional[Dict] = None
141+
) -> Dict:
140142
"""Serializes metric and dimensions set
141143
142144
Parameters

aws_lambda_powertools/metrics/metric.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ def add_metric(self, name: str, unit: Union[MetricUnit, str], value: float):
6161

6262

6363
@contextmanager
64-
def single_metric(name: str, unit: MetricUnit, value: float, namespace: str = None):
64+
def single_metric(name: str, unit: MetricUnit, value: float, namespace: Optional[str] = None):
6565
"""Context manager to simplify creation of a single metric
6666
6767
Example

aws_lambda_powertools/metrics/metrics.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ def lambda_handler():
7171
_metadata: Dict[str, Any] = {}
7272
_default_dimensions: Dict[str, Any] = {}
7373

74-
def __init__(self, service: str = None, namespace: str = None):
74+
def __init__(self, service: Optional[str] = None, namespace: Optional[str] = None):
7575
self.metric_set = self._metrics
7676
self.service = service
7777
self.namespace: Optional[str] = namespace
@@ -125,10 +125,10 @@ def clear_metrics(self):
125125

126126
def log_metrics(
127127
self,
128-
lambda_handler: Callable[[Any, Any], Any] = None,
128+
lambda_handler: Optional[Callable[[Any, Any], Any]] = None,
129129
capture_cold_start_metric: bool = False,
130130
raise_on_empty_metrics: bool = False,
131-
default_dimensions: Dict[str, str] = None,
131+
default_dimensions: Optional[Dict[str, str]] = None,
132132
):
133133
"""Decorator to serialize and publish metrics at the end of a function execution.
134134

aws_lambda_powertools/middleware_factory/factory.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import inspect
33
import logging
44
import os
5-
from typing import Callable
5+
from typing import Callable, Optional
66

77
from ..shared import constants
88
from ..shared.functions import resolve_truthy_env_var_choice
@@ -12,7 +12,7 @@
1212
logger = logging.getLogger(__name__)
1313

1414

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

108108
trace_execution = resolve_truthy_env_var_choice(
109-
choice=trace_execution, env=os.getenv(constants.MIDDLEWARE_FACTORY_TRACE_ENV, "false")
109+
env=os.getenv(constants.MIDDLEWARE_FACTORY_TRACE_ENV, "false"), choice=trace_execution
110110
)
111111

112112
@functools.wraps(decorator)
113-
def final_decorator(func: Callable = None, **kwargs):
113+
def final_decorator(func: Optional[Callable] = None, **kwargs):
114114
# If called with kwargs return new func with kwargs
115115
if func is None:
116116
return functools.partial(final_decorator, **kwargs)

aws_lambda_powertools/shared/functions.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@
22
from typing import Any, Optional, Union
33

44

5-
def resolve_truthy_env_var_choice(env: Any, choice: bool = None) -> bool:
5+
def resolve_truthy_env_var_choice(env: str, choice: Optional[bool] = None) -> bool:
66
"""Pick explicit choice over truthy env value, if available, otherwise return truthy env value
77
88
NOTE: Environment variable should be resolved by the caller.
99
1010
Parameters
1111
----------
12-
env : Any
12+
env : str
1313
environment variable actual value
1414
choice : bool
1515
explicit choice

aws_lambda_powertools/tracing/base.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22
import numbers
33
import traceback
44
from contextlib import contextmanager
5-
from typing import Any, AsyncContextManager, ContextManager, List, NoReturn, Set, Union
5+
from typing import Any, AsyncContextManager, ContextManager, List, NoReturn, Optional, Set, Union
66

77

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

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

8383
@abc.abstractmethod
84-
def close(self, end_time: int = None):
84+
def close(self, end_time: Optional[int] = None):
8585
"""Close the trace entity by setting `end_time`
8686
and flip the in progress flag to False.
8787

0 commit comments

Comments
 (0)