diff --git a/.github/actions/create-pr/action.yml b/.github/actions/create-pr/action.yml
index dcf2df738bd..39ba6f60b1f 100644
--- a/.github/actions/create-pr/action.yml
+++ b/.github/actions/create-pr/action.yml
@@ -64,7 +64,7 @@ runs:
name: Git client setup and refresh tip
run: |
git config user.name "Powertools for AWS Lambda (Python) bot"
- git config user.email "aws-lambda-powertools-feedback@amazon.com"
+ git config user.email "151832416+aws-powertools-bot@users.noreply.github.com"
git config pull.rebase true
git config remote.origin.url >&-
shell: bash
diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml
index 3fee4d6b427..4537173ac39 100644
--- a/.github/workflows/dependency-review.yml
+++ b/.github/workflows/dependency-review.yml
@@ -19,4 +19,4 @@ jobs:
- name: 'Checkout Repository'
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: 'Dependency Review'
- uses: actions/dependency-review-action@4901385134134e04cec5fbe5ddfe3b2c5bd5d976 # v4.0.0
+ uses: actions/dependency-review-action@9129d7d40b8c12c1ed0f60400d00c92d437adcce # v4.1.3
diff --git a/.github/workflows/publish_v2_layer.yml b/.github/workflows/publish_v2_layer.yml
index 3dfb4cc1446..ab6a59a09a8 100644
--- a/.github/workflows/publish_v2_layer.yml
+++ b/.github/workflows/publish_v2_layer.yml
@@ -257,11 +257,12 @@ jobs:
integrity_hash: ${{ inputs.source_code_integrity_hash }}
artifact_name: ${{ inputs.source_code_artifact_name }}
- - name: Download CDK layer artifact
+ - name: Download CDK layer artifacts
uses: actions/download-artifact@eaceaf801fd36c7dee90939fad912460b18a1ffe # v4.1.2
with:
- name: cdk-layer-stack
- path: cdk-layer-stack/
+ path: cdk-layer-stack
+ pattern: cdk-layer-stack-* # merge all Layer artifacts created per region earlier (reusable_deploy_v2_layer_stack.yml; step "Save Layer ARN artifact")
+ merge-multiple: true
- name: Replace layer versions in documentation
run: |
ls -la cdk-layer-stack/
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index fac762bdbeb..d9c7b9d8cad 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -273,7 +273,7 @@ jobs:
name: Git client setup and refresh tip
run: |
git config user.name "Powertools for AWS Lambda (Python) bot"
- git config user.email "aws-lambda-powertools-feedback@amazon.com"
+ git config user.email "151832416+aws-powertools-bot@users.noreply.github.com"
git config remote.origin.url >&-
- name: Create Git Tag
diff --git a/.github/workflows/reusable_deploy_v2_layer_stack.yml b/.github/workflows/reusable_deploy_v2_layer_stack.yml
index dd7c5384970..c022ac06c0d 100644
--- a/.github/workflows/reusable_deploy_v2_layer_stack.yml
+++ b/.github/workflows/reusable_deploy_v2_layer_stack.yml
@@ -193,16 +193,15 @@ jobs:
run: |
mkdir cdk-layer-stack
jq -r -c '.LayerV2Stack.LatestLayerArn' cdk-outputs.json > cdk-layer-stack/${{ matrix.region }}-layer-version.txt
- jq -r -c '.LayerV2Stack.LatestLayerArm64Arn' cdk-outputs.json >> cdk-layer-stack/${{ matrix.region }}-layer-version.txt
+ jq -r -c '.LayerV2Stack.LatestLayerArm64Arn' cdk-outputs.json > cdk-layer-stack/${{ matrix.region }}-layer-version.txt
cat cdk-layer-stack/${{ matrix.region }}-layer-version.txt
- name: Save Layer ARN artifact
if: ${{ inputs.stage == 'PROD' }}
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
with:
- name: cdk-layer-stack
+ name: cdk-layer-stack-${{ matrix.region }}
path: ./layer/cdk-layer-stack/* # NOTE: upload-artifact does not inherit working-directory setting.
if-no-files-found: error
retention-days: 1
- overwrite: true
- name: CDK Deploy Canary
run: npx cdk deploy --app cdk.out --context region=${{ matrix.region }} --parameters DeployStage="${{ inputs.stage }}" --parameters HasARM64Support=${{ matrix.has_arm64_support }} 'CanaryV2Stack' --require-approval never --verbose
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 45aa708bb97..2fc9e714fbf 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,37 +4,95 @@
# Unreleased
+## Bug Fixes
+
+* **event-handler:** multi-value query string and validation of scalar parameters ([#3795](https://github.com/aws-powertools/powertools-lambda-python/issues/3795))
+* **event-handler:** swagger schema respects api stage ([#3796](https://github.com/aws-powertools/powertools-lambda-python/issues/3796))
+* **event-handler:** handle aliased parameters e.g., Query(alias="categoryType") ([#3766](https://github.com/aws-powertools/powertools-lambda-python/issues/3766))
+
+## Code Refactoring
+
+* **feature-flags:** add intersection tests; structure refinement ([#3775](https://github.com/aws-powertools/powertools-lambda-python/issues/3775))
+
+## Documentation
+
+* **feature_flags:** fix incorrect line markers and envelope name ([#3792](https://github.com/aws-powertools/powertools-lambda-python/issues/3792))
+* **home:** update layer version to 62 for package version 2.33.1 ([#3778](https://github.com/aws-powertools/powertools-lambda-python/issues/3778))
+* **home:** add note about POWERTOOLS_DEV side effects in CloudWatch Logs ([#3770](https://github.com/aws-powertools/powertools-lambda-python/issues/3770))
+* **homepage:** discord flat badge style; remove former devax email ([#3768](https://github.com/aws-powertools/powertools-lambda-python/issues/3768))
+* **homepage:** remove leftover announcement banner ([#3783](https://github.com/aws-powertools/powertools-lambda-python/issues/3783))
+* **roadmap:** latest roadmap update; use new grid to de-clutter homepage ([#3755](https://github.com/aws-powertools/powertools-lambda-python/issues/3755))
+* **we-made-this:** add reinvent 2023 session ([#3790](https://github.com/aws-powertools/powertools-lambda-python/issues/3790))
+
+## Features
+
+* **feature_flags:** add intersect actions for conditions ([#3692](https://github.com/aws-powertools/powertools-lambda-python/issues/3692))
+
+## Maintenance
+
+* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 1 update ([#3784](https://github.com/aws-powertools/powertools-lambda-python/issues/3784))
+* **deps:** bump actions/dependency-review-action from 4.0.0 to 4.1.0 ([#3771](https://github.com/aws-powertools/powertools-lambda-python/issues/3771))
+* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 3 updates ([#3764](https://github.com/aws-powertools/powertools-lambda-python/issues/3764))
+* **deps:** bump squidfunk/mkdocs-material from `6a72238` to `62d3668` in /docs ([#3756](https://github.com/aws-powertools/powertools-lambda-python/issues/3756))
+* **deps:** bump squidfunk/mkdocs-material from `62d3668` to `43b898a` in /docs ([#3801](https://github.com/aws-powertools/powertools-lambda-python/issues/3801))
+* **deps:** bump actions/dependency-review-action from 4.1.0 to 4.1.2 ([#3800](https://github.com/aws-powertools/powertools-lambda-python/issues/3800))
+* **deps-dev:** bump cfn-lint from 0.85.1 to 0.85.2 ([#3786](https://github.com/aws-powertools/powertools-lambda-python/issues/3786))
+* **deps-dev:** bump pytest-asyncio from 0.21.1 to 0.23.5 ([#3773](https://github.com/aws-powertools/powertools-lambda-python/issues/3773))
+* **deps-dev:** bump aws-cdk from 2.127.0 to 2.128.0 ([#3776](https://github.com/aws-powertools/powertools-lambda-python/issues/3776))
+* **deps-dev:** bump sentry-sdk from 1.40.3 to 1.40.4 ([#3765](https://github.com/aws-powertools/powertools-lambda-python/issues/3765))
+* **deps-dev:** bump the boto-typing group with 2 updates ([#3797](https://github.com/aws-powertools/powertools-lambda-python/issues/3797))
+* **deps-dev:** bump aws-cdk-lib from 2.127.0 to 2.128.0 ([#3777](https://github.com/aws-powertools/powertools-lambda-python/issues/3777))
+* **deps-dev:** bump mkdocs-material from 9.5.9 to 9.5.10 ([#3803](https://github.com/aws-powertools/powertools-lambda-python/issues/3803))
+* **deps-dev:** bump the boto-typing group with 1 update ([#3757](https://github.com/aws-powertools/powertools-lambda-python/issues/3757))
+* **deps-dev:** bump aws-cdk-lib from 2.126.0 to 2.127.0 ([#3758](https://github.com/aws-powertools/powertools-lambda-python/issues/3758))
+* **deps-dev:** bump aws-cdk from 2.126.0 to 2.127.0 ([#3761](https://github.com/aws-powertools/powertools-lambda-python/issues/3761))
+* **deps-dev:** bump mkdocs-material from 9.5.8 to 9.5.9 ([#3759](https://github.com/aws-powertools/powertools-lambda-python/issues/3759))
+* **deps-dev:** bump sentry-sdk from 1.40.2 to 1.40.3 ([#3750](https://github.com/aws-powertools/powertools-lambda-python/issues/3750))
+* **deps-dev:** bump cfn-lint from 0.85.0 to 0.85.1 ([#3749](https://github.com/aws-powertools/powertools-lambda-python/issues/3749))
+* **deps-dev:** bump types-redis from 4.6.0.20240106 to 4.6.0.20240218 ([#3804](https://github.com/aws-powertools/powertools-lambda-python/issues/3804))
+* **deps-dev:** bump sentry-sdk from 1.40.4 to 1.40.5 ([#3805](https://github.com/aws-powertools/powertools-lambda-python/issues/3805))
+
+
+
+## [v2.33.1] - 2024-02-09
+## Bug Fixes
+
+* **typing:** make Response headers covariant ([#3745](https://github.com/aws-powertools/powertools-lambda-python/issues/3745))
+
## Documentation
* Add nathan hanks post community ([#3727](https://github.com/aws-powertools/powertools-lambda-python/issues/3727))
## Maintenance
+* version bump
* **ci:** drop support for Python 3.7 ([#3638](https://github.com/aws-powertools/powertools-lambda-python/issues/3638))
* **ci:** enable Redis e2e tests ([#3718](https://github.com/aws-powertools/powertools-lambda-python/issues/3718))
-* **deps:** bump actions/download-artifact from 3.0.2 to 4.1.1 ([#3612](https://github.com/aws-powertools/powertools-lambda-python/issues/3612))
+* **deps:** bump actions/setup-node from 4.0.1 to 4.0.2 ([#3737](https://github.com/aws-powertools/powertools-lambda-python/issues/3737))
+* **deps:** bump squidfunk/mkdocs-material from `e0d6c67` to `6a72238` in /docs ([#3735](https://github.com/aws-powertools/powertools-lambda-python/issues/3735))
* **deps:** bump actions/dependency-review-action from 3.1.5 to 4.0.0 ([#3646](https://github.com/aws-powertools/powertools-lambda-python/issues/3646))
-* **deps:** bump actions/download-artifact from 4.1.1 to 4.1.2 ([#3725](https://github.com/aws-powertools/powertools-lambda-python/issues/3725))
* **deps:** bump release-drafter/release-drafter from 5.25.0 to 6.0.0 ([#3699](https://github.com/aws-powertools/powertools-lambda-python/issues/3699))
-* **deps:** revert aws-cdk-lib as a runtime dep ([#3730](https://github.com/aws-powertools/powertools-lambda-python/issues/3730))
+* **deps:** bump actions/download-artifact from 4.1.1 to 4.1.2 ([#3725](https://github.com/aws-powertools/powertools-lambda-python/issues/3725))
* **deps:** bump squidfunk/mkdocs-material from `a4a2029` to `e0d6c67` in /docs ([#3708](https://github.com/aws-powertools/powertools-lambda-python/issues/3708))
-* **deps:** bump actions/upload-artifact from 3.1.3 to 4.3.1 ([#3714](https://github.com/aws-powertools/powertools-lambda-python/issues/3714))
-* **deps:** bump squidfunk/mkdocs-material from `e0d6c67` to `6a72238` in /docs ([#3735](https://github.com/aws-powertools/powertools-lambda-python/issues/3735))
-* **deps:** bump actions/setup-node from 4.0.1 to 4.0.2 ([#3737](https://github.com/aws-powertools/powertools-lambda-python/issues/3737))
* **deps:** bump codecov/codecov-action from 3.1.6 to 4.0.1 ([#3700](https://github.com/aws-powertools/powertools-lambda-python/issues/3700))
-* **deps-dev:** bump mypy from 1.4.1 to 1.8.0 ([#3710](https://github.com/aws-powertools/powertools-lambda-python/issues/3710))
+* **deps:** bump actions/download-artifact from 3.0.2 to 4.1.1 ([#3612](https://github.com/aws-powertools/powertools-lambda-python/issues/3612))
+* **deps:** revert aws-cdk-lib as a runtime dep ([#3730](https://github.com/aws-powertools/powertools-lambda-python/issues/3730))
+* **deps:** bump actions/upload-artifact from 3.1.3 to 4.3.1 ([#3714](https://github.com/aws-powertools/powertools-lambda-python/issues/3714))
+* **deps-dev:** bump cfn-lint from 0.83.8 to 0.85.0 ([#3724](https://github.com/aws-powertools/powertools-lambda-python/issues/3724))
* **deps-dev:** bump httpx from 0.24.1 to 0.26.0 ([#3712](https://github.com/aws-powertools/powertools-lambda-python/issues/3712))
+* **deps-dev:** bump pytest from 7.4.4 to 8.0.0 ([#3711](https://github.com/aws-powertools/powertools-lambda-python/issues/3711))
+* **deps-dev:** bump sentry-sdk from 1.40.1 to 1.40.2 ([#3740](https://github.com/aws-powertools/powertools-lambda-python/issues/3740))
* **deps-dev:** bump coverage from 7.2.7 to 7.4.1 ([#3713](https://github.com/aws-powertools/powertools-lambda-python/issues/3713))
* **deps-dev:** bump the boto-typing group with 7 updates ([#3709](https://github.com/aws-powertools/powertools-lambda-python/issues/3709))
-* **deps-dev:** bump pytest from 7.4.4 to 8.0.0 ([#3711](https://github.com/aws-powertools/powertools-lambda-python/issues/3711))
* **deps-dev:** bump types-python-dateutil from 2.8.19.14 to 2.8.19.20240106 ([#3720](https://github.com/aws-powertools/powertools-lambda-python/issues/3720))
-* **deps-dev:** bump cfn-lint from 0.83.8 to 0.85.0 ([#3724](https://github.com/aws-powertools/powertools-lambda-python/issues/3724))
+* **deps-dev:** bump mypy from 1.4.1 to 1.8.0 ([#3710](https://github.com/aws-powertools/powertools-lambda-python/issues/3710))
+* **deps-dev:** bump ruff from 0.2.0 to 0.2.1 ([#3742](https://github.com/aws-powertools/powertools-lambda-python/issues/3742))
* **deps-dev:** bump isort from 5.11.5 to 5.13.2 ([#3723](https://github.com/aws-powertools/powertools-lambda-python/issues/3723))
-* **deps-dev:** bump sentry-sdk from 1.40.1 to 1.40.2 ([#3740](https://github.com/aws-powertools/powertools-lambda-python/issues/3740))
+* **deps-dev:** bump pytest-socket from 0.6.0 to 0.7.0 ([#3721](https://github.com/aws-powertools/powertools-lambda-python/issues/3721))
* **deps-dev:** bump ruff from 0.1.15 to 0.2.0 ([#3702](https://github.com/aws-powertools/powertools-lambda-python/issues/3702))
* **deps-dev:** bump aws-cdk from 2.125.0 to 2.126.0 ([#3701](https://github.com/aws-powertools/powertools-lambda-python/issues/3701))
+* **deps-dev:** bump hvac from 1.2.1 to 2.1.0 ([#3738](https://github.com/aws-powertools/powertools-lambda-python/issues/3738))
* **deps-dev:** bump black from 23.12.1 to 24.1.1 ([#3739](https://github.com/aws-powertools/powertools-lambda-python/issues/3739))
-* **deps-dev:** bump ruff from 0.2.0 to 0.2.1 ([#3742](https://github.com/aws-powertools/powertools-lambda-python/issues/3742))
@@ -4343,7 +4401,8 @@
* Merge pull request [#5](https://github.com/aws-powertools/powertools-lambda-python/issues/5) from jfuss/feat/python38
-[Unreleased]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.33.0...HEAD
+[Unreleased]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.33.1...HEAD
+[v2.33.1]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.33.0...v2.33.1
[v2.33.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.32.0...v2.33.0
[v2.32.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.31.0...v2.32.0
[v2.31.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.30.2...v2.31.0
diff --git a/README.md b/README.md
index c1ab7abaf29..00e217c29c7 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@
[](https://github.com/aws-powertools/powertools-lambda-python/actions/workflows/python_build.yml)
[](https://app.codecov.io/gh/aws-powertools/powertools-lambda-python)
-   [](https://api.securityscorecards.dev/projects/github.com/aws-powertools/powertools-lambda-python) [](https://discord.gg/B8zZKbbyET)
+   [](https://api.securityscorecards.dev/projects/github.com/aws-powertools/powertools-lambda-python) [](https://discord.gg/B8zZKbbyET)
Powertools for AWS Lambda (Python) is a developer toolkit to implement Serverless [best practices and increase developer velocity](https://docs.powertools.aws.dev/lambda/python/latest/#features).
@@ -11,8 +11,6 @@ Powertools for AWS Lambda (Python) is a developer toolkit to implement Serverles
**[πDocumentation](https://docs.powertools.aws.dev/lambda/python/)** | **[πPyPi](https://pypi.org/project/aws-lambda-powertools/)** | **[Roadmap](https://docs.powertools.aws.dev/lambda/python/latest/roadmap/)** | **[Detailed blog post](https://aws.amazon.com/blogs/opensource/simplifying-serverless-best-practices-with-lambda-powertools/)**
-> **An AWS Developer Acceleration (DevAx) initiative by Specialist Solution Architects | **
-

## Features
@@ -83,7 +81,7 @@ This helps us understand who uses Powertools for AWS Lambda (Python) in a non-in
## Connect
* **Powertools for AWS Lambda on Discord**: `#python` - **[Invite link](https://discord.gg/B8zZKbbyET)**
-* **Email**:
+* **Email**:
## Security disclosures
diff --git a/aws_lambda_powertools/event_handler/api_gateway.py b/aws_lambda_powertools/event_handler/api_gateway.py
index 43b5bf139ea..271c767c060 100644
--- a/aws_lambda_powertools/event_handler/api_gateway.py
+++ b/aws_lambda_powertools/event_handler/api_gateway.py
@@ -1692,7 +1692,13 @@ def swagger_handler():
body=escaped_spec,
)
- body = generate_swagger_html(escaped_spec, path, swagger_js, swagger_css, swagger_base_url)
+ body = generate_swagger_html(
+ escaped_spec,
+ f"{base_path}{path}",
+ swagger_js,
+ swagger_css,
+ swagger_base_url,
+ )
return Response(
status_code=200,
diff --git a/aws_lambda_powertools/event_handler/middlewares/openapi_validation.py b/aws_lambda_powertools/event_handler/middlewares/openapi_validation.py
index 54c48189282..241a9972953 100644
--- a/aws_lambda_powertools/event_handler/middlewares/openapi_validation.py
+++ b/aws_lambda_powertools/event_handler/middlewares/openapi_validation.py
@@ -368,7 +368,10 @@ def _get_embed_body(
return received_body, field_alias_omitted
-def _normalize_multi_query_string_with_param(query_string: Optional[Dict[str, str]], params: Sequence[ModelField]):
+def _normalize_multi_query_string_with_param(
+ query_string: Dict[str, List[str]],
+ params: Sequence[ModelField],
+) -> Dict[str, Any]:
"""
Extract and normalize resolved_query_string_parameters
@@ -383,15 +386,15 @@ def _normalize_multi_query_string_with_param(query_string: Optional[Dict[str, st
-------
A dictionary containing the processed multi_query_string_parameters.
"""
- if query_string:
- for param in filter(is_scalar_field, params):
- try:
- # if the target parameter is a scalar, we keep the first value of the query string
- # regardless if there are more in the payload
- query_string[param.name] = query_string[param.name][0]
- except KeyError:
- pass
- return query_string
+ resolved_query_string: Dict[str, Any] = query_string
+ for param in filter(is_scalar_field, params):
+ try:
+ # if the target parameter is a scalar, we keep the first value of the query string
+ # regardless if there are more in the payload
+ resolved_query_string[param.alias] = query_string[param.alias][0]
+ except KeyError:
+ pass
+ return resolved_query_string
def _normalize_multi_header_values_with_param(headers: Optional[Dict[str, str]], params: Sequence[ModelField]):
diff --git a/aws_lambda_powertools/shared/version.py b/aws_lambda_powertools/shared/version.py
index 7cac1198767..4df200c6942 100644
--- a/aws_lambda_powertools/shared/version.py
+++ b/aws_lambda_powertools/shared/version.py
@@ -1,3 +1,3 @@
"""Exposes version constant to avoid circular dependencies."""
-VERSION = "2.33.0"
+VERSION = "2.34.0"
diff --git a/aws_lambda_powertools/utilities/data_classes/alb_event.py b/aws_lambda_powertools/utilities/data_classes/alb_event.py
index 98f37b4f415..1ec2535850b 100644
--- a/aws_lambda_powertools/utilities/data_classes/alb_event.py
+++ b/aws_lambda_powertools/utilities/data_classes/alb_event.py
@@ -36,11 +36,11 @@ def multi_value_query_string_parameters(self) -> Optional[Dict[str, List[str]]]:
return self.get("multiValueQueryStringParameters")
@property
- def resolved_query_string_parameters(self) -> Optional[Dict[str, Any]]:
+ def resolved_query_string_parameters(self) -> Dict[str, List[str]]:
if self.multi_value_query_string_parameters:
return self.multi_value_query_string_parameters
- return self.query_string_parameters
+ return super().resolved_query_string_parameters
@property
def resolved_headers_field(self) -> Optional[Dict[str, Any]]:
diff --git a/aws_lambda_powertools/utilities/data_classes/api_gateway_proxy_event.py b/aws_lambda_powertools/utilities/data_classes/api_gateway_proxy_event.py
index c37bd22ca53..ff24e908d1a 100644
--- a/aws_lambda_powertools/utilities/data_classes/api_gateway_proxy_event.py
+++ b/aws_lambda_powertools/utilities/data_classes/api_gateway_proxy_event.py
@@ -119,11 +119,11 @@ def multi_value_query_string_parameters(self) -> Optional[Dict[str, List[str]]]:
return self.get("multiValueQueryStringParameters")
@property
- def resolved_query_string_parameters(self) -> Optional[Dict[str, Any]]:
+ def resolved_query_string_parameters(self) -> Dict[str, List[str]]:
if self.multi_value_query_string_parameters:
return self.multi_value_query_string_parameters
- return self.query_string_parameters
+ return super().resolved_query_string_parameters
@property
def resolved_headers_field(self) -> Optional[Dict[str, Any]]:
@@ -318,16 +318,6 @@ def http_method(self) -> str:
def header_serializer(self):
return HttpApiHeadersSerializer()
- @property
- def resolved_query_string_parameters(self) -> Optional[Dict[str, Any]]:
- if self.query_string_parameters is not None:
- query_string = {
- key: value.split(",") if "," in value else value for key, value in self.query_string_parameters.items()
- }
- return query_string
-
- return {}
-
@property
def resolved_headers_field(self) -> Optional[Dict[str, Any]]:
if self.headers is not None:
diff --git a/aws_lambda_powertools/utilities/data_classes/bedrock_agent_event.py b/aws_lambda_powertools/utilities/data_classes/bedrock_agent_event.py
index 0fa97036a3e..399c435b3ec 100644
--- a/aws_lambda_powertools/utilities/data_classes/bedrock_agent_event.py
+++ b/aws_lambda_powertools/utilities/data_classes/bedrock_agent_event.py
@@ -109,10 +109,6 @@ def query_string_parameters(self) -> Optional[Dict[str, str]]:
# together with the other parameters. So we just return all parameters here.
return {x["name"]: x["value"] for x in self["parameters"]} if self.get("parameters") else None
- @property
- def resolved_query_string_parameters(self) -> Optional[Dict[str, str]]:
- return self.query_string_parameters
-
@property
def resolved_headers_field(self) -> Optional[Dict[str, Any]]:
return {}
diff --git a/aws_lambda_powertools/utilities/data_classes/common.py b/aws_lambda_powertools/utilities/data_classes/common.py
index 25fb5a4c170..067706140fd 100644
--- a/aws_lambda_powertools/utilities/data_classes/common.py
+++ b/aws_lambda_powertools/utilities/data_classes/common.py
@@ -104,7 +104,7 @@ def query_string_parameters(self) -> Optional[Dict[str, str]]:
return self.get("queryStringParameters")
@property
- def resolved_query_string_parameters(self) -> Optional[Dict[str, str]]:
+ def resolved_query_string_parameters(self) -> Dict[str, List[str]]:
"""
This property determines the appropriate query string parameter to be used
as a trusted source for validating OpenAPI.
@@ -112,7 +112,11 @@ def resolved_query_string_parameters(self) -> Optional[Dict[str, str]]:
This is necessary because different resolvers use different formats to encode
multi query string parameters.
"""
- return self.query_string_parameters
+ if self.query_string_parameters is not None:
+ query_string = {key: value.split(",") for key, value in self.query_string_parameters.items()}
+ return query_string
+
+ return {}
@property
def resolved_headers_field(self) -> Optional[Dict[str, Any]]:
@@ -186,8 +190,7 @@ def get_header_value(
name: str,
default_value: str,
case_sensitive: Optional[bool] = False,
- ) -> str:
- ...
+ ) -> str: ...
@overload
def get_header_value(
@@ -195,8 +198,7 @@ def get_header_value(
name: str,
default_value: Optional[str] = None,
case_sensitive: Optional[bool] = False,
- ) -> Optional[str]:
- ...
+ ) -> Optional[str]: ...
def get_header_value(
self,
diff --git a/aws_lambda_powertools/utilities/data_classes/vpc_lattice.py b/aws_lambda_powertools/utilities/data_classes/vpc_lattice.py
index 15144e41d7d..f997d4b3f04 100644
--- a/aws_lambda_powertools/utilities/data_classes/vpc_lattice.py
+++ b/aws_lambda_powertools/utilities/data_classes/vpc_lattice.py
@@ -73,8 +73,7 @@ def get_header_value(
name: str,
default_value: str,
case_sensitive: Optional[bool] = False,
- ) -> str:
- ...
+ ) -> str: ...
@overload
def get_header_value(
@@ -82,8 +81,7 @@ def get_header_value(
name: str,
default_value: Optional[str] = None,
case_sensitive: Optional[bool] = False,
- ) -> Optional[str]:
- ...
+ ) -> Optional[str]: ...
def get_header_value(
self,
@@ -140,10 +138,6 @@ def query_string_parameters(self) -> Dict[str, str]:
"""The request query string parameters."""
return self["query_string_parameters"]
- @property
- def resolved_query_string_parameters(self) -> Optional[Dict[str, str]]:
- return self.query_string_parameters
-
@property
def resolved_headers_field(self) -> Optional[Dict[str, Any]]:
if self.headers is not None:
@@ -255,17 +249,21 @@ def path(self) -> str:
@property
def request_context(self) -> vpcLatticeEventV2RequestContext:
- """he VPC Lattice v2 Event request context."""
+ """The VPC Lattice v2 Event request context."""
return vpcLatticeEventV2RequestContext(self["requestContext"])
@property
def query_string_parameters(self) -> Optional[Dict[str, str]]:
- """The request query string parameters."""
- return self.get("queryStringParameters")
+ """The request query string parameters.
- @property
- def resolved_query_string_parameters(self) -> Optional[Dict[str, str]]:
- return self.query_string_parameters
+ For VPC Lattice V2, the queryStringParameters will contain a Dict[str, List[str]]
+ so to keep compatibility with existing utilities, we merge all the values with a comma.
+ """
+ params = self.get("queryStringParameters")
+ if params:
+ return {key: ",".join(value) for key, value in params.items()}
+ else:
+ return None
@property
def resolved_headers_field(self) -> Optional[Dict[str, str]]:
diff --git a/aws_lambda_powertools/utilities/feature_flags/__init__.py b/aws_lambda_powertools/utilities/feature_flags/__init__.py
index db7dfca5b57..e8d8229c9dc 100644
--- a/aws_lambda_powertools/utilities/feature_flags/__init__.py
+++ b/aws_lambda_powertools/utilities/feature_flags/__init__.py
@@ -1,4 +1,5 @@
"""Advanced feature flags utility"""
+
from .appconfig import AppConfigStore
from .base import StoreProvider
from .exceptions import ConfigurationStoreError
diff --git a/aws_lambda_powertools/utilities/feature_flags/comparators.py b/aws_lambda_powertools/utilities/feature_flags/comparators.py
index 78370f1b5b1..03cb91e649a 100644
--- a/aws_lambda_powertools/utilities/feature_flags/comparators.py
+++ b/aws_lambda_powertools/utilities/feature_flags/comparators.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from datetime import datetime, tzinfo
from typing import Any, Dict, Optional
@@ -82,3 +84,66 @@ def compare_modulo_range(context_value: int, condition_value: Dict) -> bool:
end = condition_value.get(ModuloRangeValues.END.value, 1)
return start <= context_value % base <= end
+
+
+def compare_any_in_list(context_value: list, condition_value: list) -> bool:
+ """Comparator for ANY_IN_VALUE action
+
+ Parameters
+ ----------
+ context_value : list
+ user-defined context for flag evaluation
+ condition_value : list
+ schema value available for condition being evaluated
+
+ Returns
+ -------
+ bool
+ Whether any list item in context_value is available in condition_value
+ """
+ if not isinstance(context_value, list):
+ raise ValueError("Context provided must be a list. Unable to compare ANY_IN_VALUE action.")
+
+ return any(key in condition_value for key in context_value)
+
+
+def compare_all_in_list(context_value: list, condition_value: list) -> bool:
+ """Comparator for ALL_IN_VALUE action
+
+ Parameters
+ ----------
+ context_value : list
+ user-defined context for flag evaluation
+ condition_value : list
+ schema value available for condition being evaluated
+
+ Returns
+ -------
+ bool
+ Whether all list items in context_value are available in condition_value
+ """
+ if not isinstance(context_value, list):
+ raise ValueError("Context provided must be a list. Unable to compare ALL_IN_VALUE action.")
+
+ return all(key in condition_value for key in context_value)
+
+
+def compare_none_in_list(context_value: list, condition_value: list) -> bool:
+ """Comparator for NONE_IN_VALUE action
+
+ Parameters
+ ----------
+ context_value : list
+ user-defined context for flag evaluation
+ condition_value : list
+ schema value available for condition being evaluated
+
+ Returns
+ -------
+ bool
+ Whether list items in context_value are **not** available in condition_value
+ """
+ if not isinstance(context_value, list):
+ raise ValueError("Context provided must be a list. Unable to compare NONE_IN_VALUE action.")
+
+ return all(key not in condition_value for key in context_value)
diff --git a/aws_lambda_powertools/utilities/feature_flags/feature_flags.py b/aws_lambda_powertools/utilities/feature_flags/feature_flags.py
index 8610d68a8f6..bd7e19d0efe 100644
--- a/aws_lambda_powertools/utilities/feature_flags/feature_flags.py
+++ b/aws_lambda_powertools/utilities/feature_flags/feature_flags.py
@@ -1,18 +1,52 @@
+from __future__ import annotations
+
import logging
-from typing import Any, Dict, List, Optional, Union, cast
+from typing import Any, Callable, Dict, List, Optional, TypeVar, Union, cast
+
+from typing_extensions import ParamSpec
from ... import Logger
from ...shared.types import JSONType
from . import schema
from .base import StoreProvider
from .comparators import (
+ compare_all_in_list,
+ compare_any_in_list,
compare_datetime_range,
compare_days_of_week,
compare_modulo_range,
+ compare_none_in_list,
compare_time_range,
)
from .exceptions import ConfigurationStoreError
+T = TypeVar("T")
+P = ParamSpec("P")
+
+RULE_ACTION_MAPPING = {
+ schema.RuleAction.EQUALS.value: lambda a, b: a == b,
+ schema.RuleAction.NOT_EQUALS.value: lambda a, b: a != b,
+ schema.RuleAction.KEY_GREATER_THAN_VALUE.value: lambda a, b: a > b,
+ schema.RuleAction.KEY_GREATER_THAN_OR_EQUAL_VALUE.value: lambda a, b: a >= b,
+ schema.RuleAction.KEY_LESS_THAN_VALUE.value: lambda a, b: a < b,
+ schema.RuleAction.KEY_LESS_THAN_OR_EQUAL_VALUE.value: lambda a, b: a <= b,
+ schema.RuleAction.STARTSWITH.value: lambda a, b: a.startswith(b),
+ schema.RuleAction.ENDSWITH.value: lambda a, b: a.endswith(b),
+ schema.RuleAction.IN.value: lambda a, b: a in b,
+ schema.RuleAction.NOT_IN.value: lambda a, b: a not in b,
+ schema.RuleAction.KEY_IN_VALUE.value: lambda a, b: a in b,
+ schema.RuleAction.KEY_NOT_IN_VALUE.value: lambda a, b: a not in b,
+ schema.RuleAction.VALUE_IN_KEY.value: lambda a, b: b in a,
+ schema.RuleAction.VALUE_NOT_IN_KEY.value: lambda a, b: b not in a,
+ schema.RuleAction.ALL_IN_VALUE.value: lambda a, b: compare_all_in_list(a, b),
+ schema.RuleAction.ANY_IN_VALUE.value: lambda a, b: compare_any_in_list(a, b),
+ schema.RuleAction.NONE_IN_VALUE.value: lambda a, b: compare_none_in_list(a, b),
+ schema.RuleAction.SCHEDULE_BETWEEN_TIME_RANGE.value: lambda a, b: compare_time_range(a, b),
+ schema.RuleAction.SCHEDULE_BETWEEN_DATETIME_RANGE.value: lambda a, b: compare_datetime_range(a, b),
+ schema.RuleAction.SCHEDULE_BETWEEN_DAYS_OF_WEEK.value: lambda a, b: compare_days_of_week(a, b),
+ schema.RuleAction.MODULO_RANGE.value: lambda a, b: compare_modulo_range(a, b),
+}
+
class FeatureFlags:
def __init__(self, store: StoreProvider, logger: Optional[Union[logging.Logger, Logger]] = None):
@@ -46,34 +80,20 @@ def __init__(self, store: StoreProvider, logger: Optional[Union[logging.Logger,
"""
self.store = store
self.logger = logger or logging.getLogger(__name__)
+ self._exception_handlers: dict[Exception, Callable] = {}
def _match_by_action(self, action: str, condition_value: Any, context_value: Any) -> bool:
- mapping_by_action = {
- schema.RuleAction.EQUALS.value: lambda a, b: a == b,
- schema.RuleAction.NOT_EQUALS.value: lambda a, b: a != b,
- schema.RuleAction.KEY_GREATER_THAN_VALUE.value: lambda a, b: a > b,
- schema.RuleAction.KEY_GREATER_THAN_OR_EQUAL_VALUE.value: lambda a, b: a >= b,
- schema.RuleAction.KEY_LESS_THAN_VALUE.value: lambda a, b: a < b,
- schema.RuleAction.KEY_LESS_THAN_OR_EQUAL_VALUE.value: lambda a, b: a <= b,
- schema.RuleAction.STARTSWITH.value: lambda a, b: a.startswith(b),
- schema.RuleAction.ENDSWITH.value: lambda a, b: a.endswith(b),
- schema.RuleAction.IN.value: lambda a, b: a in b,
- schema.RuleAction.NOT_IN.value: lambda a, b: a not in b,
- schema.RuleAction.KEY_IN_VALUE.value: lambda a, b: a in b,
- schema.RuleAction.KEY_NOT_IN_VALUE.value: lambda a, b: a not in b,
- schema.RuleAction.VALUE_IN_KEY.value: lambda a, b: b in a,
- schema.RuleAction.VALUE_NOT_IN_KEY.value: lambda a, b: b not in a,
- schema.RuleAction.SCHEDULE_BETWEEN_TIME_RANGE.value: lambda a, b: compare_time_range(a, b),
- schema.RuleAction.SCHEDULE_BETWEEN_DATETIME_RANGE.value: lambda a, b: compare_datetime_range(a, b),
- schema.RuleAction.SCHEDULE_BETWEEN_DAYS_OF_WEEK.value: lambda a, b: compare_days_of_week(a, b),
- schema.RuleAction.MODULO_RANGE.value: lambda a, b: compare_modulo_range(a, b),
- }
-
try:
- func = mapping_by_action.get(action, lambda a, b: False)
+ func = RULE_ACTION_MAPPING.get(action, lambda a, b: False)
return func(context_value, condition_value)
except Exception as exc:
self.logger.debug(f"caught exception while matching action: action={action}, exception={str(exc)}")
+
+ handler = self._lookup_exception_handler(exc)
+ if handler:
+ self.logger.debug("Exception handler found! Delegating response.")
+ return handler(exc)
+
return False
def _evaluate_conditions(
@@ -203,6 +223,22 @@ def evaluate(self, *, name: str, context: Optional[Dict[str, Any]] = None, defau
2. Feature exists but has either no rules or no match, return feature default value
3. Feature doesn't exist in stored schema, encountered an error when fetching -> return default value provided
+ ββββββββββββββββββββββββββ ββββββββββββββββββββββββββ ββββββββββββββββββββββββββ
+ β Feature flags ββββββββΆ Get Configuration βββββββββΆ Evaluate rules β
+ ββββββββββββββββββββββββββ β β β β
+ ββββββββββββββββββββββββββ ββββββββββββββββββββββββββ
+ ββ Fetch schema ββ ββ Match rule ββ
+ ββββββββββββββ¬ββββββββββββ ββββββββββββββ¬ββββββββββββ
+ β β β β β β
+ ββββββββββββββΌββββββββββββ ββββββββββββββΌββββββββββββ
+ ββ Cache schema ββ ββ Match condition ββ
+ ββββββββββββββ¬ββββββββββββ ββββββββββββββ¬ββββββββββββ
+ β β β β β β
+ ββββββββββββββΌββββββββββββ ββββββββββββββΌββββββββββββ
+ ββ Validate schema ββ ββ Match action ββ
+ ββββββββββββββββββββββββββ ββββββββββββββββββββββββββ
+ ββββββββββββββββββββββββββ ββββββββββββββββββββββββββ
+
Parameters
----------
name: str
@@ -216,6 +252,31 @@ def evaluate(self, *, name: str, context: Optional[Dict[str, Any]] = None, defau
or there has been an error when fetching the configuration from the store
Can be boolean or any JSON values for non-boolean features.
+
+ Examples
+ --------
+
+ ```python
+ from aws_lambda_powertools.utilities.feature_flags import AppConfigStore, FeatureFlags
+ from aws_lambda_powertools.utilities.typing import LambdaContext
+
+ app_config = AppConfigStore(environment="dev", application="product-catalogue", name="features")
+
+ feature_flags = FeatureFlags(store=app_config)
+
+
+ def lambda_handler(event: dict, context: LambdaContext):
+ # Get customer's tier from incoming request
+ ctx = {"tier": event.get("tier", "standard")}
+
+ # Evaluate whether customer's tier has access to premium features
+ # based on `has_premium_features` rules
+ has_premium_features: bool = feature_flags.evaluate(name="premium_features", context=ctx, default=False)
+ if has_premium_features:
+ # enable premium features
+ ...
+ ```
+
Returns
------
JSONType
@@ -329,3 +390,45 @@ def get_enabled_features(self, *, context: Optional[Dict[str, Any]] = None) -> L
features_enabled.append(name)
return features_enabled
+
+ def validation_exception_handler(self, exc_class: Exception | list[Exception]):
+ """Registers function to handle unexpected validation exceptions when evaluating flags.
+
+ It does not override the function of a default flag value in case of network and IAM permissions.
+ For example, you won't be able to catch ConfigurationStoreError exception.
+
+ Parameters
+ ----------
+ exc_class : Exception | list[Exception]
+ One or more exceptions to catch
+
+ Examples
+ --------
+
+ ```python
+ feature_flags = FeatureFlags(store=app_config)
+
+ @feature_flags.validation_exception_handler(Exception) # any exception
+ def catch_exception(exc):
+ raise TypeError("re-raised") from exc
+ ```
+ """
+
+ def register_exception_handler(func: Callable[P, T]) -> Callable[P, T]:
+ if isinstance(exc_class, list):
+ for exp in exc_class:
+ self._exception_handlers[exp] = func
+ else:
+ self._exception_handlers[exc_class] = func
+
+ return func
+
+ return register_exception_handler
+
+ def _lookup_exception_handler(self, exc: BaseException) -> Callable | None:
+ # Use "Method Resolution Order" to allow for matching against a base class
+ # of an exception
+ for cls in type(exc).__mro__:
+ if cls in self._exception_handlers:
+ return self._exception_handlers[cls] # type: ignore[index] # index is correct
+ return None
diff --git a/aws_lambda_powertools/utilities/feature_flags/schema.py b/aws_lambda_powertools/utilities/feature_flags/schema.py
index 0dc5e8d56bc..1df16677bd8 100644
--- a/aws_lambda_powertools/utilities/feature_flags/schema.py
+++ b/aws_lambda_powertools/utilities/feature_flags/schema.py
@@ -1,8 +1,11 @@
+from __future__ import annotations
+
import logging
import re
from datetime import datetime
from enum import Enum
-from typing import Any, Callable, Dict, List, Optional, Union
+from functools import lru_cache
+from typing import Any, Dict, List, Optional, Union
from dateutil import tz
@@ -19,9 +22,11 @@
CONDITION_ACTION = "action"
FEATURE_DEFAULT_VAL_TYPE_KEY = "boolean_type"
TIME_RANGE_FORMAT = "%H:%M" # hour:min 24 hours clock
-TIME_RANGE_RE_PATTERN = re.compile(r"2[0-3]:[0-5]\d|[0-1]\d:[0-5]\d") # 24 hour clock
+TIME_RANGE_PATTERN = re.compile(r"2[0-3]:[0-5]\d|[0-1]\d:[0-5]\d") # 24 hour clock
HOUR_MIN_SEPARATOR = ":"
+LOGGER: logging.Logger | Logger = logging.getLogger(__name__)
+
class RuleAction(str, Enum):
EQUALS = "EQUALS"
@@ -38,6 +43,9 @@ class RuleAction(str, Enum):
KEY_NOT_IN_VALUE = "KEY_NOT_IN_VALUE"
VALUE_IN_KEY = "VALUE_IN_KEY"
VALUE_NOT_IN_KEY = "VALUE_NOT_IN_KEY"
+ ALL_IN_VALUE = "ALL_IN_VALUE"
+ ANY_IN_VALUE = "ANY_IN_VALUE"
+ NONE_IN_VALUE = "NONE_IN_VALUE"
SCHEDULE_BETWEEN_TIME_RANGE = "SCHEDULE_BETWEEN_TIME_RANGE" # hour:min 24 hours clock
SCHEDULE_BETWEEN_DATETIME_RANGE = "SCHEDULE_BETWEEN_DATETIME_RANGE" # full datetime format, excluding timezone
SCHEDULE_BETWEEN_DAYS_OF_WEEK = "SCHEDULE_BETWEEN_DAYS_OF_WEEK" # MONDAY, TUESDAY, .... see TimeValues enum
@@ -71,6 +79,11 @@ class TimeValues(Enum):
FRIDAY = "FRIDAY"
SATURDAY = "SATURDAY"
+ @classmethod
+ @lru_cache(maxsize=1)
+ def days(cls) -> list[str]:
+ return [day.value for day in cls if day.value not in ["START", "END", "TIMEZONE"]]
+
class ModuloRangeValues(Enum):
"""
@@ -185,7 +198,12 @@ class SchemaValidator(BaseValidator):
def __init__(self, schema: Dict[str, Any], logger: Optional[Union[logging.Logger, Logger]] = None):
self.schema = schema
- self.logger = logger or logging.getLogger(__name__)
+ self.logger = logger or LOGGER
+
+ # Validators are designed for modular testing
+ # therefore we link the custom logger with global LOGGER
+ # so custom validators can use them when necessary
+ SchemaValidator._link_global_logger(self.logger)
def validate(self) -> None:
self.logger.debug("Validating schema")
@@ -195,13 +213,18 @@ def validate(self) -> None:
features = FeaturesValidator(schema=self.schema, logger=self.logger)
features.validate()
+ @staticmethod
+ def _link_global_logger(logger: logging.Logger | Logger):
+ global LOGGER
+ LOGGER = logger
+
class FeaturesValidator(BaseValidator):
"""Validates each feature and calls RulesValidator to validate its rules"""
def __init__(self, schema: Dict, logger: Optional[Union[logging.Logger, Logger]] = None):
self.schema = schema
- self.logger = logger or logging.getLogger(__name__)
+ self.logger = logger or LOGGER
def validate(self):
for name, feature in self.schema.items():
@@ -239,7 +262,7 @@ def __init__(
self.feature = feature
self.feature_name = next(iter(self.feature))
self.rules: Optional[Dict] = self.feature.get(RULES_KEY)
- self.logger = logger or logging.getLogger(__name__)
+ self.logger = logger or LOGGER
self.boolean_feature = boolean_feature
def validate(self):
@@ -286,7 +309,7 @@ class ConditionsValidator(BaseValidator):
def __init__(self, rule: Dict[str, Any], rule_name: str, logger: Optional[Union[logging.Logger, Logger]] = None):
self.conditions: List[Dict[str, Any]] = rule.get(CONDITIONS_KEY, {})
self.rule_name = rule_name
- self.logger = logger or logging.getLogger(__name__)
+ self.logger = logger or LOGGER
def validate(self):
if not self.conditions or not isinstance(self.conditions, list):
@@ -322,23 +345,26 @@ def validate_condition_key(condition: Dict[str, Any], rule_name: str):
if not key or not isinstance(key, str):
raise SchemaValidationError(f"'key' value must be a non empty string, rule={rule_name}")
- # time actions need to have very specific keys
- # SCHEDULE_BETWEEN_TIME_RANGE => CURRENT_TIME
- # SCHEDULE_BETWEEN_DATETIME_RANGE => CURRENT_DATETIME
- # SCHEDULE_BETWEEN_DAYS_OF_WEEK => CURRENT_DAY_OF_WEEK
action = condition.get(CONDITION_ACTION, "")
- if action == RuleAction.SCHEDULE_BETWEEN_TIME_RANGE.value and key != TimeKeys.CURRENT_TIME.value:
- raise SchemaValidationError(
- f"'condition with a 'SCHEDULE_BETWEEN_TIME_RANGE' action must have a 'CURRENT_TIME' condition key, rule={rule_name}", # noqa: E501
- )
- if action == RuleAction.SCHEDULE_BETWEEN_DATETIME_RANGE.value and key != TimeKeys.CURRENT_DATETIME.value:
- raise SchemaValidationError(
- f"'condition with a 'SCHEDULE_BETWEEN_DATETIME_RANGE' action must have a 'CURRENT_DATETIME' condition key, rule={rule_name}", # noqa: E501
- )
- if action == RuleAction.SCHEDULE_BETWEEN_DAYS_OF_WEEK.value and key != TimeKeys.CURRENT_DAY_OF_WEEK.value:
- raise SchemaValidationError(
- f"'condition with a 'SCHEDULE_BETWEEN_DAYS_OF_WEEK' action must have a 'CURRENT_DAY_OF_WEEK' condition key, rule={rule_name}", # noqa: E501
- )
+
+ # To allow for growth and prevent if/elif chains, we align extra validators based on the action name.
+ # for example:
+ #
+ # SCHEDULE_BETWEEN_DAYS_OF_WEEK_KEY
+ # - extra validation: `_validate_schedule_between_days_of_week_key`
+ #
+ # maintenance: we should split to separate file/classes for better organization, e.g., visitor pattern.
+
+ custom_validator = getattr(ConditionsValidator, f"_validate_{action.lower()}_key", None)
+
+ # ~90% of actions available don't require a custom validator
+ # logging a debug statement for no-match will increase CPU cycles for most customers
+ # for that reason only, we invert and log only when extra validation is found.
+ if custom_validator is None:
+ return
+
+ LOGGER.debug(f"{action} requires key validation. Running '{custom_validator}' validator.")
+ custom_validator(key, rule_name)
@staticmethod
def validate_condition_value(condition: Dict[str, Any], rule_name: str):
@@ -347,65 +373,35 @@ def validate_condition_value(condition: Dict[str, Any], rule_name: str):
raise SchemaValidationError(f"'value' key must not be null, rule={rule_name}")
action = condition.get(CONDITION_ACTION, "")
- # time actions need to be parsed to make sure date and time format is valid and timezone is recognized
- if action == RuleAction.SCHEDULE_BETWEEN_TIME_RANGE.value:
- ConditionsValidator._validate_schedule_between_time_and_datetime_ranges(
- value,
- rule_name,
- action,
- ConditionsValidator._validate_time_value,
- )
- elif action == RuleAction.SCHEDULE_BETWEEN_DATETIME_RANGE.value:
- ConditionsValidator._validate_schedule_between_time_and_datetime_ranges(
- value,
- rule_name,
- action,
- ConditionsValidator._validate_datetime_value,
- )
- elif action == RuleAction.SCHEDULE_BETWEEN_DAYS_OF_WEEK.value:
- ConditionsValidator._validate_schedule_between_days_of_week(value, rule_name)
- # modulo range condition needs validation on base, start, and end attributes
- elif action == RuleAction.MODULO_RANGE.value:
- ConditionsValidator._validate_modulo_range(value, rule_name)
+ # To allow for growth and prevent if/elif chains, we align extra validators based on the action name.
+ # for example:
+ #
+ # SCHEDULE_BETWEEN_DAYS_OF_WEEK_KEY
+ # - extra validation: `_validate_schedule_between_days_of_week_value`
+ #
+ # maintenance: we should split to separate file/classes for better organization, e.g., visitor pattern.
- @staticmethod
- def _validate_datetime_value(datetime_str: str, rule_name: str):
- date = None
+ custom_validator = getattr(ConditionsValidator, f"_validate_{action.lower()}_value", None)
- # We try to parse first with timezone information in order to return the correct error messages
- # when a timestamp with timezone is used. Otherwise, the user would get the first error "must be a valid
- # ISO8601 time format" which is misleading
+ # ~90% of actions available don't require a custom validator
+ # logging a debug statement for no-match will increase CPU cycles for most customers
+ # for that reason only, we invert and log only when extra validation is found.
+ if custom_validator is None:
+ return
- try:
- # python < 3.11 don't support the Z timezone on datetime.fromisoformat,
- # so we replace any Z with the equivalent "+00:00"
- # datetime.fromisoformat is orders of magnitude faster than datetime.strptime
- date = datetime.fromisoformat(datetime_str.replace("Z", "+00:00"))
- except Exception:
- raise SchemaValidationError(f"'START' and 'END' must be a valid ISO8601 time format, rule={rule_name}")
+ LOGGER.debug(f"{action} requires value validation. Running '{custom_validator}' validator.")
- # we only allow timezone information to be set via the TIMEZONE field
- # this way we can encode DST into the calculation. For instance, Copenhagen is
- # UTC+2 during winter, and UTC+1 during summer, which would be impossible to define
- # using a single ISO datetime string
- if date.tzinfo is not None:
- raise SchemaValidationError(
- "'START' and 'END' must not include timezone information. Set the timezone using the 'TIMEZONE' "
- f"field, rule={rule_name} ",
- )
+ custom_validator(value, rule_name)
@staticmethod
- def _validate_time_value(time: str, rule_name: str):
- # Using a regex instead of strptime because it's several orders of magnitude faster
- match = TIME_RANGE_RE_PATTERN.match(time)
-
- if not match:
+ def _validate_schedule_between_days_of_week_key(key: str, rule_name: str):
+ if key != TimeKeys.CURRENT_DAY_OF_WEEK.value:
raise SchemaValidationError(
- f"'START' and 'END' must be a valid time format, time_format={TIME_RANGE_FORMAT}, rule={rule_name}",
+ f"'condition with a 'SCHEDULE_BETWEEN_DAYS_OF_WEEK' action must have a 'CURRENT_DAY_OF_WEEK' condition key, rule={rule_name}", # noqa: E501
)
@staticmethod
- def _validate_schedule_between_days_of_week(value: Any, rule_name: str):
+ def _validate_schedule_between_days_of_week_value(value: dict, rule_name: str):
error_str = f"condition with a CURRENT_DAY_OF_WEEK action must have a condition value dictionary with 'DAYS' and 'TIMEZONE' (optional) keys, rule={rule_name}" # noqa: E501
if not isinstance(value, dict):
raise SchemaValidationError(error_str)
@@ -413,59 +409,70 @@ def _validate_schedule_between_days_of_week(value: Any, rule_name: str):
days = value.get(TimeValues.DAYS.value)
if not isinstance(days, list) or not value:
raise SchemaValidationError(error_str)
+
+ valid_days = TimeValues.days()
for day in days:
- if not isinstance(day, str) or day not in [
- TimeValues.MONDAY.value,
- TimeValues.TUESDAY.value,
- TimeValues.WEDNESDAY.value,
- TimeValues.THURSDAY.value,
- TimeValues.FRIDAY.value,
- TimeValues.SATURDAY.value,
- TimeValues.SUNDAY.value,
- ]:
+ if not isinstance(day, str) or day not in valid_days:
raise SchemaValidationError(
f"condition value DAYS must represent a day of the week in 'TimeValues' enum, rule={rule_name}",
)
- timezone = value.get(TimeValues.TIMEZONE.value, "UTC")
- if not isinstance(timezone, str):
- raise SchemaValidationError(error_str)
+ ConditionsValidator._validate_timezone(timezone=value.get(TimeValues.TIMEZONE.value), rule=rule_name)
- # try to see if the timezone string corresponds to any known timezone
- if not tz.gettz(timezone):
- raise SchemaValidationError(f"'TIMEZONE' value must represent a valid IANA timezone, rule={rule_name}")
+ @staticmethod
+ def _validate_schedule_between_time_range_key(key: str, rule_name: str):
+ if key != TimeKeys.CURRENT_TIME.value:
+ raise SchemaValidationError(
+ f"'condition with a 'SCHEDULE_BETWEEN_TIME_RANGE' action must have a 'CURRENT_TIME' condition key, rule={rule_name}", # noqa: E501
+ )
@staticmethod
- def _validate_schedule_between_time_and_datetime_ranges(
- value: Any,
- rule_name: str,
- action_name: str,
- validator: Callable[[str, str], None],
- ):
- error_str = f"condition with a '{action_name}' action must have a condition value type dictionary with 'START' and 'END' keys, rule={rule_name}" # noqa: E501
+ def _validate_schedule_between_time_range_value(value: Dict, rule_name: str):
if not isinstance(value, dict):
- raise SchemaValidationError(error_str)
+ raise SchemaValidationError(
+ f"{RuleAction.SCHEDULE_BETWEEN_TIME_RANGE.value} action must have a dictionary with 'START' and 'END' keys, rule={rule_name}", # noqa: E501
+ )
+
+ start_time = value.get(TimeValues.START.value, "")
+ end_time = value.get(TimeValues.END.value, "")
- start_time = value.get(TimeValues.START.value)
- end_time = value.get(TimeValues.END.value)
- if not start_time or not end_time:
- raise SchemaValidationError(error_str)
if not isinstance(start_time, str) or not isinstance(end_time, str):
raise SchemaValidationError(f"'START' and 'END' must be a non empty string, rule={rule_name}")
- validator(start_time, rule_name)
- validator(end_time, rule_name)
+ # Using a regex instead of strptime because it's several orders of magnitude faster
+ if not TIME_RANGE_PATTERN.match(start_time) or not TIME_RANGE_PATTERN.match(end_time):
+ raise SchemaValidationError(
+ f"'START' and 'END' must be a valid time format, time_format={TIME_RANGE_FORMAT}, rule={rule_name}",
+ )
- timezone = value.get(TimeValues.TIMEZONE.value, "UTC")
- if not isinstance(timezone, str):
- raise SchemaValidationError(f"'TIMEZONE' must be a string, rule={rule_name}")
+ ConditionsValidator._validate_timezone(timezone=value.get(TimeValues.TIMEZONE.value), rule=rule_name)
- # try to see if the timezone string corresponds to any known timezone
- if not tz.gettz(timezone):
- raise SchemaValidationError(f"'TIMEZONE' value must represent a valid IANA timezone, rule={rule_name}")
+ @staticmethod
+ def _validate_schedule_between_datetime_range_key(key: str, rule_name: str):
+ if key != TimeKeys.CURRENT_DATETIME.value:
+ raise SchemaValidationError(
+ f"'condition with a 'SCHEDULE_BETWEEN_DATETIME_RANGE' action must have a 'CURRENT_DATETIME' condition key, rule={rule_name}", # noqa: E501
+ )
@staticmethod
- def _validate_modulo_range(value: Any, rule_name: str):
+ def _validate_schedule_between_datetime_range_value(value: dict, rule_name: str):
+ if not isinstance(value, dict):
+ raise SchemaValidationError(
+ f"{RuleAction.SCHEDULE_BETWEEN_DATETIME_RANGE.value} action must have a dictionary with 'START' and 'END' keys, rule={rule_name}", # noqa: E501
+ )
+
+ start_time = value.get(TimeValues.START.value, "")
+ end_time = value.get(TimeValues.END.value, "")
+
+ if not isinstance(start_time, str) or not isinstance(end_time, str):
+ raise SchemaValidationError(f"'START' and 'END' must be a non empty string, rule={rule_name}")
+
+ ConditionsValidator._validate_datetime(start_time, rule_name)
+ ConditionsValidator._validate_datetime(end_time, rule_name)
+ ConditionsValidator._validate_timezone(timezone=value.get(TimeValues.TIMEZONE.value), rule=rule_name)
+
+ @staticmethod
+ def _validate_modulo_range_value(value: dict, rule_name: str):
error_str = f"condition with a 'MODULO_RANGE' action must have a condition value type dictionary with 'BASE', 'START' and 'END' keys, rule={rule_name}" # noqa: E501
if not isinstance(value, dict):
raise SchemaValidationError(error_str)
@@ -473,8 +480,10 @@ def _validate_modulo_range(value: Any, rule_name: str):
base = value.get(ModuloRangeValues.BASE.value)
start = value.get(ModuloRangeValues.START.value)
end = value.get(ModuloRangeValues.END.value)
+
if base is None or start is None or end is None:
raise SchemaValidationError(error_str)
+
if not isinstance(base, int) or not isinstance(start, int) or not isinstance(end, int):
raise SchemaValidationError(f"'BASE', 'START' and 'END' must be integers, rule={rule_name}")
@@ -482,3 +491,55 @@ def _validate_modulo_range(value: Any, rule_name: str):
raise SchemaValidationError(
f"condition with 'MODULO_RANGE' action must satisfy 0 <= START <= END <= BASE-1, rule={rule_name}",
)
+
+ @staticmethod
+ def _validate_all_in_value_value(value: list, rule_name: str):
+ if not (isinstance(value, list)):
+ raise SchemaValidationError(f"ALL_IN_VALUE action must have a list value, rule={rule_name}")
+
+ @staticmethod
+ def _validate_any_in_value_value(value: list, rule_name: str):
+ if not (isinstance(value, list)):
+ raise SchemaValidationError(f"ANY_IN_VALUE action must have a list value, rule={rule_name}")
+
+ @staticmethod
+ def _validate_none_in_value_value(value: list, rule_name: str):
+ if not (isinstance(value, list)):
+ raise SchemaValidationError(f"NONE_IN_VALUE action must have a list value, rule={rule_name}")
+
+ @staticmethod
+ def _validate_datetime(datetime_str: str, rule_name: str):
+ date = None
+
+ # We try to parse first with timezone information in order to return the correct error messages
+ # when a timestamp with timezone is used. Otherwise, the user would get the first error "must be a valid
+ # ISO8601 time format" which is misleading
+
+ try:
+ # python < 3.11 don't support the Z timezone on datetime.fromisoformat,
+ # so we replace any Z with the equivalent "+00:00"
+ # datetime.fromisoformat is orders of magnitude faster than datetime.strptime
+ date = datetime.fromisoformat(datetime_str.replace("Z", "+00:00"))
+ except Exception:
+ raise SchemaValidationError(f"'START' and 'END' must be a valid ISO8601 time format, rule={rule_name}")
+
+ # we only allow timezone information to be set via the TIMEZONE field
+ # this way we can encode DST into the calculation. For instance, Copenhagen is
+ # UTC+2 during winter, and UTC+1 during summer, which would be impossible to define
+ # using a single ISO datetime string
+ if date.tzinfo is not None:
+ raise SchemaValidationError(
+ "'START' and 'END' must not include timezone information. Set the timezone using the 'TIMEZONE' "
+ f"field, rule={rule_name} ",
+ )
+
+ @staticmethod
+ def _validate_timezone(rule: str, timezone: str | None = None):
+ timezone = timezone or "UTC"
+
+ if not isinstance(timezone, str):
+ raise SchemaValidationError(f"'TIMEZONE' must be a string, rule={str}")
+
+ # try to see if the timezone string corresponds to any known timezone
+ if not tz.gettz(timezone):
+ raise SchemaValidationError(f"'TIMEZONE' value must represent a valid IANA timezone, rule={rule}")
diff --git a/docs/Dockerfile b/docs/Dockerfile
index 9de94938c2b..4445acc55c7 100644
--- a/docs/Dockerfile
+++ b/docs/Dockerfile
@@ -1,5 +1,5 @@
# v9.1.18
-FROM squidfunk/mkdocs-material@sha256:6a72238e24c73e4cebb1ceddf8603778d25739ffbf480a314628a3d81aee2214
+FROM squidfunk/mkdocs-material@sha256:43b898a5520bbe5ee0080568c002491cd8fcd2269e64f7ad2ba4c9c419acb866
# pip-compile --generate-hashes --output-file=requirements.txt requirements.in
COPY requirements.txt /tmp/
RUN pip install --require-hashes -r /tmp/requirements.txt
diff --git a/docs/index.md b/docs/index.md
index a5132120490..7aa9a7d956d 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -7,461 +7,482 @@ description: Powertools for AWS Lambda (Python)
Powertools for AWS Lambda (Python) is a developer toolkit to implement Serverless best practices and increase developer velocity.
-???+ tip
- Powertools for AWS Lambda (Python) is also available for [Java](https://docs.powertools.aws.dev/lambda/java/){target="_blank"}, [TypeScript](https://docs.powertools.aws.dev/lambda/typescript/latest/){target="_blank" }, and [.NET](https://docs.powertools.aws.dev/lambda/dotnet/){target="_blank"}
+
+
-??? hint "Support this project by becoming a reference customer, sharing your work, or using Layers/SAR :heart:"
+- :material-battery-charging:{ .lg .middle } __Features__
- You can choose to support us in three ways:
+ ---
- 1) [**Become a reference customer**](https://github.com/aws-powertools/powertools-lambda-python/issues/new?assignees=&labels=customer-reference&template=support_powertools.yml&title=%5BSupport+Lambda+Powertools%5D%3A+%3Cyour+organization+name%3E){target="_blank"}. This gives us permission to list your company in our documentation.
+ Adopt one, a few, or all industry practices. **Progressively**.
- 2) [**Share your work**](https://github.com/aws-powertools/powertools-lambda-python/issues/new?assignees=&labels=community-content&template=share_your_work.yml&title=%5BI+Made+This%5D%3A+%3CTITLE%3E){target="_blank"}. Blog posts, video, sample projects you used Powertools!
+ [:octicons-arrow-right-24: All features](#features)
- 3) Use [**Lambda Layers**](#lambda-layer) or [**SAR**](#sar), if possible. This helps us understand who uses Powertools for AWS Lambda (Python) in a non-intrusive way, and helps us gain future investments for other Powertools for AWS Lambda languages.
+- :heart:{ .lg .middle } __Support this project__
- When using Layers, you can add Powertools for AWS Lambda (Python) as a dev dependency (or as part of your virtual env) to not impact the development process.
+ ---
-## Install
+ Become a public reference customer, share your work, contribute, use Lambda Layers, etc.
-You can install Powertools for AWS Lambda (Python) using one of the following options:
+ [:octicons-arrow-right-24: Support](#support-powertools-for-aws-lambda-python)
-* **Lambda Layer (x86_64)**: [**arn:aws:lambda:{region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:61**](# "Replace {region} with your AWS region, e.g., eu-west-1"){: .copyMe}:clipboard:
-* **Lambda Layer (arm64)**: [**arn:aws:lambda:{region}:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:61**](# "Replace {region} with your AWS region, e.g., eu-west-1"){: .copyMe}:clipboard:
-* **Pip**: **[`pip install "aws-lambda-powertools"`](#){: .copyMe}:clipboard:**
+- :material-file-code:{ .lg .middle } __Available languages__
-!!! question "Looking for Pip signed releases? [Learn more about verifying signed builds](./security.md#verifying-signed-builds)"
+ ---
-??? question "Using Pip? You might need to install additional dependencies."
- [**Tracer**](./core/tracer.md){target="_blank"}, [**Validation**](./utilities/validation.md){target="_blank"} and [**Parser**](./utilities/parser.md){target="_blank"} require additional dependencies. If you prefer to install all of them, use [**`pip install "aws-lambda-powertools[all]"`**](#){: .copyMe}:clipboard:.
+ Powertools for AWS Lambda is also available in other languages
- For example:
+ :octicons-arrow-right-24: [Java](https://docs.powertools.aws.dev/lambda/java/){target="_blank"}, [TypeScript](https://docs.powertools.aws.dev/lambda/typescript/latest/){target="_blank" }, and [.NET](https://docs.powertools.aws.dev/lambda/dotnet/){target="_blank"}
- * **Tracer**: **[`pip install "aws-lambda-powertools[tracer]"`](#){: .copyMe}:clipboard:**
- * **Validation**: **[`pip install "aws-lambda-powertools[validation]"`](#){: .copyMe}:clipboard:**
- * **Parser**: **[`pip install "aws-lambda-powertools[parser]"`](#){: .copyMe}:clipboard:**
- * **Tracer** and **Parser**: **[`pip install "aws-lambda-powertools[tracer,parser]"`](#){: .copyMe}:clipboard:**
+
-### Local development
+## Install
-!!! info "Using Lambda Layer? Simply add [**`"aws-lambda-powertools[all]"`**](#){: .copyMe}:clipboard: as a development dependency."
+You can install Powertools for AWS Lambda (Python) using your favorite dependency management, or Lambda Layers:
-Powertools for AWS Lambda (Python) relies on the [AWS SDK bundled in the Lambda runtime](https://docs.aws.amazon.com/lambda/latest/dg/lambda-python.html){target="_blank"}. This helps us achieve an optimal package size and initialization. However, when developing locally, you need to install AWS SDK as a development dependency (not as a production dependency):
+=== "Pip"
-* **Pip**: [**`pip install "aws-lambda-powertools[aws-sdk]"`**](#){: .copyMe}:clipboard:
-* **Poetry**: [**`poetry add "aws-lambda-powertools[aws-sdk]" --group dev`**](#){: .copyMe}:clipboard:
-* **Pipenv**: [**`pipenv install --dev "aws-lambda-powertools[aws-sdk]"`**](#){: .copyMe}:clipboard:
+ You can install [all extra dependencies](#extra-dependencies) at once with the `[all]` extras.
-??? question "Why is that necessary?"
- Powertools for AWS Lambda (Python) relies on the AWS SDK being available to use in the target runtime (AWS Lambda).
+ * **pip**: [**`pip install "aws-lambda-powertools[all]"`**](#){: .copyMe}
+ * **poetry**: [**`poetry add "aws-lambda-powertools[all]"`**](#){: .copyMe}
+ * **pdm**: [**`pdm add "aws-lambda-powertools[all]"`**](#){: .copyMe}
- As a result, it affects your favorite IDE in terms of code auto-completion, or running your tests suite locally with no Lambda emulation such as [AWS SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html){target="_blank"}.
+ Alternatively, see [extra dependencies](#extra-dependencies) if you want to install only what you need.
-**A word about dependency resolution**
+=== "Lambda Layer"
-In this context, `[aws-sdk]` is an alias to the `boto3` package. Due to dependency resolution, it'll either install:
+ You can add our layer both in the [AWS Lambda Console _(under `Layers`)_](https://eu-west-1.console.aws.amazon.com/lambda/home#/add/layer){target="_blank"}, or via your favorite infrastructure as code framework with the ARN value.
-* **(A)** the SDK version available in [Lambda runtime](https://docs.aws.amazon.com/lambda/latest/dg/lambda-python.html){target="_blank"}
-* **(B)** a more up-to-date version if another package you use also depends on `boto3`, for example [Powertools for AWS Lambda (Python) Tracer](core/tracer.md){target="_blank"}
+ For the latter, make sure to replace `{region}` with your AWS region, e.g., `eu-west-1`.
-### Lambda Layer
+ * **x86 architecture**: [__arn:aws:lambda:{region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:62__](# "Replace {region} with your AWS region, e.g., eu-west-1"){: .copyMe}:clipboard:
+ * **ARM architecture**: [__arn:aws:lambda:{region}:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:62__](# "Replace {region} with your AWS region, e.g., eu-west-1"){: .copyMe}:clipboard:
-???+ warning "As of now, Container Image deployment (OCI) or inline Lambda functions do not support Lambda Layers."
+ ???+ note "Code snippets for popular infrastructure as code frameworks"
-[Lambda Layer](https://docs.aws.amazon.com/lambda/latest/dg/configuration-layers.html){target="_blank"} is a .zip file archive that can contain additional code, pre-packaged dependencies, data, or configuration files. Layers promote code sharing and separation of responsibilities so that you can iterate faster on writing business logic.
+ === "x86_64"
-For our Layers, we compile and optimize [all dependencies](https://github.com/aws-powertools/powertools-lambda-python/blob/develop/pyproject.toml#L98){target="_blank"}, and [remove duplicate dependencies already available in the Lambda runtime](https://github.com/awslabs/cdk-aws-lambda-powertools-layer/blob/main/layer/Python/Dockerfile#L36){target="_blank"} to achieve the most optimal size.
+ === "SAM"
-You can include Powertools for AWS Lambda (Python) Lambda Layer using [AWS Lambda Console](https://docs.aws.amazon.com/lambda/latest/dg/invocation-layers.html#invocation-layers-using){target="_blank"}, or your preferred deployment framework.
+ ```yaml hl_lines="5"
+ MyLambdaFunction:
+ Type: AWS::Serverless::Function
+ Properties:
+ Layers:
+ - !Sub arn:aws:lambda:${AWS::Region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:62
+ ```
-??? note "Note: Click to expand and copy any regional Lambda Layer ARN"
+ === "Serverless framework"
- === "x86_64"
+ ```yaml hl_lines="5"
+ functions:
+ hello:
+ handler: lambda_function.lambda_handler
+ layers:
+ - arn:aws:lambda:${aws:region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:62
+ ```
- | Region | Layer ARN |
- | ---------------- | ---------------------------------------------------------------------------------------------------------- |
- | `af-south-1` | [arn:aws:lambda:af-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:61](#){: .copyMe}:clipboard: |
- | `ap-east-1` | [arn:aws:lambda:ap-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:61](#){: .copyMe}:clipboard: |
- | `ap-northeast-1` | [arn:aws:lambda:ap-northeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:61](#){: .copyMe}:clipboard: |
- | `ap-northeast-2` | [arn:aws:lambda:ap-northeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:61](#){: .copyMe}:clipboard: |
- | `ap-northeast-3` | [arn:aws:lambda:ap-northeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV2:61](#){: .copyMe}:clipboard: |
- | `ap-south-1` | [arn:aws:lambda:ap-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:61](#){: .copyMe}:clipboard: |
- | `ap-south-2` | [arn:aws:lambda:ap-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:61](#){: .copyMe}:clipboard: |
- | `ap-southeast-1` | [arn:aws:lambda:ap-southeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:61](#){: .copyMe}:clipboard: |
- | `ap-southeast-2` | [arn:aws:lambda:ap-southeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:61](#){: .copyMe}:clipboard: |
- | `ap-southeast-3` | [arn:aws:lambda:ap-southeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV2:61](#){: .copyMe}:clipboard: |
- | `ap-southeast-4` | [arn:aws:lambda:ap-southeast-4:017000801446:layer:AWSLambdaPowertoolsPythonV2:61](#){: .copyMe}:clipboard: |
- | `ca-central-1` | [arn:aws:lambda:ca-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:61](#){: .copyMe}:clipboard: |
- | `ca-west-1` | [arn:aws:lambda:ca-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:61](#){: .copyMe}:clipboard: |
- | `eu-central-1` | [arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:61](#){: .copyMe}:clipboard: |
- | `eu-central-2` | [arn:aws:lambda:eu-central-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:61](#){: .copyMe}:clipboard: |
- | `eu-north-1` | [arn:aws:lambda:eu-north-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:61](#){: .copyMe}:clipboard: |
- | `eu-south-1` | [arn:aws:lambda:eu-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:61](#){: .copyMe}:clipboard: |
- | `eu-south-2` | [arn:aws:lambda:eu-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:61](#){: .copyMe}:clipboard: |
- | `eu-west-1` | [arn:aws:lambda:eu-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:61](#){: .copyMe}:clipboard: |
- | `eu-west-2` | [arn:aws:lambda:eu-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:61](#){: .copyMe}:clipboard: |
- | `eu-west-3` | [arn:aws:lambda:eu-west-3:017000801446:layer:AWSLambdaPowertoolsPythonV2:61](#){: .copyMe}:clipboard: |
- | `il-central-1` | [arn:aws:lambda:il-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:61](#){: .copyMe}:clipboard: |
- | `me-central-1` | [arn:aws:lambda:me-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:61](#){: .copyMe}:clipboard: |
- | `me-south-1` | [arn:aws:lambda:me-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:61](#){: .copyMe}:clipboard: |
- | `sa-east-1` | [arn:aws:lambda:sa-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:61](#){: .copyMe}:clipboard: |
- | `us-east-1` | [arn:aws:lambda:us-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:61](#){: .copyMe}:clipboard: |
- | `us-east-2` | [arn:aws:lambda:us-east-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:61](#){: .copyMe}:clipboard: |
- | `us-west-1` | [arn:aws:lambda:us-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:61](#){: .copyMe}:clipboard: |
- | `us-west-2` | [arn:aws:lambda:us-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:61](#){: .copyMe}:clipboard: |
+ === "CDK"
- === "arm64"
+ ```python hl_lines="11 16"
+ from aws_cdk import core, aws_lambda
- | Region | Layer ARN |
- | ---------------- | ---------------------------------------------------------------------------------------------------------------- |
- | `af-south-1` | [arn:aws:lambda:af-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:61](#){: .copyMe}:clipboard: |
- | `ap-east-1` | [arn:aws:lambda:ap-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:61](#){: .copyMe}:clipboard: |
- | `ap-northeast-1` | [arn:aws:lambda:ap-northeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:61](#){: .copyMe}:clipboard: |
- | `ap-northeast-2` | [arn:aws:lambda:ap-northeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:61](#){: .copyMe}:clipboard: |
- | `ap-northeast-3` | [arn:aws:lambda:ap-northeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:61](#){: .copyMe}:clipboard: |
- | `ap-south-1` | [arn:aws:lambda:ap-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:61](#){: .copyMe}:clipboard: |
- | `ap-south-2` | [arn:aws:lambda:ap-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:61](#){: .copyMe}:clipboard: |
- | `ap-southeast-1` | [arn:aws:lambda:ap-southeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:61](#){: .copyMe}:clipboard: |
- | `ap-southeast-2` | [arn:aws:lambda:ap-southeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:61](#){: .copyMe}:clipboard: |
- | `ap-southeast-3` | [arn:aws:lambda:ap-southeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:61](#){: .copyMe}:clipboard: |
- | `ca-central-1` | [arn:aws:lambda:ca-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:61](#){: .copyMe}:clipboard: |
- | `eu-central-1` | [arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:61](#){: .copyMe}:clipboard: |
- | `eu-central-2` | [arn:aws:lambda:eu-central-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:61](#){: .copyMe}:clipboard: |
- | `eu-north-1` | [arn:aws:lambda:eu-north-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:61](#){: .copyMe}:clipboard: |
- | `eu-south-1` | [arn:aws:lambda:eu-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:61](#){: .copyMe}:clipboard: |
- | `eu-south-2` | [arn:aws:lambda:eu-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:61](#){: .copyMe}:clipboard: |
- | `eu-west-1` | [arn:aws:lambda:eu-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:61](#){: .copyMe}:clipboard: |
- | `eu-west-2` | [arn:aws:lambda:eu-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:61](#){: .copyMe}:clipboard: |
- | `eu-west-3` | [arn:aws:lambda:eu-west-3:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:61](#){: .copyMe}:clipboard: |
- | `il-central-1` | [arn:aws:lambda:il-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:61](#){: .copyMe}:clipboard: |
- | `me-central-1` | [arn:aws:lambda:me-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:61](#){: .copyMe}:clipboard: |
- | `me-south-1` | [arn:aws:lambda:me-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:61](#){: .copyMe}:clipboard: |
- | `sa-east-1` | [arn:aws:lambda:sa-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:61](#){: .copyMe}:clipboard: |
- | `us-east-1` | [arn:aws:lambda:us-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:61](#){: .copyMe}:clipboard: |
- | `us-east-2` | [arn:aws:lambda:us-east-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:61](#){: .copyMe}:clipboard: |
- | `us-west-1` | [arn:aws:lambda:us-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:61](#){: .copyMe}:clipboard: |
- | `us-west-2` | [arn:aws:lambda:us-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:61](#){: .copyMe}:clipboard: |
-
-??? note "Note: Click to expand and copy code snippets for popular frameworks"
+ class SampleApp(core.Construct):
- === "x86_64"
+ def __init__(self, scope: core.Construct, id_: str, env: core.Environment) -> None:
+ super().__init__(scope, id_)
- === "SAM"
+ powertools_layer = aws_lambda.LayerVersion.from_layer_version_arn(
+ self,
+ id="lambda-powertools",
+ layer_version_arn=f"arn:aws:lambda:{env.region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:62"
+ )
+ aws_lambda.Function(self,
+ 'sample-app-lambda',
+ runtime=aws_lambda.Runtime.PYTHON_3_9,
+ layers=[powertools_layer]
+ # other props...
+ )
+ ```
- ```yaml hl_lines="5"
- MyLambdaFunction:
- Type: AWS::Serverless::Function
- Properties:
- Layers:
- - !Sub arn:aws:lambda:${AWS::Region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:61
- ```
-
- === "Serverless framework"
-
- ```yaml hl_lines="5"
- functions:
- hello:
- handler: lambda_function.lambda_handler
- layers:
- - arn:aws:lambda:${aws:region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:61
- ```
-
- === "CDK"
-
- ```python hl_lines="11 16"
- from aws_cdk import core, aws_lambda
-
- class SampleApp(core.Construct):
-
- def __init__(self, scope: core.Construct, id_: str, env: core.Environment) -> None:
- super().__init__(scope, id_)
-
- powertools_layer = aws_lambda.LayerVersion.from_layer_version_arn(
- self,
- id="lambda-powertools",
- layer_version_arn=f"arn:aws:lambda:{env.region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:61"
- )
- aws_lambda.Function(self,
- 'sample-app-lambda',
- runtime=aws_lambda.Runtime.PYTHON_3_9,
- layers=[powertools_layer]
- # other props...
- )
- ```
-
- === "Terraform"
-
- ```terraform hl_lines="9 38"
- terraform {
- required_version = "~> 1.0.5"
- required_providers {
- aws = "~> 3.50.0"
- }
- }
-
- provider "aws" {
- region = "{region}"
- }
-
- resource "aws_iam_role" "iam_for_lambda" {
- name = "iam_for_lambda"
-
- assume_role_policy = <
- ? Choose the runtime that you want to use: Python
- ? Do you want to configure advanced settings? Yes
- ...
- ? Do you want to enable Lambda layers for this function? Yes
- ? Enter up to 5 existing Lambda layer ARNs (comma-separated): arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:61
- β― amplify push -y
-
-
- # Updating an existing function and add the layer
- β― amplify update function
- ? Select the Lambda function you want to update test2
- General information
- - Name:
- ? Which setting do you want to update? Lambda layers configuration
- ? Do you want to enable Lambda layers for this function? Yes
- ? Enter up to 5 existing Lambda layer ARNs (comma-separated): arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:61
- ? Do you want to edit the local lambda function now? No
- ```
+ runtime=aws.lambda_.Runtime.PYTHON3D9,
+ handler="index.handler",
+ role=role.arn,
+ architectures=["x86_64"],
+ code=pulumi.FileArchive("lambda_function_payload.zip")
+ )
+ ```
+
+ === "Amplify"
+
+ ```zsh
+ # Create a new one with the layer
+ β― amplify add function
+ ? Select which capability you want to add: Lambda function (serverless function)
+ ? Provide an AWS Lambda function name:
+ ? Choose the runtime that you want to use: Python
+ ? Do you want to configure advanced settings? Yes
+ ...
+ ? Do you want to enable Lambda layers for this function? Yes
+ ? Enter up to 5 existing Lambda layer ARNs (comma-separated): arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:62
+ β― amplify push -y
+
+
+ # Updating an existing function and add the layer
+ β― amplify update function
+ ? Select the Lambda function you want to update test2
+ General information
+ - Name:
+ ? Which setting do you want to update? Lambda layers configuration
+ ? Do you want to enable Lambda layers for this function? Yes
+ ? Enter up to 5 existing Lambda layer ARNs (comma-separated): arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:62
+ ? Do you want to edit the local lambda function now? No
+ ```
+
+ === "arm64"
+
+ === "SAM"
+
+ ```yaml hl_lines="6"
+ MyLambdaFunction:
+ Type: AWS::Serverless::Function
+ Properties:
+ Architectures: [arm64]
+ Layers:
+ - !Sub arn:aws:lambda:${AWS::Region}:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:62
+ ```
+
+ === "Serverless framework"
+
+ ```yaml hl_lines="6"
+ functions:
+ hello:
+ handler: lambda_function.lambda_handler
+ architecture: arm64
+ layers:
+ - arn:aws:lambda:${aws:region}:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:62
+ ```
+
+ === "CDK"
+
+ ```python hl_lines="11 17"
+ from aws_cdk import core, aws_lambda
+
+ class SampleApp(core.Construct):
+
+ def __init__(self, scope: core.Construct, id_: str, env: core.Environment) -> None:
+ super().__init__(scope, id_)
+
+ powertools_layer = aws_lambda.LayerVersion.from_layer_version_arn(
+ self,
+ id="lambda-powertools",
+ layer_version_arn=f"arn:aws:lambda:{env.region}:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:62"
+ )
+ aws_lambda.Function(self,
+ 'sample-app-lambda',
+ runtime=aws_lambda.Runtime.PYTHON_3_9,
+ architecture=aws_lambda.Architecture.ARM_64,
+ layers=[powertools_layer]
+ # other props...
+ )
+ ```
+
+ === "Terraform"
+
+ ```terraform hl_lines="9 37"
+ terraform {
+ required_version = "~> 1.0.5"
+ required_providers {
+ aws = "~> 3.50.0"
+ }
+ }
- === "arm64"
+ provider "aws" {
+ region = "{region}"
+ }
- === "SAM"
+ resource "aws_iam_role" "iam_for_lambda" {
+ name = "iam_for_lambda"
- ```yaml hl_lines="6"
- MyLambdaFunction:
- Type: AWS::Serverless::Function
- Properties:
- Architectures: [arm64]
- Layers:
- - !Sub arn:aws:lambda:${AWS::Region}:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:61
- ```
-
- === "Serverless framework"
-
- ```yaml hl_lines="6"
- functions:
- hello:
- handler: lambda_function.lambda_handler
- architecture: arm64
- layers:
- - arn:aws:lambda:${aws:region}:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:61
- ```
-
- === "CDK"
-
- ```python hl_lines="11 17"
- from aws_cdk import core, aws_lambda
-
- class SampleApp(core.Construct):
-
- def __init__(self, scope: core.Construct, id_: str, env: core.Environment) -> None:
- super().__init__(scope, id_)
-
- powertools_layer = aws_lambda.LayerVersion.from_layer_version_arn(
- self,
- id="lambda-powertools",
- layer_version_arn=f"arn:aws:lambda:{env.region}:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:61"
- )
- aws_lambda.Function(self,
- 'sample-app-lambda',
- runtime=aws_lambda.Runtime.PYTHON_3_9,
- architecture=aws_lambda.Architecture.ARM_64,
- layers=[powertools_layer]
- # other props...
- )
- ```
-
- === "Terraform"
-
- ```terraform hl_lines="9 37"
- terraform {
- required_version = "~> 1.0.5"
- required_providers {
- aws = "~> 3.50.0"
- }
- }
-
- provider "aws" {
- region = "{region}"
- }
-
- resource "aws_iam_role" "iam_for_lambda" {
- name = "iam_for_lambda"
-
- assume_role_policy = <
+ ? Choose the runtime that you want to use: Python
+ ? Do you want to configure advanced settings? Yes
+ ...
+ ? Do you want to enable Lambda layers for this function? Yes
+ ? Enter up to 5 existing Lambda layer ARNs (comma-separated): arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:62
+ β― amplify push -y
+
+
+ # Updating an existing function and add the layer
+ β― amplify update function
+ ? Select the Lambda function you want to update test2
+ General information
+ - Name:
+ ? Which setting do you want to update? Lambda layers configuration
+ ? Do you want to enable Lambda layers for this function? Yes
+ ? Enter up to 5 existing Lambda layer ARNs (comma-separated): arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:62
+ ? Do you want to edit the local lambda function now? No
+ ```
+
+### Extra dependencies
+
+The vast majority of [features](#features) rely on standard library and AWS SDK _(boto3)_ only. The following features however require additional dependencies:
+
+| Feature | Pip | Dependency |
+| ------------------- | --------------------------------------------------------------------------------- | ----------------------------------- |
+| Tracer | **[`pip install "aws-lambda-powertools[tracer]"`](#){: .copyMe}:clipboard:** | `aws-xray-sdk` |
+| Validation | **[`pip install "aws-lambda-powertools[validation]"`](#){: .copyMe}:clipboard:** | `fastjsonschema` |
+| Parser | **[`pip install "aws-lambda-powertools[parser]"`](#){: .copyMe}:clipboard:** | `pydantic` |
+| Data Masking | **[`pip install "aws-lambda-powertools[datamasking]"`](#){: .copyMe}:clipboard:** | `aws-encryption-sdk`, `jsonpath-ng` |
+| Idempotency (Redis) | **[`pip install "aws-lambda-powertools[redis]"`](#){: .copyMe}:clipboard:** | `redis` |
+
+> New to pip?
+
+You can use `,` delimiter to install multiple at once: [**`pip install "aws-lambda-powertools[tracer,parser,datamasking"]`**](#){: .copyMe}:clipboard:
- ```
+### Local development
- === "Pulumi"
+!!! info "Using Lambda Layer? Simply add [**`"aws-lambda-powertools[all]"`**](#){: .copyMe}:clipboard: as a development dependency."
- ```python
- import json
- import pulumi
- import pulumi_aws as aws
+Powertools for AWS Lambda (Python) relies on the [AWS SDK bundled in the Lambda runtime](https://docs.aws.amazon.com/lambda/latest/dg/lambda-python.html){target="_blank"}. This helps us achieve an optimal package size and initialization. However, when developing locally, you need to install AWS SDK as a development dependency to support IDE auto-completion and to run your tests locally:
- role = aws.iam.Role("role",
- assume_role_policy=json.dumps({
- "Version": "2012-10-17",
- "Statement": [
- {
- "Action": "sts:AssumeRole",
- "Principal": {
- "Service": "lambda.amazonaws.com"
- },
- "Effect": "Allow"
- }
- ]
- }),
- managed_policy_arns=[aws.iam.ManagedPolicy.AWS_LAMBDA_BASIC_EXECUTION_ROLE]
- )
-
- lambda_function = aws.lambda_.Function("function",
- layers=[pulumi.Output.concat("arn:aws:lambda:",aws.get_region_output().name,":017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:11")],
- tracing_config={
- "mode": "Active"
- },
- runtime=aws.lambda_.Runtime.PYTHON3D9,
- handler="index.handler",
- role=role.arn,
- architectures=["arm64"],
- code=pulumi.FileArchive("lambda_function_payload.zip")
- )
- ```
-
- === "Amplify"
-
- ```zsh
- # Create a new one with the layer
- β― amplify add function
- ? Select which capability you want to add: Lambda function (serverless function)
- ? Provide an AWS Lambda function name:
- ? Choose the runtime that you want to use: Python
- ? Do you want to configure advanced settings? Yes
- ...
- ? Do you want to enable Lambda layers for this function? Yes
- ? Enter up to 5 existing Lambda layer ARNs (comma-separated): arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:61
- β― amplify push -y
-
-
- # Updating an existing function and add the layer
- β― amplify update function
- ? Select the Lambda function you want to update test2
- General information
- - Name:
- ? Which setting do you want to update? Lambda layers configuration
- ? Do you want to enable Lambda layers for this function? Yes
- ? Enter up to 5 existing Lambda layer ARNs (comma-separated): arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:61
- ? Do you want to edit the local lambda function now? No
- ```
-
-??? question "Want to inspect the contents of the Layer?"
- Change {region} to your AWS region, e.g. `eu-west-1`
-
- ```bash title="AWS CLI"
- aws lambda get-layer-version-by-arn --arn arn:aws:lambda:{region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:61 --region {region}
- ```
+- __Pip__: [**`pip install "aws-lambda-powertools[aws-sdk]"`**](#){: .copyMe}:clipboard:
+- __Poetry__: [**`poetry add "aws-lambda-powertools[aws-sdk]" --group dev`**](#){: .copyMe}:clipboard:
+- __Pdm__: [**`pdm add -dG "aws-lambda-powertools[aws-sdk]"`**](#){: .copyMe}:clipboard:
+
+__A word about dependency resolution__
+
+In this context, `[aws-sdk]` is an alias to the `boto3` package. Due to dependency resolution, it'll either install:
+
+- __(A)__ the SDK version available in [Lambda runtime](https://docs.aws.amazon.com/lambda/latest/dg/lambda-python.html){target="_blank"}
+- __(B)__ a more up-to-date version if another package you use also depends on `boto3`, for example [Powertools for AWS Lambda (Python) Tracer](core/tracer.md){target="_blank"}
+
+### Lambda Layer
+
+[Lambda Layer](https://docs.aws.amazon.com/lambda/latest/dg/configuration-layers.html){target="_blank"} is a .zip file archive that can contain additional code, pre-packaged dependencies, data, or configuration files. We compile and optimize [all dependencies](#extra-dependencies), and [remove duplicate dependencies already available in the Lambda runtime](https://github.com/awslabs/cdk-aws-lambda-powertools-layer/blob/main/layer/Python/Dockerfile#L36){target="_blank"} to achieve the most optimal size.
+
+??? note "Note: Click to expand and copy any regional Lambda Layer ARN"
+
+ === "x86_64"
- The pre-signed URL to download this Lambda Layer will be within `Location` key.
+ | Region | Layer ARN |
+ | ---------------- | ---------------------------------------------------------------------------------------------------------- |
+ | `af-south-1` | [arn:aws:lambda:af-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:62](#){: .copyMe}:clipboard: |
+ | `ap-east-1` | [arn:aws:lambda:ap-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:62](#){: .copyMe}:clipboard: |
+ | `ap-northeast-1` | [arn:aws:lambda:ap-northeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:62](#){: .copyMe}:clipboard: |
+ | `ap-northeast-2` | [arn:aws:lambda:ap-northeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:62](#){: .copyMe}:clipboard: |
+ | `ap-northeast-3` | [arn:aws:lambda:ap-northeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV2:62](#){: .copyMe}:clipboard: |
+ | `ap-south-1` | [arn:aws:lambda:ap-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:62](#){: .copyMe}:clipboard: |
+ | `ap-south-2` | [arn:aws:lambda:ap-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:62](#){: .copyMe}:clipboard: |
+ | `ap-southeast-1` | [arn:aws:lambda:ap-southeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:62](#){: .copyMe}:clipboard: |
+ | `ap-southeast-2` | [arn:aws:lambda:ap-southeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:62](#){: .copyMe}:clipboard: |
+ | `ap-southeast-3` | [arn:aws:lambda:ap-southeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV2:62](#){: .copyMe}:clipboard: |
+ | `ap-southeast-4` | [arn:aws:lambda:ap-southeast-4:017000801446:layer:AWSLambdaPowertoolsPythonV2:62](#){: .copyMe}:clipboard: |
+ | `ca-central-1` | [arn:aws:lambda:ca-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:62](#){: .copyMe}:clipboard: |
+ | `ca-west-1` | [arn:aws:lambda:ca-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:62](#){: .copyMe}:clipboard: |
+ | `eu-central-1` | [arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:62](#){: .copyMe}:clipboard: |
+ | `eu-central-2` | [arn:aws:lambda:eu-central-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:62](#){: .copyMe}:clipboard: |
+ | `eu-north-1` | [arn:aws:lambda:eu-north-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:62](#){: .copyMe}:clipboard: |
+ | `eu-south-1` | [arn:aws:lambda:eu-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:62](#){: .copyMe}:clipboard: |
+ | `eu-south-2` | [arn:aws:lambda:eu-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:62](#){: .copyMe}:clipboard: |
+ | `eu-west-1` | [arn:aws:lambda:eu-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:62](#){: .copyMe}:clipboard: |
+ | `eu-west-2` | [arn:aws:lambda:eu-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:62](#){: .copyMe}:clipboard: |
+ | `eu-west-3` | [arn:aws:lambda:eu-west-3:017000801446:layer:AWSLambdaPowertoolsPythonV2:62](#){: .copyMe}:clipboard: |
+ | `il-central-1` | [arn:aws:lambda:il-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:62](#){: .copyMe}:clipboard: |
+ | `me-central-1` | [arn:aws:lambda:me-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:62](#){: .copyMe}:clipboard: |
+ | `me-south-1` | [arn:aws:lambda:me-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:62](#){: .copyMe}:clipboard: |
+ | `sa-east-1` | [arn:aws:lambda:sa-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:62](#){: .copyMe}:clipboard: |
+ | `us-east-1` | [arn:aws:lambda:us-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:62](#){: .copyMe}:clipboard: |
+ | `us-east-2` | [arn:aws:lambda:us-east-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:62](#){: .copyMe}:clipboard: |
+ | `us-west-1` | [arn:aws:lambda:us-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:62](#){: .copyMe}:clipboard: |
+ | `us-west-2` | [arn:aws:lambda:us-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:62](#){: .copyMe}:clipboard: |
+
+ === "arm64"
+
+ | Region | Layer ARN |
+ | ---------------- | ---------------------------------------------------------------------------------------------------------------- |
+ | `af-south-1` | [arn:aws:lambda:af-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:62](#){: .copyMe}:clipboard: |
+ | `ap-east-1` | [arn:aws:lambda:ap-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:62](#){: .copyMe}:clipboard: |
+ | `ap-northeast-1` | [arn:aws:lambda:ap-northeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:62](#){: .copyMe}:clipboard: |
+ | `ap-northeast-2` | [arn:aws:lambda:ap-northeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:62](#){: .copyMe}:clipboard: |
+ | `ap-northeast-3` | [arn:aws:lambda:ap-northeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:62](#){: .copyMe}:clipboard: |
+ | `ap-south-1` | [arn:aws:lambda:ap-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:62](#){: .copyMe}:clipboard: |
+ | `ap-south-2` | [arn:aws:lambda:ap-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:62](#){: .copyMe}:clipboard: |
+ | `ap-southeast-1` | [arn:aws:lambda:ap-southeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:62](#){: .copyMe}:clipboard: |
+ | `ap-southeast-2` | [arn:aws:lambda:ap-southeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:62](#){: .copyMe}:clipboard: |
+ | `ap-southeast-3` | [arn:aws:lambda:ap-southeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:62](#){: .copyMe}:clipboard: |
+ | `ca-central-1` | [arn:aws:lambda:ca-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:62](#){: .copyMe}:clipboard: |
+ | `eu-central-1` | [arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:62](#){: .copyMe}:clipboard: |
+ | `eu-central-2` | [arn:aws:lambda:eu-central-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:62](#){: .copyMe}:clipboard: |
+ | `eu-north-1` | [arn:aws:lambda:eu-north-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:62](#){: .copyMe}:clipboard: |
+ | `eu-south-1` | [arn:aws:lambda:eu-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:62](#){: .copyMe}:clipboard: |
+ | `eu-south-2` | [arn:aws:lambda:eu-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:62](#){: .copyMe}:clipboard: |
+ | `eu-west-1` | [arn:aws:lambda:eu-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:62](#){: .copyMe}:clipboard: |
+ | `eu-west-2` | [arn:aws:lambda:eu-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:62](#){: .copyMe}:clipboard: |
+ | `eu-west-3` | [arn:aws:lambda:eu-west-3:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:62](#){: .copyMe}:clipboard: |
+ | `il-central-1` | [arn:aws:lambda:il-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:62](#){: .copyMe}:clipboard: |
+ | `me-central-1` | [arn:aws:lambda:me-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:62](#){: .copyMe}:clipboard: |
+ | `me-south-1` | [arn:aws:lambda:me-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:62](#){: .copyMe}:clipboard: |
+ | `sa-east-1` | [arn:aws:lambda:sa-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:62](#){: .copyMe}:clipboard: |
+ | `us-east-1` | [arn:aws:lambda:us-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:62](#){: .copyMe}:clipboard: |
+ | `us-east-2` | [arn:aws:lambda:us-east-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:62](#){: .copyMe}:clipboard: |
+ | `us-west-1` | [arn:aws:lambda:us-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:62](#){: .copyMe}:clipboard: |
+ | `us-west-2` | [arn:aws:lambda:us-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:62](#){: .copyMe}:clipboard: |
+
+**Want to inspect the contents of the Layer?**
+
+Replace `{region}` with your AWS region, _e.g. `eu-west-1`_. The pre-signed URL to download this Lambda Layer will be within `Location` key in the CLI output.
+
+```bash title="AWS CLI command to download Lambda Layer content"
+aws lambda get-layer-version-by-arn --arn arn:aws:lambda:{region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:62 --region {region}
+```
#### SAR
@@ -469,10 +490,10 @@ Serverless Application Repository (SAR) App deploys a CloudFormation stack with
Compared with the [public Layer ARN](#lambda-layer) option, SAR allows you to choose a semantic version and deploys a Layer in your target account.
-| App | ARN | Description |
-| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------- |
-| [aws-lambda-powertools-python-layer](https://serverlessrepo.aws.amazon.com/applications/eu-west-1/057560766410/aws-lambda-powertools-python-layer){target="_blank"} | [arn:aws:serverlessrepo:eu-west-1:057560766410:applications/aws-lambda-powertools-python-layer](#){: .copyMe}:clipboard: | Contains all extra dependencies (e.g: pydantic). |
-| [aws-lambda-powertools-python-layer-arm64](https://serverlessrepo.aws.amazon.com/applications/eu-west-1/057560766410/aws-lambda-powertools-python-layer-arm64){target="_blank"} | [arn:aws:serverlessrepo:eu-west-1:057560766410:applications/aws-lambda-powertools-python-layer-arm64](#){: .copyMe}:clipboard: | Contains all extra dependencies (e.g: pydantic). For arm64 functions. |
+| App | ARN | Description |
+| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------- |
+| [**aws-lambda-powertools-python-layer**](https://serverlessrepo.aws.amazon.com/applications/eu-west-1/057560766410/aws-lambda-powertools-python-layer){target="_blank"} | [arn:aws:serverlessrepo:eu-west-1:057560766410:applications/aws-lambda-powertools-python-layer](#){: .copyMe}:clipboard: | Contains all extra dependencies (e.g: pydantic). |
+| [**aws-lambda-powertools-python-layer-arm64**](https://serverlessrepo.aws.amazon.com/applications/eu-west-1/057560766410/aws-lambda-powertools-python-layer-arm64){target="_blank"} | [arn:aws:serverlessrepo:eu-west-1:057560766410:applications/aws-lambda-powertools-python-layer-arm64](#){: .copyMe}:clipboard: | Contains all extra dependencies (e.g: pydantic). For arm64 functions. |
??? note "Click to expand and copy SAR code snippets for popular frameworks"
@@ -602,78 +623,63 @@ Compared with the [public Layer ARN](#lambda-layer) option, SAR allows you to ch
}
```
-??? example "Example: Least-privileged IAM permissions to deploy Layer"
-
- > Credits to [mwarkentin](https://github.com/mwarkentin){target="_blank" rel="nofollow"} for providing the scoped down IAM permissions.
-
- The region and the account id for `CloudFormationTransform` and `GetCfnTemplate` are fixed.
-
- === "template.yml"
-
- ```yaml hl_lines="21-52"
- AWSTemplateFormatVersion: "2010-09-09"
- Resources:
- PowertoolsLayerIamRole:
- Type: "AWS::IAM::Role"
- Properties:
- AssumeRolePolicyDocument:
- Version: "2012-10-17"
- Statement:
- - Effect: "Allow"
- Principal:
- Service:
- - "cloudformation.amazonaws.com"
- Action:
- - "sts:AssumeRole"
- Path: "/"
- PowertoolsLayerIamPolicy:
- Type: "AWS::IAM::Policy"
- Properties:
- PolicyName: PowertoolsLambdaLayerPolicy
- PolicyDocument:
- Version: "2012-10-17"
- Statement:
- - Sid: CloudFormationTransform
- Effect: Allow
- Action: cloudformation:CreateChangeSet
- Resource:
- - arn:aws:cloudformation:us-east-1:aws:transform/Serverless-2016-10-31
- - Sid: GetCfnTemplate
- Effect: Allow
- Action:
- - serverlessrepo:CreateCloudFormationTemplate
- - serverlessrepo:GetCloudFormationTemplate
- Resource:
- # this is arn of the Powertools for AWS Lambda (Python) SAR app
- - arn:aws:serverlessrepo:eu-west-1:057560766410:applications/aws-lambda-powertools-python-layer
- - Sid: S3AccessLayer
- Effect: Allow
- Action:
- - s3:GetObject
- Resource:
- # AWS publishes to an external S3 bucket locked down to your account ID
- # The below example is us publishing Powertools for AWS Lambda (Python)
- # Bucket: awsserverlessrepo-changesets-plntc6bfnfj
- # Key: *****/arn:aws:serverlessrepo:eu-west-1:057560766410:applications-aws-lambda-powertools-python-layer-versions-1.10.2/aeeccf50-****-****-****-*********
- - arn:aws:s3:::awsserverlessrepo-changesets-*/*
- - Sid: GetLayerVersion
- Effect: Allow
- Action:
- - lambda:PublishLayerVersion
- - lambda:GetLayerVersion
- Resource:
- - !Sub arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:layer:aws-lambda-powertools-python-layer*
- Roles:
- - Ref: "PowertoolsLayerIamRole"
- ```
-
-??? note "Click to expand and copy an AWS CLI command to list all versions available in SAR"
-
- You can fetch available versions via SAR ListApplicationVersions API:
-
- ```bash title="AWS CLI example"
- aws serverlessrepo list-application-versions \
- --application-id arn:aws:serverlessrepo:eu-west-1:057560766410:applications/aws-lambda-powertools-python-layer
+ Credits to [mwarkentin](https://github.com/mwarkentin){target="_blank" rel="nofollow"} for providing the scoped down IAM permissions below.
+
+ ```yaml hl_lines="21-52" title="Least-privileged IAM permissions SAM example"
+ AWSTemplateFormatVersion: "2010-09-09"
+ Resources:
+ PowertoolsLayerIamRole:
+ Type: "AWS::IAM::Role"
+ Properties:
+ AssumeRolePolicyDocument:
+ Version: "2012-10-17"
+ Statement:
+ - Effect: "Allow"
+ Principal:
+ Service:
+ - "cloudformation.amazonaws.com"
+ Action:
+ - "sts:AssumeRole"
+ Path: "/"
+ PowertoolsLayerIamPolicy:
+ Type: "AWS::IAM::Policy"
+ Properties:
+ PolicyName: PowertoolsLambdaLayerPolicy
+ PolicyDocument:
+ Version: "2012-10-17"
+ Statement:
+ - Sid: CloudFormationTransform
+ Effect: Allow
+ Action: cloudformation:CreateChangeSet
+ Resource:
+ - arn:aws:cloudformation:us-east-1:aws:transform/Serverless-2016-10-31
+ - Sid: GetCfnTemplate
+ Effect: Allow
+ Action:
+ - serverlessrepo:CreateCloudFormationTemplate
+ - serverlessrepo:GetCloudFormationTemplate
+ Resource:
+ # this is arn of the Powertools for AWS Lambda (Python) SAR app
+ - arn:aws:serverlessrepo:eu-west-1:057560766410:applications/aws-lambda-powertools-python-layer
+ - Sid: S3AccessLayer
+ Effect: Allow
+ Action:
+ - s3:GetObject
+ Resource:
+ # AWS publishes to an external S3 bucket locked down to your account ID
+ # The below example is us publishing Powertools for AWS Lambda (Python)
+ # Bucket: awsserverlessrepo-changesets-plntc6bfnfj
+ # Key: *****/arn:aws:serverlessrepo:eu-west-1:057560766410:applications-aws-lambda-powertools-python-layer-versions-1.10.2/aeeccf50-****-****-****-*********
+ - arn:aws:s3:::awsserverlessrepo-changesets-*/*
+ - Sid: GetLayerVersion
+ Effect: Allow
+ Action:
+ - lambda:PublishLayerVersion
+ - lambda:GetLayerVersion
+ Resource:
+ - !Sub arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:layer:aws-lambda-powertools-python-layer*
+ Roles:
+ - Ref: "PowertoolsLayerIamRole"
```
## Quick getting started
@@ -688,22 +694,22 @@ Core utilities such as Tracing, Logging, Metrics, and Event Handler will be avai
| Utility | Description |
| --------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| [**Tracing**](./core/tracer.md){target="_blank"} | Decorators and utilities to trace Lambda function handlers, and both synchronous and asynchronous functions |
-| [**Logger**](./core/logger.md){target="_blank"} | Structured logging made easier, and decorator to enrich structured logging with key Lambda context details |
-| [**Metrics**](./core/metrics.md){target="_blank"} | Custom Metrics created asynchronously via CloudWatch Embedded Metric Format (EMF) |
-| [**Event handler: AppSync**](./core/event_handler/appsync.md){target="_blank"} | AppSync event handler for Lambda Direct Resolver and Amplify GraphQL Transformer function |
-| [**Event handler: API Gateway, ALB and Lambda Function URL**](https://docs.powertools.aws.dev/lambda/python/latest/core/event_handler/api_gateway/) | Amazon API Gateway REST/HTTP API and ALB event handler for Lambda functions invoked using Proxy integration, and Lambda Function URL |
-| [**Middleware factory**](./utilities/middleware_factory.md){target="_blank"} | Decorator factory to create your own middleware to run logic before, and after each Lambda invocation |
-| [**Parameters**](./utilities/parameters.md){target="_blank"} | Retrieve parameter values from AWS Systems Manager Parameter Store, AWS Secrets Manager, or Amazon DynamoDB, and cache them for a specific amount of time |
-| [**Batch processing**](./utilities/batch.md){target="_blank"} | Handle partial failures for AWS SQS batch processing |
-| [**Typing**](./utilities/typing.md){target="_blank"} | Static typing classes to speedup development in your IDE |
-| [**Validation**](./utilities/validation.md){target="_blank"} | JSON Schema validator for inbound events and responses |
-| [**Event source data classes**](./utilities/data_classes.md){target="_blank"} | Data classes describing the schema of common Lambda event triggers |
-| [**Parser**](./utilities/parser.md){target="_blank"} | Data parsing and deep validation using Pydantic |
-| [**Idempotency**](./utilities/idempotency.md){target="_blank"} | Idempotent Lambda handler |
-| [**Data Masking**](./utilities/data_masking.md){target="_blank"} | Protect confidential data with easy removal or encryption |
-| [**Feature Flags**](./utilities/feature_flags.md){target="_blank"} | A simple rule engine to evaluate when one or multiple features should be enabled depending on the input |
-| [**Streaming**](./utilities/streaming.md){target="_blank"} | Streams datasets larger than the available memory as streaming data. |
+| [__Tracing__](./core/tracer.md){target="_blank"} | Decorators and utilities to trace Lambda function handlers, and both synchronous and asynchronous functions |
+| [__Logger__](./core/logger.md){target="_blank"} | Structured logging made easier, and decorator to enrich structured logging with key Lambda context details |
+| [__Metrics__](./core/metrics.md){target="_blank"} | Custom Metrics created asynchronously via CloudWatch Embedded Metric Format (EMF) |
+| [__Event handler: AppSync__](./core/event_handler/appsync.md){target="_blank"} | AppSync event handler for Lambda Direct Resolver and Amplify GraphQL Transformer function |
+| [__Event handler: API Gateway, ALB and Lambda Function URL__](https://docs.powertools.aws.dev/lambda/python/latest/core/event_handler/api_gateway/) | Amazon API Gateway REST/HTTP API and ALB event handler for Lambda functions invoked using Proxy integration, and Lambda Function URL |
+| [__Middleware factory__](./utilities/middleware_factory.md){target="_blank"} | Decorator factory to create your own middleware to run logic before, and after each Lambda invocation |
+| [__Parameters__](./utilities/parameters.md){target="_blank"} | Retrieve parameter values from AWS Systems Manager Parameter Store, AWS Secrets Manager, or Amazon DynamoDB, and cache them for a specific amount of time |
+| [__Batch processing__](./utilities/batch.md){target="_blank"} | Handle partial failures for AWS SQS batch processing |
+| [__Typing__](./utilities/typing.md){target="_blank"} | Static typing classes to speedup development in your IDE |
+| [__Validation__](./utilities/validation.md){target="_blank"} | JSON Schema validator for inbound events and responses |
+| [__Event source data classes__](./utilities/data_classes.md){target="_blank"} | Data classes describing the schema of common Lambda event triggers |
+| [__Parser__](./utilities/parser.md){target="_blank"} | Data parsing and deep validation using Pydantic |
+| [__Idempotency__](./utilities/idempotency.md){target="_blank"} | Idempotent Lambda handler |
+| [__Data Masking__](./utilities/data_masking.md){target="_blank"} | Protect confidential data with easy removal or encryption |
+| [__Feature Flags__](./utilities/feature_flags.md){target="_blank"} | A simple rule engine to evaluate when one or multiple features should be enabled depending on the input |
+| [__Streaming__](./utilities/streaming.md){target="_blank"} | Streams datasets larger than the available memory as streaming data. |
## Environment variables
@@ -712,34 +718,33 @@ Core utilities such as Tracing, Logging, Metrics, and Event Handler will be avai
| Environment variable | Description | Utility | Default |
| ----------------------------------------- | -------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | --------------------- |
-| **POWERTOOLS_SERVICE_NAME** | Sets service name used for tracing namespace, metrics dimension and structured logging | All | `"service_undefined"` |
-| **POWERTOOLS_METRICS_NAMESPACE** | Sets namespace used for metrics | [Metrics](./core/metrics.md){target="_blank"} | `None` |
-| **POWERTOOLS_TRACE_DISABLED** | Explicitly disables tracing | [Tracing](./core/tracer.md){target="_blank"} | `false` |
-| **POWERTOOLS_TRACER_CAPTURE_RESPONSE** | Captures Lambda or method return as metadata. | [Tracing](./core/tracer.md){target="_blank"} | `true` |
-| **POWERTOOLS_TRACER_CAPTURE_ERROR** | Captures Lambda or method exception as metadata. | [Tracing](./core/tracer.md){target="_blank"} | `true` |
-| **POWERTOOLS_TRACE_MIDDLEWARES** | Creates sub-segment for each custom middleware | [Middleware factory](./utilities/middleware_factory.md){target="_blank"} | `false` |
-| **POWERTOOLS_LOGGER_LOG_EVENT** | Logs incoming event | [Logging](./core/logger.md){target="_blank"} | `false` |
-| **POWERTOOLS_LOGGER_SAMPLE_RATE** | Debug log sampling | [Logging](./core/logger.md){target="_blank"} | `0` |
-| **POWERTOOLS_LOG_DEDUPLICATION_DISABLED** | Disables log deduplication filter protection to use Pytest Live Log feature | [Logging](./core/logger.md){target="_blank"} | `false` |
-| **POWERTOOLS_PARAMETERS_MAX_AGE** | Adjust how long values are kept in cache (in seconds) | [Parameters](./utilities/parameters.md#adjusting-cache-ttl){target="_blank"} | `5` |
-| **POWERTOOLS_PARAMETERS_SSM_DECRYPT** | Sets whether to decrypt or not values retrieved from AWS SSM Parameters Store | [Parameters](./utilities/parameters.md#ssmprovider){target="_blank"} | `false` |
-| **POWERTOOLS_DEV** | Increases verbosity across utilities | Multiple; see [POWERTOOLS_DEV effect below](#optimizing-for-non-production-environments) | `false` |
-| **POWERTOOLS_LOG_LEVEL** | Sets logging level | [Logging](./core/logger.md){target="_blank"} | `INFO` |
+| __POWERTOOLS_SERVICE_NAME__ | Sets service name used for tracing namespace, metrics dimension and structured logging | All | `"service_undefined"` |
+| __POWERTOOLS_METRICS_NAMESPACE__ | Sets namespace used for metrics | [Metrics](./core/metrics.md){target="_blank"} | `None` |
+| __POWERTOOLS_TRACE_DISABLED__ | Explicitly disables tracing | [Tracing](./core/tracer.md){target="_blank"} | `false` |
+| __POWERTOOLS_TRACER_CAPTURE_RESPONSE__ | Captures Lambda or method return as metadata. | [Tracing](./core/tracer.md){target="_blank"} | `true` |
+| __POWERTOOLS_TRACER_CAPTURE_ERROR__ | Captures Lambda or method exception as metadata. | [Tracing](./core/tracer.md){target="_blank"} | `true` |
+| __POWERTOOLS_TRACE_MIDDLEWARES__ | Creates sub-segment for each custom middleware | [Middleware factory](./utilities/middleware_factory.md){target="_blank"} | `false` |
+| __POWERTOOLS_LOGGER_LOG_EVENT__ | Logs incoming event | [Logging](./core/logger.md){target="_blank"} | `false` |
+| __POWERTOOLS_LOGGER_SAMPLE_RATE__ | Debug log sampling | [Logging](./core/logger.md){target="_blank"} | `0` |
+| __POWERTOOLS_LOG_DEDUPLICATION_DISABLED__ | Disables log deduplication filter protection to use Pytest Live Log feature | [Logging](./core/logger.md){target="_blank"} | `false` |
+| __POWERTOOLS_PARAMETERS_MAX_AGE__ | Adjust how long values are kept in cache (in seconds) | [Parameters](./utilities/parameters.md#adjusting-cache-ttl){target="_blank"} | `5` |
+| __POWERTOOLS_PARAMETERS_SSM_DECRYPT__ | Sets whether to decrypt or not values retrieved from AWS SSM Parameters Store | [Parameters](./utilities/parameters.md#ssmprovider){target="_blank"} | `false` |
+| __POWERTOOLS_DEV__ | Increases verbosity across utilities | Multiple; see [POWERTOOLS_DEV effect below](#optimizing-for-non-production-environments) | `false` |
+| __POWERTOOLS_LOG_LEVEL__ | Sets logging level | [Logging](./core/logger.md){target="_blank"} | `INFO` |
### Optimizing for non-production environments
-Whether you're prototyping locally or against a non-production environment, you can use `POWERTOOLS_DEV` to increase verbosity across multiple utilities.
+!!! info "We will emit a warning when this feature is used to help you detect misuse in production."
-???+ info
- We will emit a warning when `POWERTOOLS_DEV` is enabled to help you detect misuse in production environments.
+Whether you're prototyping locally or against a non-production environment, you can use `POWERTOOLS_DEV` to increase verbosity across multiple utilities.
When `POWERTOOLS_DEV` is set to a truthy value (`1`, `true`), it'll have the following effects:
-| Utility | Effect |
-| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
-| **Logger** | Increase JSON indentation to 4. This will ease local debugging when running functions locally under emulators or direct calls while not affecting unit tests |
-| **Event Handler** | Enable full traceback errors in the response, indent request/responses, and CORS in dev mode (`*`). |
-| **Tracer** | Future-proof safety to disables tracing operations in non-Lambda environments. This already happens automatically in the Tracer utility. |
+| Utility | Effect |
+| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| __Logger__ | Increase JSON indentation to 4. This will ease local debugging when running functions locally under emulators or direct calls while not affecting unit tests.
However, Amazon CloudWatch Logs view will degrade as each new line is treated as a new message. |
+| __Event Handler__ | Enable full traceback errors in the response, indent request/responses, and CORS in dev mode (`*`). |
+| __Tracer__ | Future-proof safety to disables tracing operations in non-Lambda environments. This already happens automatically in the Tracer utility. |
## Debug mode
@@ -747,42 +752,98 @@ As a best practice for libraries, Powertools module logging statements are suppr
When necessary, you can use `POWERTOOLS_DEBUG` environment variable to enable debugging. This will provide additional information on every internal operation.
-## How to support Powertools for AWS Lambda (Python)?
+## Support Powertools for AWS Lambda (Python)
+
+There are many ways you can help us gain future investments to improve everyone's experience:
+
+
+
+- :heart:{ .lg .middle } __Become a public reference__
+
+ ---
+
+ Add your company name and logo on our [landing page](https://powertools.aws.dev).
+
+ [:octicons-arrow-right-24: GitHub Issue template]((https://github.com/aws-powertools/powertools-lambda-python/issues/new?assignees=&labels=customer-reference&template=support_powertools.yml&title=%5BSupport+Lambda+Powertools%5D%3A+%3Cyour+organization+name%3E){target="_blank"})
+
+- :mega:{ .lg .middle } __Share your work__
+
+ ---
+
+ Blog posts, video, and sample projects about Powertools for AWS Lambda.
+
+ [:octicons-arrow-right-24: GitHub Issue template](https://github.com/aws-powertools/powertools-lambda-python/issues/new?assignees=&labels=community-content&template=share_your_work.yml&title=%5BI+Made+This%5D%3A+%3CTITLE%3E){target="_blank"}
+
+- :partying_face:{ .lg .middle } __Join the community__
+
+ ---
+
+ Connect, ask questions, and share what features you use.
+
+ [:octicons-arrow-right-24: Discord invite](https://discord.gg/B8zZKbbyET){target="blank"}
+
+
### Becoming a reference customer
-Knowing which companies are using this library is important to help prioritize the project internally. If your company is using Powertools for AWS Lambda (Python), you can request to have your name and logo added to the README file by raising a [Support Powertools for AWS Lambda (Python) (become a reference)](https://github.com/aws-powertools/powertools-lambda-python/issues/new?assignees=&labels=customer-reference&template=support_powertools.yml&title=%5BSupport+Lambda+Powertools%5D%3A+%3Cyour+organization+name%3E){target="_blank"} issue.
+Knowing which companies are using this library is important to help prioritize the project internally. The following companies, among others, use Powertools:
+
+
-Share what you did with Powertools for AWS Lambda (Python) ππ. Blog post, workshops, presentation, sample apps and others. Check out what the community has already shared about Powertools for AWS Lambda (Python) [here](https://docs.powertools.aws.dev/lambda/python/latest/we_made_this/){target="_blank"}.
+### Using Lambda Layers
-### Using Lambda Layer or SAR
+!!! note "Layers help us understand who uses Powertools for AWS Lambda (Python) in a non-intrusive way."
-This helps us understand who uses Powertools for AWS Lambda (Python) in a non-intrusive way, and helps us gain future investments for other Powertools for AWS Lambda languages. When [using Layers](https://docs.powertools.aws.dev/lambda/python/latest/#lambda-layer), you can add Powertools for AWS Lambda (Python) as a dev dependency (or as part of your virtual env) to not impact the development process.
+When [using Layers](#lambda-layer), you can add Powertools for AWS Lambda (Python) as a dev dependency to not impact the development process. For Layers, we pre-package all dependencies, compile and optimize for storage and both x86 and ARM architecture.
## Tenets
These are our core principles to guide our decision making.
-* **AWS Lambda only**. We optimise for AWS Lambda function environments and supported runtimes only. Utilities might work with web frameworks and non-Lambda environments, though they are not officially supported.
-* **Eases the adoption of best practices**. The main priority of the utilities is to facilitate best practices adoption, as defined in the AWS Well-Architected Serverless Lens; all other functionality is optional.
-* **Keep it lean**. Additional dependencies are carefully considered for security and ease of maintenance, and prevent negatively impacting startup time.
-* **We strive for backwards compatibility**. New features and changes should keep backwards compatibility. If a breaking change cannot be avoided, the deprecation and migration process should be clearly defined.
-* **We work backwards from the community**. We aim to strike a balance of what would work best for 80% of customers. Emerging practices are considered and discussed via Requests for Comment (RFCs)
-* **Progressive**. Utilities are designed to be incrementally adoptable for customers at any stage of their Serverless journey. They follow language idioms and their communityβs common practices.
+- __AWS Lambda only__. We optimise for AWS Lambda function environments and supported runtimes only. Utilities might work with web frameworks and non-Lambda environments, though they are not officially supported.
+- __Eases the adoption of best practices__. The main priority of the utilities is to facilitate best practices adoption, as defined in the AWS Well-Architected Serverless Lens; all other functionality is optional.
+- __Keep it lean__. Additional dependencies are carefully considered for security and ease of maintenance, and prevent negatively impacting startup time.
+- __We strive for backwards compatibility__. New features and changes should keep backwards compatibility. If a breaking change cannot be avoided, the deprecation and migration process should be clearly defined.
+- __We work backwards from the community__. We aim to strike a balance of what would work best for 80% of customers. Emerging practices are considered and discussed via Requests for Comment (RFCs)
+- __Progressive__. Utilities are designed to be incrementally adoptable for customers at any stage of their Serverless journey. They follow language idioms and their communityβs common practices.
diff --git a/docs/overrides/main.html b/docs/overrides/main.html
index 44935986883..0af326afb24 100644
--- a/docs/overrides/main.html
+++ b/docs/overrides/main.html
@@ -1,10 +1,5 @@
{% extends "base.html" %}
-{% block announce %}
-
π¨ As of February 8, 2024, AWS Lambda will no longer allow Python 3.7 functions to be updated. Inline with this, Powertools releases will stop supporting it.
-
Please ensure you update your functions to Python 3.8 or later to continue to use the latest version of Powertools for AWS Lambda (Python).