diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml
index 9ce63fe3db6..c9597418e3f 100644
--- a/.github/actionlint.yaml
+++ b/.github/actionlint.yaml
@@ -1,4 +1,3 @@
self-hosted-runner:
labels:
- - aws-powertools_ubuntu-latest_4-core
- aws-powertools_ubuntu-latest_8-core
diff --git a/.github/workflows/on_schedule_monthly_roadmap_reminder.yml b/.github/workflows/on_schedule_monthly_roadmap_reminder.yml
new file mode 100644
index 00000000000..bc1bcf78a98
--- /dev/null
+++ b/.github/workflows/on_schedule_monthly_roadmap_reminder.yml
@@ -0,0 +1,20 @@
+name: Monthly roadmap reminder
+
+on:
+ workflow_dispatch: {}
+# schedule:
+# - cron: '0 0 1 * *'
+
+permissions:
+ contents: read
+ pull-requests: read
+ issues: read
+
+
+jobs:
+ call-workflow-passing-data:
+ uses: aws-powertools/actions/.github/workflows/monthly_roadmap_reminder.yml@fd4575466e5c2ac10703ac16f5aa9fb8890f532a
+ with:
+ token: ${{ github.token }}
+
+
diff --git a/.github/workflows/publish_v2_layer.yml b/.github/workflows/publish_v2_layer.yml
index 81b1c0cc476..a479761da83 100644
--- a/.github/workflows/publish_v2_layer.yml
+++ b/.github/workflows/publish_v2_layer.yml
@@ -124,7 +124,7 @@ jobs:
- name: Set up Docker Buildx
id: builder
- uses: docker/setup-buildx-action@2b51285047da1547ffb1b2203d8be4c0af6b1f20 # v3.2.0
+ uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0
with:
install: true
driver: docker
diff --git a/.github/workflows/quality_check.yml b/.github/workflows/quality_check.yml
index 6dca729946d..a867d8a5184 100644
--- a/.github/workflows/quality_check.yml
+++ b/.github/workflows/quality_check.yml
@@ -71,7 +71,7 @@ jobs:
- name: Complexity baseline
run: make complexity-baseline
- name: Upload coverage to Codecov
- uses: codecov/codecov-action@c16abc29c95fcf9174b58eb7e1abf4c866893bc8 # 4.1.1
+ uses: codecov/codecov-action@84508663e988701840491b86de86b666e8a86bed # 4.3.0
with:
file: ./coverage.xml
env_vars: PYTHON
diff --git a/.github/workflows/secure_workflows.yml b/.github/workflows/secure_workflows.yml
index b1db349d5e8..e7c4bd952c3 100644
--- a/.github/workflows/secure_workflows.yml
+++ b/.github/workflows/secure_workflows.yml
@@ -32,6 +32,6 @@ jobs:
- name: Checkout code
uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2
- name: Ensure 3rd party workflows have SHA pinned
- uses: zgosalvez/github-actions-ensure-sha-pinned-actions@ba37328d4ea95eaf8b3bd6c6cef308f709a5f2ec # v3.0.3
+ uses: zgosalvez/github-actions-ensure-sha-pinned-actions@19ebcb0babbd282ae1822a0b9c28f3f1f25cea45 # v3.0.4
with:
allowlist: slsa-framework/slsa-github-generator
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 06d77913f14..53e0dce5e28 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,86 @@
## Bug Fixes
+* **docs:** clarified usage of validation with fine grained responses ([#4101](https://github.com/aws-powertools/powertools-lambda-python/issues/4101))
+* **event_source:** fix typo in physicalname attribute for AmazonMQ events ([#4053](https://github.com/aws-powertools/powertools-lambda-python/issues/4053))
+* **typing:** make the case_sensitive field a boolean only ([#4128](https://github.com/aws-powertools/powertools-lambda-python/issues/4128))
+* **typing:** improve overloads to ensure the return type follows the default_value type ([#4114](https://github.com/aws-powertools/powertools-lambda-python/issues/4114))
+
+## Documentation
+
+* **we-made-this:** new article on how to stream data with AWS Lambda & Powertools for AWS Lambda ([#4068](https://github.com/aws-powertools/powertools-lambda-python/issues/4068))
+
+## Features
+
+* **Idempotency:** add feature for manipulating idempotent responses ([#4037](https://github.com/aws-powertools/powertools-lambda-python/issues/4037))
+* **logger:** add method to return currently configured keys ([#4033](https://github.com/aws-powertools/powertools-lambda-python/issues/4033))
+
+## Maintenance
+
+* **ci:** add monthly roadmap reminder workflow ([#4075](https://github.com/aws-powertools/powertools-lambda-python/issues/4075))
+* **ci:** prevent deprecated custom runner from being used ([#4061](https://github.com/aws-powertools/powertools-lambda-python/issues/4061))
+* **deps:** bump squidfunk/mkdocs-material from `065f3af` to `6b124e1` in /docs ([#4055](https://github.com/aws-powertools/powertools-lambda-python/issues/4055))
+* **deps:** bump idna from 3.6 to 3.7 ([#4121](https://github.com/aws-powertools/powertools-lambda-python/issues/4121))
+* **deps:** bump squidfunk/mkdocs-material from `3307665` to `065f3af` in /docs ([#4052](https://github.com/aws-powertools/powertools-lambda-python/issues/4052))
+* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 3 updates ([#4042](https://github.com/aws-powertools/powertools-lambda-python/issues/4042))
+* **deps:** bump sqlparse from 0.4.4 to 0.5.0 ([#4138](https://github.com/aws-powertools/powertools-lambda-python/issues/4138))
+* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 1 update ([#4066](https://github.com/aws-powertools/powertools-lambda-python/issues/4066))
+* **deps:** bump pydantic from 1.10.14 to 1.10.15 ([#4064](https://github.com/aws-powertools/powertools-lambda-python/issues/4064))
+* **deps:** bump datadog-lambda from 5.91.0 to 5.92.0 ([#4038](https://github.com/aws-powertools/powertools-lambda-python/issues/4038))
+* **deps:** bump golang.org/x/sync from 0.6.0 to 0.7.0 in /layer/scripts/layer-balancer in the layer-balancer group ([#4071](https://github.com/aws-powertools/powertools-lambda-python/issues/4071))
+* **deps:** bump codecov/codecov-action from 4.1.1 to 4.2.0 ([#4072](https://github.com/aws-powertools/powertools-lambda-python/issues/4072))
+* **deps:** bump squidfunk/mkdocs-material from `6b124e1` to `521644b` in /docs ([#4141](https://github.com/aws-powertools/powertools-lambda-python/issues/4141))
+* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 3.0.3 to 3.0.4 ([#4099](https://github.com/aws-powertools/powertools-lambda-python/issues/4099))
+* **deps:** bump codecov/codecov-action from 4.2.0 to 4.3.0 ([#4098](https://github.com/aws-powertools/powertools-lambda-python/issues/4098))
+* **deps:** bump typing-extensions from 4.10.0 to 4.11.0 ([#4080](https://github.com/aws-powertools/powertools-lambda-python/issues/4080))
+* **deps:** bump github.com/aws/aws-sdk-go-v2/config from 1.27.10 to 1.27.11 in /layer/scripts/layer-balancer in the layer-balancer group ([#4079](https://github.com/aws-powertools/powertools-lambda-python/issues/4079))
+* **deps:** bump docker/setup-buildx-action from 3.2.0 to 3.3.0 ([#4091](https://github.com/aws-powertools/powertools-lambda-python/issues/4091))
+* **deps-dev:** bump mkdocs-material from 9.5.17 to 9.5.18 ([#4143](https://github.com/aws-powertools/powertools-lambda-python/issues/4143))
+* **deps-dev:** bump aws-cdk-lib from 2.135.0 to 2.136.0 ([#4092](https://github.com/aws-powertools/powertools-lambda-python/issues/4092))
+* **deps-dev:** bump cfn-lint from 0.86.1 to 0.86.2 ([#4081](https://github.com/aws-powertools/powertools-lambda-python/issues/4081))
+* **deps-dev:** bump aws-cdk from 2.135.0 to 2.136.0 ([#4090](https://github.com/aws-powertools/powertools-lambda-python/issues/4090))
+* **deps-dev:** bump aws-cdk-aws-lambda-python-alpha from 2.135.0a0 to 2.136.0a0 ([#4095](https://github.com/aws-powertools/powertools-lambda-python/issues/4095))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.106 to 0.1.107 ([#4082](https://github.com/aws-powertools/powertools-lambda-python/issues/4082))
+* **deps-dev:** bump types-redis from 4.6.0.20240311 to 4.6.0.20240409 ([#4094](https://github.com/aws-powertools/powertools-lambda-python/issues/4094))
+* **deps-dev:** bump filelock from 3.13.3 to 3.13.4 ([#4096](https://github.com/aws-powertools/powertools-lambda-python/issues/4096))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.107 to 0.1.110 ([#4097](https://github.com/aws-powertools/powertools-lambda-python/issues/4097))
+* **deps-dev:** bump aws-cdk from 2.136.0 to 2.136.1 ([#4106](https://github.com/aws-powertools/powertools-lambda-python/issues/4106))
+* **deps-dev:** bump aws-cdk-lib from 2.136.0 to 2.136.1 ([#4107](https://github.com/aws-powertools/powertools-lambda-python/issues/4107))
+* **deps-dev:** bump sentry-sdk from 1.44.0 to 1.44.1 ([#4065](https://github.com/aws-powertools/powertools-lambda-python/issues/4065))
+* **deps-dev:** bump aws-cdk-aws-lambda-python-alpha from 2.134.0a0 to 2.135.0a0 ([#4063](https://github.com/aws-powertools/powertools-lambda-python/issues/4063))
+* **deps-dev:** bump sentry-sdk from 1.44.1 to 1.45.0 ([#4108](https://github.com/aws-powertools/powertools-lambda-python/issues/4108))
+* **deps-dev:** bump the boto-typing group with 2 updates ([#4062](https://github.com/aws-powertools/powertools-lambda-python/issues/4062))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.110 to 0.1.112 ([#4109](https://github.com/aws-powertools/powertools-lambda-python/issues/4109))
+* **deps-dev:** bump aws-cdk from 2.136.1 to 2.137.0 ([#4115](https://github.com/aws-powertools/powertools-lambda-python/issues/4115))
+* **deps-dev:** bump mypy-boto3-cloudwatch from 1.34.75 to 1.34.83 in the boto-typing group ([#4116](https://github.com/aws-powertools/powertools-lambda-python/issues/4116))
+* **deps-dev:** bump aws-cdk from 2.134.0 to 2.135.0 ([#4058](https://github.com/aws-powertools/powertools-lambda-python/issues/4058))
+* **deps-dev:** bump aws-cdk-lib from 2.134.0 to 2.135.0 ([#4057](https://github.com/aws-powertools/powertools-lambda-python/issues/4057))
+* **deps-dev:** bump mkdocs-material from 9.5.16 to 9.5.17 ([#4056](https://github.com/aws-powertools/powertools-lambda-python/issues/4056))
+* **deps-dev:** bump ruff from 0.3.5 to 0.3.7 ([#4123](https://github.com/aws-powertools/powertools-lambda-python/issues/4123))
+* **deps-dev:** bump aws-cdk-lib from 2.136.1 to 2.137.0 ([#4119](https://github.com/aws-powertools/powertools-lambda-python/issues/4119))
+* **deps-dev:** bump ruff from 0.3.4 to 0.3.5 ([#4049](https://github.com/aws-powertools/powertools-lambda-python/issues/4049))
+* **deps-dev:** bump mkdocs-material from 9.5.15 to 9.5.16 ([#4050](https://github.com/aws-powertools/powertools-lambda-python/issues/4050))
+* **deps-dev:** bump the boto-typing group with 1 update ([#4047](https://github.com/aws-powertools/powertools-lambda-python/issues/4047))
+* **deps-dev:** bump aws-cdk-aws-lambda-python-alpha from 2.136.0a0 to 2.137.0a0 ([#4124](https://github.com/aws-powertools/powertools-lambda-python/issues/4124))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.105 to 0.1.106 ([#4048](https://github.com/aws-powertools/powertools-lambda-python/issues/4048))
+* **deps-dev:** bump mypy-boto3-cloudformation from 1.34.77 to 1.34.84 in the boto-typing group ([#4126](https://github.com/aws-powertools/powertools-lambda-python/issues/4126))
+* **deps-dev:** bump black from 24.3.0 to 24.4.0 ([#4135](https://github.com/aws-powertools/powertools-lambda-python/issues/4135))
+* **deps-dev:** bump aws-cdk-aws-lambda-python-alpha from 2.133.0a0 to 2.134.0a0 ([#4039](https://github.com/aws-powertools/powertools-lambda-python/issues/4039))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.112 to 0.1.113 ([#4136](https://github.com/aws-powertools/powertools-lambda-python/issues/4136))
+* **deps-dev:** bump cfn-lint from 0.86.2 to 0.86.3 ([#4137](https://github.com/aws-powertools/powertools-lambda-python/issues/4137))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.113 to 0.1.115 ([#4142](https://github.com/aws-powertools/powertools-lambda-python/issues/4142))
+* **deps-dev:** bump aws-cdk-lib from 2.133.0 to 2.134.0 ([#4031](https://github.com/aws-powertools/powertools-lambda-python/issues/4031))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.104 to 0.1.105 ([#4030](https://github.com/aws-powertools/powertools-lambda-python/issues/4030))
+* **deps-dev:** bump aws-cdk from 2.133.0 to 2.134.0 ([#4032](https://github.com/aws-powertools/powertools-lambda-python/issues/4032))
+* **deps-dev:** bump the boto-typing group with 1 update ([#4029](https://github.com/aws-powertools/powertools-lambda-python/issues/4029))
+* **deps-dev:** bump sentry-sdk from 1.43.0 to 1.44.0 ([#4040](https://github.com/aws-powertools/powertools-lambda-python/issues/4040))
+* **docs:** update highlighted lines on the Typing examples ([#4131](https://github.com/aws-powertools/powertools-lambda-python/issues/4131))
+
+
+
+## [v2.36.0] - 2024-03-27
+## Bug Fixes
+
* **event_handler:** always add 422 response to the schema ([#3995](https://github.com/aws-powertools/powertools-lambda-python/issues/3995))
* **event_handler:** make decoded_body field optional in ApiGateway resolver ([#3937](https://github.com/aws-powertools/powertools-lambda-python/issues/3937))
* **tracer:** add name sanitization for X-Ray subsegments ([#4005](https://github.com/aws-powertools/powertools-lambda-python/issues/4005))
@@ -17,6 +97,7 @@
## Documentation
+* **batch:** improved the example demonstrating how to create a custom partial processor. ([#4024](https://github.com/aws-powertools/powertools-lambda-python/issues/4024))
* **bedrock-agents:** fix type in Bedrock operation example ([#3948](https://github.com/aws-powertools/powertools-lambda-python/issues/3948))
* **tutorial:** fix "Simplifying with Tracer" section in the tutorial ([#3962](https://github.com/aws-powertools/powertools-lambda-python/issues/3962))
@@ -26,47 +107,49 @@
* **data_classes:** Add CloudWatchAlarmEvent data class ([#3868](https://github.com/aws-powertools/powertools-lambda-python/issues/3868))
* **event-handler:** add compress option when serving Swagger HTML ([#3946](https://github.com/aws-powertools/powertools-lambda-python/issues/3946))
* **event_handler:** define exception_handler directly from the router ([#3979](https://github.com/aws-powertools/powertools-lambda-python/issues/3979))
+* **metrics:** allow custom timestamps for metrics ([#4006](https://github.com/aws-powertools/powertools-lambda-python/issues/4006))
* **parameters:** add feature for creating and updating Parameters and Secrets ([#2858](https://github.com/aws-powertools/powertools-lambda-python/issues/2858))
* **tracer:** auto-disable tracer when for AWS SAM and Chalice environments ([#3949](https://github.com/aws-powertools/powertools-lambda-python/issues/3949))
## Maintenance
-* **deps:** bump actions/checkout from 4.1.1 to 4.1.2 ([#3939](https://github.com/aws-powertools/powertools-lambda-python/issues/3939))
+* version bump
+* **deps:** bump squidfunk/mkdocs-material from `3678304` to `6c81a89` in /docs ([#3973](https://github.com/aws-powertools/powertools-lambda-python/issues/3973))
* **deps:** bump datadog-lambda from 5.89.0 to 5.90.0 ([#3941](https://github.com/aws-powertools/powertools-lambda-python/issues/3941))
+* **deps:** bump actions/checkout from 4.1.1 to 4.1.2 ([#3939](https://github.com/aws-powertools/powertools-lambda-python/issues/3939))
* **deps:** bump redis from 5.0.2 to 5.0.3 ([#3929](https://github.com/aws-powertools/powertools-lambda-python/issues/3929))
-* **deps:** bump codecov/codecov-action from 4.1.0 to 4.1.1 ([#4021](https://github.com/aws-powertools/powertools-lambda-python/issues/4021))
* **deps:** bump slsa-framework/slsa-github-generator from 1.9.0 to 1.10.0 ([#3997](https://github.com/aws-powertools/powertools-lambda-python/issues/3997))
-* **deps:** bump datadog-lambda from 5.90.0 to 5.91.0 ([#3958](https://github.com/aws-powertools/powertools-lambda-python/issues/3958))
+* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 1 update ([#4001](https://github.com/aws-powertools/powertools-lambda-python/issues/4001))
* **deps:** bump actions/dependency-review-action from 4.2.3 to 4.2.4 ([#4012](https://github.com/aws-powertools/powertools-lambda-python/issues/4012))
-* **deps:** bump pypa/gh-action-pypi-publish from 1.8.12 to 1.8.14 ([#3918](https://github.com/aws-powertools/powertools-lambda-python/issues/3918))
+* **deps:** bump docker/setup-buildx-action from 3.1.0 to 3.2.0 ([#3955](https://github.com/aws-powertools/powertools-lambda-python/issues/3955))
* **deps:** bump actions/dependency-review-action from 4.1.3 to 4.2.3 ([#3993](https://github.com/aws-powertools/powertools-lambda-python/issues/3993))
+* **deps:** bump datadog-lambda from 5.90.0 to 5.91.0 ([#3958](https://github.com/aws-powertools/powertools-lambda-python/issues/3958))
+* **deps:** bump pypa/gh-action-pypi-publish from 1.8.12 to 1.8.14 ([#3918](https://github.com/aws-powertools/powertools-lambda-python/issues/3918))
* **deps:** bump squidfunk/mkdocs-material from `6c81a89` to `3307665` in /docs ([#4017](https://github.com/aws-powertools/powertools-lambda-python/issues/4017))
* **deps:** bump actions/dependency-review-action from 4.2.4 to 4.2.5 ([#4023](https://github.com/aws-powertools/powertools-lambda-python/issues/4023))
-* **deps:** bump docker/setup-buildx-action from 3.1.0 to 3.2.0 ([#3955](https://github.com/aws-powertools/powertools-lambda-python/issues/3955))
-* **deps:** bump actions/setup-python from 5.0.0 to 5.1.0 ([#4022](https://github.com/aws-powertools/powertools-lambda-python/issues/4022))
* **deps:** bump aws-encryption-sdk from 3.1.1 to 3.2.0 ([#3983](https://github.com/aws-powertools/powertools-lambda-python/issues/3983))
+* **deps:** bump actions/setup-python from 5.0.0 to 5.1.0 ([#4022](https://github.com/aws-powertools/powertools-lambda-python/issues/4022))
+* **deps:** bump codecov/codecov-action from 4.1.0 to 4.1.1 ([#4021](https://github.com/aws-powertools/powertools-lambda-python/issues/4021))
* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 3 updates ([#3972](https://github.com/aws-powertools/powertools-lambda-python/issues/3972))
-* **deps:** bump squidfunk/mkdocs-material from `3678304` to `6c81a89` in /docs ([#3973](https://github.com/aws-powertools/powertools-lambda-python/issues/3973))
-* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 1 update ([#4001](https://github.com/aws-powertools/powertools-lambda-python/issues/4001))
-* **deps-dev:** bump mkdocs-material from 9.5.13 to 9.5.14 ([#3978](https://github.com/aws-powertools/powertools-lambda-python/issues/3978))
+* **deps-dev:** bump filelock from 3.13.1 to 3.13.3 ([#4014](https://github.com/aws-powertools/powertools-lambda-python/issues/4014))
* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.90 to 0.1.91 ([#3975](https://github.com/aws-powertools/powertools-lambda-python/issues/3975))
* **deps-dev:** bump types-python-dateutil from 2.9.0.20240315 to 2.9.0.20240316 ([#3977](https://github.com/aws-powertools/powertools-lambda-python/issues/3977))
+* **deps-dev:** bump mkdocs-material from 9.5.13 to 9.5.14 ([#3978](https://github.com/aws-powertools/powertools-lambda-python/issues/3978))
* **deps-dev:** bump aws-cdk-aws-lambda-python-alpha from 2.132.1a0 to 2.133.0a0 ([#3976](https://github.com/aws-powertools/powertools-lambda-python/issues/3976))
-* **deps-dev:** bump the boto-typing group with 2 updates ([#3982](https://github.com/aws-powertools/powertools-lambda-python/issues/3982))
* **deps-dev:** bump the boto-typing group with 2 updates ([#3974](https://github.com/aws-powertools/powertools-lambda-python/issues/3974))
-* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.91 to 0.1.94 ([#3985](https://github.com/aws-powertools/powertools-lambda-python/issues/3985))
+* **deps-dev:** bump the boto-typing group with 2 updates ([#3982](https://github.com/aws-powertools/powertools-lambda-python/issues/3982))
* **deps-dev:** bump ruff from 0.3.2 to 0.3.3 ([#3967](https://github.com/aws-powertools/powertools-lambda-python/issues/3967))
-* **deps-dev:** bump pytest-asyncio from 0.23.5.post1 to 0.23.6 ([#3984](https://github.com/aws-powertools/powertools-lambda-python/issues/3984))
-* **deps-dev:** bump the boto-typing group with 1 update ([#3991](https://github.com/aws-powertools/powertools-lambda-python/issues/3991))
+* **deps-dev:** bump aws-cdk-lib from 2.132.1 to 2.133.0 ([#3965](https://github.com/aws-powertools/powertools-lambda-python/issues/3965))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.91 to 0.1.94 ([#3985](https://github.com/aws-powertools/powertools-lambda-python/issues/3985))
* **deps-dev:** bump black from 24.2.0 to 24.3.0 ([#3968](https://github.com/aws-powertools/powertools-lambda-python/issues/3968))
* **deps-dev:** bump types-python-dateutil from 2.8.19.20240311 to 2.9.0.20240315 ([#3966](https://github.com/aws-powertools/powertools-lambda-python/issues/3966))
* **deps-dev:** bump aws-cdk from 2.132.1 to 2.133.0 ([#3963](https://github.com/aws-powertools/powertools-lambda-python/issues/3963))
* **deps-dev:** bump the boto-typing group with 1 update ([#3964](https://github.com/aws-powertools/powertools-lambda-python/issues/3964))
-* **deps-dev:** bump sentry-sdk from 1.42.0 to 1.43.0 ([#3992](https://github.com/aws-powertools/powertools-lambda-python/issues/3992))
-* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.99 to 0.1.101 ([#4015](https://github.com/aws-powertools/powertools-lambda-python/issues/4015))
+* **deps-dev:** bump pytest-asyncio from 0.23.5.post1 to 0.23.6 ([#3984](https://github.com/aws-powertools/powertools-lambda-python/issues/3984))
+* **deps-dev:** bump the boto-typing group with 1 update ([#3991](https://github.com/aws-powertools/powertools-lambda-python/issues/3991))
* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.89 to 0.1.90 ([#3957](https://github.com/aws-powertools/powertools-lambda-python/issues/3957))
* **deps-dev:** bump the boto-typing group with 1 update ([#3956](https://github.com/aws-powertools/powertools-lambda-python/issues/3956))
-* **deps-dev:** bump aws-cdk-lib from 2.132.1 to 2.133.0 ([#3965](https://github.com/aws-powertools/powertools-lambda-python/issues/3965))
+* **deps-dev:** bump sentry-sdk from 1.42.0 to 1.43.0 ([#3992](https://github.com/aws-powertools/powertools-lambda-python/issues/3992))
* **deps-dev:** bump coverage from 7.4.3 to 7.4.4 ([#3959](https://github.com/aws-powertools/powertools-lambda-python/issues/3959))
* **deps-dev:** bump ruff from 0.3.3 to 0.3.4 ([#3996](https://github.com/aws-powertools/powertools-lambda-python/issues/3996))
* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.88 to 0.1.89 ([#3952](https://github.com/aws-powertools/powertools-lambda-python/issues/3952))
@@ -86,7 +169,7 @@
* **deps-dev:** bump types-redis from 4.6.0.20240218 to 4.6.0.20240311 ([#3931](https://github.com/aws-powertools/powertools-lambda-python/issues/3931))
* **deps-dev:** bump types-python-dateutil from 2.8.19.20240106 to 2.8.19.20240311 ([#3932](https://github.com/aws-powertools/powertools-lambda-python/issues/3932))
* **deps-dev:** bump pytest-mock from 3.13.0 to 3.14.0 ([#4007](https://github.com/aws-powertools/powertools-lambda-python/issues/4007))
-* **deps-dev:** bump filelock from 3.13.1 to 3.13.3 ([#4014](https://github.com/aws-powertools/powertools-lambda-python/issues/4014))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.99 to 0.1.101 ([#4015](https://github.com/aws-powertools/powertools-lambda-python/issues/4015))
* **deps-dev:** bump ruff from 0.3.0 to 0.3.2 ([#3925](https://github.com/aws-powertools/powertools-lambda-python/issues/3925))
* **deps-dev:** bump mypy from 1.8.0 to 1.9.0 ([#3921](https://github.com/aws-powertools/powertools-lambda-python/issues/3921))
* **deps-dev:** bump bandit from 1.7.7 to 1.7.8 ([#3920](https://github.com/aws-powertools/powertools-lambda-python/issues/3920))
@@ -4613,7 +4696,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.35.1...HEAD
+[Unreleased]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.36.0...HEAD
+[v2.36.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.35.1...v2.36.0
[v2.35.1]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.35.0...v2.35.1
[v2.35.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.34.2...v2.35.0
[v2.34.2]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.34.1...v2.34.2
diff --git a/aws_lambda_powertools/event_handler/api_gateway.py b/aws_lambda_powertools/event_handler/api_gateway.py
index 29601247b48..87433b020d5 100644
--- a/aws_lambda_powertools/event_handler/api_gateway.py
+++ b/aws_lambda_powertools/event_handler/api_gateway.py
@@ -34,7 +34,6 @@
from aws_lambda_powertools.event_handler.exceptions import NotFoundError, ServiceError
from aws_lambda_powertools.event_handler.openapi.constants import DEFAULT_API_VERSION, DEFAULT_OPENAPI_VERSION
from aws_lambda_powertools.event_handler.openapi.exceptions import RequestValidationError
-from aws_lambda_powertools.event_handler.openapi.swagger_ui.html import generate_swagger_html
from aws_lambda_powertools.event_handler.openapi.types import (
COMPONENT_REF_PREFIX,
METHODS_WITH_BODY,
@@ -83,10 +82,14 @@
Contact,
License,
OpenAPI,
+ SecurityScheme,
Server,
Tag,
)
from aws_lambda_powertools.event_handler.openapi.params import Dependant
+ from aws_lambda_powertools.event_handler.openapi.swagger_ui.oauth2 import (
+ OAuth2Config,
+ )
from aws_lambda_powertools.event_handler.openapi.types import (
TypeModelOrEnum,
)
@@ -282,6 +285,7 @@ def __init__(
tags: Optional[List[str]],
operation_id: Optional[str],
include_in_schema: bool,
+ security: Optional[List[Dict[str, List[str]]]],
middlewares: Optional[List[Callable[..., Response]]],
):
"""
@@ -317,6 +321,8 @@ def __init__(
The OpenAPI operationId for this route
include_in_schema: bool
Whether or not to include this route in the OpenAPI schema
+ security: List[Dict[str, List[str]]], optional
+ The OpenAPI security for this route
middlewares: Optional[List[Callable[..., Response]]]
The list of route middlewares to be called in order.
"""
@@ -339,6 +345,7 @@ def __init__(
self.response_description = response_description
self.tags = tags or []
self.include_in_schema = include_in_schema
+ self.security = security
self.middlewares = middlewares or []
self.operation_id = operation_id or self._generate_operation_id()
@@ -486,6 +493,10 @@ def _get_openapi_path(
)
parameters.extend(operation_params)
+ # Add security if present
+ if self.security:
+ operation["security"] = self.security
+
# Add the parameters to the OpenAPI operation
if parameters:
all_parameters = {(param["in"], param["name"]): param for param in parameters}
@@ -885,6 +896,7 @@ def route(
tags: Optional[List[str]] = None,
operation_id: Optional[str] = None,
include_in_schema: bool = True,
+ security: Optional[List[Dict[str, List[str]]]] = None,
middlewares: Optional[List[Callable[..., Any]]] = None,
):
raise NotImplementedError()
@@ -943,6 +955,7 @@ def get(
tags: Optional[List[str]] = None,
operation_id: Optional[str] = None,
include_in_schema: bool = True,
+ security: Optional[List[Dict[str, List[str]]]] = None,
middlewares: Optional[List[Callable[..., Any]]] = None,
):
"""Get route decorator with GET `method`
@@ -980,6 +993,7 @@ def lambda_handler(event, context):
tags,
operation_id,
include_in_schema,
+ security,
middlewares,
)
@@ -996,6 +1010,7 @@ def post(
tags: Optional[List[str]] = None,
operation_id: Optional[str] = None,
include_in_schema: bool = True,
+ security: Optional[List[Dict[str, List[str]]]] = None,
middlewares: Optional[List[Callable[..., Any]]] = None,
):
"""Post route decorator with POST `method`
@@ -1034,6 +1049,7 @@ def lambda_handler(event, context):
tags,
operation_id,
include_in_schema,
+ security,
middlewares,
)
@@ -1050,6 +1066,7 @@ def put(
tags: Optional[List[str]] = None,
operation_id: Optional[str] = None,
include_in_schema: bool = True,
+ security: Optional[List[Dict[str, List[str]]]] = None,
middlewares: Optional[List[Callable[..., Any]]] = None,
):
"""Put route decorator with PUT `method`
@@ -1088,6 +1105,7 @@ def lambda_handler(event, context):
tags,
operation_id,
include_in_schema,
+ security,
middlewares,
)
@@ -1104,6 +1122,7 @@ def delete(
tags: Optional[List[str]] = None,
operation_id: Optional[str] = None,
include_in_schema: bool = True,
+ security: Optional[List[Dict[str, List[str]]]] = None,
middlewares: Optional[List[Callable[..., Any]]] = None,
):
"""Delete route decorator with DELETE `method`
@@ -1141,6 +1160,7 @@ def lambda_handler(event, context):
tags,
operation_id,
include_in_schema,
+ security,
middlewares,
)
@@ -1157,6 +1177,7 @@ def patch(
tags: Optional[List[str]] = None,
operation_id: Optional[str] = None,
include_in_schema: bool = True,
+ security: Optional[List[Dict[str, List[str]]]] = None,
middlewares: Optional[List[Callable]] = None,
):
"""Patch route decorator with PATCH `method`
@@ -1197,6 +1218,7 @@ def lambda_handler(event, context):
tags,
operation_id,
include_in_schema,
+ security,
middlewares,
)
@@ -1419,6 +1441,8 @@ def get_openapi_schema(
terms_of_service: Optional[str] = None,
contact: Optional["Contact"] = None,
license_info: Optional["License"] = None,
+ security_schemes: Optional[Dict[str, "SecurityScheme"]] = None,
+ security: Optional[List[Dict[str, List[str]]]] = None,
) -> "OpenAPI":
"""
Returns the OpenAPI schema as a pydantic model.
@@ -1445,6 +1469,10 @@ def get_openapi_schema(
The contact information for the exposed API.
license_info: License, optional
The license information for the exposed API.
+ security_schemes: Dict[str, "SecurityScheme"]], optional
+ A declaration of the security schemes available to be used in the specification.
+ security: List[Dict[str, List[str]]], optional
+ A declaration of which security mechanisms are applied globally across the API.
Returns
-------
@@ -1457,25 +1485,12 @@ def get_openapi_schema(
get_compat_model_name_map,
get_definitions,
)
- from aws_lambda_powertools.event_handler.openapi.models import OpenAPI, PathItem, Server, Tag
- from aws_lambda_powertools.event_handler.openapi.pydantic_loader import PYDANTIC_V2
+ from aws_lambda_powertools.event_handler.openapi.models import OpenAPI, PathItem, Tag
from aws_lambda_powertools.event_handler.openapi.types import (
COMPONENT_REF_TEMPLATE,
)
- # Pydantic V2 has no support for OpenAPI schema 3.0
- if PYDANTIC_V2 and not openapi_version.startswith("3.1"):
- warnings.warn(
- "You are using Pydantic v2, which is incompatible with OpenAPI schema 3.0. Forcing OpenAPI 3.1",
- stacklevel=2,
- )
- openapi_version = "3.1.0"
- elif not PYDANTIC_V2 and not openapi_version.startswith("3.0"):
- warnings.warn(
- "You are using Pydantic v1, which is incompatible with OpenAPI schema 3.1. Forcing OpenAPI 3.0",
- stacklevel=2,
- )
- openapi_version = "3.0.3"
+ openapi_version = self._determine_openapi_version(openapi_version)
# Start with the bare minimum required for a valid OpenAPI schema
info: Dict[str, Any] = {"title": title, "version": version}
@@ -1490,13 +1505,12 @@ def get_openapi_schema(
info.update({field: value for field, value in optional_fields.items() if value})
- output: Dict[str, Any] = {"openapi": openapi_version, "info": info}
- if servers:
- output["servers"] = servers
- else:
- # If the servers property is not provided, or is an empty array, the default value would be a Server Object
- # with an url value of /.
- output["servers"] = [Server(url="/")]
+ output: Dict[str, Any] = {
+ "openapi": openapi_version,
+ "info": info,
+ "servers": self._get_openapi_servers(servers),
+ "security": self._get_openapi_security(security, security_schemes),
+ }
components: Dict[str, Dict[str, Any]] = {}
paths: Dict[str, Dict[str, Any]] = {}
@@ -1534,6 +1548,8 @@ def get_openapi_schema(
if definitions:
components["schemas"] = {k: definitions[k] for k in sorted(definitions)}
+ if security_schemes:
+ components["securitySchemes"] = security_schemes
if components:
output["components"] = components
if tags:
@@ -1543,6 +1559,50 @@ def get_openapi_schema(
return OpenAPI(**output)
+ @staticmethod
+ def _get_openapi_servers(servers: Optional[List["Server"]]) -> List["Server"]:
+ from aws_lambda_powertools.event_handler.openapi.models import Server
+
+ # If the 'servers' property is not provided or is an empty array,
+ # the default behavior is to return a Server Object with a URL value of "/".
+ return servers if servers else [Server(url="/")]
+
+ @staticmethod
+ def _get_openapi_security(
+ security: Optional[List[Dict[str, List[str]]]],
+ security_schemes: Optional[Dict[str, "SecurityScheme"]],
+ ) -> Optional[List[Dict[str, List[str]]]]:
+ if not security:
+ return None
+
+ if not security_schemes:
+ raise ValueError("security_schemes must be provided if security is provided")
+
+ # Check if all keys in security are present in the security_schemes
+ if any(key not in security_schemes for sec in security for key in sec):
+ raise ValueError("Some security schemes not found in security_schemes")
+
+ return security
+
+ @staticmethod
+ def _determine_openapi_version(openapi_version):
+ from aws_lambda_powertools.event_handler.openapi.pydantic_loader import PYDANTIC_V2
+
+ # Pydantic V2 has no support for OpenAPI schema 3.0
+ if PYDANTIC_V2 and not openapi_version.startswith("3.1"):
+ warnings.warn(
+ "You are using Pydantic v2, which is incompatible with OpenAPI schema 3.0. Forcing OpenAPI 3.1",
+ stacklevel=2,
+ )
+ openapi_version = "3.1.0"
+ elif not PYDANTIC_V2 and not openapi_version.startswith("3.0"):
+ warnings.warn(
+ "You are using Pydantic v1, which is incompatible with OpenAPI schema 3.1. Forcing OpenAPI 3.0",
+ stacklevel=2,
+ )
+ openapi_version = "3.0.3"
+ return openapi_version
+
def get_openapi_json_schema(
self,
*,
@@ -1556,6 +1616,8 @@ def get_openapi_json_schema(
terms_of_service: Optional[str] = None,
contact: Optional["Contact"] = None,
license_info: Optional["License"] = None,
+ security_schemes: Optional[Dict[str, "SecurityScheme"]] = None,
+ security: Optional[List[Dict[str, List[str]]]] = None,
) -> str:
"""
Returns the OpenAPI schema as a JSON serializable dict
@@ -1582,6 +1644,10 @@ def get_openapi_json_schema(
The contact information for the exposed API.
license_info: License, optional
The license information for the exposed API.
+ security_schemes: Dict[str, "SecurityScheme"]], optional
+ A declaration of the security schemes available to be used in the specification.
+ security: List[Dict[str, List[str]]], optional
+ A declaration of which security mechanisms are applied globally across the API.
Returns
-------
@@ -1602,6 +1668,8 @@ def get_openapi_json_schema(
terms_of_service=terms_of_service,
contact=contact,
license_info=license_info,
+ security_schemes=security_schemes,
+ security=security,
),
by_alias=True,
exclude_none=True,
@@ -1625,6 +1693,9 @@ def enable_swagger(
swagger_base_url: Optional[str] = None,
middlewares: Optional[List[Callable[..., Response]]] = None,
compress: bool = False,
+ security_schemes: Optional[Dict[str, "SecurityScheme"]] = None,
+ security: Optional[List[Dict[str, List[str]]]] = None,
+ oauth2_config: Optional["OAuth2Config"] = None,
):
"""
Returns the OpenAPI schema as a JSON serializable dict
@@ -1659,12 +1730,34 @@ def enable_swagger(
List of middlewares to be used for the swagger route.
compress: bool, default = False
Whether or not to enable gzip compression swagger route.
+ security_schemes: Dict[str, "SecurityScheme"], optional
+ A declaration of the security schemes available to be used in the specification.
+ security: List[Dict[str, List[str]]], optional
+ A declaration of which security mechanisms are applied globally across the API.
+ oauth2_config: OAuth2Config, optional
+ The OAuth2 configuration for the Swagger UI.
"""
from aws_lambda_powertools.event_handler.openapi.compat import model_json
from aws_lambda_powertools.event_handler.openapi.models import Server
+ from aws_lambda_powertools.event_handler.openapi.swagger_ui import (
+ generate_oauth2_redirect_html,
+ generate_swagger_html,
+ )
@self.get(path, middlewares=middlewares, include_in_schema=False, compress=compress)
def swagger_handler():
+ query_params = self.current_event.query_string_parameters or {}
+
+ # Check for query parameters; if "format" is specified as "oauth2-redirect",
+ # send the oauth2-redirect HTML stanza so OAuth2 can be used
+ # Source: https://github.com/swagger-api/swagger-ui/blob/master/dist/oauth2-redirect.html
+ if query_params.get("format") == "oauth2-redirect":
+ return Response(
+ status_code=200,
+ content_type="text/html",
+ body=generate_oauth2_redirect_html(),
+ )
+
base_path = self._get_base_path()
if swagger_base_url:
@@ -1690,6 +1783,8 @@ def swagger_handler():
terms_of_service=terms_of_service,
contact=contact,
license_info=license_info,
+ security_schemes=security_schemes,
+ security=security,
)
# The .replace('', '<\\/') part is necessary to prevent a potential issue where the JSON string contains
@@ -1705,7 +1800,6 @@ def swagger_handler():
# Check for query parameters; if "format" is specified as "json",
# respond with the JSON used in the OpenAPI spec
# Example: https://www.example.com/swagger?format=json
- query_params = self.current_event.query_string_parameters or {}
if query_params.get("format") == "json":
return Response(
status_code=200,
@@ -1719,6 +1813,7 @@ def swagger_handler():
swagger_js,
swagger_css,
swagger_base_url,
+ oauth2_config,
)
return Response(
@@ -1741,6 +1836,7 @@ def route(
tags: Optional[List[str]] = None,
operation_id: Optional[str] = None,
include_in_schema: bool = True,
+ security: Optional[List[Dict[str, List[str]]]] = None,
middlewares: Optional[List[Callable[..., Any]]] = None,
):
"""Route decorator includes parameter `method`"""
@@ -1767,6 +1863,7 @@ def register_resolver(func: Callable):
tags,
operation_id,
include_in_schema,
+ security,
middlewares,
)
@@ -2218,6 +2315,7 @@ def route(
tags: Optional[List[str]] = None,
operation_id: Optional[str] = None,
include_in_schema: bool = True,
+ security: Optional[List[Dict[str, List[str]]]] = None,
middlewares: Optional[List[Callable[..., Any]]] = None,
):
def register_route(func: Callable):
@@ -2239,6 +2337,7 @@ def register_route(func: Callable):
frozen_tags,
operation_id,
include_in_schema,
+ security,
)
# Collate Middleware for routes
@@ -2318,6 +2417,7 @@ def route(
tags: Optional[List[str]] = None,
operation_id: Optional[str] = None,
include_in_schema: bool = True,
+ security: Optional[List[Dict[str, List[str]]]] = None,
middlewares: Optional[List[Callable[..., Any]]] = None,
):
# NOTE: see #1552 for more context.
@@ -2334,6 +2434,7 @@ def route(
tags,
operation_id,
include_in_schema,
+ security,
middlewares,
)
diff --git a/aws_lambda_powertools/event_handler/bedrock_agent.py b/aws_lambda_powertools/event_handler/bedrock_agent.py
index 0ce0f3ff725..4d1a6096f32 100644
--- a/aws_lambda_powertools/event_handler/bedrock_agent.py
+++ b/aws_lambda_powertools/event_handler/bedrock_agent.py
@@ -102,6 +102,8 @@ def get( # type: ignore[override]
include_in_schema: bool = True,
middlewares: Optional[List[Callable[..., Any]]] = None,
) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
+ security = None
+
return super(BedrockAgentResolver, self).get(
rule,
cors,
@@ -114,6 +116,7 @@ def get( # type: ignore[override]
tags,
operation_id,
include_in_schema,
+ security,
middlewares,
)
@@ -134,6 +137,8 @@ def post( # type: ignore[override]
include_in_schema: bool = True,
middlewares: Optional[List[Callable[..., Any]]] = None,
):
+ security = None
+
return super().post(
rule,
cors,
@@ -146,6 +151,7 @@ def post( # type: ignore[override]
tags,
operation_id,
include_in_schema,
+ security,
middlewares,
)
@@ -166,6 +172,8 @@ def put( # type: ignore[override]
include_in_schema: bool = True,
middlewares: Optional[List[Callable[..., Any]]] = None,
):
+ security = None
+
return super().put(
rule,
cors,
@@ -178,6 +186,7 @@ def put( # type: ignore[override]
tags,
operation_id,
include_in_schema,
+ security,
middlewares,
)
@@ -198,6 +207,8 @@ def patch( # type: ignore[override]
include_in_schema: bool = True,
middlewares: Optional[List[Callable]] = None,
):
+ security = None
+
return super().patch(
rule,
cors,
@@ -210,6 +221,7 @@ def patch( # type: ignore[override]
tags,
operation_id,
include_in_schema,
+ security,
middlewares,
)
@@ -230,6 +242,8 @@ def delete( # type: ignore[override]
include_in_schema: bool = True,
middlewares: Optional[List[Callable[..., Any]]] = None,
):
+ security = None
+
return super().delete(
rule,
cors,
@@ -242,6 +256,7 @@ def delete( # type: ignore[override]
tags,
operation_id,
include_in_schema,
+ security,
middlewares,
)
diff --git a/aws_lambda_powertools/event_handler/openapi/models.py b/aws_lambda_powertools/event_handler/openapi/models.py
index ace398ec532..04345ddaad7 100644
--- a/aws_lambda_powertools/event_handler/openapi/models.py
+++ b/aws_lambda_powertools/event_handler/openapi/models.py
@@ -441,12 +441,13 @@ class SecurityBase(BaseModel):
description: Optional[str] = None
if PYDANTIC_V2:
- model_config = {"extra": "allow"}
+ model_config = {"extra": "allow", "populate_by_name": True}
else:
class Config:
extra = "allow"
+ allow_population_by_field_name = True
class APIKeyIn(Enum):
diff --git a/aws_lambda_powertools/event_handler/openapi/swagger_ui/__init__.py b/aws_lambda_powertools/event_handler/openapi/swagger_ui/__init__.py
index e69de29bb2d..bc6eda8abb3 100644
--- a/aws_lambda_powertools/event_handler/openapi/swagger_ui/__init__.py
+++ b/aws_lambda_powertools/event_handler/openapi/swagger_ui/__init__.py
@@ -0,0 +1,13 @@
+from aws_lambda_powertools.event_handler.openapi.swagger_ui.html import (
+ generate_swagger_html,
+)
+from aws_lambda_powertools.event_handler.openapi.swagger_ui.oauth2 import (
+ OAuth2Config,
+ generate_oauth2_redirect_html,
+)
+
+__all__ = [
+ "generate_swagger_html",
+ "generate_oauth2_redirect_html",
+ "OAuth2Config",
+]
diff --git a/aws_lambda_powertools/event_handler/openapi/swagger_ui/html.py b/aws_lambda_powertools/event_handler/openapi/swagger_ui/html.py
index fcd644f39f5..8b748d9338a 100644
--- a/aws_lambda_powertools/event_handler/openapi/swagger_ui/html.py
+++ b/aws_lambda_powertools/event_handler/openapi/swagger_ui/html.py
@@ -1,4 +1,16 @@
-def generate_swagger_html(spec: str, path: str, swagger_js: str, swagger_css: str, swagger_base_url: str) -> str:
+from typing import Optional
+
+from aws_lambda_powertools.event_handler.openapi.swagger_ui.oauth2 import OAuth2Config
+
+
+def generate_swagger_html(
+ spec: str,
+ path: str,
+ swagger_js: str,
+ swagger_css: str,
+ swagger_base_url: str,
+ oauth2_config: Optional[OAuth2Config],
+) -> str:
"""
Generate Swagger UI HTML page
@@ -8,10 +20,14 @@ def generate_swagger_html(spec: str, path: str, swagger_js: str, swagger_css: st
The OpenAPI spec
path: str
The path to the Swagger documentation
- js_url: str
- The URL to the Swagger UI JavaScript file
- css_url: str
- The URL to the Swagger UI CSS file
+ swagger_js: str
+ Swagger UI JavaScript source code or URL
+ swagger_css: str
+ Swagger UI CSS source code or URL
+ swagger_base_url: str
+ The base URL for Swagger UI
+ oauth2_config: OAuth2Config, optional
+ The OAuth2 configuration.
"""
# If Swagger base URL is present, generate HTML content with linked CSS and JavaScript files
@@ -23,6 +39,11 @@ def generate_swagger_html(spec: str, path: str, swagger_js: str, swagger_css: st
swagger_css_content = f""
swagger_js_content = f""
+ # Prepare oauth2 config
+ oauth2_content = (
+ f"ui.initOAuth({oauth2_config.json(exclude_none=True, exclude_unset=True)});" if oauth2_config else ""
+ )
+
return f"""
@@ -45,6 +66,9 @@ def generate_swagger_html(spec: str, path: str, swagger_js: str, swagger_css: st
{swagger_js_content}
""".strip()
diff --git a/aws_lambda_powertools/event_handler/openapi/swagger_ui/oauth2.py b/aws_lambda_powertools/event_handler/openapi/swagger_ui/oauth2.py
new file mode 100644
index 00000000000..29250ae0056
--- /dev/null
+++ b/aws_lambda_powertools/event_handler/openapi/swagger_ui/oauth2.py
@@ -0,0 +1,158 @@
+# ruff: noqa: E501
+import warnings
+from typing import Dict, Optional, Sequence
+
+from pydantic import BaseModel, Field, validator
+
+from aws_lambda_powertools.event_handler.openapi.pydantic_loader import PYDANTIC_V2
+from aws_lambda_powertools.shared.functions import powertools_dev_is_set
+
+
+# Based on https://swagger.io/docs/open-source-tools/swagger-ui/usage/oauth2/
+class OAuth2Config(BaseModel):
+ """
+ OAuth2 configuration for Swagger UI
+ """
+
+ # The client ID for the OAuth2 application
+ clientId: Optional[str] = Field(alias="client_id", default=None)
+
+ # The client secret for the OAuth2 application. This is sensitive information and requires the explicit presence
+ # of the POWERTOOLS_DEV environment variable.
+ clientSecret: Optional[str] = Field(alias="client_secret", default=None)
+
+ # The realm in which the OAuth2 application is registered. Optional.
+ realm: Optional[str] = Field(default=None)
+
+ # The name of the OAuth2 application
+ appName: str = Field(alias="app_name")
+
+ # The scopes that the OAuth2 application requires. Defaults to an empty list.
+ scopes: Sequence[str] = Field(default=[])
+
+ # Additional query string parameters to be included in the OAuth2 request. Defaults to an empty dictionary.
+ additionalQueryStringParams: Dict[str, str] = Field(alias="additional_query_string_params", default={})
+
+ # Whether to use basic authentication with the access code grant type. Defaults to False.
+ useBasicAuthenticationWithAccessCodeGrant: bool = Field(
+ alias="use_basic_authentication_with_access_code_grant",
+ default=False,
+ )
+
+ # Whether to use PKCE with the authorization code grant type. Defaults to False.
+ usePkceWithAuthorizationCodeGrant: bool = Field(alias="use_pkce_with_authorization_code_grant", default=False)
+
+ if PYDANTIC_V2:
+ model_config = {"extra": "allow"}
+ else:
+
+ class Config:
+ extra = "allow"
+ allow_population_by_field_name = True
+
+ @validator("clientSecret", always=True)
+ def client_secret_only_on_dev(cls, v: Optional[str]) -> Optional[str]:
+ if not v:
+ return None
+
+ if not powertools_dev_is_set():
+ raise ValueError(
+ "cannot use client_secret without POWERTOOLS_DEV mode. See "
+ "https://docs.powertools.aws.dev/lambda/python/latest/#optimizing-for-non-production-environments",
+ )
+ else:
+ warnings.warn(
+ "OAuth2Config is using client_secret and POWERTOOLS_DEV is set. This reveals sensitive information. "
+ "DO NOT USE THIS OUTSIDE LOCAL DEVELOPMENT",
+ stacklevel=2,
+ )
+ return v
+
+
+def generate_oauth2_redirect_html() -> str:
+ """
+ Generates the HTML content for the OAuth2 redirect page.
+
+ Source: https://github.com/swagger-api/swagger-ui/blob/master/dist/oauth2-redirect.html
+ """
+ return """
+
+
+
+ Swagger UI: OAuth2 Redirect
+
+
+
+
+
+ """.strip()
diff --git a/aws_lambda_powertools/logging/formatter.py b/aws_lambda_powertools/logging/formatter.py
index 8b34b326435..ac623303ab1 100644
--- a/aws_lambda_powertools/logging/formatter.py
+++ b/aws_lambda_powertools/logging/formatter.py
@@ -48,6 +48,9 @@ class BasePowertoolsFormatter(logging.Formatter, metaclass=ABCMeta):
def append_keys(self, **additional_keys) -> None:
raise NotImplementedError()
+ def get_current_keys(self) -> Dict[str, Any]:
+ return {}
+
def remove_keys(self, keys: Iterable[str]) -> None:
raise NotImplementedError()
@@ -231,6 +234,9 @@ def formatTime(self, record: logging.LogRecord, datefmt: Optional[str] = None) -
def append_keys(self, **additional_keys) -> None:
self.log_format.update(additional_keys)
+ def get_current_keys(self) -> Dict[str, Any]:
+ return self.log_format
+
def remove_keys(self, keys: Iterable[str]) -> None:
for key in keys:
self.log_format.pop(key, None)
diff --git a/aws_lambda_powertools/logging/logger.py b/aws_lambda_powertools/logging/logger.py
index f86833a7851..dc03e1af8eb 100644
--- a/aws_lambda_powertools/logging/logger.py
+++ b/aws_lambda_powertools/logging/logger.py
@@ -583,6 +583,9 @@ def debug(
def append_keys(self, **additional_keys: object) -> None:
self.registered_formatter.append_keys(**additional_keys)
+ def get_current_keys(self) -> Dict[str, Any]:
+ return self.registered_formatter.get_current_keys()
+
def remove_keys(self, keys: Iterable[str]) -> None:
self.registered_formatter.remove_keys(keys)
diff --git a/aws_lambda_powertools/shared/version.py b/aws_lambda_powertools/shared/version.py
index 2b1c7133b54..351af8fac73 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.35.1"
+VERSION = "2.37.0"
diff --git a/aws_lambda_powertools/utilities/data_classes/active_mq_event.py b/aws_lambda_powertools/utilities/data_classes/active_mq_event.py
index f0839a70442..ec66918e2ce 100644
--- a/aws_lambda_powertools/utilities/data_classes/active_mq_event.py
+++ b/aws_lambda_powertools/utilities/data_classes/active_mq_event.py
@@ -59,7 +59,7 @@ def properties(self) -> dict:
@property
def destination_physicalname(self) -> str:
- return self["destination"]["physicalname"]
+ return self["destination"]["physicalName"]
@property
def delivery_mode(self) -> Optional[int]:
diff --git a/aws_lambda_powertools/utilities/data_classes/api_gateway_authorizer_event.py b/aws_lambda_powertools/utilities/data_classes/api_gateway_authorizer_event.py
index 0b1aec43e8a..b87c8ddaf20 100644
--- a/aws_lambda_powertools/utilities/data_classes/api_gateway_authorizer_event.py
+++ b/aws_lambda_powertools/utilities/data_classes/api_gateway_authorizer_event.py
@@ -167,7 +167,7 @@ def get_header_value(
self,
name: str,
default_value: str,
- case_sensitive: Optional[bool] = False,
+ case_sensitive: bool = False,
) -> str: ...
@overload
@@ -175,14 +175,14 @@ def get_header_value(
self,
name: str,
default_value: Optional[str] = None,
- case_sensitive: Optional[bool] = False,
+ case_sensitive: bool = False,
) -> Optional[str]: ...
def get_header_value(
self,
name: str,
default_value: Optional[str] = None,
- case_sensitive: Optional[bool] = False,
+ case_sensitive: bool = False,
) -> Optional[str]:
"""Get header value by name
@@ -283,11 +283,22 @@ def path_parameters(self) -> Optional[Dict[str, str]]:
def stage_variables(self) -> Optional[Dict[str, str]]:
return self.get("stageVariables")
+ @overload
+ def get_header_value(self, name: str, default_value: str, case_sensitive: bool = False) -> str: ...
+
+ @overload
+ def get_header_value(
+ self,
+ name: str,
+ default_value: Optional[str] = None,
+ case_sensitive: bool = False,
+ ) -> Optional[str]: ...
+
def get_header_value(
self,
name: str,
default_value: Optional[str] = None,
- case_sensitive: Optional[bool] = False,
+ case_sensitive: bool = False,
) -> Optional[str]:
"""Get header value by name
diff --git a/aws_lambda_powertools/utilities/data_classes/appsync_resolver_event.py b/aws_lambda_powertools/utilities/data_classes/appsync_resolver_event.py
index 14973009fb9..f58308377ff 100644
--- a/aws_lambda_powertools/utilities/data_classes/appsync_resolver_event.py
+++ b/aws_lambda_powertools/utilities/data_classes/appsync_resolver_event.py
@@ -1,4 +1,4 @@
-from typing import Any, Dict, List, Optional, Union
+from typing import Any, Dict, List, Optional, Union, overload
from aws_lambda_powertools.utilities.data_classes.common import DictWrapper
from aws_lambda_powertools.utilities.data_classes.shared_functions import (
@@ -214,11 +214,27 @@ def stash(self) -> Optional[dict]:
a pipeline resolver."""
return self.get("stash")
+ @overload
+ def get_header_value(
+ self,
+ name: str,
+ default_value: str,
+ case_sensitive: bool = False,
+ ) -> str: ...
+
+ @overload
+ def get_header_value(
+ self,
+ name: str,
+ default_value: Optional[str] = None,
+ case_sensitive: bool = False,
+ ) -> Optional[str]: ...
+
def get_header_value(
self,
name: str,
default_value: Optional[str] = None,
- case_sensitive: Optional[bool] = False,
+ case_sensitive: bool = False,
) -> Optional[str]:
"""Get header value by name
diff --git a/aws_lambda_powertools/utilities/data_classes/common.py b/aws_lambda_powertools/utilities/data_classes/common.py
index ffca15cc318..b78a6e4939c 100644
--- a/aws_lambda_powertools/utilities/data_classes/common.py
+++ b/aws_lambda_powertools/utilities/data_classes/common.py
@@ -172,6 +172,12 @@ def http_method(self) -> str:
"""The HTTP method used. Valid values include: DELETE, GET, HEAD, OPTIONS, PATCH, POST, and PUT."""
return self["httpMethod"]
+ @overload
+ def get_query_string_value(self, name: str, default_value: str) -> str: ...
+
+ @overload
+ def get_query_string_value(self, name: str, default_value: Optional[str] = None) -> Optional[str]: ...
+
def get_query_string_value(self, name: str, default_value: Optional[str] = None) -> Optional[str]:
"""Get query string value by name
@@ -222,7 +228,7 @@ def get_header_value(
self,
name: str,
default_value: str,
- case_sensitive: Optional[bool] = False,
+ case_sensitive: bool = False,
) -> str: ...
@overload
@@ -230,14 +236,14 @@ def get_header_value(
self,
name: str,
default_value: Optional[str] = None,
- case_sensitive: Optional[bool] = False,
+ case_sensitive: bool = False,
) -> Optional[str]: ...
def get_header_value(
self,
name: str,
default_value: Optional[str] = None,
- case_sensitive: Optional[bool] = False,
+ case_sensitive: bool = False,
) -> Optional[str]:
"""Get header value by name
diff --git a/aws_lambda_powertools/utilities/data_classes/kafka_event.py b/aws_lambda_powertools/utilities/data_classes/kafka_event.py
index d3d1425f0f2..f20c5254730 100644
--- a/aws_lambda_powertools/utilities/data_classes/kafka_event.py
+++ b/aws_lambda_powertools/utilities/data_classes/kafka_event.py
@@ -1,6 +1,6 @@
import base64
from functools import cached_property
-from typing import Any, Dict, Iterator, List, Optional
+from typing import Any, Dict, Iterator, List, Optional, overload
from aws_lambda_powertools.utilities.data_classes.common import DictWrapper
from aws_lambda_powertools.utilities.data_classes.shared_functions import (
@@ -69,10 +69,26 @@ def decoded_headers(self) -> Dict[str, bytes]:
"""Decodes the headers as a single dictionary."""
return {k: bytes(v) for chunk in self.headers for k, v in chunk.items()}
+ @overload
def get_header_value(
self,
name: str,
- default_value: Optional[Any] = None,
+ default_value: str,
+ case_sensitive: bool = True,
+ ) -> str: ...
+
+ @overload
+ def get_header_value(
+ self,
+ name: str,
+ default_value: Optional[str] = None,
+ case_sensitive: bool = True,
+ ) -> Optional[str]: ...
+
+ def get_header_value(
+ self,
+ name: str,
+ default_value: Optional[str] = None,
case_sensitive: bool = True,
) -> Optional[str]:
"""Get a decoded header value by name."""
diff --git a/aws_lambda_powertools/utilities/data_classes/s3_object_event.py b/aws_lambda_powertools/utilities/data_classes/s3_object_event.py
index a7953c32c59..dc79b72766f 100644
--- a/aws_lambda_powertools/utilities/data_classes/s3_object_event.py
+++ b/aws_lambda_powertools/utilities/data_classes/s3_object_event.py
@@ -1,4 +1,4 @@
-from typing import Dict, Optional
+from typing import Dict, Optional, overload
from aws_lambda_powertools.utilities.data_classes.common import DictWrapper
from aws_lambda_powertools.utilities.data_classes.shared_functions import (
@@ -73,11 +73,27 @@ def headers(self) -> Dict[str, str]:
The case of the original headers is retained in this map."""
return self["headers"]
+ @overload
+ def get_header_value(
+ self,
+ name: str,
+ default_value: str,
+ case_sensitive: bool = False,
+ ) -> str: ...
+
+ @overload
+ def get_header_value(
+ self,
+ name: str,
+ default_value: Optional[str] = None,
+ case_sensitive: bool = False,
+ ) -> Optional[str]: ...
+
def get_header_value(
self,
name: str,
default_value: Optional[str] = None,
- case_sensitive: Optional[bool] = False,
+ case_sensitive: bool = False,
) -> Optional[str]:
"""Get header value by name
diff --git a/aws_lambda_powertools/utilities/data_classes/shared_functions.py b/aws_lambda_powertools/utilities/data_classes/shared_functions.py
index 43a3aad281b..0e88a5dac93 100644
--- a/aws_lambda_powertools/utilities/data_classes/shared_functions.py
+++ b/aws_lambda_powertools/utilities/data_classes/shared_functions.py
@@ -1,7 +1,7 @@
from __future__ import annotations
import base64
-from typing import Any, Dict
+from typing import Any, Dict, overload
def base64_decode(value: str) -> str:
@@ -21,11 +21,29 @@ def base64_decode(value: str) -> str:
return base64.b64decode(value).decode("UTF-8")
+@overload
def get_header_value(
headers: dict[str, Any],
name: str,
- default_value: str | None,
- case_sensitive: bool | None,
+ default_value: str,
+ case_sensitive: bool = False,
+) -> str: ...
+
+
+@overload
+def get_header_value(
+ headers: dict[str, Any],
+ name: str,
+ default_value: str | None = None,
+ case_sensitive: bool = False,
+) -> str | None: ...
+
+
+def get_header_value(
+ headers: dict[str, Any],
+ name: str,
+ default_value: str | None = None,
+ case_sensitive: bool = False,
) -> str | None:
"""
Get the value of a header by its name.
@@ -39,7 +57,7 @@ def get_header_value(
default_value: str, optional
The default value to return if the header is not found. Default is None.
case_sensitive: bool, optional
- Indicates whether the header name should be case-sensitive. Default is None.
+ Indicates whether the header name should be case-sensitive. Default is False.
Returns
-------
@@ -62,6 +80,22 @@ def get_header_value(
)
+@overload
+def get_query_string_value(
+ query_string_parameters: Dict[str, str] | None,
+ name: str,
+ default_value: str,
+) -> str: ...
+
+
+@overload
+def get_query_string_value(
+ query_string_parameters: Dict[str, str] | None,
+ name: str,
+ default_value: str | None = None,
+) -> str | None: ...
+
+
def get_query_string_value(
query_string_parameters: Dict[str, str] | None,
name: str,
diff --git a/aws_lambda_powertools/utilities/data_classes/sqs_event.py b/aws_lambda_powertools/utilities/data_classes/sqs_event.py
index e800fd0aac4..dda63430dc6 100644
--- a/aws_lambda_powertools/utilities/data_classes/sqs_event.py
+++ b/aws_lambda_powertools/utilities/data_classes/sqs_event.py
@@ -55,6 +55,13 @@ def message_deduplication_id(self) -> Optional[str]:
the 5-minute deduplication interval."""
return self.get("MessageDeduplicationId")
+ @property
+ def dead_letter_queue_source_arn(self) -> Optional[str]:
+ """The SQS queue ARN that sent the record to this DLQ.
+ Only present when a Lambda function is using a DLQ as an event source.
+ """
+ return self.get("DeadLetterQueueSourceArn")
+
class SQSMessageAttribute(DictWrapper):
"""The user-specified message attribute value."""
diff --git a/aws_lambda_powertools/utilities/data_classes/vpc_lattice.py b/aws_lambda_powertools/utilities/data_classes/vpc_lattice.py
index f997d4b3f04..3fe9762fcd7 100644
--- a/aws_lambda_powertools/utilities/data_classes/vpc_lattice.py
+++ b/aws_lambda_powertools/utilities/data_classes/vpc_lattice.py
@@ -47,6 +47,12 @@ def http_method(self) -> str:
"""The HTTP method used. Valid values include: DELETE, GET, HEAD, OPTIONS, PATCH, POST, and PUT."""
return self["method"]
+ @overload
+ def get_query_string_value(self, name: str, default_value: str) -> str: ...
+
+ @overload
+ def get_query_string_value(self, name: str, default_value: Optional[str] = None) -> Optional[str]: ...
+
def get_query_string_value(self, name: str, default_value: Optional[str] = None) -> Optional[str]:
"""Get query string value by name
@@ -72,7 +78,7 @@ def get_header_value(
self,
name: str,
default_value: str,
- case_sensitive: Optional[bool] = False,
+ case_sensitive: bool = False,
) -> str: ...
@overload
@@ -80,14 +86,14 @@ def get_header_value(
self,
name: str,
default_value: Optional[str] = None,
- case_sensitive: Optional[bool] = False,
+ case_sensitive: bool = False,
) -> Optional[str]: ...
def get_header_value(
self,
name: str,
default_value: Optional[str] = None,
- case_sensitive: Optional[bool] = False,
+ case_sensitive: bool = False,
) -> Optional[str]:
"""Get header value by name
diff --git a/aws_lambda_powertools/utilities/idempotency/__init__.py b/aws_lambda_powertools/utilities/idempotency/__init__.py
index ae27330cc1f..0c46553cc59 100644
--- a/aws_lambda_powertools/utilities/idempotency/__init__.py
+++ b/aws_lambda_powertools/utilities/idempotency/__init__.py
@@ -2,6 +2,9 @@
Utility for adding idempotency to lambda functions
"""
+from aws_lambda_powertools.utilities.idempotency.hook import (
+ IdempotentHookFunction,
+)
from aws_lambda_powertools.utilities.idempotency.persistence.base import (
BasePersistenceLayer,
)
@@ -17,4 +20,5 @@
"idempotent",
"idempotent_function",
"IdempotencyConfig",
+ "IdempotentHookFunction",
)
diff --git a/aws_lambda_powertools/utilities/idempotency/base.py b/aws_lambda_powertools/utilities/idempotency/base.py
index 771547fe33c..f5ed9e2e476 100644
--- a/aws_lambda_powertools/utilities/idempotency/base.py
+++ b/aws_lambda_powertools/utilities/idempotency/base.py
@@ -3,7 +3,9 @@
from copy import deepcopy
from typing import Any, Callable, Dict, Optional, Tuple
-from aws_lambda_powertools.utilities.idempotency.config import IdempotencyConfig
+from aws_lambda_powertools.utilities.idempotency.config import (
+ IdempotencyConfig,
+)
from aws_lambda_powertools.utilities.idempotency.exceptions import (
IdempotencyAlreadyInProgressError,
IdempotencyInconsistentStateError,
@@ -227,7 +229,15 @@ def _handle_for_status(self, data_record: DataRecord) -> Optional[Any]:
)
response_dict: Optional[dict] = data_record.response_json_as_dict()
if response_dict is not None:
- return self.output_serializer.from_dict(response_dict)
+ serialized_response = self.output_serializer.from_dict(response_dict)
+ if self.config.response_hook is not None:
+ logger.debug("Response hook configured, invoking function")
+ return self.config.response_hook(
+ serialized_response,
+ data_record,
+ )
+ return serialized_response
+
return None
def _get_function_response(self):
diff --git a/aws_lambda_powertools/utilities/idempotency/config.py b/aws_lambda_powertools/utilities/idempotency/config.py
index e78f339fdc9..826dbbe4089 100644
--- a/aws_lambda_powertools/utilities/idempotency/config.py
+++ b/aws_lambda_powertools/utilities/idempotency/config.py
@@ -1,5 +1,6 @@
from typing import Dict, Optional
+from aws_lambda_powertools.utilities.idempotency import IdempotentHookFunction
from aws_lambda_powertools.utilities.typing import LambdaContext
@@ -15,6 +16,7 @@ def __init__(
local_cache_max_items: int = 256,
hash_function: str = "md5",
lambda_context: Optional[LambdaContext] = None,
+ response_hook: Optional[IdempotentHookFunction] = None,
):
"""
Initialize the base persistence layer
@@ -37,6 +39,8 @@ def __init__(
Function to use for calculating hashes, by default md5.
lambda_context: LambdaContext, optional
Lambda Context containing information about the invocation, function and execution environment.
+ response_hook: IdempotentHookFunction, optional
+ Hook function to be called when an idempotent response is returned from the idempotent store.
"""
self.event_key_jmespath = event_key_jmespath
self.payload_validation_jmespath = payload_validation_jmespath
@@ -47,6 +51,7 @@ def __init__(
self.local_cache_max_items = local_cache_max_items
self.hash_function = hash_function
self.lambda_context: Optional[LambdaContext] = lambda_context
+ self.response_hook: Optional[IdempotentHookFunction] = response_hook
def register_lambda_context(self, lambda_context: LambdaContext):
"""Captures the Lambda context, to calculate the remaining time before the invocation times out"""
diff --git a/aws_lambda_powertools/utilities/idempotency/hook.py b/aws_lambda_powertools/utilities/idempotency/hook.py
new file mode 100644
index 00000000000..0027399b937
--- /dev/null
+++ b/aws_lambda_powertools/utilities/idempotency/hook.py
@@ -0,0 +1,13 @@
+from typing import Any
+
+from aws_lambda_powertools.shared.types import Protocol
+from aws_lambda_powertools.utilities.idempotency.persistence.datarecord import DataRecord
+
+
+class IdempotentHookFunction(Protocol):
+ """
+ The IdempotentHookFunction.
+ This class defines the calling signature for IdempotentHookFunction callbacks.
+ """
+
+ def __call__(self, response: Any, idempotent_data: DataRecord) -> Any: ...
diff --git a/aws_lambda_powertools/utilities/parser/models/sqs.py b/aws_lambda_powertools/utilities/parser/models/sqs.py
index 63ea4b76e0e..317b76c3227 100644
--- a/aws_lambda_powertools/utilities/parser/models/sqs.py
+++ b/aws_lambda_powertools/utilities/parser/models/sqs.py
@@ -15,6 +15,7 @@ class SqsAttributesModel(BaseModel):
SentTimestamp: datetime
SequenceNumber: Optional[str] = None
AWSTraceHeader: Optional[str] = None
+ DeadLetterQueueSourceArn: Optional[str] = None
class SqsMsgAttributeModel(BaseModel):
diff --git a/docs/Dockerfile b/docs/Dockerfile
index 5633fd4605c..b7534b44425 100644
--- a/docs/Dockerfile
+++ b/docs/Dockerfile
@@ -1,5 +1,5 @@
# v9.1.18
-FROM squidfunk/mkdocs-material@sha256:33076657e536b6b8439168296a193098aef3c4c88cc2cecd0736cd391b90e7fd
+FROM squidfunk/mkdocs-material@sha256:521644b58bc0c806083ef66e8b1027861bd3e98c433b251c436c5c0cc7733603
# 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/core/event_handler/api_gateway.md b/docs/core/event_handler/api_gateway.md
index 8740c264c46..aaf9352ebc0 100644
--- a/docs/core/event_handler/api_gateway.md
+++ b/docs/core/event_handler/api_gateway.md
@@ -874,6 +874,13 @@ You can use the `Response` class to have full control over the response. For exa
--8<-- "examples/event_handler_rest/src/fine_grained_responses_output.json"
```
+???- note "Using `Response` with data validation?"
+ When using the [data validation](#data-validation) feature with `enable_validation=True`, you must specify the concrete type for the `Response` class. This allows the validation middleware to infer the underlying type and perform validation correctly.
+
+ ```python hl_lines="8 26 32"
+ --8<-- "examples/event_handler_rest/src/data_validation_fine_grained_response.py"
+ ```
+
### Compress
You can compress with gzip and base64 encode your responses via `compress` parameter. You have the option to pass the `compress` parameter when working with a specific route or using the Response object.
@@ -984,6 +991,18 @@ To implement these customizations, include extra parameters when defining your r
--8<-- "examples/event_handler_rest/src/customizing_api_operations.py"
```
+#### Customizing OpenAPI metadata
+
+--8<-- "docs/core/event_handler/_openapi_customization_metadata.md"
+
+Include extra parameters when exporting your OpenAPI specification to apply these customizations:
+
+=== "customizing_api_metadata.py"
+
+ ```python hl_lines="25-31"
+ --8<-- "examples/event_handler_rest/src/customizing_api_metadata.py"
+ ```
+
#### Customizing Swagger UI
???+note "Customizing the Swagger metadata"
@@ -1007,16 +1026,44 @@ Below is an example configuration for serving Swagger UI from a custom path or C
--8<-- "examples/event_handler_rest/src/customizing_swagger_middlewares.py"
```
-#### Customizing OpenAPI metadata
+#### Security schemes
---8<-- "docs/core/event_handler/_openapi_customization_metadata.md"
+???-info "Does Powertools implement any of the security schemes?"
+ No. Powertools adds support for generating OpenAPI documentation with [security schemes](https://swagger.io/docs/specification/authentication/), but it doesn't implement any of the security schemes itself, so you must implement the security mechanisms separately.
-Include extra parameters when exporting your OpenAPI specification to apply these customizations:
+OpenAPI uses the term security scheme for [authentication and authorization schemes](https://swagger.io/docs/specification/authentication/){target="_blank"}.
+When you're describing your API, declare security schemes at the top level, and reference them globally or per operation.
-=== "customizing_api_metadata.py"
+=== "Global OpenAPI security schemes"
- ```python hl_lines="25-31"
- --8<-- "examples/event_handler_rest/src/customizing_api_metadata.py"
+ ```python title="security_schemes_global.py" hl_lines="32-42"
+ --8<-- "examples/event_handler_rest/src/security_schemes_global.py"
+ ```
+
+ 1. Using the oauth security scheme defined earlier, scoped to the "admin" role.
+
+=== "Per Operation security"
+
+ ```python title="security_schemes_per_operation.py" hl_lines="17 32-41"
+ --8<-- "examples/event_handler_rest/src/security_schemes_per_operation.py"
+ ```
+
+ 1. Using the oauth security scheme defined bellow, scoped to the "admin" role.
+
+OpenAPI 3 lets you describe APIs protected using the following security schemes:
+
+| Security Scheme | Type | Description |
+|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| [HTTP auth](https://www.iana.org/assignments/http-authschemes/http-authschemes.xhtml){target="_blank"} | `HTTPBase` | HTTP authentication schemes using the Authorization header (e.g: [Basic auth](https://swagger.io/docs/specification/authentication/basic-authentication/){target="_blank"}, [Bearer](https://swagger.io/docs/specification/authentication/bearer-authentication/){target="_blank"}) |
+| [API keys](https://swagger.io/docs/specification/authentication/api-keys/https://swagger.io/docs/specification/authentication/api-keys/){target="_blank"} (e.g: query strings, cookies) | `APIKey` | API keys in headers, query strings or [cookies](https://swagger.io/docs/specification/authentication/cookie-authentication/){target="_blank"}. |
+| [OAuth 2](https://swagger.io/docs/specification/authentication/oauth2/){target="_blank"} | `OAuth2` | Authorization protocol that gives an API client limited access to user data on a web server. |
+| [OpenID Connect Discovery](https://swagger.io/docs/specification/authentication/openid-connect-discovery/){target="_blank"} | `OpenIdConnect` | Identity layer built [on top of the OAuth 2.0 protocol](https://openid.net/developers/how-connect-works/){target="_blank"} and supported by some OAuth 2.0. |
+
+???-note "Using OAuth2 with the Swagger UI?"
+ You can use the `OAuth2Config` option to configure a default OAuth2 app on the generated Swagger UI.
+
+ ```python hl_lines="10 15-18 22"
+ --8<-- "examples/event_handler_rest/src/swagger_with_oauth2.py"
```
### Custom serializer
diff --git a/docs/core/logger.md b/docs/core/logger.md
index 8c915fcd589..bf600e285ca 100644
--- a/docs/core/logger.md
+++ b/docs/core/logger.md
@@ -274,6 +274,16 @@ Logger is commonly initialized in the global scope. Due to [Lambda Execution Con
--8<-- "examples/logger/src/clear_state_event_two.json"
```
+### Accessing currently configured keys
+
+You can view all currently configured keys from the Logger state using the `get_current_keys()` method. This method is useful when you need to avoid overwriting keys that are already configured.
+
+=== "get_current_keys.py"
+
+ ```python hl_lines="4 11"
+ --8<-- "examples/logger/src/get_current_keys.py"
+ ```
+
### Log levels
The default log level is `INFO`. It can be set using the `level` constructor option, `setLevel()` method or by using the `POWERTOOLS_LOG_LEVEL` environment variable.
@@ -732,7 +742,7 @@ The `log` argument is the final log record containing [our standard keys](#stand
For exceptional cases where you want to completely replace our formatter logic, you can subclass `BasePowertoolsFormatter`.
???+ warning
- You will need to implement `append_keys`, `clear_state`, override `format`, and optionally `remove_keys` to keep the same feature set Powertools for AWS Lambda (Python) Logger provides. This also means keeping state of logging keys added.
+ You will need to implement `append_keys`, `clear_state`, override `format`, and optionally `get_current_keys`, and `remove_keys` to keep the same feature set Powertools for AWS Lambda (Python) Logger provides. This also means tracking the added logging keys.
=== "bring_your_own_formatter_from_scratch.py"
diff --git a/docs/index.md b/docs/index.md
index a894d0052fc..e18f42bf467 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -67,8 +67,8 @@ You can install Powertools for AWS Lambda (Python) using your favorite dependenc
For the latter, make sure to replace `{region}` with your AWS region, e.g., `eu-west-1`.
- * x86 architecture: __arn:aws:lambda:{region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:67__{: .copyMe}:clipboard:
- * ARM architecture: __arn:aws:lambda:{region}:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:67__{: .copyMe}:clipboard:
+ * x86 architecture: __arn:aws:lambda:{region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:68__{: .copyMe}:clipboard:
+ * ARM architecture: __arn:aws:lambda:{region}:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:68__{: .copyMe}:clipboard:
???+ note "Code snippets for popular infrastructure as code frameworks"
@@ -81,7 +81,7 @@ You can install Powertools for AWS Lambda (Python) using your favorite dependenc
Type: AWS::Serverless::Function
Properties:
Layers:
- - !Sub arn:aws:lambda:${AWS::Region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:67
+ - !Sub arn:aws:lambda:${AWS::Region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:68
```
=== "Serverless framework"
@@ -91,7 +91,7 @@ You can install Powertools for AWS Lambda (Python) using your favorite dependenc
hello:
handler: lambda_function.lambda_handler
layers:
- - arn:aws:lambda:${aws:region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:67
+ - arn:aws:lambda:${aws:region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:68
```
=== "CDK"
@@ -107,7 +107,7 @@ You can install Powertools for AWS Lambda (Python) using your favorite dependenc
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:67"
+ layer_version_arn=f"arn:aws:lambda:{env.region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:68"
)
aws_lambda.Function(self,
'sample-app-lambda',
@@ -156,7 +156,7 @@ You can install Powertools for AWS Lambda (Python) using your favorite dependenc
role = aws_iam_role.iam_for_lambda.arn
handler = "index.test"
runtime = "python3.9"
- layers = ["arn:aws:lambda:{region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:67"]
+ layers = ["arn:aws:lambda:{region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:68"]
source_code_hash = filebase64sha256("lambda_function_payload.zip")
}
@@ -209,7 +209,7 @@ You can install Powertools for AWS Lambda (Python) using your favorite dependenc
? 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:67
+ ? Enter up to 5 existing Lambda layer ARNs (comma-separated): arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:68
❯ amplify push -y
@@ -220,7 +220,7 @@ You can install Powertools for AWS Lambda (Python) using your favorite dependenc
- 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:67
+ ? Enter up to 5 existing Lambda layer ARNs (comma-separated): arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:68
? Do you want to edit the local lambda function now? No
```
@@ -234,7 +234,7 @@ You can install Powertools for AWS Lambda (Python) using your favorite dependenc
Properties:
Architectures: [arm64]
Layers:
- - !Sub arn:aws:lambda:${AWS::Region}:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:67
+ - !Sub arn:aws:lambda:${AWS::Region}:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:68
```
=== "Serverless framework"
@@ -245,7 +245,7 @@ You can install Powertools for AWS Lambda (Python) using your favorite dependenc
handler: lambda_function.lambda_handler
architecture: arm64
layers:
- - arn:aws:lambda:${aws:region}:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:67
+ - arn:aws:lambda:${aws:region}:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:68
```
=== "CDK"
@@ -261,7 +261,7 @@ You can install Powertools for AWS Lambda (Python) using your favorite dependenc
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:67"
+ layer_version_arn=f"arn:aws:lambda:{env.region}:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:68"
)
aws_lambda.Function(self,
'sample-app-lambda',
@@ -311,7 +311,7 @@ You can install Powertools for AWS Lambda (Python) using your favorite dependenc
role = aws_iam_role.iam_for_lambda.arn
handler = "index.test"
runtime = "python3.9"
- layers = ["arn:aws:lambda:{region}:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:67"]
+ layers = ["arn:aws:lambda:{region}:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:68"]
architectures = ["arm64"]
source_code_hash = filebase64sha256("lambda_function_payload.zip")
@@ -367,7 +367,7 @@ You can install Powertools for AWS Lambda (Python) using your favorite dependenc
? 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:67
+ ? Enter up to 5 existing Lambda layer ARNs (comma-separated): arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:68
❯ amplify push -y
@@ -378,7 +378,7 @@ You can install Powertools for AWS Lambda (Python) using your favorite dependenc
- 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:67
+ ? Enter up to 5 existing Lambda layer ARNs (comma-separated): arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:68
? Do you want to edit the local lambda function now? No
```
@@ -409,74 +409,74 @@ In this context, `[aws-sdk]` is an alias to the `boto3` package. Due to dependen
| Region | Layer ARN |
| -------------------- | --------------------------------------------------------------------------------------------------------- |
- | **`af-south-1`** | **arn:aws:lambda:af-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:67**{: .copyMe}:clipboard: |
- | **`ap-east-1`** | **arn:aws:lambda:ap-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:67**{: .copyMe}:clipboard: |
- | **`ap-northeast-1`** | **arn:aws:lambda:ap-northeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:67**{: .copyMe}:clipboard: |
- | **`ap-northeast-2`** | **arn:aws:lambda:ap-northeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:67**{: .copyMe}:clipboard: |
- | **`ap-northeast-3`** | **arn:aws:lambda:ap-northeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV2:67**{: .copyMe}:clipboard: |
- | **`ap-south-1`** | **arn:aws:lambda:ap-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:67**{: .copyMe}:clipboard: |
- | **`ap-south-2`** | **arn:aws:lambda:ap-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:67**{: .copyMe}:clipboard: |
- | **`ap-southeast-1`** | **arn:aws:lambda:ap-southeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:67**{: .copyMe}:clipboard: |
- | **`ap-southeast-2`** | **arn:aws:lambda:ap-southeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:67**{: .copyMe}:clipboard: |
- | **`ap-southeast-3`** | **arn:aws:lambda:ap-southeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV2:67**{: .copyMe}:clipboard: |
- | **`ap-southeast-4`** | **arn:aws:lambda:ap-southeast-4:017000801446:layer:AWSLambdaPowertoolsPythonV2:67**{: .copyMe}:clipboard: |
- | **`ca-central-1`** | **arn:aws:lambda:ca-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:67**{: .copyMe}:clipboard: |
- | **`ca-west-1`** | **arn:aws:lambda:ca-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:67**{: .copyMe}:clipboard: |
- | **`eu-central-1`** | **arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:67**{: .copyMe}:clipboard: |
- | **`eu-central-2`** | **arn:aws:lambda:eu-central-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:67**{: .copyMe}:clipboard: |
- | **`eu-north-1`** | **arn:aws:lambda:eu-north-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:67**{: .copyMe}:clipboard: |
- | **`eu-south-1`** | **arn:aws:lambda:eu-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:67**{: .copyMe}:clipboard: |
- | **`eu-south-2`** | **arn:aws:lambda:eu-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:67**{: .copyMe}:clipboard: |
- | **`eu-west-1`** | **arn:aws:lambda:eu-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:67**{: .copyMe}:clipboard: |
- | **`eu-west-2`** | **arn:aws:lambda:eu-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:67**{: .copyMe}:clipboard: |
- | **`eu-west-3`** | **arn:aws:lambda:eu-west-3:017000801446:layer:AWSLambdaPowertoolsPythonV2:67**{: .copyMe}:clipboard: |
- | **`il-central-1`** | **arn:aws:lambda:il-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:67**{: .copyMe}:clipboard: |
- | **`me-central-1`** | **arn:aws:lambda:me-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:67**{: .copyMe}:clipboard: |
- | **`me-south-1`** | **arn:aws:lambda:me-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:67**{: .copyMe}:clipboard: |
- | **`sa-east-1`** | **arn:aws:lambda:sa-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:67**{: .copyMe}:clipboard: |
- | **`us-east-1`** | **arn:aws:lambda:us-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:67**{: .copyMe}:clipboard: |
- | **`us-east-2`** | **arn:aws:lambda:us-east-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:67**{: .copyMe}:clipboard: |
- | **`us-west-1`** | **arn:aws:lambda:us-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:67**{: .copyMe}:clipboard: |
- | **`us-west-2`** | **arn:aws:lambda:us-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:67**{: .copyMe}:clipboard: |
+ | **`af-south-1`** | **arn:aws:lambda:af-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:68**{: .copyMe}:clipboard: |
+ | **`ap-east-1`** | **arn:aws:lambda:ap-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:68**{: .copyMe}:clipboard: |
+ | **`ap-northeast-1`** | **arn:aws:lambda:ap-northeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:68**{: .copyMe}:clipboard: |
+ | **`ap-northeast-2`** | **arn:aws:lambda:ap-northeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:68**{: .copyMe}:clipboard: |
+ | **`ap-northeast-3`** | **arn:aws:lambda:ap-northeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV2:68**{: .copyMe}:clipboard: |
+ | **`ap-south-1`** | **arn:aws:lambda:ap-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:68**{: .copyMe}:clipboard: |
+ | **`ap-south-2`** | **arn:aws:lambda:ap-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:68**{: .copyMe}:clipboard: |
+ | **`ap-southeast-1`** | **arn:aws:lambda:ap-southeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:68**{: .copyMe}:clipboard: |
+ | **`ap-southeast-2`** | **arn:aws:lambda:ap-southeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:68**{: .copyMe}:clipboard: |
+ | **`ap-southeast-3`** | **arn:aws:lambda:ap-southeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV2:68**{: .copyMe}:clipboard: |
+ | **`ap-southeast-4`** | **arn:aws:lambda:ap-southeast-4:017000801446:layer:AWSLambdaPowertoolsPythonV2:68**{: .copyMe}:clipboard: |
+ | **`ca-central-1`** | **arn:aws:lambda:ca-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:68**{: .copyMe}:clipboard: |
+ | **`ca-west-1`** | **arn:aws:lambda:ca-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:68**{: .copyMe}:clipboard: |
+ | **`eu-central-1`** | **arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:68**{: .copyMe}:clipboard: |
+ | **`eu-central-2`** | **arn:aws:lambda:eu-central-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:68**{: .copyMe}:clipboard: |
+ | **`eu-north-1`** | **arn:aws:lambda:eu-north-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:68**{: .copyMe}:clipboard: |
+ | **`eu-south-1`** | **arn:aws:lambda:eu-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:68**{: .copyMe}:clipboard: |
+ | **`eu-south-2`** | **arn:aws:lambda:eu-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:68**{: .copyMe}:clipboard: |
+ | **`eu-west-1`** | **arn:aws:lambda:eu-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:68**{: .copyMe}:clipboard: |
+ | **`eu-west-2`** | **arn:aws:lambda:eu-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:68**{: .copyMe}:clipboard: |
+ | **`eu-west-3`** | **arn:aws:lambda:eu-west-3:017000801446:layer:AWSLambdaPowertoolsPythonV2:68**{: .copyMe}:clipboard: |
+ | **`il-central-1`** | **arn:aws:lambda:il-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:68**{: .copyMe}:clipboard: |
+ | **`me-central-1`** | **arn:aws:lambda:me-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:68**{: .copyMe}:clipboard: |
+ | **`me-south-1`** | **arn:aws:lambda:me-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:68**{: .copyMe}:clipboard: |
+ | **`sa-east-1`** | **arn:aws:lambda:sa-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:68**{: .copyMe}:clipboard: |
+ | **`us-east-1`** | **arn:aws:lambda:us-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:68**{: .copyMe}:clipboard: |
+ | **`us-east-2`** | **arn:aws:lambda:us-east-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:68**{: .copyMe}:clipboard: |
+ | **`us-west-1`** | **arn:aws:lambda:us-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:68**{: .copyMe}:clipboard: |
+ | **`us-west-2`** | **arn:aws:lambda:us-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:68**{: .copyMe}:clipboard: |
=== "arm64"
| Region | Layer ARN |
| -------------------- | --------------------------------------------------------------------------------------------------------------- |
- | **`af-south-1`** | **arn:aws:lambda:af-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:67**{: .copyMe}:clipboard: |
- | **`ap-east-1`** | **arn:aws:lambda:ap-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:67**{: .copyMe}:clipboard: |
- | **`ap-northeast-1`** | **arn:aws:lambda:ap-northeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:67**{: .copyMe}:clipboard: |
- | **`ap-northeast-2`** | **arn:aws:lambda:ap-northeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:67**{: .copyMe}:clipboard: |
- | **`ap-northeast-3`** | **arn:aws:lambda:ap-northeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:67**{: .copyMe}:clipboard: |
- | **`ap-south-1`** | **arn:aws:lambda:ap-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:67**{: .copyMe}:clipboard: |
- | **`ap-south-2`** | **arn:aws:lambda:ap-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:67**{: .copyMe}:clipboard: |
- | **`ap-southeast-1`** | **arn:aws:lambda:ap-southeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:67**{: .copyMe}:clipboard: |
- | **`ap-southeast-2`** | **arn:aws:lambda:ap-southeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:67**{: .copyMe}:clipboard: |
- | **`ap-southeast-3`** | **arn:aws:lambda:ap-southeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:67**{: .copyMe}:clipboard: |
- | **`ca-central-1`** | **arn:aws:lambda:ca-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:67**{: .copyMe}:clipboard: |
- | **`eu-central-1`** | **arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:67**{: .copyMe}:clipboard: |
- | **`eu-central-2`** | **arn:aws:lambda:eu-central-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:67**{: .copyMe}:clipboard: |
- | **`eu-north-1`** | **arn:aws:lambda:eu-north-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:67**{: .copyMe}:clipboard: |
- | **`eu-south-1`** | **arn:aws:lambda:eu-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:67**{: .copyMe}:clipboard: |
- | **`eu-south-2`** | **arn:aws:lambda:eu-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:67**{: .copyMe}:clipboard: |
- | **`eu-west-1`** | **arn:aws:lambda:eu-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:67**{: .copyMe}:clipboard: |
- | **`eu-west-2`** | **arn:aws:lambda:eu-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:67**{: .copyMe}:clipboard: |
- | **`eu-west-3`** | **arn:aws:lambda:eu-west-3:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:67**{: .copyMe}:clipboard: |
- | **`il-central-1`** | **arn:aws:lambda:il-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:67**{: .copyMe}:clipboard: |
- | **`me-central-1`** | **arn:aws:lambda:me-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:67**{: .copyMe}:clipboard: |
- | **`me-south-1`** | **arn:aws:lambda:me-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:67**{: .copyMe}:clipboard: |
- | **`sa-east-1`** | **arn:aws:lambda:sa-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:67**{: .copyMe}:clipboard: |
- | **`us-east-1`** | **arn:aws:lambda:us-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:67**{: .copyMe}:clipboard: |
- | **`us-east-2`** | **arn:aws:lambda:us-east-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:67**{: .copyMe}:clipboard: |
- | **`us-west-1`** | **arn:aws:lambda:us-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:67**{: .copyMe}:clipboard: |
- | **`us-west-2`** | **arn:aws:lambda:us-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:67**{: .copyMe}:clipboard: |
+ | **`af-south-1`** | **arn:aws:lambda:af-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:68**{: .copyMe}:clipboard: |
+ | **`ap-east-1`** | **arn:aws:lambda:ap-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:68**{: .copyMe}:clipboard: |
+ | **`ap-northeast-1`** | **arn:aws:lambda:ap-northeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:68**{: .copyMe}:clipboard: |
+ | **`ap-northeast-2`** | **arn:aws:lambda:ap-northeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:68**{: .copyMe}:clipboard: |
+ | **`ap-northeast-3`** | **arn:aws:lambda:ap-northeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:68**{: .copyMe}:clipboard: |
+ | **`ap-south-1`** | **arn:aws:lambda:ap-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:68**{: .copyMe}:clipboard: |
+ | **`ap-south-2`** | **arn:aws:lambda:ap-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:68**{: .copyMe}:clipboard: |
+ | **`ap-southeast-1`** | **arn:aws:lambda:ap-southeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:68**{: .copyMe}:clipboard: |
+ | **`ap-southeast-2`** | **arn:aws:lambda:ap-southeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:68**{: .copyMe}:clipboard: |
+ | **`ap-southeast-3`** | **arn:aws:lambda:ap-southeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:68**{: .copyMe}:clipboard: |
+ | **`ca-central-1`** | **arn:aws:lambda:ca-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:68**{: .copyMe}:clipboard: |
+ | **`eu-central-1`** | **arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:68**{: .copyMe}:clipboard: |
+ | **`eu-central-2`** | **arn:aws:lambda:eu-central-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:68**{: .copyMe}:clipboard: |
+ | **`eu-north-1`** | **arn:aws:lambda:eu-north-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:68**{: .copyMe}:clipboard: |
+ | **`eu-south-1`** | **arn:aws:lambda:eu-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:68**{: .copyMe}:clipboard: |
+ | **`eu-south-2`** | **arn:aws:lambda:eu-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:68**{: .copyMe}:clipboard: |
+ | **`eu-west-1`** | **arn:aws:lambda:eu-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:68**{: .copyMe}:clipboard: |
+ | **`eu-west-2`** | **arn:aws:lambda:eu-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:68**{: .copyMe}:clipboard: |
+ | **`eu-west-3`** | **arn:aws:lambda:eu-west-3:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:68**{: .copyMe}:clipboard: |
+ | **`il-central-1`** | **arn:aws:lambda:il-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:68**{: .copyMe}:clipboard: |
+ | **`me-central-1`** | **arn:aws:lambda:me-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:68**{: .copyMe}:clipboard: |
+ | **`me-south-1`** | **arn:aws:lambda:me-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:68**{: .copyMe}:clipboard: |
+ | **`sa-east-1`** | **arn:aws:lambda:sa-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:68**{: .copyMe}:clipboard: |
+ | **`us-east-1`** | **arn:aws:lambda:us-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:68**{: .copyMe}:clipboard: |
+ | **`us-east-2`** | **arn:aws:lambda:us-east-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:68**{: .copyMe}:clipboard: |
+ | **`us-west-1`** | **arn:aws:lambda:us-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:68**{: .copyMe}:clipboard: |
+ | **`us-west-2`** | **arn:aws:lambda:us-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:68**{: .copyMe}:clipboard: |
**Want to inspect the contents of the Layer?**
The pre-signed URL to download this Lambda Layer will be within `Location` key in the CLI output. The CLI output will also contain the Powertools for AWS Lambda version it contains.
```bash title="AWS CLI command to download Lambda Layer content"
-aws lambda get-layer-version-by-arn --arn arn:aws:lambda:eu-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:67 --region eu-west-1
+aws lambda get-layer-version-by-arn --arn arn:aws:lambda:eu-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:68 --region eu-west-1
```
#### SAR
diff --git a/docs/utilities/idempotency.md b/docs/utilities/idempotency.md
index 17848a7828b..e448d82e28e 100644
--- a/docs/utilities/idempotency.md
+++ b/docs/utilities/idempotency.md
@@ -73,8 +73,8 @@ We currently support Amazon DynamoDB and Redis as a storage layer. The following
If you're not [changing the default configuration for the DynamoDB persistence layer](#dynamodbpersistencelayer), this is the expected default configuration:
| Configuration | Value | Notes |
-| ------------------ | ------------ | ----------------------------------------------------------------------------------- |
-| Partition key | `id` |
+| ------------------ | ------------ |-------------------------------------------------------------------------------------|
+| Partition key | `id` | |
| TTL attribute name | `expiration` | This can only be configured after your table is created if you're using AWS Console |
???+ tip "Tip: You can share a single state table for all functions"
@@ -454,6 +454,40 @@ sequenceDiagram
Idempotent successful request cached
+#### Successful request with response_hook configured
+
+
+```mermaid
+sequenceDiagram
+ participant Client
+ participant Lambda
+ participant Response hook
+ participant Persistence Layer
+ alt initial request
+ Client->>Lambda: Invoke (event)
+ Lambda->>Persistence Layer: Get or set idempotency_key=hash(payload)
+ activate Persistence Layer
+ Note over Lambda,Persistence Layer: Set record status to INPROGRESS.
Prevents concurrent invocations
with the same payload
+ Lambda-->>Lambda: Call your function
+ Lambda->>Persistence Layer: Update record with result
+ deactivate Persistence Layer
+ Persistence Layer-->>Persistence Layer: Update record
+ Note over Lambda,Persistence Layer: Set record status to COMPLETE.
New invocations with the same payload
now return the same result
+ Lambda-->>Client: Response sent to client
+ else retried request
+ Client->>Lambda: Invoke (event)
+ Lambda->>Persistence Layer: Get or set idempotency_key=hash(payload)
+ activate Persistence Layer
+ Persistence Layer-->>Response hook: Already exists in persistence layer.
+ deactivate Persistence Layer
+ Note over Response hook,Persistence Layer: Record status is COMPLETE and not expired
+ Response hook->>Lambda: Response hook invoked
+ Lambda-->>Client: Manipulated idempotent response sent to client
+ end
+```
+Successful idempotent request with a response hook
+
+
#### Expired idempotency records
@@ -699,15 +733,16 @@ For advanced configurations, such as setting up SSL certificates or customizing
Idempotent decorator can be further configured with **`IdempotencyConfig`** as seen in the previous example. These are the available options for further configuration
-| Parameter | Default | Description |
-| ------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| **event_key_jmespath** | `""` | JMESPath expression to extract the idempotency key from the event record using [built-in functions](./jmespath_functions.md#built-in-jmespath-functions){target="_blank"} |
-| **payload_validation_jmespath** | `""` | JMESPath expression to validate whether certain parameters have changed in the event while the event payload |
-| **raise_on_no_idempotency_key** | `False` | Raise exception if no idempotency key was found in the request |
-| **expires_after_seconds** | 3600 | The number of seconds to wait before a record is expired |
-| **use_local_cache** | `False` | Whether to locally cache idempotency results |
-| **local_cache_max_items** | 256 | Max number of items to store in local cache |
-| **hash_function** | `md5` | Function to use for calculating hashes, as provided by [hashlib](https://docs.python.org/3/library/hashlib.html){target="_blank" rel="nofollow"} in the standard library. |
+| Parameter | Default | Description |
+|---------------------------------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| **event_key_jmespath** | `""` | JMESPath expression to extract the idempotency key from the event record using [built-in functions](./jmespath_functions.md#built-in-jmespath-functions){target="_blank"} |
+| **payload_validation_jmespath** | `""` | JMESPath expression to validate whether certain parameters have changed in the event while the event payload |
+| **raise_on_no_idempotency_key** | `False` | Raise exception if no idempotency key was found in the request |
+| **expires_after_seconds** | 3600 | The number of seconds to wait before a record is expired |
+| **use_local_cache** | `False` | Whether to locally cache idempotency results |
+| **local_cache_max_items** | 256 | Max number of items to store in local cache |
+| **hash_function** | `md5` | Function to use for calculating hashes, as provided by [hashlib](https://docs.python.org/3/library/hashlib.html){target="_blank" rel="nofollow"} in the standard library. |
+| **response_hook** | `None` | Function to use for processing the stored Idempotent response. This function hook is called when an existing idempotent response is found. See [Manipulating The Idempotent Response](idempotency.md#manipulating-the-idempotent-response) |
### Handling concurrent executions with the same payload
@@ -909,6 +944,36 @@ You can create your own persistent store from scratch by inheriting the `BasePer
For example, the `_put_record` method needs to raise an exception if a non-expired record already exists in the data store with a matching key.
+### Manipulating the Idempotent Response
+
+You can set up a `response_hook` in the `IdempotentConfig` class to manipulate the returned data when an operation is idempotent. The hook function will be called with the current deserialized response object and the Idempotency record.
+
+=== "Using an Idempotent Response Hook"
+
+ ```python hl_lines="18 20 23 32"
+ --8<-- "examples/idempotency/src/working_with_response_hook.py"
+ ```
+
+=== "Sample event"
+
+ ```json
+ --8<-- "examples/idempotency/src/working_with_response_hook_payload.json"
+ ```
+
+???+ info "Info: Using custom de-serialization?"
+
+ The response_hook is called after the custom de-serialization so the payload you process will be the de-serialized version.
+
+#### Being a good citizen
+
+When using response hooks to manipulate returned data from idempotent operations, it's important to follow best practices to avoid introducing complexity or issues. Keep these guidelines in mind:
+
+1. **Response hook works exclusively when operations are idempotent.** The hook will not be called when an operation is not idempotent, or when the idempotent logic fails.
+
+2. **Catch and Handle Exceptions.** Your response hook code should catch and handle any exceptions that may arise from your logic. Unhandled exceptions will cause the Lambda function to fail unexpectedly.
+
+3. **Keep Hook Logic Simple** Response hooks should consist of minimal and straightforward logic for manipulating response data. Avoid complex conditional branching and aim for hooks that are easy to reason about.
+
## Compatibility with other utilities
### Batch
diff --git a/docs/utilities/typing.md b/docs/utilities/typing.md
index 9e2cc5024ab..5678bdf2527 100644
--- a/docs/utilities/typing.md
+++ b/docs/utilities/typing.md
@@ -38,7 +38,7 @@ Using `LambdaContext` typing makes it possible to access information and hints o
=== "working_with_context_function.py"
- ```python hl_lines="6 16 25 26"
+ ```python hl_lines="6 15 22 23"
--8<-- "examples/typing/src/working_with_context_function.py"
```
diff --git a/docs/we_made_this.md b/docs/we_made_this.md
index ea51f65c3d2..efa29478471 100644
--- a/docs/we_made_this.md
+++ b/docs/we_made_this.md
@@ -114,6 +114,14 @@ This article will guide you through personalizing observability by integrating C
[Creating a serverless API using Powertools for AWS Lambda and CDK](https://www.ranthebuilder.cloud/post/boost-app-engagement-with-aws-cloudwatch-metrics-powertools-for-aws){target="_blank" rel="nofollow"}
+### Streaming data with AWS Lambda & Powertools for AWS Lambda
+
+This article will walk you through using Powertools for AWS Lambda to optimize your Lambda function when streaming large files from S3.
+
+> **Author: [Tom Reid](https://www.linkedin.com/in/tom-reid-5a2a3a/){target="_blank" rel="nofollow"}** :material-linkedin:
+
+[Streaming data with AWS Lambda & Powertools for AWS Lambda](https://towardsdev.com/streaming-data-with-aws-lambda-5f0e81f854cd){target="_blank" rel="nofollow"}
+
## Videos
#### Building a resilient input handling with Parser
diff --git a/examples/event_handler_graphql/src/custom_models.py b/examples/event_handler_graphql/src/custom_models.py
index ae2f0180e15..61e03318d14 100644
--- a/examples/event_handler_graphql/src/custom_models.py
+++ b/examples/event_handler_graphql/src/custom_models.py
@@ -26,11 +26,11 @@ class Location(TypedDict, total=False):
class MyCustomModel(AppSyncResolverEvent):
@property
def country_viewer(self) -> str:
- return self.get_header_value(name="cloudfront-viewer-country", default_value="", case_sensitive=False) # type: ignore[return-value] # sentinel typing # noqa: E501
+ return self.get_header_value(name="cloudfront-viewer-country", default_value="", case_sensitive=False)
@property
def api_key(self) -> str:
- return self.get_header_value(name="x-api-key", default_value="", case_sensitive=False) # type: ignore[return-value] # sentinel typing # noqa: E501
+ return self.get_header_value(name="x-api-key", default_value="", case_sensitive=False)
@app.resolver(type_name="Query", field_name="listLocations")
diff --git a/examples/event_handler_rest/sam/swagger_ui_oauth2_template.yaml b/examples/event_handler_rest/sam/swagger_ui_oauth2_template.yaml
new file mode 100644
index 00000000000..629fa02f88b
--- /dev/null
+++ b/examples/event_handler_rest/sam/swagger_ui_oauth2_template.yaml
@@ -0,0 +1,84 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Transform: AWS::Serverless-2016-10-31
+Description: Sample SAM Template for Oauth2 Cognito User Pool + Swagger UI
+
+Globals:
+ Function:
+ Timeout: 5
+ Runtime: python3.12
+ Tracing: Active
+ Environment:
+ Variables:
+ LOG_LEVEL: INFO
+ POWERTOOLS_LOGGER_SAMPLE_RATE: 0.1
+ POWERTOOLS_LOGGER_LOG_EVENT: true
+ POWERTOOLS_SERVICE_NAME: example
+
+Resources:
+ HelloWorldFunction:
+ Type: AWS::Serverless::Function
+ Properties:
+ CodeUri: ../src
+ Handler: swagger_ui_oauth2.lambda_handler
+ Environment:
+ Variables:
+ COGNITO_USER_POOL_DOMAIN: !Ref UserPoolDomain
+ Events:
+ AnyApiEvent:
+ Type: Api
+ Properties:
+ Path: /{proxy+} # Send requests on any path to the lambda function
+ Method: ANY # Send requests using any http method to the lambda function
+
+ CognitoUserPool:
+ Type: AWS::Cognito::UserPool
+ Properties:
+ UserPoolName: PowertoolsUserPool
+ Policies:
+ PasswordPolicy:
+ MinimumLength: 8
+ RequireLowercase: true
+ RequireNumbers: true
+ RequireSymbols: true
+ RequireUppercase: true
+
+ CognitoUserPoolClient:
+ Type: AWS::Cognito::UserPoolClient
+ Properties:
+ ClientName: PowertoolsClient
+ UserPoolId: !Ref CognitoUserPool
+ GenerateSecret: true
+ RefreshTokenValidity: 30
+ ExplicitAuthFlows:
+ - ALLOW_USER_PASSWORD_AUTH
+ - ALLOW_REFRESH_TOKEN_AUTH
+ SupportedIdentityProviders:
+ - COGNITO
+ CallbackURLs:
+ # NOTE: for this to work, your OAuth2 redirect url needs to precisely follow this format:
+ # https://.execute-api..amazonaws.com//swagger?format=oauth2-redirect
+ - !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/${ServerlessRestApi.Stage}/swagger?format=oauth2-redirect"
+ AllowedOAuthFlows:
+ - code
+ AllowedOAuthScopes:
+ - openid
+ - email
+ - profile
+ - aws.cognito.signin.user.admin
+ AllowedOAuthFlowsUserPoolClient: true
+
+ UserPoolDomain:
+ Type: AWS::Cognito::UserPoolDomain
+ Properties:
+ Domain: powertools-swagger-oauth2
+ UserPoolId: !Ref CognitoUserPool
+
+Outputs:
+ HelloWorldApiUrl:
+ Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/${ServerlessRestApi.Stage}/swagger"
+
+ CognitoOAuthClientId:
+ Value: !GetAtt CognitoUserPoolClient.ClientId
+
+ CognitoDomain:
+ Value: !Ref UserPoolDomain
diff --git a/examples/event_handler_rest/src/data_validation_fine_grained_response.py b/examples/event_handler_rest/src/data_validation_fine_grained_response.py
new file mode 100644
index 00000000000..1209c6a1af1
--- /dev/null
+++ b/examples/event_handler_rest/src/data_validation_fine_grained_response.py
@@ -0,0 +1,39 @@
+from http import HTTPStatus
+from typing import Optional
+
+import requests
+from pydantic import BaseModel, Field
+
+from aws_lambda_powertools import Logger, Tracer
+from aws_lambda_powertools.event_handler import APIGatewayRestResolver, Response, content_types
+from aws_lambda_powertools.logging import correlation_paths
+from aws_lambda_powertools.utilities.typing import LambdaContext
+
+tracer = Tracer()
+logger = Logger()
+app = APIGatewayRestResolver(enable_validation=True)
+
+
+class Todo(BaseModel):
+ userId: int
+ id_: Optional[int] = Field(alias="id", default=None)
+ title: str
+ completed: bool
+
+
+@app.get("/todos/")
+@tracer.capture_method
+def get_todo_by_id(todo_id: int) -> Response[Todo]:
+ todo = requests.get(f"https://jsonplaceholder.typicode.com/todos/{todo_id}")
+ todo.raise_for_status()
+ return Response(
+ status_code=HTTPStatus.OK.value,
+ content_type=content_types.APPLICATION_JSON,
+ body=todo.json(),
+ )
+
+
+@logger.inject_lambda_context(correlation_id_path=correlation_paths.API_GATEWAY_REST)
+@tracer.capture_lambda_handler
+def lambda_handler(event: dict, context: LambdaContext) -> dict:
+ return app.resolve(event, context)
diff --git a/examples/event_handler_rest/src/security_schemes_global.py b/examples/event_handler_rest/src/security_schemes_global.py
new file mode 100644
index 00000000000..3a3ef5ce6f4
--- /dev/null
+++ b/examples/event_handler_rest/src/security_schemes_global.py
@@ -0,0 +1,44 @@
+from aws_lambda_powertools import Logger, Tracer
+from aws_lambda_powertools.event_handler import (
+ APIGatewayRestResolver,
+)
+from aws_lambda_powertools.event_handler.openapi.models import (
+ OAuth2,
+ OAuthFlowAuthorizationCode,
+ OAuthFlows,
+)
+
+tracer = Tracer()
+logger = Logger()
+
+app = APIGatewayRestResolver(enable_validation=True)
+
+
+@app.get("/")
+def helloworld() -> dict:
+ return {"hello": "world"}
+
+
+@logger.inject_lambda_context
+@tracer.capture_lambda_handler
+def lambda_handler(event, context):
+ return app.resolve(event, context)
+
+
+if __name__ == "__main__":
+ print(
+ app.get_openapi_json_schema(
+ title="My API",
+ security_schemes={
+ "oauth": OAuth2(
+ flows=OAuthFlows(
+ authorizationCode=OAuthFlowAuthorizationCode(
+ authorizationUrl="https://xxx.amazoncognito.com/oauth2/authorize",
+ tokenUrl="https://xxx.amazoncognito.com/oauth2/token",
+ ),
+ ),
+ ),
+ },
+ security=[{"oauth": ["admin"]}], # (1)!
+ ),
+ )
diff --git a/examples/event_handler_rest/src/security_schemes_per_operation.py b/examples/event_handler_rest/src/security_schemes_per_operation.py
new file mode 100644
index 00000000000..66770a787c7
--- /dev/null
+++ b/examples/event_handler_rest/src/security_schemes_per_operation.py
@@ -0,0 +1,43 @@
+from aws_lambda_powertools import Logger, Tracer
+from aws_lambda_powertools.event_handler import (
+ APIGatewayRestResolver,
+)
+from aws_lambda_powertools.event_handler.openapi.models import (
+ OAuth2,
+ OAuthFlowAuthorizationCode,
+ OAuthFlows,
+)
+
+tracer = Tracer()
+logger = Logger()
+
+app = APIGatewayRestResolver(enable_validation=True)
+
+
+@app.get("/", security=[{"oauth": ["admin"]}]) # (1)!
+def helloworld() -> dict:
+ return {"hello": "world"}
+
+
+@logger.inject_lambda_context
+@tracer.capture_lambda_handler
+def lambda_handler(event, context):
+ return app.resolve(event, context)
+
+
+if __name__ == "__main__":
+ print(
+ app.get_openapi_json_schema(
+ title="My API",
+ security_schemes={
+ "oauth": OAuth2(
+ flows=OAuthFlows(
+ authorizationCode=OAuthFlowAuthorizationCode(
+ authorizationUrl="https://xxx.amazoncognito.com/oauth2/authorize",
+ tokenUrl="https://xxx.amazoncognito.com/oauth2/token",
+ ),
+ ),
+ ),
+ },
+ ),
+ )
diff --git a/examples/event_handler_rest/src/swagger_ui_oauth2.py b/examples/event_handler_rest/src/swagger_ui_oauth2.py
new file mode 100644
index 00000000000..1dc7f173735
--- /dev/null
+++ b/examples/event_handler_rest/src/swagger_ui_oauth2.py
@@ -0,0 +1,53 @@
+import os
+
+from aws_lambda_powertools import Logger, Tracer
+from aws_lambda_powertools.event_handler import (
+ APIGatewayRestResolver,
+ Response,
+)
+from aws_lambda_powertools.event_handler.openapi.models import (
+ OAuth2,
+ OAuthFlowAuthorizationCode,
+ OAuthFlows,
+)
+from aws_lambda_powertools.event_handler.openapi.swagger_ui import OAuth2Config
+
+tracer = Tracer()
+logger = Logger()
+
+region = os.getenv("AWS_REGION")
+cognito_domain = os.getenv("COGNITO_USER_POOL_DOMAIN")
+
+app = APIGatewayRestResolver(enable_validation=True)
+app.enable_swagger(
+ # NOTE: for this to work, your OAuth2 redirect url needs to precisely follow this format:
+ # https://.execute-api..amazonaws.com//swagger?format=oauth2-redirect
+ oauth2_config=OAuth2Config(app_name="OAuth2 Test"),
+ security_schemes={
+ "oauth": OAuth2(
+ flows=OAuthFlows(
+ authorizationCode=OAuthFlowAuthorizationCode(
+ authorizationUrl=f"https://{cognito_domain}.auth.{region}.amazoncognito.com/oauth2/authorize",
+ tokenUrl=f"https://{cognito_domain}.auth.{region}.amazoncognito.com/oauth2/token",
+ ),
+ ),
+ ),
+ },
+ security=[{"oauth": []}],
+)
+
+
+@app.get("/")
+def helloworld() -> Response[dict]:
+ logger.info("Hello, World!")
+ return Response(
+ status_code=200,
+ body={"message": "Hello, World"},
+ content_type="application/json",
+ )
+
+
+@logger.inject_lambda_context(log_event=True)
+@tracer.capture_lambda_handler
+def lambda_handler(event, context):
+ return app.resolve(event, context)
diff --git a/examples/event_handler_rest/src/swagger_with_oauth2.py b/examples/event_handler_rest/src/swagger_with_oauth2.py
new file mode 100644
index 00000000000..4a2a86cdd40
--- /dev/null
+++ b/examples/event_handler_rest/src/swagger_with_oauth2.py
@@ -0,0 +1,45 @@
+from aws_lambda_powertools import Logger, Tracer
+from aws_lambda_powertools.event_handler import (
+ APIGatewayRestResolver,
+)
+from aws_lambda_powertools.event_handler.openapi.models import (
+ OAuth2,
+ OAuthFlowAuthorizationCode,
+ OAuthFlows,
+)
+from aws_lambda_powertools.event_handler.openapi.swagger_ui import OAuth2Config
+
+tracer = Tracer()
+logger = Logger()
+
+oauth2 = OAuth2Config(
+ client_id="xxxxxxxxxxxxxxxxxxxxxxxxxxxx",
+ app_name="OAuth2 app",
+)
+
+app = APIGatewayRestResolver(enable_validation=True)
+app.enable_swagger(
+ oauth2_config=oauth2,
+ security_schemes={
+ "oauth": OAuth2(
+ flows=OAuthFlows(
+ authorizationCode=OAuthFlowAuthorizationCode(
+ authorizationUrl="https://xxx.amazoncognito.com/oauth2/authorize",
+ tokenUrl="https://xxx.amazoncognito.com/oauth2/token",
+ ),
+ ),
+ ),
+ },
+ security=[{"oauth": []}],
+)
+
+
+@app.get("/")
+def hello() -> str:
+ return "world"
+
+
+@logger.inject_lambda_context
+@tracer.capture_lambda_handler
+def lambda_handler(event, context):
+ return app.resolve(event, context)
diff --git a/examples/idempotency/src/working_with_response_hook.py b/examples/idempotency/src/working_with_response_hook.py
new file mode 100644
index 00000000000..725a56f32ba
--- /dev/null
+++ b/examples/idempotency/src/working_with_response_hook.py
@@ -0,0 +1,56 @@
+import datetime
+import uuid
+from typing import Dict
+
+from aws_lambda_powertools import Logger
+from aws_lambda_powertools.utilities.idempotency import (
+ DynamoDBPersistenceLayer,
+ IdempotencyConfig,
+ idempotent_function,
+)
+from aws_lambda_powertools.utilities.idempotency.persistence.base import (
+ DataRecord,
+)
+from aws_lambda_powertools.utilities.typing import LambdaContext
+
+logger = Logger()
+
+
+def my_response_hook(response: Dict, idempotent_data: DataRecord) -> Dict:
+ # Return inserted Header data into the Idempotent Response
+ response["x-idempotent-key"] = idempotent_data.idempotency_key
+
+ # expiry_timestamp could be None so include if set
+ expiry_timestamp = idempotent_data.expiry_timestamp
+ if expiry_timestamp:
+ expiry_time = datetime.datetime.fromtimestamp(int(expiry_timestamp))
+ response["x-idempotent-expiration"] = expiry_time.isoformat()
+
+ # Must return the response here
+ return response
+
+
+dynamodb = DynamoDBPersistenceLayer(table_name="IdempotencyTable")
+config = IdempotencyConfig(response_hook=my_response_hook)
+
+
+@idempotent_function(data_keyword_argument="order", config=config, persistence_store=dynamodb)
+def process_order(order: dict) -> dict:
+ # create the order_id
+ order_id = str(uuid.uuid4())
+
+ # create your logic to save the order
+ # append the order_id created
+ order["order_id"] = order_id
+
+ # return the order
+ return {"order": order}
+
+
+def lambda_handler(event: dict, context: LambdaContext):
+ config.register_lambda_context(context) # see Lambda timeouts section
+ try:
+ logger.info(f"Processing order id {event.get('order_id')}")
+ return process_order(order=event.get("order"))
+ except Exception as err:
+ return {"status_code": 400, "error": f"Error processing {str(err)}"}
diff --git a/examples/idempotency/src/working_with_response_hook_payload.json b/examples/idempotency/src/working_with_response_hook_payload.json
new file mode 100644
index 00000000000..85fdd958d59
--- /dev/null
+++ b/examples/idempotency/src/working_with_response_hook_payload.json
@@ -0,0 +1,8 @@
+{
+ "order" : {
+ "user_id": "xyz",
+ "product_id": "123456789",
+ "quantity": 2,
+ "value": 30
+ }
+}
diff --git a/examples/logger/sam/template.yaml b/examples/logger/sam/template.yaml
index 511b6cd47b1..6835e853733 100644
--- a/examples/logger/sam/template.yaml
+++ b/examples/logger/sam/template.yaml
@@ -14,7 +14,7 @@ Globals:
Layers:
# Find the latest Layer version in the official documentation
# https://docs.powertools.aws.dev/lambda/python/latest/#lambda-layer
- - !Sub arn:aws:lambda:${AWS::Region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:67
+ - !Sub arn:aws:lambda:${AWS::Region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:68
Resources:
LoggerLambdaHandlerExample:
diff --git a/examples/logger/src/bring_your_own_formatter_from_scratch.py b/examples/logger/src/bring_your_own_formatter_from_scratch.py
index b7e7761b562..425f00b0d28 100644
--- a/examples/logger/src/bring_your_own_formatter_from_scratch.py
+++ b/examples/logger/src/bring_your_own_formatter_from_scratch.py
@@ -1,6 +1,6 @@
import json
import logging
-from typing import Iterable, List, Optional
+from typing import Any, Dict, Iterable, List, Optional
from aws_lambda_powertools import Logger
from aws_lambda_powertools.logging.formatter import BasePowertoolsFormatter
@@ -16,6 +16,9 @@ def append_keys(self, **additional_keys):
# also used by `inject_lambda_context` decorator
self.log_format.update(additional_keys)
+ def current_keys(self) -> Dict[str, Any]:
+ return self.log_format
+
def remove_keys(self, keys: Iterable[str]):
for key in keys:
self.log_format.pop(key, None)
diff --git a/examples/logger/src/get_current_keys.py b/examples/logger/src/get_current_keys.py
new file mode 100644
index 00000000000..c0ae49165b7
--- /dev/null
+++ b/examples/logger/src/get_current_keys.py
@@ -0,0 +1,14 @@
+from aws_lambda_powertools import Logger
+from aws_lambda_powertools.utilities.typing import LambdaContext
+
+logger = Logger()
+
+
+@logger.inject_lambda_context
+def lambda_handler(event: dict, context: LambdaContext) -> str:
+ logger.info("Collecting payment")
+
+ if "order" not in logger.get_current_keys():
+ logger.append_keys(order=event.get("order"))
+
+ return "hello world"
diff --git a/examples/metrics/sam/template.yaml b/examples/metrics/sam/template.yaml
index 355fb7ea9f1..f6774c998dc 100644
--- a/examples/metrics/sam/template.yaml
+++ b/examples/metrics/sam/template.yaml
@@ -15,7 +15,7 @@ Globals:
Layers:
# Find the latest Layer version in the official documentation
# https://docs.powertools.aws.dev/lambda/python/latest/#lambda-layer
- - !Sub arn:aws:lambda:${AWS::Region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:67
+ - !Sub arn:aws:lambda:${AWS::Region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:68
Resources:
CaptureLambdaHandlerExample:
diff --git a/examples/tracer/sam/template.yaml b/examples/tracer/sam/template.yaml
index f85babd449b..1e463e00f20 100644
--- a/examples/tracer/sam/template.yaml
+++ b/examples/tracer/sam/template.yaml
@@ -13,7 +13,7 @@ Globals:
Layers:
# Find the latest Layer version in the official documentation
# https://docs.powertools.aws.dev/lambda/python/latest/#lambda-layer
- - !Sub arn:aws:lambda:${AWS::Region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:67
+ - !Sub arn:aws:lambda:${AWS::Region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:68
Resources:
CaptureLambdaHandlerExample:
diff --git a/layer/scripts/layer-balancer/go.mod b/layer/scripts/layer-balancer/go.mod
index 8b226402756..1a32cf3ee24 100644
--- a/layer/scripts/layer-balancer/go.mod
+++ b/layer/scripts/layer-balancer/go.mod
@@ -3,25 +3,25 @@ module layerbalancer
go 1.18
require (
- github.com/aws/aws-sdk-go-v2 v1.26.0
- github.com/aws/aws-sdk-go-v2/config v1.27.9
- github.com/aws/aws-sdk-go-v2/service/lambda v1.53.3
+ github.com/aws/aws-sdk-go-v2 v1.26.1
+ github.com/aws/aws-sdk-go-v2/config v1.27.11
+ github.com/aws/aws-sdk-go-v2/service/lambda v1.54.0
golang.org/x/exp v0.0.0-20230321023759-10a507213a29
- golang.org/x/sync v0.6.0
+ golang.org/x/sync v0.7.0
)
require (
- github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.1 // indirect
- github.com/aws/aws-sdk-go-v2/credentials v1.17.9 // indirect
- github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.0 // indirect
- github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.4 // indirect
- github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.4 // indirect
+ github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2 // indirect
+ github.com/aws/aws-sdk-go-v2/credentials v1.17.11 // indirect
+ github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1 // indirect
+ github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 // indirect
+ github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect
- github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.1 // indirect
- github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.6 // indirect
- github.com/aws/aws-sdk-go-v2/service/sso v1.20.3 // indirect
- github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.3 // indirect
- github.com/aws/aws-sdk-go-v2/service/sts v1.28.5 // indirect
- github.com/aws/smithy-go v1.20.1 // indirect
+ github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 // indirect
+ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7 // indirect
+ github.com/aws/aws-sdk-go-v2/service/sso v1.20.5 // indirect
+ github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.4 // indirect
+ github.com/aws/aws-sdk-go-v2/service/sts v1.28.6 // indirect
+ github.com/aws/smithy-go v1.20.2 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
)
diff --git a/layer/scripts/layer-balancer/go.sum b/layer/scripts/layer-balancer/go.sum
index edf71430f73..2655f1ecfc2 100644
--- a/layer/scripts/layer-balancer/go.sum
+++ b/layer/scripts/layer-balancer/go.sum
@@ -1,33 +1,33 @@
-github.com/aws/aws-sdk-go-v2 v1.26.0 h1:/Ce4OCiM3EkpW7Y+xUnfAFpchU78K7/Ug01sZni9PgA=
-github.com/aws/aws-sdk-go-v2 v1.26.0/go.mod h1:35hUlJVYd+M++iLI3ALmVwMOyRYMmRqUXpTtRGW+K9I=
-github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.1 h1:gTK2uhtAPtFcdRRJilZPx8uJLL2J85xK11nKtWL0wfU=
-github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.1/go.mod h1:sxpLb+nZk7tIfCWChfd+h4QwHNUR57d8hA1cleTkjJo=
-github.com/aws/aws-sdk-go-v2/config v1.27.9 h1:gRx/NwpNEFSk+yQlgmk1bmxxvQ5TyJ76CWXs9XScTqg=
-github.com/aws/aws-sdk-go-v2/config v1.27.9/go.mod h1:dK1FQfpwpql83kbD873E9vz4FyAxuJtR22wzoXn3qq0=
-github.com/aws/aws-sdk-go-v2/credentials v1.17.9 h1:N8s0/7yW+h8qR8WaRlPQeJ6czVMNQVNtNdUqf6cItao=
-github.com/aws/aws-sdk-go-v2/credentials v1.17.9/go.mod h1:446YhIdmSV0Jf/SLafGZalQo+xr2iw7/fzXGDPTU1yQ=
-github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.0 h1:af5YzcLf80tv4Em4jWVD75lpnOHSBkPUZxZfGkrI3HI=
-github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.0/go.mod h1:nQ3how7DMnFMWiU1SpECohgC82fpn4cKZ875NDMmwtA=
-github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.4 h1:0ScVK/4qZ8CIW0k8jOeFVsyS/sAiXpYxRBLolMkuLQM=
-github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.4/go.mod h1:84KyjNZdHC6QZW08nfHI6yZgPd+qRgaWcYsyLUo3QY8=
-github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.4 h1:sHmMWWX5E7guWEFQ9SVo6A3S4xpPrWnd77a6y4WM6PU=
-github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.4/go.mod h1:WjpDrhWisWOIoS9n3nk67A3Ll1vfULJ9Kq6h29HTD48=
+github.com/aws/aws-sdk-go-v2 v1.26.1 h1:5554eUqIYVWpU0YmeeYZ0wU64H2VLBs8TlhRB2L+EkA=
+github.com/aws/aws-sdk-go-v2 v1.26.1/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM=
+github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2 h1:x6xsQXGSmW6frevwDA+vi/wqhp1ct18mVXYN08/93to=
+github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2/go.mod h1:lPprDr1e6cJdyYeGXnRaJoP4Md+cDBvi2eOj00BlGmg=
+github.com/aws/aws-sdk-go-v2/config v1.27.11 h1:f47rANd2LQEYHda2ddSCKYId18/8BhSRM4BULGmfgNA=
+github.com/aws/aws-sdk-go-v2/config v1.27.11/go.mod h1:SMsV78RIOYdve1vf36z8LmnszlRWkwMQtomCAI0/mIE=
+github.com/aws/aws-sdk-go-v2/credentials v1.17.11 h1:YuIB1dJNf1Re822rriUOTxopaHHvIq0l/pX3fwO+Tzs=
+github.com/aws/aws-sdk-go-v2/credentials v1.17.11/go.mod h1:AQtFPsDH9bI2O+71anW6EKL+NcD7LG3dpKGMV4SShgo=
+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1 h1:FVJ0r5XTHSmIHJV6KuDmdYhEpvlHpiSd38RQWhut5J4=
+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1/go.mod h1:zusuAeqezXzAB24LGuzuekqMAEgWkVYukBec3kr3jUg=
+github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 h1:aw39xVGeRWlWx9EzGVnhOR4yOjQDHPQ6o6NmBlscyQg=
+github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5/go.mod h1:FSaRudD0dXiMPK2UjknVwwTYyZMRsHv3TtkabsZih5I=
+github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 h1:PG1F3OD1szkuQPzDw3CIQsRIrtTlUC3lP84taWzHlq0=
+github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5/go.mod h1:jU1li6RFryMz+so64PpKtudI+QzbKoIEivqdf6LNpOc=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY=
-github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.1 h1:EyBZibRTVAs6ECHZOw5/wlylS9OcTzwyjeQMudmREjE=
-github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.1/go.mod h1:JKpmtYhhPs7D97NL/ltqz7yCkERFW5dOlHyVl66ZYF8=
-github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.6 h1:b+E7zIUHMmcB4Dckjpkapoy47W6C9QBv/zoUP+Hn8Kc=
-github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.6/go.mod h1:S2fNV0rxrP78NhPbCZeQgY8H9jdDMeGtwcfZIRxzBqU=
-github.com/aws/aws-sdk-go-v2/service/lambda v1.53.3 h1:KsKBuL+bIKhY7SMk+MXSBAj8PLHsTqlU2d0px98azyI=
-github.com/aws/aws-sdk-go-v2/service/lambda v1.53.3/go.mod h1:trTURvQC8AJ41JYhFpVrZKY5tfzGgVUcSijVgfmgl8w=
-github.com/aws/aws-sdk-go-v2/service/sso v1.20.3 h1:mnbuWHOcM70/OFUlZZ5rcdfA8PflGXXiefU/O+1S3+8=
-github.com/aws/aws-sdk-go-v2/service/sso v1.20.3/go.mod h1:5HFu51Elk+4oRBZVxmHrSds5jFXmFj8C3w7DVF2gnrs=
-github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.3 h1:uLq0BKatTmDzWa/Nu4WO0M1AaQDaPpwTKAeByEc6WFM=
-github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.3/go.mod h1:b+qdhjnxj8GSR6t5YfphOffeoQSQ1KmpoVVuBn+PWxs=
-github.com/aws/aws-sdk-go-v2/service/sts v1.28.5 h1:J/PpTf/hllOjx8Xu9DMflff3FajfLxqM5+tepvVXmxg=
-github.com/aws/aws-sdk-go-v2/service/sts v1.28.5/go.mod h1:0ih0Z83YDH/QeQ6Ori2yGE2XvWYv/Xm+cZc01LC6oK0=
-github.com/aws/smithy-go v1.20.1 h1:4SZlSlMr36UEqC7XOyRVb27XMeZubNcBNN+9IgEPIQw=
-github.com/aws/smithy-go v1.20.1/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
+github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 h1:Ji0DY1xUsUr3I8cHps0G+XM3WWU16lP6yG8qu1GAZAs=
+github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2/go.mod h1:5CsjAbs3NlGQyZNFACh+zztPDI7fU6eW9QsxjfnuBKg=
+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7 h1:ogRAwT1/gxJBcSWDMZlgyFUM962F51A5CRhDLbxLdmo=
+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7/go.mod h1:YCsIZhXfRPLFFCl5xxY+1T9RKzOKjCut+28JSX2DnAk=
+github.com/aws/aws-sdk-go-v2/service/lambda v1.54.0 h1:gazALVrZ7RIG6gJXut3c7NKtPgs9eQ8BFCA9uoliayk=
+github.com/aws/aws-sdk-go-v2/service/lambda v1.54.0/go.mod h1:rFAo+jemFgeqYzDbbCbz2QWQs1Fnk1meTUK9fWkED9M=
+github.com/aws/aws-sdk-go-v2/service/sso v1.20.5 h1:vN8hEbpRnL7+Hopy9dzmRle1xmDc7o8tmY0klsr175w=
+github.com/aws/aws-sdk-go-v2/service/sso v1.20.5/go.mod h1:qGzynb/msuZIE8I75DVRCUXw3o3ZyBmUvMwQ2t/BrGM=
+github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.4 h1:Jux+gDDyi1Lruk+KHF91tK2KCuY61kzoCpvtvJJBtOE=
+github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.4/go.mod h1:mUYPBhaF2lGiukDEjJX2BLRRKTmoUSitGDUgM4tRxak=
+github.com/aws/aws-sdk-go-v2/service/sts v1.28.6 h1:cwIxeBttqPN3qkaAjcEcsh8NYr8n2HZPkcKgPAi1phU=
+github.com/aws/aws-sdk-go-v2/service/sts v1.28.6/go.mod h1:FZf1/nKNEkHdGGJP/cI2MoIMquumuRK6ol3QQJNDxmw=
+github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q=
+github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
@@ -39,8 +39,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug=
golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
-golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
-golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
+golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
diff --git a/package-lock.json b/package-lock.json
index 6b5efa81825..4e56ba532aa 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,13 +11,13 @@
"package-lock.json": "^1.0.0"
},
"devDependencies": {
- "aws-cdk": "^2.133.0"
+ "aws-cdk": "^2.137.0"
}
},
"node_modules/aws-cdk": {
- "version": "2.133.0",
- "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.133.0.tgz",
- "integrity": "sha512-EwH8VgQQ8ODeMwjE3p+WhbcbWNkCbvuJJl+Py9IB5znGf7GwLcEmOu4YWBsBGPVu41SXbSAf36twMBrJytCFZA==",
+ "version": "2.137.0",
+ "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.137.0.tgz",
+ "integrity": "sha512-3pf3SVDwNZvo3EfhO3yl1B+KbRHz7T4UmPifUEKfOwk7ABAFLRSNddZuUlF560XSBTFLkrZoeBDa0/MLJT6F4g==",
"dev": true,
"bin": {
"cdk": "bin/cdk"
diff --git a/package.json b/package.json
index 6521bc16769..c80f6bab4ba 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
"name": "aws-lambda-powertools-python-e2e",
"version": "1.0.0",
"devDependencies": {
- "aws-cdk": "^2.133.0"
+ "aws-cdk": "^2.137.0"
},
"dependencies": {
"package-lock.json": "^1.0.0"
diff --git a/poetry.lock b/poetry.lock
index 5b2d55ddf74..db3ebd33227 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -158,31 +158,31 @@ typeguard = ">=2.13.3,<2.14.0"
[[package]]
name = "aws-cdk-aws-lambda-python-alpha"
-version = "2.133.0a0"
+version = "2.137.0a0"
description = "The CDK Construct Library for AWS Lambda in Python"
optional = false
python-versions = "~=3.8"
files = [
- {file = "aws-cdk.aws-lambda-python-alpha-2.133.0a0.tar.gz", hash = "sha256:1fb72aab5ce0183a0f94f30973e5c4d3c3cb7d191f334165b99ae4e1a7a09aa0"},
- {file = "aws_cdk.aws_lambda_python_alpha-2.133.0a0-py3-none-any.whl", hash = "sha256:34e843875b4ce1f794b059545f0db3780011f04a1d614eae04f5507a8b4479a7"},
+ {file = "aws-cdk.aws-lambda-python-alpha-2.137.0a0.tar.gz", hash = "sha256:6168c6d0394d4c908da921fa8e3822a7a1dda8a7b27c28b597466be598385d05"},
+ {file = "aws_cdk.aws_lambda_python_alpha-2.137.0a0-py3-none-any.whl", hash = "sha256:4e58fb736cee3a44ae735edd5b90cadebc6aacb63a58f2baff44d27d0d5a803d"},
]
[package.dependencies]
-aws-cdk-lib = ">=2.133.0,<3.0.0"
+aws-cdk-lib = ">=2.137.0,<3.0.0"
constructs = ">=10.0.0,<11.0.0"
-jsii = ">=1.94.0,<2.0.0"
+jsii = ">=1.96.0,<2.0.0"
publication = ">=0.0.3"
typeguard = ">=2.13.3,<2.14.0"
[[package]]
name = "aws-cdk-lib"
-version = "2.133.0"
+version = "2.137.0"
description = "Version 2 of the AWS Cloud Development Kit library"
optional = false
python-versions = "~=3.8"
files = [
- {file = "aws-cdk-lib-2.133.0.tar.gz", hash = "sha256:ef09b237f6840bdaae5081b35bbc9566a9bc38aaf6bd736441db9ee06298312e"},
- {file = "aws_cdk_lib-2.133.0-py3-none-any.whl", hash = "sha256:59c534e54e2b68ef514b41c5799f65094a0e580a6e0b5f0a275409d34d4bafc7"},
+ {file = "aws-cdk-lib-2.137.0.tar.gz", hash = "sha256:ecdd3aaf6a25cefecb6dd16342013d792e9315798854eb1251a442331e27aef9"},
+ {file = "aws_cdk_lib-2.137.0-py3-none-any.whl", hash = "sha256:0cd861c315e4711dbffbc4ee3afe3cdc18c5154d4ab078a355e92011a6416c59"},
]
[package.dependencies]
@@ -190,7 +190,7 @@ files = [
"aws-cdk.asset-kubectl-v20" = ">=2.1.2,<3.0.0"
"aws-cdk.asset-node-proxy-agent-v6" = ">=2.0.1,<3.0.0"
constructs = ">=10.0.0,<11.0.0"
-jsii = ">=1.94.0,<2.0.0"
+jsii = ">=1.96.0,<2.0.0"
publication = ">=0.0.3"
typeguard = ">=2.13.3,<2.14.0"
@@ -227,13 +227,13 @@ requests = ">=0.14.0"
[[package]]
name = "aws-sam-translator"
-version = "1.86.0"
+version = "1.87.0"
description = "AWS SAM Translator is a library that transform SAM templates into AWS CloudFormation templates"
optional = false
-python-versions = ">=3.8, <=4.0, !=4.0"
+python-versions = "!=4.0,<=4.0,>=3.8"
files = [
- {file = "aws-sam-translator-1.86.0.tar.gz", hash = "sha256:a748dcd7886024cb7586abbbdbabe8c787c44c6547bb6602879d7bb8a6934d05"},
- {file = "aws_sam_translator-1.86.0-py3-none-any.whl", hash = "sha256:97a44e5ac8b0d141c31f4ed35c57aa94429a0e6cef7fe989831c9a1c40455473"},
+ {file = "aws-sam-translator-1.87.0.tar.gz", hash = "sha256:80f4fb6d53774634b6ea84af5fdfa9ad94a46945bc4ad4ef11c8008540dfa7f8"},
+ {file = "aws_sam_translator-1.87.0-py3-none-any.whl", hash = "sha256:40cef8980d656107406dafe30aef34b67be33929d2b9492e93f8e9ce07ece1c1"},
]
[package.dependencies]
@@ -303,33 +303,33 @@ yaml = ["PyYAML"]
[[package]]
name = "black"
-version = "24.3.0"
+version = "24.4.0"
description = "The uncompromising code formatter."
optional = false
python-versions = ">=3.8"
files = [
- {file = "black-24.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7d5e026f8da0322b5662fa7a8e752b3fa2dac1c1cbc213c3d7ff9bdd0ab12395"},
- {file = "black-24.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9f50ea1132e2189d8dff0115ab75b65590a3e97de1e143795adb4ce317934995"},
- {file = "black-24.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2af80566f43c85f5797365077fb64a393861a3730bd110971ab7a0c94e873e7"},
- {file = "black-24.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:4be5bb28e090456adfc1255e03967fb67ca846a03be7aadf6249096100ee32d0"},
- {file = "black-24.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4f1373a7808a8f135b774039f61d59e4be7eb56b2513d3d2f02a8b9365b8a8a9"},
- {file = "black-24.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:aadf7a02d947936ee418777e0247ea114f78aff0d0959461057cae8a04f20597"},
- {file = "black-24.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c02e4ea2ae09d16314d30912a58ada9a5c4fdfedf9512d23326128ac08ac3d"},
- {file = "black-24.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:bf21b7b230718a5f08bd32d5e4f1db7fc8788345c8aea1d155fc17852b3410f5"},
- {file = "black-24.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:2818cf72dfd5d289e48f37ccfa08b460bf469e67fb7c4abb07edc2e9f16fb63f"},
- {file = "black-24.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4acf672def7eb1725f41f38bf6bf425c8237248bb0804faa3965c036f7672d11"},
- {file = "black-24.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7ed6668cbbfcd231fa0dc1b137d3e40c04c7f786e626b405c62bcd5db5857e4"},
- {file = "black-24.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:56f52cfbd3dabe2798d76dbdd299faa046a901041faf2cf33288bc4e6dae57b5"},
- {file = "black-24.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:79dcf34b33e38ed1b17434693763301d7ccbd1c5860674a8f871bd15139e7837"},
- {file = "black-24.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e19cb1c6365fd6dc38a6eae2dcb691d7d83935c10215aef8e6c38edee3f77abd"},
- {file = "black-24.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65b76c275e4c1c5ce6e9870911384bff5ca31ab63d19c76811cb1fb162678213"},
- {file = "black-24.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:b5991d523eee14756f3c8d5df5231550ae8993e2286b8014e2fdea7156ed0959"},
- {file = "black-24.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c45f8dff244b3c431b36e3224b6be4a127c6aca780853574c00faf99258041eb"},
- {file = "black-24.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6905238a754ceb7788a73f02b45637d820b2f5478b20fec82ea865e4f5d4d9f7"},
- {file = "black-24.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7de8d330763c66663661a1ffd432274a2f92f07feeddd89ffd085b5744f85e7"},
- {file = "black-24.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:7bb041dca0d784697af4646d3b62ba4a6b028276ae878e53f6b4f74ddd6db99f"},
- {file = "black-24.3.0-py3-none-any.whl", hash = "sha256:41622020d7120e01d377f74249e677039d20e6344ff5851de8a10f11f513bf93"},
- {file = "black-24.3.0.tar.gz", hash = "sha256:a0c9c4a0771afc6919578cec71ce82a3e31e054904e7197deacbc9382671c41f"},
+ {file = "black-24.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6ad001a9ddd9b8dfd1b434d566be39b1cd502802c8d38bbb1ba612afda2ef436"},
+ {file = "black-24.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e3a3a092b8b756c643fe45f4624dbd5a389f770a4ac294cf4d0fce6af86addaf"},
+ {file = "black-24.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dae79397f367ac8d7adb6c779813328f6d690943f64b32983e896bcccd18cbad"},
+ {file = "black-24.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:71d998b73c957444fb7c52096c3843875f4b6b47a54972598741fe9a7f737fcb"},
+ {file = "black-24.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8e5537f456a22cf5cfcb2707803431d2feeb82ab3748ade280d6ccd0b40ed2e8"},
+ {file = "black-24.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:64e60a7edd71fd542a10a9643bf369bfd2644de95ec71e86790b063aa02ff745"},
+ {file = "black-24.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5cd5b4f76056cecce3e69b0d4c228326d2595f506797f40b9233424e2524c070"},
+ {file = "black-24.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:64578cf99b6b46a6301bc28bdb89f9d6f9b592b1c5837818a177c98525dbe397"},
+ {file = "black-24.4.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f95cece33329dc4aa3b0e1a771c41075812e46cf3d6e3f1dfe3d91ff09826ed2"},
+ {file = "black-24.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4396ca365a4310beef84d446ca5016f671b10f07abdba3e4e4304218d2c71d33"},
+ {file = "black-24.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44d99dfdf37a2a00a6f7a8dcbd19edf361d056ee51093b2445de7ca09adac965"},
+ {file = "black-24.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:21f9407063ec71c5580b8ad975653c66508d6a9f57bd008bb8691d273705adcd"},
+ {file = "black-24.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:652e55bb722ca026299eb74e53880ee2315b181dfdd44dca98e43448620ddec1"},
+ {file = "black-24.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7f2966b9b2b3b7104fca9d75b2ee856fe3fdd7ed9e47c753a4bb1a675f2caab8"},
+ {file = "black-24.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bb9ca06e556a09f7f7177bc7cb604e5ed2d2df1e9119e4f7d2f1f7071c32e5d"},
+ {file = "black-24.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:d4e71cdebdc8efeb6deaf5f2deb28325f8614d48426bed118ecc2dcaefb9ebf3"},
+ {file = "black-24.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6644f97a7ef6f401a150cca551a1ff97e03c25d8519ee0bbc9b0058772882665"},
+ {file = "black-24.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:75a2d0b4f5eb81f7eebc31f788f9830a6ce10a68c91fbe0fade34fff7a2836e6"},
+ {file = "black-24.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb949f56a63c5e134dfdca12091e98ffb5fd446293ebae123d10fc1abad00b9e"},
+ {file = "black-24.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:7852b05d02b5b9a8c893ab95863ef8986e4dda29af80bbbda94d7aee1abf8702"},
+ {file = "black-24.4.0-py3-none-any.whl", hash = "sha256:74eb9b5420e26b42c00a3ff470dc0cd144b80a766128b1771d07643165e08d0e"},
+ {file = "black-24.4.0.tar.gz", hash = "sha256:f07b69fda20578367eaebbd670ff8fc653ab181e1ff95d84497f9fa20e7d0641"},
]
[package.dependencies]
@@ -429,38 +429,38 @@ ujson = ["ujson (>=5.7.0)"]
[[package]]
name = "cdk-nag"
-version = "2.28.72"
+version = "2.28.92"
description = "Check CDK v2 applications for best practices using a combination on available rule packs."
optional = false
python-versions = "~=3.8"
files = [
- {file = "cdk-nag-2.28.72.tar.gz", hash = "sha256:f0f265c399dbcedd3293264b19707b91941318a085435c61f35e946c57024f8a"},
- {file = "cdk_nag-2.28.72-py3-none-any.whl", hash = "sha256:0595efe44b3c0b144d3f38ac9b63be70cbb1490fda2c031bdda8af4535c63899"},
+ {file = "cdk-nag-2.28.92.tar.gz", hash = "sha256:cdfa6358846226083c0567f37855e4baf7dc6b56235e06a9cf1059f6a12900d7"},
+ {file = "cdk_nag-2.28.92-py3-none-any.whl", hash = "sha256:faa59880e3f5b37962a915906e9ba04984c9883498c43f2488b17c90b758286b"},
]
[package.dependencies]
aws-cdk-lib = ">=2.116.0,<3.0.0"
constructs = ">=10.0.5,<11.0.0"
-jsii = ">=1.96.0,<2.0.0"
+jsii = ">=1.97.0,<2.0.0"
publication = ">=0.0.3"
typeguard = ">=2.13.3,<2.14.0"
[[package]]
name = "cdklabs-generative-ai-cdk-constructs"
-version = "0.1.104"
+version = "0.1.119"
description = "AWS Generative AI CDK Constructs is a library for well-architected generative AI patterns."
optional = false
python-versions = "~=3.8"
files = [
- {file = "cdklabs.generative-ai-cdk-constructs-0.1.104.tar.gz", hash = "sha256:69f0b6e1fcf53f8f509d2ce2d60a18bd6b07114b07c519ab0189d3f1902b3026"},
- {file = "cdklabs.generative_ai_cdk_constructs-0.1.104-py3-none-any.whl", hash = "sha256:6fb3862d3050be498ba3a03b0d65e1ee0df0b91b7150fc52f26d4bdde1f93b56"},
+ {file = "cdklabs.generative-ai-cdk-constructs-0.1.119.tar.gz", hash = "sha256:f6f460f814802c76afff1f329794732d5c895771c51438c8bfde54b65a144eb6"},
+ {file = "cdklabs.generative_ai_cdk_constructs-0.1.119-py3-none-any.whl", hash = "sha256:740fdf88e532311f16b27c81b0f9d8cd0b878d69337e3aa2614f7f160fbd01a1"},
]
[package.dependencies]
aws-cdk-lib = ">=2.122.0,<3.0.0"
-cdk-nag = ">=2.28.72,<3.0.0"
+cdk-nag = ">=2.28.91,<3.0.0"
constructs = ">=10.3.0,<11.0.0"
-jsii = ">=1.96.0,<2.0.0"
+jsii = ">=1.97.0,<2.0.0"
publication = ">=0.0.3"
typeguard = ">=2.13.3,<2.14.0"
@@ -541,17 +541,17 @@ pycparser = "*"
[[package]]
name = "cfn-lint"
-version = "0.86.1"
+version = "0.86.3"
description = "Checks CloudFormation templates for practices and behaviour that could potentially be improved"
optional = false
python-versions = "!=4.0,<=4.0,>=3.8"
files = [
- {file = "cfn-lint-0.86.1.tar.gz", hash = "sha256:ed41e596d807fea2de74dbbfc0cb8b48f8787572c50e3b58cce05382a5af3a64"},
- {file = "cfn_lint-0.86.1-py3-none-any.whl", hash = "sha256:e599b23f1e3745c11585008fc1d186665dded2fb0ded3cc05e30e7d7b0830082"},
+ {file = "cfn_lint-0.86.3-py3-none-any.whl", hash = "sha256:aea8789d2dccf7cc7eafd659aae52c40701372e93b1ad53aeed51c82fb145d96"},
+ {file = "cfn_lint-0.86.3.tar.gz", hash = "sha256:044c929557cd45cab3005055fa0e7e70997d8dcfd2217e75f9ecfd6b328ac8eb"},
]
[package.dependencies]
-aws-sam-translator = ">=1.86.0"
+aws-sam-translator = ">=1.87.0"
jschema-to-python = ">=1.2.3,<1.3.0"
jsonpatch = "*"
jsonschema = ">=3.0,<5"
@@ -850,13 +850,13 @@ requests = ">=2.6.0"
[[package]]
name = "datadog-lambda"
-version = "5.91.0"
+version = "5.92.0"
description = "The Datadog AWS Lambda Library"
optional = false
-python-versions = ">=3.8.0,<4"
+python-versions = "<4,>=3.8.0"
files = [
- {file = "datadog_lambda-5.91.0-py3-none-any.whl", hash = "sha256:4d7d2235f306e64ccd9f02dc54b4412be1914e643f5b92113f8fa9cfe2d176d3"},
- {file = "datadog_lambda-5.91.0.tar.gz", hash = "sha256:cdb35ce0a730233a886d37163aeb58c736b6d6e152e5236e2eca19cca5a187f4"},
+ {file = "datadog_lambda-5.92.0-py3-none-any.whl", hash = "sha256:1272781932d3f93dbf822872af09f13043a242b0f0aad01e54593e1cb762ff5a"},
+ {file = "datadog_lambda-5.92.0.tar.gz", hash = "sha256:24307abb921d2a27c11e570ef6846c9eeed904b477abb574f0bc461764efe434"},
]
[package.dependencies]
@@ -1108,13 +1108,13 @@ devel = ["colorama", "json-spec", "jsonschema", "pylint", "pytest", "pytest-benc
[[package]]
name = "filelock"
-version = "3.13.3"
+version = "3.13.4"
description = "A platform independent file lock."
optional = false
python-versions = ">=3.8"
files = [
- {file = "filelock-3.13.3-py3-none-any.whl", hash = "sha256:5ffa845303983e7a0b7ae17636509bc97997d58afeafa72fb141a17b152284cb"},
- {file = "filelock-3.13.3.tar.gz", hash = "sha256:a79895a25bbefdf55d1a2a0a80968f7dbb28edcd6d4234a0afb3f37ecde4b546"},
+ {file = "filelock-3.13.4-py3-none-any.whl", hash = "sha256:404e5e9253aa60ad457cae1be07c0f0ca90a63931200a47d9b6a6af84fd7b45f"},
+ {file = "filelock-3.13.4.tar.gz", hash = "sha256:d13f466618bfde72bd2c18255e269f72542c6e70e7bac83a0232d6b1cc5c8cf4"},
]
[package.extras]
@@ -1245,13 +1245,13 @@ parser = ["pyhcl (>=0.4.4,<0.5.0)"]
[[package]]
name = "idna"
-version = "3.6"
+version = "3.7"
description = "Internationalized Domain Names in Applications (IDNA)"
optional = false
python-versions = ">=3.5"
files = [
- {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"},
- {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"},
+ {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"},
+ {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"},
]
[[package]]
@@ -1460,13 +1460,13 @@ pbr = "*"
[[package]]
name = "jsii"
-version = "1.96.0"
+version = "1.97.0"
description = "Python client for jsii runtime"
optional = false
python-versions = "~=3.8"
files = [
- {file = "jsii-1.96.0-py3-none-any.whl", hash = "sha256:7101f25ba9ccc66f7bbee710ece1f4f66e7c418fcdab9e8996b6573dd9bd096b"},
- {file = "jsii-1.96.0.tar.gz", hash = "sha256:b331f22a105ba437fa171e790eebd40d19f45773c32e55b8f904413ee6d7b2d9"},
+ {file = "jsii-1.97.0-py3-none-any.whl", hash = "sha256:5dd347cc9d279072c109829aaff11dae1c7f13169ce60887f1c1ab2c4cd4abcd"},
+ {file = "jsii-1.97.0.tar.gz", hash = "sha256:e6db98e34730cd972d180b7f4e21182b9a5105f537672716940b930ee933a1f2"},
]
[package.dependencies]
@@ -1822,13 +1822,13 @@ mkdocs = ">=0.17"
[[package]]
name = "mkdocs-material"
-version = "9.5.15"
+version = "9.5.18"
description = "Documentation that simply works"
optional = false
python-versions = ">=3.8"
files = [
- {file = "mkdocs_material-9.5.15-py3-none-any.whl", hash = "sha256:e5c96dec3d19491de49ca643fc1dbb92b278e43cdb816c775bc47db77d9b62fb"},
- {file = "mkdocs_material-9.5.15.tar.gz", hash = "sha256:39f03cca45e82bf54eb7456b5a18bd252eabfdd67f237a229471484a0a4d4635"},
+ {file = "mkdocs_material-9.5.18-py3-none-any.whl", hash = "sha256:1e0e27fc9fe239f9064318acf548771a4629d5fd5dfd45444fd80a953fe21eb4"},
+ {file = "mkdocs_material-9.5.18.tar.gz", hash = "sha256:a43f470947053fa2405c33995f282d24992c752a50114f23f30da9d8d0c57e62"},
]
[package.dependencies]
@@ -1978,13 +1978,13 @@ typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.12\""}
[[package]]
name = "mypy-boto3-cloudformation"
-version = "1.34.66"
-description = "Type annotations for boto3.CloudFormation 1.34.66 service generated with mypy-boto3-builder 7.23.2"
+version = "1.34.84"
+description = "Type annotations for boto3.CloudFormation 1.34.84 service generated with mypy-boto3-builder 7.23.2"
optional = false
python-versions = ">=3.8"
files = [
- {file = "mypy-boto3-cloudformation-1.34.66.tar.gz", hash = "sha256:295ddb878f3f4b61489b83bd4bad949382deb8a478ff5ae66c22ca3ffbabbe95"},
- {file = "mypy_boto3_cloudformation-1.34.66-py3-none-any.whl", hash = "sha256:e0080e4ba0cd464479f3ee285faddd863e40147e8f331b78445aef210afc91ed"},
+ {file = "mypy_boto3_cloudformation-1.34.84-py3-none-any.whl", hash = "sha256:580954031cb3650588b91f592e8f51855b2ff435d763ac0d69cf271c8433315f"},
+ {file = "mypy_boto3_cloudformation-1.34.84.tar.gz", hash = "sha256:82d14df3757f30b5a1d34650839d415d265d4de41cf355d63e10221fcc67f177"},
]
[package.dependencies]
@@ -1992,13 +1992,13 @@ typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.12\""}
[[package]]
name = "mypy-boto3-cloudwatch"
-version = "1.34.40"
-description = "Type annotations for boto3.CloudWatch 1.34.40 service generated with mypy-boto3-builder 7.23.1"
+version = "1.34.83"
+description = "Type annotations for boto3.CloudWatch 1.34.83 service generated with mypy-boto3-builder 7.23.2"
optional = false
python-versions = ">=3.8"
files = [
- {file = "mypy-boto3-cloudwatch-1.34.40.tar.gz", hash = "sha256:33f0b747389ee5d72fe9319597b8a52395a24f5b9816b0862feec581afb148bc"},
- {file = "mypy_boto3_cloudwatch-1.34.40-py3-none-any.whl", hash = "sha256:9e66e0ab1006cb45ff36df1ab453bdef6ec700d6940466161049dc6efbd3e1b4"},
+ {file = "mypy-boto3-cloudwatch-1.34.83.tar.gz", hash = "sha256:766e166c5b463d9885a5929dc16bb592e0fa7d7beaf569aa4f501d85a848bc13"},
+ {file = "mypy_boto3_cloudwatch-1.34.83-py3-none-any.whl", hash = "sha256:6af4fff0ec7c09e423df5a69fff4df8a74044462686e8679b4fe73c106787854"},
]
[package.dependencies]
@@ -2020,13 +2020,13 @@ typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.12\""}
[[package]]
name = "mypy-boto3-lambda"
-version = "1.34.58"
-description = "Type annotations for boto3.Lambda 1.34.58 service generated with mypy-boto3-builder 7.23.2"
+version = "1.34.77"
+description = "Type annotations for boto3.Lambda 1.34.77 service generated with mypy-boto3-builder 7.23.2"
optional = false
python-versions = ">=3.8"
files = [
- {file = "mypy-boto3-lambda-1.34.58.tar.gz", hash = "sha256:903822c74bd1b34748eb2d72eab0f132fbc3b392c8041aa8fe4d9552a44b0c65"},
- {file = "mypy_boto3_lambda-1.34.58-py3-none-any.whl", hash = "sha256:6ab1b9611ff396e9310ad77f02994a6e03d40c8a0eea51085e355b4fd2a0cbc9"},
+ {file = "mypy-boto3-lambda-1.34.77.tar.gz", hash = "sha256:7b81d2a5604fb592e92fe0b284ecd259de071703360a33b71c9b54df46d81c9c"},
+ {file = "mypy_boto3_lambda-1.34.77-py3-none-any.whl", hash = "sha256:e21022d2eef12aa731af80790410afdba9412b056339823252813bae2adbf553"},
]
[package.dependencies]
@@ -2062,13 +2062,13 @@ typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.12\""}
[[package]]
name = "mypy-boto3-secretsmanager"
-version = "1.34.63"
-description = "Type annotations for boto3.SecretsManager 1.34.63 service generated with mypy-boto3-builder 7.23.2"
+version = "1.34.72"
+description = "Type annotations for boto3.SecretsManager 1.34.72 service generated with mypy-boto3-builder 7.23.2"
optional = false
python-versions = ">=3.8"
files = [
- {file = "mypy-boto3-secretsmanager-1.34.63.tar.gz", hash = "sha256:a193373d718f747d53878b5cc7fef2e9b8fde3892e4788ac690fd2b16a35564c"},
- {file = "mypy_boto3_secretsmanager-1.34.63-py3-none-any.whl", hash = "sha256:3ed3587f9b5a5eb4e8c81edeeaa0b4a9118d8f07cfc4915c137f8edc315028ab"},
+ {file = "mypy-boto3-secretsmanager-1.34.72.tar.gz", hash = "sha256:d0733c5b53e8b5e7bda00f4b42ed84af12e36a20c848917e49c173a0422ef03c"},
+ {file = "mypy_boto3_secretsmanager-1.34.72-py3-none-any.whl", hash = "sha256:c5ed74e4a56e345a6396c609d2808fbe64570f90bb76b256d65e1294eaa24c69"},
]
[package.dependencies]
@@ -2311,47 +2311,47 @@ files = [
[[package]]
name = "pydantic"
-version = "1.10.14"
+version = "1.10.15"
description = "Data validation and settings management using python type hints"
optional = false
python-versions = ">=3.7"
files = [
- {file = "pydantic-1.10.14-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7f4fcec873f90537c382840f330b90f4715eebc2bc9925f04cb92de593eae054"},
- {file = "pydantic-1.10.14-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e3a76f571970fcd3c43ad982daf936ae39b3e90b8a2e96c04113a369869dc87"},
- {file = "pydantic-1.10.14-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82d886bd3c3fbeaa963692ef6b643159ccb4b4cefaf7ff1617720cbead04fd1d"},
- {file = "pydantic-1.10.14-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:798a3d05ee3b71967844a1164fd5bdb8c22c6d674f26274e78b9f29d81770c4e"},
- {file = "pydantic-1.10.14-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:23d47a4b57a38e8652bcab15a658fdb13c785b9ce217cc3a729504ab4e1d6bc9"},
- {file = "pydantic-1.10.14-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f9f674b5c3bebc2eba401de64f29948ae1e646ba2735f884d1594c5f675d6f2a"},
- {file = "pydantic-1.10.14-cp310-cp310-win_amd64.whl", hash = "sha256:24a7679fab2e0eeedb5a8924fc4a694b3bcaac7d305aeeac72dd7d4e05ecbebf"},
- {file = "pydantic-1.10.14-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9d578ac4bf7fdf10ce14caba6f734c178379bd35c486c6deb6f49006e1ba78a7"},
- {file = "pydantic-1.10.14-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fa7790e94c60f809c95602a26d906eba01a0abee9cc24150e4ce2189352deb1b"},
- {file = "pydantic-1.10.14-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aad4e10efa5474ed1a611b6d7f0d130f4aafadceb73c11d9e72823e8f508e663"},
- {file = "pydantic-1.10.14-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1245f4f61f467cb3dfeced2b119afef3db386aec3d24a22a1de08c65038b255f"},
- {file = "pydantic-1.10.14-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:21efacc678a11114c765eb52ec0db62edffa89e9a562a94cbf8fa10b5db5c046"},
- {file = "pydantic-1.10.14-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:412ab4a3f6dbd2bf18aefa9f79c7cca23744846b31f1d6555c2ee2b05a2e14ca"},
- {file = "pydantic-1.10.14-cp311-cp311-win_amd64.whl", hash = "sha256:e897c9f35281f7889873a3e6d6b69aa1447ceb024e8495a5f0d02ecd17742a7f"},
- {file = "pydantic-1.10.14-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d604be0f0b44d473e54fdcb12302495fe0467c56509a2f80483476f3ba92b33c"},
- {file = "pydantic-1.10.14-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a42c7d17706911199798d4c464b352e640cab4351efe69c2267823d619a937e5"},
- {file = "pydantic-1.10.14-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:596f12a1085e38dbda5cbb874d0973303e34227b400b6414782bf205cc14940c"},
- {file = "pydantic-1.10.14-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bfb113860e9288d0886e3b9e49d9cf4a9d48b441f52ded7d96db7819028514cc"},
- {file = "pydantic-1.10.14-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:bc3ed06ab13660b565eed80887fcfbc0070f0aa0691fbb351657041d3e874efe"},
- {file = "pydantic-1.10.14-cp37-cp37m-win_amd64.whl", hash = "sha256:ad8c2bc677ae5f6dbd3cf92f2c7dc613507eafe8f71719727cbc0a7dec9a8c01"},
- {file = "pydantic-1.10.14-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c37c28449752bb1f47975d22ef2882d70513c546f8f37201e0fec3a97b816eee"},
- {file = "pydantic-1.10.14-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:49a46a0994dd551ec051986806122767cf144b9702e31d47f6d493c336462597"},
- {file = "pydantic-1.10.14-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53e3819bd20a42470d6dd0fe7fc1c121c92247bca104ce608e609b59bc7a77ee"},
- {file = "pydantic-1.10.14-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0fbb503bbbbab0c588ed3cd21975a1d0d4163b87e360fec17a792f7d8c4ff29f"},
- {file = "pydantic-1.10.14-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:336709883c15c050b9c55a63d6c7ff09be883dbc17805d2b063395dd9d9d0022"},
- {file = "pydantic-1.10.14-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4ae57b4d8e3312d486e2498d42aed3ece7b51848336964e43abbf9671584e67f"},
- {file = "pydantic-1.10.14-cp38-cp38-win_amd64.whl", hash = "sha256:dba49d52500c35cfec0b28aa8b3ea5c37c9df183ffc7210b10ff2a415c125c4a"},
- {file = "pydantic-1.10.14-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c66609e138c31cba607d8e2a7b6a5dc38979a06c900815495b2d90ce6ded35b4"},
- {file = "pydantic-1.10.14-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d986e115e0b39604b9eee3507987368ff8148222da213cd38c359f6f57b3b347"},
- {file = "pydantic-1.10.14-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:646b2b12df4295b4c3148850c85bff29ef6d0d9621a8d091e98094871a62e5c7"},
- {file = "pydantic-1.10.14-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282613a5969c47c83a8710cc8bfd1e70c9223feb76566f74683af889faadc0ea"},
- {file = "pydantic-1.10.14-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:466669501d08ad8eb3c4fecd991c5e793c4e0bbd62299d05111d4f827cded64f"},
- {file = "pydantic-1.10.14-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:13e86a19dca96373dcf3190fcb8797d40a6f12f154a244a8d1e8e03b8f280593"},
- {file = "pydantic-1.10.14-cp39-cp39-win_amd64.whl", hash = "sha256:08b6ec0917c30861e3fe71a93be1648a2aa4f62f866142ba21670b24444d7fd8"},
- {file = "pydantic-1.10.14-py3-none-any.whl", hash = "sha256:8ee853cd12ac2ddbf0ecbac1c289f95882b2d4482258048079d13be700aa114c"},
- {file = "pydantic-1.10.14.tar.gz", hash = "sha256:46f17b832fe27de7850896f3afee50ea682220dd218f7e9c88d436788419dca6"},
+ {file = "pydantic-1.10.15-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:22ed12ee588b1df028a2aa5d66f07bf8f8b4c8579c2e96d5a9c1f96b77f3bb55"},
+ {file = "pydantic-1.10.15-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:75279d3cac98186b6ebc2597b06bcbc7244744f6b0b44a23e4ef01e5683cc0d2"},
+ {file = "pydantic-1.10.15-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50f1666a9940d3d68683c9d96e39640f709d7a72ff8702987dab1761036206bb"},
+ {file = "pydantic-1.10.15-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82790d4753ee5d00739d6cb5cf56bceb186d9d6ce134aca3ba7befb1eedbc2c8"},
+ {file = "pydantic-1.10.15-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:d207d5b87f6cbefbdb1198154292faee8017d7495a54ae58db06762004500d00"},
+ {file = "pydantic-1.10.15-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e49db944fad339b2ccb80128ffd3f8af076f9f287197a480bf1e4ca053a866f0"},
+ {file = "pydantic-1.10.15-cp310-cp310-win_amd64.whl", hash = "sha256:d3b5c4cbd0c9cb61bbbb19ce335e1f8ab87a811f6d589ed52b0254cf585d709c"},
+ {file = "pydantic-1.10.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c3d5731a120752248844676bf92f25a12f6e45425e63ce22e0849297a093b5b0"},
+ {file = "pydantic-1.10.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c365ad9c394f9eeffcb30a82f4246c0006417f03a7c0f8315d6211f25f7cb654"},
+ {file = "pydantic-1.10.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3287e1614393119c67bd4404f46e33ae3be3ed4cd10360b48d0a4459f420c6a3"},
+ {file = "pydantic-1.10.15-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be51dd2c8596b25fe43c0a4a59c2bee4f18d88efb8031188f9e7ddc6b469cf44"},
+ {file = "pydantic-1.10.15-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6a51a1dd4aa7b3f1317f65493a182d3cff708385327c1c82c81e4a9d6d65b2e4"},
+ {file = "pydantic-1.10.15-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4e316e54b5775d1eb59187f9290aeb38acf620e10f7fd2f776d97bb788199e53"},
+ {file = "pydantic-1.10.15-cp311-cp311-win_amd64.whl", hash = "sha256:0d142fa1b8f2f0ae11ddd5e3e317dcac060b951d605fda26ca9b234b92214986"},
+ {file = "pydantic-1.10.15-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7ea210336b891f5ea334f8fc9f8f862b87acd5d4a0cbc9e3e208e7aa1775dabf"},
+ {file = "pydantic-1.10.15-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3453685ccd7140715e05f2193d64030101eaad26076fad4e246c1cc97e1bb30d"},
+ {file = "pydantic-1.10.15-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bea1f03b8d4e8e86702c918ccfd5d947ac268f0f0cc6ed71782e4b09353b26f"},
+ {file = "pydantic-1.10.15-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:005655cabc29081de8243126e036f2065bd7ea5b9dff95fde6d2c642d39755de"},
+ {file = "pydantic-1.10.15-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:af9850d98fc21e5bc24ea9e35dd80a29faf6462c608728a110c0a30b595e58b7"},
+ {file = "pydantic-1.10.15-cp37-cp37m-win_amd64.whl", hash = "sha256:d31ee5b14a82c9afe2bd26aaa405293d4237d0591527d9129ce36e58f19f95c1"},
+ {file = "pydantic-1.10.15-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5e09c19df304b8123938dc3c53d3d3be6ec74b9d7d0d80f4f4b5432ae16c2022"},
+ {file = "pydantic-1.10.15-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7ac9237cd62947db00a0d16acf2f3e00d1ae9d3bd602b9c415f93e7a9fc10528"},
+ {file = "pydantic-1.10.15-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:584f2d4c98ffec420e02305cf675857bae03c9d617fcfdc34946b1160213a948"},
+ {file = "pydantic-1.10.15-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bbc6989fad0c030bd70a0b6f626f98a862224bc2b1e36bfc531ea2facc0a340c"},
+ {file = "pydantic-1.10.15-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d573082c6ef99336f2cb5b667b781d2f776d4af311574fb53d908517ba523c22"},
+ {file = "pydantic-1.10.15-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6bd7030c9abc80134087d8b6e7aa957e43d35714daa116aced57269a445b8f7b"},
+ {file = "pydantic-1.10.15-cp38-cp38-win_amd64.whl", hash = "sha256:3350f527bb04138f8aff932dc828f154847fbdc7a1a44c240fbfff1b57f49a12"},
+ {file = "pydantic-1.10.15-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:51d405b42f1b86703555797270e4970a9f9bd7953f3990142e69d1037f9d9e51"},
+ {file = "pydantic-1.10.15-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a980a77c52723b0dc56640ced396b73a024d4b74f02bcb2d21dbbac1debbe9d0"},
+ {file = "pydantic-1.10.15-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67f1a1fb467d3f49e1708a3f632b11c69fccb4e748a325d5a491ddc7b5d22383"},
+ {file = "pydantic-1.10.15-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:676ed48f2c5bbad835f1a8ed8a6d44c1cd5a21121116d2ac40bd1cd3619746ed"},
+ {file = "pydantic-1.10.15-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:92229f73400b80c13afcd050687f4d7e88de9234d74b27e6728aa689abcf58cc"},
+ {file = "pydantic-1.10.15-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2746189100c646682eff0bce95efa7d2e203420d8e1c613dc0c6b4c1d9c1fde4"},
+ {file = "pydantic-1.10.15-cp39-cp39-win_amd64.whl", hash = "sha256:394f08750bd8eaad714718812e7fab615f873b3cdd0b9d84e76e51ef3b50b6b7"},
+ {file = "pydantic-1.10.15-py3-none-any.whl", hash = "sha256:28e552a060ba2740d0d2aabe35162652c1459a0b9069fe0db7f4ee0e18e74d58"},
+ {file = "pydantic-1.10.15.tar.gz", hash = "sha256:ca832e124eda231a60a041da4f013e3ff24949d94a01154b137fc2f2a43c3ffb"},
]
[package.dependencies]
@@ -2961,28 +2961,28 @@ files = [
[[package]]
name = "ruff"
-version = "0.3.4"
+version = "0.3.7"
description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false
python-versions = ">=3.7"
files = [
- {file = "ruff-0.3.4-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:60c870a7d46efcbc8385d27ec07fe534ac32f3b251e4fc44b3cbfd9e09609ef4"},
- {file = "ruff-0.3.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6fc14fa742e1d8f24910e1fff0bd5e26d395b0e0e04cc1b15c7c5e5fe5b4af91"},
- {file = "ruff-0.3.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3ee7880f653cc03749a3bfea720cf2a192e4f884925b0cf7eecce82f0ce5854"},
- {file = "ruff-0.3.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cf133dd744f2470b347f602452a88e70dadfbe0fcfb5fd46e093d55da65f82f7"},
- {file = "ruff-0.3.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f3860057590e810c7ffea75669bdc6927bfd91e29b4baa9258fd48b540a4365"},
- {file = "ruff-0.3.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:986f2377f7cf12efac1f515fc1a5b753c000ed1e0a6de96747cdf2da20a1b369"},
- {file = "ruff-0.3.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fd98e85869603e65f554fdc5cddf0712e352fe6e61d29d5a6fe087ec82b76c"},
- {file = "ruff-0.3.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64abeed785dad51801b423fa51840b1764b35d6c461ea8caef9cf9e5e5ab34d9"},
- {file = "ruff-0.3.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df52972138318bc7546d92348a1ee58449bc3f9eaf0db278906eb511889c4b50"},
- {file = "ruff-0.3.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:98e98300056445ba2cc27d0b325fd044dc17fcc38e4e4d2c7711585bd0a958ed"},
- {file = "ruff-0.3.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:519cf6a0ebed244dce1dc8aecd3dc99add7a2ee15bb68cf19588bb5bf58e0488"},
- {file = "ruff-0.3.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:bb0acfb921030d00070539c038cd24bb1df73a2981e9f55942514af8b17be94e"},
- {file = "ruff-0.3.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:cf187a7e7098233d0d0c71175375c5162f880126c4c716fa28a8ac418dcf3378"},
- {file = "ruff-0.3.4-py3-none-win32.whl", hash = "sha256:af27ac187c0a331e8ef91d84bf1c3c6a5dea97e912a7560ac0cef25c526a4102"},
- {file = "ruff-0.3.4-py3-none-win_amd64.whl", hash = "sha256:de0d5069b165e5a32b3c6ffbb81c350b1e3d3483347196ffdf86dc0ef9e37dd6"},
- {file = "ruff-0.3.4-py3-none-win_arm64.whl", hash = "sha256:6810563cc08ad0096b57c717bd78aeac888a1bfd38654d9113cb3dc4d3f74232"},
- {file = "ruff-0.3.4.tar.gz", hash = "sha256:f0f4484c6541a99862b693e13a151435a279b271cff20e37101116a21e2a1ad1"},
+ {file = "ruff-0.3.7-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:0e8377cccb2f07abd25e84fc5b2cbe48eeb0fea9f1719cad7caedb061d70e5ce"},
+ {file = "ruff-0.3.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:15a4d1cc1e64e556fa0d67bfd388fed416b7f3b26d5d1c3e7d192c897e39ba4b"},
+ {file = "ruff-0.3.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d28bdf3d7dc71dd46929fafeec98ba89b7c3550c3f0978e36389b5631b793663"},
+ {file = "ruff-0.3.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:379b67d4f49774ba679593b232dcd90d9e10f04d96e3c8ce4a28037ae473f7bb"},
+ {file = "ruff-0.3.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c060aea8ad5ef21cdfbbe05475ab5104ce7827b639a78dd55383a6e9895b7c51"},
+ {file = "ruff-0.3.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:ebf8f615dde968272d70502c083ebf963b6781aacd3079081e03b32adfe4d58a"},
+ {file = "ruff-0.3.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d48098bd8f5c38897b03604f5428901b65e3c97d40b3952e38637b5404b739a2"},
+ {file = "ruff-0.3.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da8a4fda219bf9024692b1bc68c9cff4b80507879ada8769dc7e985755d662ea"},
+ {file = "ruff-0.3.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c44e0149f1d8b48c4d5c33d88c677a4aa22fd09b1683d6a7ff55b816b5d074f"},
+ {file = "ruff-0.3.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3050ec0af72b709a62ecc2aca941b9cd479a7bf2b36cc4562f0033d688e44fa1"},
+ {file = "ruff-0.3.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a29cc38e4c1ab00da18a3f6777f8b50099d73326981bb7d182e54a9a21bb4ff7"},
+ {file = "ruff-0.3.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5b15cc59c19edca917f51b1956637db47e200b0fc5e6e1878233d3a938384b0b"},
+ {file = "ruff-0.3.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e491045781b1e38b72c91247cf4634f040f8d0cb3e6d3d64d38dcf43616650b4"},
+ {file = "ruff-0.3.7-py3-none-win32.whl", hash = "sha256:bc931de87593d64fad3a22e201e55ad76271f1d5bfc44e1a1887edd0903c7d9f"},
+ {file = "ruff-0.3.7-py3-none-win_amd64.whl", hash = "sha256:5ef0e501e1e39f35e03c2acb1d1238c595b8bb36cf7a170e7c1df1b73da00e74"},
+ {file = "ruff-0.3.7-py3-none-win_arm64.whl", hash = "sha256:789e144f6dc7019d1f92a812891c645274ed08af6037d11fc65fcbc183b7d59f"},
+ {file = "ruff-0.3.7.tar.gz", hash = "sha256:d5c1aebee5162c2226784800ae031f660c350e7a3402c4d1f8ea4e97e232e3ba"},
]
[[package]]
@@ -3019,13 +3019,13 @@ pbr = "*"
[[package]]
name = "sentry-sdk"
-version = "1.43.0"
+version = "1.45.0"
description = "Python client for Sentry (https://sentry.io)"
optional = false
python-versions = "*"
files = [
- {file = "sentry-sdk-1.43.0.tar.gz", hash = "sha256:41df73af89d22921d8733714fb0fc5586c3461907e06688e6537d01a27e0e0f6"},
- {file = "sentry_sdk-1.43.0-py2.py3-none-any.whl", hash = "sha256:8d768724839ca18d7b4c7463ef7528c40b7aa2bfbf7fe554d5f9a7c044acfd36"},
+ {file = "sentry-sdk-1.45.0.tar.gz", hash = "sha256:509aa9678c0512344ca886281766c2e538682f8acfa50fd8d405f8c417ad0625"},
+ {file = "sentry_sdk-1.45.0-py2.py3-none-any.whl", hash = "sha256:1ce29e30240cc289a027011103a8c83885b15ef2f316a60bcc7c5300afa144f1"},
]
[package.dependencies]
@@ -3115,19 +3115,18 @@ files = [
[[package]]
name = "sqlparse"
-version = "0.4.4"
+version = "0.5.0"
description = "A non-validating SQL parser."
optional = false
-python-versions = ">=3.5"
+python-versions = ">=3.8"
files = [
- {file = "sqlparse-0.4.4-py3-none-any.whl", hash = "sha256:5430a4fe2ac7d0f93e66f1efc6e1338a41884b7ddf2a350cedd20ccc4d9d28f3"},
- {file = "sqlparse-0.4.4.tar.gz", hash = "sha256:d446183e84b8349fa3061f0fe7f06ca94ba65b426946ffebe6e3e8295332420c"},
+ {file = "sqlparse-0.5.0-py3-none-any.whl", hash = "sha256:c204494cd97479d0e39f28c93d46c0b2d5959c7b9ab904762ea6c7af211c8663"},
+ {file = "sqlparse-0.5.0.tar.gz", hash = "sha256:714d0a4932c059d16189f58ef5411ec2287a4360f17cdd0edd2d09d4c5087c93"},
]
[package.extras]
-dev = ["build", "flake8"]
+dev = ["build", "hatch"]
doc = ["sphinx"]
-test = ["pytest", "pytest-cov"]
[[package]]
name = "stevedore"
@@ -3244,13 +3243,13 @@ files = [
[[package]]
name = "types-redis"
-version = "4.6.0.20240311"
+version = "4.6.0.20240417"
description = "Typing stubs for redis"
optional = false
python-versions = ">=3.8"
files = [
- {file = "types-redis-4.6.0.20240311.tar.gz", hash = "sha256:e049bbdff0e0a1f8e701b64636811291d21bff79bf1e7850850a44055224a85f"},
- {file = "types_redis-4.6.0.20240311-py3-none-any.whl", hash = "sha256:6b9d68a29aba1ee400c823d8e5fe88675282eb69d7211e72fe65dbe54b33daca"},
+ {file = "types-redis-4.6.0.20240417.tar.gz", hash = "sha256:8be4b3e5945120acdef0a2348c04be42894e84c6d616288b908a3d8ed5e89a8d"},
+ {file = "types_redis-4.6.0.20240417-py3-none-any.whl", hash = "sha256:4c35cbd90ff18c8da6f97a05d2fe97eb3abfe09acf3a4357b6c5e2d4a59385a1"},
]
[package.dependencies]
@@ -3284,13 +3283,13 @@ files = [
[[package]]
name = "typing-extensions"
-version = "4.10.0"
+version = "4.11.0"
description = "Backported and Experimental Type Hints for Python 3.8+"
optional = false
python-versions = ">=3.8"
files = [
- {file = "typing_extensions-4.10.0-py3-none-any.whl", hash = "sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475"},
- {file = "typing_extensions-4.10.0.tar.gz", hash = "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb"},
+ {file = "typing_extensions-4.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"},
+ {file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"},
]
[[package]]
@@ -3515,4 +3514,4 @@ validation = ["fastjsonschema"]
[metadata]
lock-version = "2.0"
python-versions = ">=3.8,<4.0.0"
-content-hash = "06ded72b67c32472b583b6be33c596f2caf71327cf931280f8aa5c7a4314898b"
+content-hash = "2ac9055e10d0ebeec41f77c2ced085f2f03b777f3f55c3b0b41aaeaac9223557"
diff --git a/pyproject.toml b/pyproject.toml
index 6bd6733599a..5c2d2f7048f 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[tool.poetry]
name = "aws_lambda_powertools"
-version = "2.35.1"
+version = "2.37.0"
description = "Powertools for AWS Lambda (Python) is a developer toolkit to implement Serverless best practices and increase developer velocity."
authors = ["Amazon Web Services"]
include = ["aws_lambda_powertools/py.typed", "THIRD-PARTY-LICENSES"]
@@ -44,7 +44,7 @@ fastjsonschema = { version = "^2.14.5", optional = true }
pydantic = { version = "^1.8.2", optional = true }
boto3 = { version = "^1.26.164", optional = true }
redis = { version = ">=4.4,<6.0", optional = true }
-typing-extensions = "^4.6.2"
+typing-extensions = "^4.11.0"
datadog-lambda = { version = ">=4.77,<6.0", optional = true }
aws-encryption-sdk = { version = "^3.1.1", optional = true }
jsonpath-ng = { version = "^1.6.0", optional = true }
@@ -52,7 +52,7 @@ jsonpath-ng = { version = "^1.6.0", optional = true }
[tool.poetry.dev-dependencies]
coverage = { extras = ["toml"], version = "^7.4" }
pytest = "^8.1.1"
-black = "^24.3"
+black = "^24.4"
boto3 = "^1.26.164"
isort = "^5.13.2"
pytest-cov = "^5.0.0"
@@ -65,34 +65,34 @@ xenon = "^0.9.1"
mkdocs-git-revision-date-plugin = "^0.3.2"
mike = "^1.1.2"
pytest-xdist = "^3.5.0"
-aws-cdk-lib = "^2.133.0"
+aws-cdk-lib = "^2.137.0"
"aws-cdk.aws-apigatewayv2-alpha" = "^2.38.1-alpha.0"
"aws-cdk.aws-apigatewayv2-integrations-alpha" = "^2.38.1-alpha.0"
"aws-cdk.aws-apigatewayv2-authorizers-alpha" = "^2.38.1-alpha.0"
-"aws-cdk.aws-lambda-python-alpha" = "^2.133.0a0"
-"cdklabs.generative-ai-cdk-constructs" = "^0.1.104"
+"aws-cdk.aws-lambda-python-alpha" = "^2.137.0a0"
+"cdklabs.generative-ai-cdk-constructs" = "^0.1.119"
pytest-benchmark = "^4.0.0"
mypy-boto3-appconfig = "^1.34.58"
-mypy-boto3-cloudformation = "^1.34.66"
-mypy-boto3-cloudwatch = "^1.34.40"
+mypy-boto3-cloudformation = "^1.34.84"
+mypy-boto3-cloudwatch = "^1.34.83"
mypy-boto3-dynamodb = "^1.34.67"
-mypy-boto3-lambda = "^1.34.58"
+mypy-boto3-lambda = "^1.34.77"
mypy-boto3-logs = "^1.34.66"
-mypy-boto3-secretsmanager = "^1.34.63"
+mypy-boto3-secretsmanager = "^1.34.72"
mypy-boto3-ssm = "^1.34.61"
mypy-boto3-s3 = "^1.34.65"
mypy-boto3-xray = "^1.34.0"
types-requests = "^2.31.0"
typing-extensions = "^4.6.2"
-mkdocs-material = "^9.5.15"
-filelock = "^3.13.3"
+mkdocs-material = "^9.5.18"
+filelock = "^3.13.4"
checksumdir = "^1.2.0"
mypy-boto3-appconfigdata = "^1.34.24"
ijson = "^3.2.2"
typed-ast = { version = "^1.5.5", python = "< 3.8" }
hvac = "^2.1.0"
aws-requests-auth = "^0.4.3"
-datadog-lambda = "^5.91.0"
+datadog-lambda = "^5.92.0"
[tool.poetry.extras]
parser = ["pydantic"]
@@ -110,12 +110,12 @@ datadog = ["datadog-lambda"]
datamasking = ["aws-encryption-sdk", "jsonpath-ng"]
[tool.poetry.group.dev.dependencies]
-cfn-lint = "0.86.1"
+cfn-lint = "0.86.3"
mypy = "^1.1.1"
types-python-dateutil = "^2.8.19.6"
httpx = ">=0.23.3,<0.28.0"
sentry-sdk = "^1.22.2"
-ruff = ">=0.0.272,<0.3.5"
+ruff = ">=0.0.272,<0.3.8"
retry2 = "^0.9.5"
pytest-socket = ">=0.6,<0.8"
types-redis = "^4.6.0.7"
diff --git a/tests/events/activeMQEvent.json b/tests/events/activeMQEvent.json
index 57417cfd229..18ef927c4fd 100644
--- a/tests/events/activeMQEvent.json
+++ b/tests/events/activeMQEvent.json
@@ -9,7 +9,7 @@
"connectionId": "myJMSCoID",
"redelivered": false,
"destination": {
- "physicalname": "testQueue"
+ "physicalName": "testQueue"
},
"timestamp": 1598827811958,
"brokerInTime": 1598827811958,
@@ -25,7 +25,7 @@
"connectionId": "myJMSCoID2",
"redelivered": false,
"destination": {
- "physicalname": "testQueue"
+ "physicalName": "testQueue"
},
"timestamp": 1598827811958,
"brokerInTime": 1598827811958,
@@ -42,7 +42,7 @@
"connectionId": "myJMSCoID1",
"persistent": false,
"destination": {
- "physicalname": "testQueue"
+ "physicalName": "testQueue"
},
"timestamp": 1598827811958,
"brokerInTime": 1598827811958,
diff --git a/tests/events/sqsDlqTriggerEvent.json b/tests/events/sqsDlqTriggerEvent.json
new file mode 100644
index 00000000000..c47ee95aeef
--- /dev/null
+++ b/tests/events/sqsDlqTriggerEvent.json
@@ -0,0 +1,21 @@
+{
+ "Records": [
+ {
+ "messageId": "db37cc61-1bb0-4e77-b6f3-7cf87f44a72a",
+ "receiptHandle": "AQEBl1pqxv+ZHkarVAWZUyWgj2mmqJGLBTo6YFOi/bw1QpBTpJBGJPLOTZrjKztKIbAB8EXkG7zHlbkn+Ze/AHMKKuhST9azHu8LyF4Ffu9uPkZc5xzggXlfFBWH3TUKyV+F5Obaj3esyX8YfM/zfgjbRuu5nc2tfPhvaSYEaTZsdMpzIB5tyKvHxAltLxK7upRHeoT768M9UrFYswarFTBn8piDbnsPsUhi8Q9G4Q4xSI0fLQANmryBsRJIzGQTVxenDad+MJ7XEL+hD3p2DmW+ycvv6WD7bdedqQuroQG8+ca1Dz7s3CBbXw9ZZnUziPa7LH1j1Lky5bAxpNF+BlurRS9pFBnomhwpylrGxtGfaEmUW1G7jnrG97sZNOLOFUykbQgroZPXmjzMBdvtgq9ZmQfCch3LOXN267+PKc56VR4=",
+ "body": "hello world",
+ "attributes": {
+ "DeadLetterQueueSourceArn": "arn:aws:sqs:eu-central-1:123456789012:sqs-redrive-SampleQueue-RNvLCpwGmLi7",
+ "ApproximateReceiveCount": "2",
+ "SentTimestamp": "1713185156609",
+ "SenderId": "AMCXIENQZJOLO23YVJ4VO",
+ "ApproximateFirstReceiveTimestamp": "1713185156612"
+ },
+ "messageAttributes": {},
+ "md5OfBody": "6a204bd89f3c8348afd5c77c717a097a",
+ "eventSource": "aws:sqs",
+ "eventSourceARN": "arn:aws:sqs:eu-central-1:123456789012:sqs-redrive-SampleDLQ-Emgp9MFSLBZm",
+ "awsRegion": "eu-central-1"
+ }
+ ]
+}
diff --git a/tests/functional/event_handler/test_openapi_security.py b/tests/functional/event_handler/test_openapi_security.py
new file mode 100644
index 00000000000..7120a815edd
--- /dev/null
+++ b/tests/functional/event_handler/test_openapi_security.py
@@ -0,0 +1,62 @@
+import pytest
+
+from aws_lambda_powertools.event_handler import APIGatewayRestResolver
+from aws_lambda_powertools.event_handler.openapi.models import APIKey, APIKeyIn
+
+
+def test_openapi_top_level_security():
+ app = APIGatewayRestResolver()
+
+ @app.get("/")
+ def handler():
+ raise NotImplementedError()
+
+ schema = app.get_openapi_schema(
+ security_schemes={
+ "apiKey": APIKey(name="X-API-KEY", description="API Key", in_=APIKeyIn.header),
+ },
+ security=[{"apiKey": []}],
+ )
+
+ security = schema.security
+ assert security is not None
+
+ assert len(security) == 1
+ assert security[0] == {"apiKey": []}
+
+
+def test_openapi_top_level_security_missing():
+ app = APIGatewayRestResolver()
+
+ @app.get("/")
+ def handler():
+ raise NotImplementedError()
+
+ with pytest.raises(ValueError):
+ app.get_openapi_schema(
+ security=[{"apiKey": []}],
+ )
+
+
+def test_openapi_operation_security():
+ app = APIGatewayRestResolver()
+
+ @app.get("/", security=[{"apiKey": []}])
+ def handler():
+ raise NotImplementedError()
+
+ schema = app.get_openapi_schema(
+ security_schemes={
+ "apiKey": APIKey(name="X-API-KEY", description="API Key", in_=APIKeyIn.header),
+ },
+ )
+
+ security = schema.security
+ assert security is None
+
+ operation = schema.paths["/"].get
+ security = operation.security
+ assert security is not None
+
+ assert len(security) == 1
+ assert security[0] == {"apiKey": []}
diff --git a/tests/functional/event_handler/test_openapi_security_schemes.py b/tests/functional/event_handler/test_openapi_security_schemes.py
new file mode 100644
index 00000000000..dc785ba56d0
--- /dev/null
+++ b/tests/functional/event_handler/test_openapi_security_schemes.py
@@ -0,0 +1,112 @@
+from aws_lambda_powertools.event_handler import APIGatewayRestResolver
+from aws_lambda_powertools.event_handler.openapi.models import (
+ APIKey,
+ APIKeyIn,
+ HTTPBearer,
+ OAuth2,
+ OAuthFlowImplicit,
+ OAuthFlows,
+ OpenIdConnect,
+)
+
+
+def test_openapi_security_scheme_api_key():
+ app = APIGatewayRestResolver()
+
+ @app.get("/")
+ def handler():
+ raise NotImplementedError()
+
+ schema = app.get_openapi_schema(
+ security_schemes={
+ "apiKey": APIKey(name="X-API-KEY", description="API Key", in_=APIKeyIn.header),
+ },
+ )
+
+ security_schemes = schema.components.securitySchemes
+ assert security_schemes is not None
+
+ assert "apiKey" in security_schemes
+ api_key_scheme = security_schemes["apiKey"]
+ assert api_key_scheme.type_.value == "apiKey"
+ assert api_key_scheme.name == "X-API-KEY"
+ assert api_key_scheme.description == "API Key"
+ assert api_key_scheme.in_.value == "header"
+
+
+def test_openapi_security_scheme_http():
+ app = APIGatewayRestResolver()
+
+ @app.get("/")
+ def handler():
+ raise NotImplementedError()
+
+ schema = app.get_openapi_schema(
+ security_schemes={
+ "bearerAuth": HTTPBearer(
+ description="JWT Token",
+ bearerFormat="JWT",
+ ),
+ },
+ )
+
+ security_schemes = schema.components.securitySchemes
+ assert security_schemes is not None
+
+ assert "bearerAuth" in security_schemes
+ http_scheme = security_schemes["bearerAuth"]
+ assert http_scheme.type_.value == "http"
+ assert http_scheme.scheme == "bearer"
+ assert http_scheme.bearerFormat == "JWT"
+
+
+def test_openapi_security_scheme_oauth2():
+ app = APIGatewayRestResolver()
+
+ @app.get("/")
+ def handler():
+ raise NotImplementedError()
+
+ schema = app.get_openapi_schema(
+ security_schemes={
+ "oauth2": OAuth2(
+ flows=OAuthFlows(
+ implicit=OAuthFlowImplicit(
+ authorizationUrl="https://example.com/oauth2/authorize",
+ ),
+ ),
+ ),
+ },
+ )
+
+ security_schemes = schema.components.securitySchemes
+ assert security_schemes is not None
+
+ assert "oauth2" in security_schemes
+ oauth2_scheme = security_schemes["oauth2"]
+ assert oauth2_scheme.type_.value == "oauth2"
+ assert oauth2_scheme.flows.implicit.authorizationUrl == "https://example.com/oauth2/authorize"
+
+
+def test_openapi_security_scheme_open_id_connect():
+ app = APIGatewayRestResolver()
+
+ @app.get("/")
+ def handler():
+ raise NotImplementedError()
+
+ schema = app.get_openapi_schema(
+ security_schemes={
+ "openIdConnect": OpenIdConnect(
+ openIdConnectUrl="https://example.com/oauth2/authorize",
+ ),
+ },
+ )
+
+ security_schemes = schema.components.securitySchemes
+ assert security_schemes is not None
+
+ assert "openIdConnect" in security_schemes
+ open_id_connect_scheme = security_schemes["openIdConnect"]
+ assert open_id_connect_scheme.type_.value == "openIdConnect"
+ assert open_id_connect_scheme.openIdConnectUrl == "https://example.com/oauth2/authorize"
diff --git a/tests/functional/event_handler/test_openapi_swagger.py b/tests/functional/event_handler/test_openapi_swagger.py
index 82c9b4874d0..11ec0cf24da 100644
--- a/tests/functional/event_handler/test_openapi_swagger.py
+++ b/tests/functional/event_handler/test_openapi_swagger.py
@@ -1,7 +1,11 @@
import json
+import warnings
from typing import Dict
+import pytest
+
from aws_lambda_powertools.event_handler import APIGatewayRestResolver
+from aws_lambda_powertools.event_handler.openapi.swagger_ui import OAuth2Config
from tests.functional.utils import load_event
LOAD_GW_EVENT = load_event("apiGatewayProxyEvent.json")
@@ -112,3 +116,26 @@ def test_openapi_swagger_with_rest_api_stage():
result = app(event, {})
assert result["statusCode"] == 200
assert "ui.specActions.updateUrl('/prod/swagger?format=json')" in result["body"]
+
+
+def test_openapi_swagger_oauth2_without_powertools_dev():
+ with pytest.raises(ValueError) as exc:
+ OAuth2Config(app_name="OAuth2 app", client_id="client_id", client_secret="verysecret")
+
+ assert "cannot use client_secret without POWERTOOLS_DEV mode" in str(exc.value)
+
+
+def test_openapi_swagger_oauth2_with_powertools_dev(monkeypatch):
+ monkeypatch.setenv("POWERTOOLS_DEV", "1")
+
+ with warnings.catch_warnings(record=True) as w:
+ warnings.simplefilter("default")
+
+ OAuth2Config(app_name="OAuth2 app", client_id="client_id", client_secret="verysecret")
+
+ assert str(w[-1].message) == (
+ "OAuth2Config is using client_secret and POWERTOOLS_DEV is set. This reveals sensitive information. "
+ "DO NOT USE THIS OUTSIDE LOCAL DEVELOPMENT"
+ )
+
+ monkeypatch.delenv("POWERTOOLS_DEV")
diff --git a/tests/functional/idempotency/test_idempotency.py b/tests/functional/idempotency/test_idempotency.py
index 2591cf8e043..d33469d680f 100644
--- a/tests/functional/idempotency/test_idempotency.py
+++ b/tests/functional/idempotency/test_idempotency.py
@@ -1,7 +1,8 @@
import copy
import datetime
import warnings
-from unittest.mock import MagicMock
+from typing import Any
+from unittest.mock import MagicMock, Mock
import jmespath
import pytest
@@ -240,6 +241,39 @@ def lambda_handler(event, context):
stubber.deactivate()
+@pytest.mark.parametrize("idempotency_config", [{"use_local_cache": False}, {"use_local_cache": True}], indirect=True)
+def test_idempotent_lambda_expired(
+ idempotency_config: IdempotencyConfig,
+ persistence_store: DynamoDBPersistenceLayer,
+ lambda_apigw_event,
+ lambda_response,
+ expected_params_update_item,
+ expected_params_put_item,
+ lambda_context,
+):
+ """
+ Test idempotent decorator when lambda is called with an event it successfully handled already, but outside of the
+ expiry window
+ """
+
+ stubber = stub.Stubber(persistence_store.client)
+
+ ddb_response = {}
+
+ stubber.add_response("put_item", ddb_response, expected_params_put_item)
+ stubber.add_response("update_item", ddb_response, expected_params_update_item)
+ stubber.activate()
+
+ @idempotent(config=idempotency_config, persistence_store=persistence_store)
+ def lambda_handler(event, context):
+ return lambda_response
+
+ lambda_handler(lambda_apigw_event, lambda_context)
+
+ stubber.assert_no_pending_responses()
+ stubber.deactivate()
+
+
@pytest.mark.parametrize("idempotency_config", [{"use_local_cache": True}], indirect=True)
def test_idempotent_lambda_first_execution_cached(
idempotency_config: IdempotencyConfig,
@@ -324,39 +358,6 @@ def lambda_handler(event, context):
stubber.deactivate()
-@pytest.mark.parametrize("idempotency_config", [{"use_local_cache": False}, {"use_local_cache": True}], indirect=True)
-def test_idempotent_lambda_expired(
- idempotency_config: IdempotencyConfig,
- persistence_store: DynamoDBPersistenceLayer,
- lambda_apigw_event,
- lambda_response,
- expected_params_update_item,
- expected_params_put_item,
- lambda_context,
-):
- """
- Test idempotent decorator when lambda is called with an event it successfully handled already, but outside of the
- expiry window
- """
-
- stubber = stub.Stubber(persistence_store.client)
-
- ddb_response = {}
-
- stubber.add_response("put_item", ddb_response, expected_params_put_item)
- stubber.add_response("update_item", ddb_response, expected_params_update_item)
- stubber.activate()
-
- @idempotent(config=idempotency_config, persistence_store=persistence_store)
- def lambda_handler(event, context):
- return lambda_response
-
- lambda_handler(lambda_apigw_event, lambda_context)
-
- stubber.assert_no_pending_responses()
- stubber.deactivate()
-
-
@pytest.mark.parametrize("idempotency_config", [{"use_local_cache": False}, {"use_local_cache": True}], indirect=True)
def test_idempotent_lambda_exception(
idempotency_config: IdempotencyConfig,
@@ -1986,3 +1987,89 @@ def lambda_handler(event, context):
# THEN we should not cache a transaction that failed validation
assert cache_spy.call_count == 0
+
+
+@pytest.mark.parametrize("idempotency_config", [{"use_local_cache": False}, {"use_local_cache": True}], indirect=True)
+def test_responsehook_lambda_first_execution(
+ idempotency_config: IdempotencyConfig,
+ persistence_store: DynamoDBPersistenceLayer,
+ lambda_apigw_event,
+ expected_params_update_item,
+ expected_params_put_item,
+ lambda_response,
+ lambda_context,
+):
+ """
+ Test response_hook is not called for the idempotent decorator when lambda is executed
+ with an event with a previously unknown event key
+ """
+
+ idempotent_response_hook = Mock()
+
+ stubber = stub.Stubber(persistence_store.client)
+ ddb_response = {}
+
+ stubber.add_response("put_item", ddb_response, expected_params_put_item)
+ stubber.add_response("update_item", ddb_response, expected_params_update_item)
+ stubber.activate()
+
+ idempotency_config.response_hook = idempotent_response_hook
+
+ @idempotent(config=idempotency_config, persistence_store=persistence_store)
+ def lambda_handler(event, context):
+ return lambda_response
+
+ lambda_handler(lambda_apigw_event, lambda_context)
+
+ stubber.assert_no_pending_responses()
+ stubber.deactivate()
+
+ assert not idempotent_response_hook.called
+
+
+@pytest.mark.parametrize("idempotency_config", [{"use_local_cache": False}, {"use_local_cache": True}], indirect=True)
+def test_idempotent_lambda_already_completed_response_hook_is_called(
+ idempotency_config: IdempotencyConfig,
+ persistence_store: DynamoDBPersistenceLayer,
+ lambda_apigw_event,
+ timestamp_future,
+ hashed_idempotency_key,
+ serialized_lambda_response,
+ deserialized_lambda_response,
+ lambda_context,
+):
+ """
+ Test idempotent decorator where event with matching event key has already been successfully processed
+ """
+
+ def idempotent_response_hook(response: Any, idempotent_data: DataRecord) -> Any:
+ """Modify the response provided by adding a new key"""
+ response["idempotent_response"] = True
+
+ return response
+
+ idempotency_config.response_hook = idempotent_response_hook
+
+ stubber = stub.Stubber(persistence_store.client)
+ ddb_response = {
+ "Item": {
+ "id": {"S": hashed_idempotency_key},
+ "expiration": {"N": timestamp_future},
+ "data": {"S": serialized_lambda_response},
+ "status": {"S": "COMPLETED"},
+ },
+ }
+ stubber.add_client_error("put_item", "ConditionalCheckFailedException", modeled_fields=ddb_response)
+ stubber.activate()
+
+ @idempotent(config=idempotency_config, persistence_store=persistence_store)
+ def lambda_handler(event, context):
+ raise Exception
+
+ lambda_resp = lambda_handler(lambda_apigw_event, lambda_context)
+
+ # Then idempotent_response value will be added to the response
+ assert lambda_resp["idempotent_response"]
+
+ stubber.assert_no_pending_responses()
+ stubber.deactivate()
diff --git a/tests/functional/test_logger.py b/tests/functional/test_logger.py
index d9e3b8d4e37..7aa4037cb9c 100644
--- a/tests/functional/test_logger.py
+++ b/tests/functional/test_logger.py
@@ -9,10 +9,9 @@
import string
import sys
import warnings
-from ast import Dict
from collections import namedtuple
from datetime import datetime, timezone
-from typing import Any, Callable, Iterable, List, Optional, Union
+from typing import Any, Callable, Dict, Iterable, List, Optional, Union
import pytest
@@ -606,6 +605,45 @@ def test_logger_append_remove_keys(stdout, service_name):
assert (extra_keys.items() <= keys_removed_log.items()) is False
+def test_logger_append_and_show_current_keys(stdout, service_name):
+ # GIVEN a Logger is initialized
+ logger = Logger(service=service_name, stream=stdout)
+ extra_keys = {"request_id": "id", "context": "value"}
+
+ # WHEN keys are updated
+ logger.append_keys(**extra_keys)
+
+ # THEN appended keys must be present in logger
+ current_keys = logger.get_current_keys()
+ assert "request_id" in current_keys
+ assert "context" in current_keys
+
+
+def test_logger_formatter_without_get_current_keys_method(stdout, service_name):
+ class CustomFormatter(BasePowertoolsFormatter):
+ def append_keys(self, **additional_keys):
+ # Fake method
+ pass
+
+ def clear_state(self) -> None:
+ # Fake method
+ pass
+
+ custom_formater = CustomFormatter()
+
+ # GIVEN a Logger is initialized with a Logger Formatter from scratch
+
+ logger = Logger(service=service_name, stream=stdout, logger_formatter=custom_formater)
+ extra_keys = {"request_id": "id", "context": "value"}
+
+ # WHEN keys are updated
+ logger.append_keys(**extra_keys)
+
+ # THEN the appended keys will not persist
+ # unless the customer implements the required methods and persists the log_format
+ assert logger.get_current_keys() == {}
+
+
def test_logger_custom_formatter(stdout, service_name, lambda_context):
class CustomFormatter(BasePowertoolsFormatter):
custom_format = {}
diff --git a/tests/unit/data_classes/test_active_mq_event.py b/tests/unit/data_classes/test_active_mq_event.py
index 885718d50ad..f4e835edce9 100644
--- a/tests/unit/data_classes/test_active_mq_event.py
+++ b/tests/unit/data_classes/test_active_mq_event.py
@@ -27,7 +27,7 @@ def test_active_mq_event():
assert message.broker_in_time == raw_event["messages"][0]["brokerInTime"]
assert message.broker_out_time == raw_event["messages"][0]["brokerOutTime"]
assert message.properties.get("testKey") == raw_event["messages"][0]["properties"]["testKey"]
- assert message.destination_physicalname == raw_event["messages"][0]["destination"]["physicalname"]
+ assert message.destination_physicalname == raw_event["messages"][0]["destination"]["physicalName"]
assert message.delivery_mode is None
assert message.correlation_id is None
assert message.reply_to is None
diff --git a/tests/unit/data_classes/test_sqs_event.py b/tests/unit/data_classes/test_sqs_event.py
index 620ba802e95..b1664924c5e 100644
--- a/tests/unit/data_classes/test_sqs_event.py
+++ b/tests/unit/data_classes/test_sqs_event.py
@@ -44,6 +44,33 @@ def test_seq_trigger_event():
assert record_2.json_body == {"message": "foo1"}
+def test_sqs_dlq_trigger_event():
+ raw_event = load_event("sqsDlqTriggerEvent.json")
+ parsed_event = SQSEvent(raw_event)
+
+ records = list(parsed_event.records)
+ record = records[0]
+ attributes = record.attributes
+
+ assert len(records) == 1
+ assert record.message_id == raw_event["Records"][0]["messageId"]
+ assert record.receipt_handle == raw_event["Records"][0]["receiptHandle"]
+ assert record.body == raw_event["Records"][0]["body"]
+ assert attributes.aws_trace_header is None
+ raw_attributes = raw_event["Records"][0]["attributes"]
+ assert attributes.approximate_receive_count == raw_attributes["ApproximateReceiveCount"]
+ assert attributes.sent_timestamp == raw_attributes["SentTimestamp"]
+ assert attributes.sender_id == raw_attributes["SenderId"]
+ assert attributes.approximate_first_receive_timestamp == raw_attributes["ApproximateFirstReceiveTimestamp"]
+ assert attributes.sequence_number is None
+ assert attributes.message_group_id is None
+ assert attributes.message_deduplication_id is None
+ assert (
+ attributes.dead_letter_queue_source_arn
+ == raw_attributes["DeadLetterQueueSourceArn"]
+ )
+
+
def test_decode_nested_s3_event():
raw_event = load_event("s3SqsEvent.json")
event = SQSEvent(raw_event)
diff --git a/tests/unit/parser/test_sqs.py b/tests/unit/parser/test_sqs.py
index 0d948acb39d..acae8c1093f 100644
--- a/tests/unit/parser/test_sqs.py
+++ b/tests/unit/parser/test_sqs.py
@@ -1,6 +1,7 @@
import pytest
from aws_lambda_powertools.utilities.parser import ValidationError, envelopes, parse
+from aws_lambda_powertools.utilities.parser.models import SqsModel
from tests.functional.utils import load_event
from tests.functional.validator.conftest import sqs_event # noqa: F401
from tests.unit.parser.schemas import MyAdvancedSqsBusiness, MySqsBusiness
@@ -85,3 +86,38 @@ def test_handle_sqs_trigger_event_no_envelope():
assert test_attr.stringValue == message_attributes_raw["stringValue"]
assert test_attr.binaryValue == message_attributes_raw["binaryValue"]
assert test_attr.dataType == message_attributes_raw["dataType"]
+
+
+def test_sqs_dlq_trigger_event():
+ raw_event = load_event("sqsDlqTriggerEvent.json")
+ parsed_event = SqsModel(**raw_event)
+
+ records = parsed_event.Records
+ record = records[0]
+ raw_record = raw_event["Records"][0]
+ assert len(records) == 1
+
+ assert record.messageId == raw_record["messageId"]
+ assert record.receiptHandle == raw_record["receiptHandle"]
+ assert record.body == raw_record["body"]
+ assert record.eventSource == raw_record["eventSource"]
+ assert record.eventSourceARN == raw_record["eventSourceARN"]
+ assert record.awsRegion == raw_record["awsRegion"]
+ assert record.md5OfBody == raw_record["md5OfBody"]
+
+ attributes = record.attributes
+ assert attributes.AWSTraceHeader is None
+ assert attributes.ApproximateReceiveCount == raw_record["attributes"]["ApproximateReceiveCount"]
+ assert attributes.SequenceNumber is None
+ assert attributes.MessageGroupId is None
+ assert attributes.MessageDeduplicationId is None
+ assert attributes.SenderId == raw_record["attributes"]["SenderId"]
+ convert_time = int(round(attributes.ApproximateFirstReceiveTimestamp.timestamp() * 1000))
+ assert convert_time == int(raw_record["attributes"]["ApproximateFirstReceiveTimestamp"])
+ convert_time = int(round(attributes.SentTimestamp.timestamp() * 1000))
+ assert convert_time == int(raw_record["attributes"]["SentTimestamp"])
+
+ assert (
+ attributes.DeadLetterQueueSourceArn
+ == raw_record["attributes"]["DeadLetterQueueSourceArn"]
+ )