Skip to content

Commit 9a335f0

Browse files
committed
* 'develop' of https://github.com/awslabs/aws-lambda-powertools-python: (24 commits) docs: consistency around admonitions and snippets (#919) chore(deps-dev): bump mypy from 0.920 to 0.930 (#925) fix(event-sources): handle dynamodb null type as none, not bool (#929) fix(apigateway): support @app.not_found() syntax & housekeeping (#926) docs: Added GraphQL Sample API to Examples section of README.md (#930) feat(idempotency): support dataclasses & pydantic models payloads (#908) feat(tracer): ignore tracing for certain hostname(s) or url(s) (#910) feat(event-sources): cache parsed json in data class (#909) fix(warning): future distutils deprecation (#921) docs(batch): remove leftover from legacy docs(layer): bump Lambda Layer to version 6 chore: bump to 1.23.0 docs(apigateway): add new not_found feature (#915) docs: external reference to cloudformation custom resource helper (#914) feat(logger): allow handler with custom kwargs signature (#913) chore: minor housekeeping before release (#912) chore(deps-dev): bump mypy from 0.910 to 0.920 (#903) feat(batch): new BatchProcessor for SQS, DynamoDB, Kinesis (#886) fix(parser): overload parse when using envelope (#885) fix(parser): kinesis sequence number is str, not int (#907) ...
2 parents 39c8dd6 + f985c40 commit 9a335f0

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

68 files changed

+4630
-2207
lines changed

.pre-commit-config.yaml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,6 @@ repos:
1111
- id: trailing-whitespace
1212
- id: end-of-file-fixer
1313
- id: check-toml
14-
- repo: https://github.com/pre-commit/pygrep-hooks
15-
rev: v1.5.1
16-
hooks:
17-
- id: python-use-type-annotations
1814
- repo: local
1915
hooks:
2016
- id: black

CHANGELOG.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,56 @@ This project follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) fo
77

88
## [Unreleased]
99

10+
11+
## 1.23.0 - 2021-12-20
12+
13+
### Bug Fixes
14+
15+
* **apigateway:** allow list of HTTP methods in route method ([#838](https://github.com/awslabs/aws-lambda-powertools-python/issues/838))
16+
* **event-sources:** pass authorizer data to APIGatewayEventAuthorizer ([#897](https://github.com/awslabs/aws-lambda-powertools-python/issues/897))
17+
* **event-sources:** handle claimsOverrideDetails set to null ([#878](https://github.com/awslabs/aws-lambda-powertools-python/issues/878))
18+
* **idempotency:** include decorated fn name in hash ([#869](https://github.com/awslabs/aws-lambda-powertools-python/issues/869))
19+
* **metrics:** explicit type to single_metric ctx manager ([#865](https://github.com/awslabs/aws-lambda-powertools-python/issues/865))
20+
* **parameters:** mypy appconfig transform and return types ([#877](https://github.com/awslabs/aws-lambda-powertools-python/issues/877))
21+
* **parser:** mypy overload parse when using envelope ([#885](https://github.com/awslabs/aws-lambda-powertools-python/issues/885))
22+
* **parser:** kinesis sequence number is str, not int ([#907](https://github.com/awslabs/aws-lambda-powertools-python/issues/907))
23+
* **parser:** mypy support for payload type override as models ([#883](https://github.com/awslabs/aws-lambda-powertools-python/issues/883))
24+
* **tracer:** add warm start annotation (ColdStart=False) ([#851](https://github.com/awslabs/aws-lambda-powertools-python/issues/851))
25+
26+
### Documentation
27+
28+
* **nav**: reference cloudformation custom resource helper (CRD) ([#914](https://github.com/awslabs/aws-lambda-powertools-python/issues/914))
29+
* add new public Slack invite
30+
* disable search blur in non-prod env
31+
* update Lambda Layers version
32+
* **apigateway:** add new not_found feature ([#915](https://github.com/awslabs/aws-lambda-powertools-python/issues/915))
33+
* **apigateway:** fix sample layout provided ([#864](https://github.com/awslabs/aws-lambda-powertools-python/issues/864))
34+
* **appsync:** fix users.py typo to locations [#830](https://github.com/awslabs/aws-lambda-powertools-python/issues/830)
35+
* **lambda_layer:** fix CDK layer syntax
36+
37+
### Features
38+
39+
* **apigateway:** add exception_handler support ([#898](https://github.com/awslabs/aws-lambda-powertools-python/issues/898))
40+
* **apigateway:** access parent api resolver from router ([#842](https://github.com/awslabs/aws-lambda-powertools-python/issues/842))
41+
* **batch:** new BatchProcessor for SQS, DynamoDB, Kinesis ([#886](https://github.com/awslabs/aws-lambda-powertools-python/issues/886))
42+
* **logger:** allow handler with custom kwargs signature ([#913](https://github.com/awslabs/aws-lambda-powertools-python/issues/913))
43+
* **tracer:** add service annotation when service is set ([#861](https://github.com/awslabs/aws-lambda-powertools-python/issues/861))
44+
45+
### Maintenance
46+
47+
* minor housekeeping before release ([#912](https://github.com/awslabs/aws-lambda-powertools-python/issues/912))
48+
* correct pr label order
49+
* **ci:** split latest docs workflow
50+
* **deps:** bump fastjsonschema from 2.15.1 to 2.15.2 ([#891](https://github.com/awslabs/aws-lambda-powertools-python/issues/891))
51+
* **deps:** bump actions/setup-python from 2.2.2 to 2.3.0 ([#831](https://github.com/awslabs/aws-lambda-powertools-python/issues/831))
52+
* **deps:** support arm64 when developing locally ([#862](https://github.com/awslabs/aws-lambda-powertools-python/issues/862))
53+
* **deps:** bump actions/setup-python from 2.3.0 to 2.3.1 ([#852](https://github.com/awslabs/aws-lambda-powertools-python/issues/852))
54+
* **deps:** bump aws-xray-sdk from 2.8.0 to 2.9.0 ([#876](https://github.com/awslabs/aws-lambda-powertools-python/issues/876))
55+
* **deps-dev:** bump mypy from 0.910 to 0.920 ([#903](https://github.com/awslabs/aws-lambda-powertools-python/issues/903))
56+
* **deps-dev:** bump flake8 from 3.9.2 to 4.0.1 ([#789](https://github.com/awslabs/aws-lambda-powertools-python/issues/789))
57+
* **deps-dev:** bump black from 21.10b0 to 21.11b1 ([#839](https://github.com/awslabs/aws-lambda-powertools-python/issues/839))
58+
* **deps-dev:** bump black from 21.11b1 to 21.12b0 ([#872](https://github.com/awslabs/aws-lambda-powertools-python/issues/872))
59+
1060
## 1.22.0 - 2021-11-17
1161

1262
Tenet update! We've updated **Idiomatic** tenet to **Progressive** to reflect the new Router feature in Event Handler, and more importantly the new wave of customers coming from SRE, Data Analysis, and Data Science background.

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ With [pip](https://pip.pypa.io/en/latest/index.html) installed, run: ``pip insta
3838
* [Serverless Shopping cart](https://github.com/aws-samples/aws-serverless-shopping-cart)
3939
* [Serverless Airline](https://github.com/aws-samples/aws-serverless-airline-booking)
4040
* [Serverless E-commerce platform](https://github.com/aws-samples/aws-serverless-ecommerce-platform)
41+
* [Serverless GraphQL Nanny Booking Api](https://github.com/trey-rosius/babysitter_api)
4142

4243
## Credits
4344

aws_lambda_powertools/event_handler/api_gateway.py

Lines changed: 52 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@
1010
from enum import Enum
1111
from functools import partial
1212
from http import HTTPStatus
13-
from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Union
13+
from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Type, Union
1414

1515
from aws_lambda_powertools.event_handler import content_types
16-
from aws_lambda_powertools.event_handler.exceptions import ServiceError
16+
from aws_lambda_powertools.event_handler.exceptions import NotFoundError, ServiceError
1717
from aws_lambda_powertools.shared import constants
1818
from aws_lambda_powertools.shared.functions import resolve_truthy_env_var_choice
1919
from aws_lambda_powertools.shared.json_encoder import Encoder
@@ -27,7 +27,6 @@
2727
_SAFE_URI = "-._~()'!*:@,;" # https://www.ietf.org/rfc/rfc3986.txt
2828
# API GW/ALB decode non-safe URI chars; we must support them too
2929
_UNSAFE_URI = "%<>\[\]{}|^" # noqa: W605
30-
3130
_NAMED_GROUP_BOUNDARY_PATTERN = fr"(?P\1[{_SAFE_URI}{_UNSAFE_URI}\\w]+)"
3231

3332

@@ -435,6 +434,7 @@ def __init__(
435434
self._proxy_type = proxy_type
436435
self._routes: List[Route] = []
437436
self._route_keys: List[str] = []
437+
self._exception_handlers: Dict[Type, Callable] = {}
438438
self._cors = cors
439439
self._cors_enabled: bool = cors is not None
440440
self._cors_methods: Set[str] = {"OPTIONS"}
@@ -579,7 +579,7 @@ def _remove_prefix(self, path: str) -> str:
579579
@staticmethod
580580
def _path_starts_with(path: str, prefix: str):
581581
"""Returns true if the `path` starts with a prefix plus a `/`"""
582-
if not isinstance(prefix, str) or len(prefix) == 0:
582+
if not isinstance(prefix, str) or prefix == "":
583583
return False
584584

585585
return path.startswith(prefix + "/")
@@ -596,6 +596,10 @@ def _not_found(self, method: str) -> ResponseBuilder:
596596
headers["Access-Control-Allow-Methods"] = ",".join(sorted(self._cors_methods))
597597
return ResponseBuilder(Response(status_code=204, content_type=None, headers=headers, body=None))
598598

599+
handler = self._lookup_exception_handler(NotFoundError)
600+
if handler:
601+
return ResponseBuilder(handler(NotFoundError()))
602+
599603
return ResponseBuilder(
600604
Response(
601605
status_code=HTTPStatus.NOT_FOUND.value,
@@ -609,16 +613,11 @@ def _call_route(self, route: Route, args: Dict[str, str]) -> ResponseBuilder:
609613
"""Actually call the matching route with any provided keyword arguments."""
610614
try:
611615
return ResponseBuilder(self._to_response(route.func(**args)), route)
612-
except ServiceError as e:
613-
return ResponseBuilder(
614-
Response(
615-
status_code=e.status_code,
616-
content_type=content_types.APPLICATION_JSON,
617-
body=self._json_dump({"statusCode": e.status_code, "message": e.msg}),
618-
),
619-
route,
620-
)
621-
except Exception:
616+
except Exception as exc:
617+
response_builder = self._call_exception_handler(exc, route)
618+
if response_builder:
619+
return response_builder
620+
622621
if self._debug:
623622
# If the user has turned on debug mode,
624623
# we'll let the original exception propagate so
@@ -628,10 +627,48 @@ def _call_route(self, route: Route, args: Dict[str, str]) -> ResponseBuilder:
628627
status_code=500,
629628
content_type=content_types.TEXT_PLAIN,
630629
body="".join(traceback.format_exc()),
631-
)
630+
),
631+
route,
632632
)
633+
633634
raise
634635

636+
def not_found(self, func: Optional[Callable] = None):
637+
if func is None:
638+
return self.exception_handler(NotFoundError)
639+
return self.exception_handler(NotFoundError)(func)
640+
641+
def exception_handler(self, exc_class: Type[Exception]):
642+
def register_exception_handler(func: Callable):
643+
self._exception_handlers[exc_class] = func
644+
645+
return register_exception_handler
646+
647+
def _lookup_exception_handler(self, exp_type: Type) -> Optional[Callable]:
648+
# Use "Method Resolution Order" to allow for matching against a base class
649+
# of an exception
650+
for cls in exp_type.__mro__:
651+
if cls in self._exception_handlers:
652+
return self._exception_handlers[cls]
653+
return None
654+
655+
def _call_exception_handler(self, exp: Exception, route: Route) -> Optional[ResponseBuilder]:
656+
handler = self._lookup_exception_handler(type(exp))
657+
if handler:
658+
return ResponseBuilder(handler(exp), route)
659+
660+
if isinstance(exp, ServiceError):
661+
return ResponseBuilder(
662+
Response(
663+
status_code=exp.status_code,
664+
content_type=content_types.APPLICATION_JSON,
665+
body=self._json_dump({"statusCode": exp.status_code, "message": exp.msg}),
666+
),
667+
route,
668+
)
669+
670+
return None
671+
635672
def _to_response(self, result: Union[Dict, Response]) -> Response:
636673
"""Convert the route's result to a Response
637674

aws_lambda_powertools/logging/logger.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ def handler(event, context):
328328
)
329329

330330
@functools.wraps(lambda_handler)
331-
def decorate(event, context):
331+
def decorate(event, context, **kwargs):
332332
lambda_context = build_lambda_context_model(context)
333333
cold_start = _is_cold_start()
334334

aws_lambda_powertools/shared/functions.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,23 @@
1-
from distutils.util import strtobool
21
from typing import Any, Optional, Union
32

43

4+
def strtobool(value: str) -> bool:
5+
"""Convert a string representation of truth to True or False.
6+
7+
True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values
8+
are 'n', 'no', 'f', 'false', 'off', and '0'. Raises ValueError if
9+
'value' is anything else.
10+
11+
> note:: Copied from distutils.util.
12+
"""
13+
value = value.lower()
14+
if value in ("y", "yes", "t", "true", "on", "1"):
15+
return True
16+
if value in ("n", "no", "f", "false", "off", "0"):
17+
return False
18+
raise ValueError(f"invalid truth value {value!r}")
19+
20+
521
def resolve_truthy_env_var_choice(env: str, choice: Optional[bool] = None) -> bool:
622
"""Pick explicit choice over truthy env value, if available, otherwise return truthy env value
723

aws_lambda_powertools/tracing/tracer.py

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import logging
66
import numbers
77
import os
8-
from typing import Any, Callable, Dict, Optional, Sequence, Union, cast, overload
8+
from typing import Any, Callable, Dict, List, Optional, Sequence, Union, cast, overload
99

1010
from ..shared import constants
1111
from ..shared.functions import resolve_env_var_choice, resolve_truthy_env_var_choice
@@ -758,7 +758,7 @@ def _patch_xray_provider(self):
758758
# Due to Lazy Import, we need to activate `core` attrib via import
759759
# we also need to include `patch`, `patch_all` methods
760760
# to ensure patch calls are done via the provider
761-
from aws_xray_sdk.core import xray_recorder
761+
from aws_xray_sdk.core import xray_recorder # type: ignore
762762

763763
provider = xray_recorder
764764
provider.patch = aws_xray_sdk.core.patch
@@ -778,3 +778,27 @@ def _disable_xray_trace_batching(self):
778778

779779
def _is_xray_provider(self):
780780
return "aws_xray_sdk" in self.provider.__module__
781+
782+
def ignore_endpoint(self, hostname: Optional[str] = None, urls: Optional[List[str]] = None):
783+
"""If you want to ignore certain httplib requests you can do so based on the hostname or URL that is being
784+
requested.
785+
786+
> NOTE: If the provider is not xray, nothing will be added to ignore list
787+
788+
Documentation
789+
--------------
790+
- https://github.com/aws/aws-xray-sdk-python#ignoring-httplib-requests
791+
792+
Parameters
793+
----------
794+
hostname : Optional, str
795+
The hostname is matched using the Python fnmatch library which does Unix glob style matching.
796+
urls: Optional, List[str]
797+
List of urls to ignore. Example `tracer.ignore_endpoint(urls=["/ignored-url"])`
798+
"""
799+
if not self._is_xray_provider():
800+
return
801+
802+
from aws_xray_sdk.ext.httplib import add_ignored # type: ignore
803+
804+
add_ignored(hostname=hostname, urls=urls)

aws_lambda_powertools/utilities/batch/__init__.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,25 @@
44
Batch processing utility
55
"""
66

7-
from .base import BasePartialProcessor, batch_processor
8-
from .sqs import PartialSQSProcessor, sqs_batch_processor
7+
from aws_lambda_powertools.utilities.batch.base import (
8+
BasePartialProcessor,
9+
BatchProcessor,
10+
EventType,
11+
FailureResponse,
12+
SuccessResponse,
13+
batch_processor,
14+
)
15+
from aws_lambda_powertools.utilities.batch.exceptions import ExceptionInfo
16+
from aws_lambda_powertools.utilities.batch.sqs import PartialSQSProcessor, sqs_batch_processor
917

10-
__all__ = ("BasePartialProcessor", "PartialSQSProcessor", "batch_processor", "sqs_batch_processor")
18+
__all__ = (
19+
"BatchProcessor",
20+
"BasePartialProcessor",
21+
"ExceptionInfo",
22+
"EventType",
23+
"FailureResponse",
24+
"PartialSQSProcessor",
25+
"SuccessResponse",
26+
"batch_processor",
27+
"sqs_batch_processor",
28+
)

0 commit comments

Comments
 (0)