diff --git a/.github/workflows/build_changelog.yml b/.github/workflows/build_changelog.yml
index b14c38c39a5..ffa6163ca03 100644
--- a/.github/workflows/build_changelog.yml
+++ b/.github/workflows/build_changelog.yml
@@ -17,8 +17,8 @@ on:
# branches:
# - develop
schedule:
- # Note: run daily at 7am UTC time until upstream git-chlog uses stable sorting
- - cron: "0 7 * * *"
+ # Note: run daily at 10am UTC time until upstream git-chlog uses stable sorting
+ - cron: "0 10 * * *"
permissions:
contents: read
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 2d51f3032f1..d49fb8749eb 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -28,7 +28,7 @@ jobs:
steps:
- name: Checkout repository
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
+ uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml
index c6eb377eb1b..24a0dd11f57 100644
--- a/.github/workflows/dependency-review.yml
+++ b/.github/workflows/dependency-review.yml
@@ -17,6 +17,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: 'Checkout Repository'
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
+ uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: 'Dependency Review'
- uses: actions/dependency-review-action@0c155c5e8556a497adf53f2c18edabf945ed8e70 # v4.3.2
+ uses: actions/dependency-review-action@5a2ce3f5b92ee19cbb1541a4984c76d921601d7c # v4.3.4
diff --git a/.github/workflows/label_pr_on_title.yml b/.github/workflows/label_pr_on_title.yml
index 78432a4a53a..c17e3740586 100644
--- a/.github/workflows/label_pr_on_title.yml
+++ b/.github/workflows/label_pr_on_title.yml
@@ -50,7 +50,7 @@ jobs:
pull-requests: write # label respective PR
steps:
- name: Checkout repository
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
+ uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: "Label PR based on title"
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
env:
diff --git a/.github/workflows/on_closed_issues.yml b/.github/workflows/on_closed_issues.yml
index 61a14b028d4..61f4d20460d 100644
--- a/.github/workflows/on_closed_issues.yml
+++ b/.github/workflows/on_closed_issues.yml
@@ -21,7 +21,7 @@ jobs:
permissions:
issues: write # comment on issues
steps:
- - uses: aws-actions/closed-issue-message@8b6324312193476beecf11f8e8539d73a3553bf4
+ - uses: aws-actions/closed-issue-message@80edfc24bdf1283400eb04d20a8a605ae8bf7d48
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
message: |
diff --git a/.github/workflows/on_label_added.yml b/.github/workflows/on_label_added.yml
index d5ead643063..45bc470bf4e 100644
--- a/.github/workflows/on_label_added.yml
+++ b/.github/workflows/on_label_added.yml
@@ -47,7 +47,7 @@ jobs:
permissions:
pull-requests: write # comment on PR
steps:
- - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
+ - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
# Maintenance: Persist state per PR as an artifact to avoid spam on label add
- name: "Suggest split large Pull Request"
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
diff --git a/.github/workflows/on_merged_pr.yml b/.github/workflows/on_merged_pr.yml
index b1d389f0eb8..fa221b9a4bc 100644
--- a/.github/workflows/on_merged_pr.yml
+++ b/.github/workflows/on_merged_pr.yml
@@ -49,7 +49,7 @@ jobs:
issues: write # label issue with pending-release
if: needs.get_pr_details.outputs.prIsMerged == 'true'
steps:
- - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
+ - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: "Label PR related issue for release"
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
env:
diff --git a/.github/workflows/on_opened_pr.yml b/.github/workflows/on_opened_pr.yml
index c7f1965bd45..2175e167140 100644
--- a/.github/workflows/on_opened_pr.yml
+++ b/.github/workflows/on_opened_pr.yml
@@ -47,7 +47,7 @@ jobs:
needs: get_pr_details
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
+ - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: "Ensure related issue is present"
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
env:
@@ -66,7 +66,7 @@ jobs:
permissions:
pull-requests: write # label and comment on PR if missing acknowledge section (requirement)
steps:
- - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
+ - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: "Ensure acknowledgement section is present"
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
env:
diff --git a/.github/workflows/ossf_scorecard.yml b/.github/workflows/ossf_scorecard.yml
index 7baaef518ad..7c8b9280e22 100644
--- a/.github/workflows/ossf_scorecard.yml
+++ b/.github/workflows/ossf_scorecard.yml
@@ -22,12 +22,12 @@ jobs:
steps:
- name: "Checkout code"
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
+ uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
persist-credentials: false
- name: "Run analysis"
- uses: ossf/scorecard-action@dc50aa9510b46c811795eb24b2f1ba02a914e534 # v2.3.3
+ uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0
with:
results_file: results.sarif
results_format: sarif
@@ -35,7 +35,7 @@ jobs:
repo_token: ${{ secrets.SCORECARD_TOKEN }} # read-only fine-grained token to read branch protection settings
- name: "Upload results"
- uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
+ uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0
with:
name: SARIF file
path: results.sarif
diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml
new file mode 100644
index 00000000000..24b56da85cd
--- /dev/null
+++ b/.github/workflows/pre-release.yml
@@ -0,0 +1,275 @@
+name: Pre-Release
+
+# PRE-RELEASE PROCESS
+#
+# === Automated activities ===
+#
+# 1. [Seal] Bump to release version and export source code with integrity hash
+# 2. [Quality check] Restore sealed source code, run tests, linting, security and complexity base line
+# 3. [Build] Restore sealed source code, create and export hashed build artifact for PyPi release (wheel, tarball)
+# 4. [Provenance] Generates provenance for build, signs attestation with GitHub OIDC claims to confirm it came from this release pipeline, commit, org, repo, branch, hash, etc.
+# 5. [Release] Restore built artifact, and publish package to PyPi prod repository
+# 6. [PR to bump version] Restore sealed source code, and create a PR to update trunk with latest released project metadata
+
+# NOTE
+#
+# See MAINTAINERS.md "Releasing a new version" for release mechanisms
+#
+# Every job is isolated and starts a new fresh container.
+
+env:
+ RELEASE_COMMIT: ${{ github.sha }}
+
+on:
+ workflow_dispatch:
+ inputs:
+ skip_code_quality:
+ description: "Skip tests, linting, and baseline. Only use if release fail for reasons beyond our control and you need a quick release."
+ default: false
+ type: boolean
+ required: false
+ skip_pypi:
+ description: "Skip publishing to PyPi. Used for testing release steps."
+ default: false
+ type: boolean
+ required: false
+ schedule:
+ # Note: run daily on weekdays at 8am UTC time
+ - cron: "0 8 * * 1-5"
+
+permissions:
+ contents: read
+
+jobs:
+
+ # This job bumps the package version to the pre-release version
+ # creates an integrity hash from the source code
+ # uploads the artifact with the integrity hash as the key name
+ # so subsequent jobs can restore from a trusted point in time to prevent tampering
+ seal:
+ # ignore forks
+ if: github.repository == 'aws-powertools/powertools-lambda-python'
+
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ outputs:
+ integrity_hash: ${{ steps.seal_source_code.outputs.integrity_hash }}
+ artifact_name: ${{ steps.seal_source_code.outputs.artifact_name }}
+ RELEASE_VERSION: ${{ steps.release_version.outputs.RELEASE_VERSION }}
+ steps:
+ # NOTE: Different from prod release, we need both poetry and source code available in earlier steps to bump and verify.
+
+ # We use a pinned version of Poetry to be certain it won't modify source code before we create a hash
+ - name: Install poetry
+ run: |
+ pipx install git+https://github.com/python-poetry/poetry@68b88e5390720a3dd84f02940ec5200bfce39ac6 # v1.5.0
+ pipx inject poetry git+https://github.com/monim67/poetry-bumpversion@315fe3324a699fa12ec20e202eb7375d4327d1c4 # v0.3.1
+
+ - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
+ with:
+ ref: ${{ env.RELEASE_COMMIT }}
+
+ - name: Bump and export release version
+ id: release_version
+ run: |
+ RELEASE_VERSION="$(poetry version prerelease --short | head -n1 | tr -d '\n')"
+
+ echo "RELEASE_VERSION=${RELEASE_VERSION}" >> "$GITHUB_OUTPUT"
+
+ - name: Verifies pre-release version semantics
+ # verify pre-release semantics before proceeding to avoid versioning pollution
+ # e.g., 2.40.0a1 and 2.40.0b2 are valid while 2.40.0 is not
+ # NOTE. we do it in a separate step to handle edge cases like
+ # `poetry` CLI uses immutable install, versioning behaviour could change even in a minor version (we had breaking changes before)
+ # a separate step allows us to pinpoint what happened (before/after)
+ run: |
+ if [[ ! "$RELEASE_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+[a-b].*$ ]]; then
+ echo "Version $VERSION doesn't look like a pre-release version; aborting"
+ exit 1
+ fi
+ env:
+ RELEASE_VERSION: ${{ steps.release_version.outputs.RELEASE_VERSION}}
+
+ - name: Seal and upload
+ id: seal_source_code
+ uses: ./.github/actions/seal
+ with:
+ artifact_name_prefix: "source"
+
+ # This job runs our automated test suite, complexity and security baselines
+ # it ensures previously merged have been tested as part of the pull request process
+ #
+ # NOTE
+ #
+ # we don't upload the artifact after testing to prevent any tampering of our source code dependencies
+ quality_check:
+ needs: seal
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ steps:
+ # NOTE: we need actions/checkout to configure git first (pre-commit hooks in make dev)
+ - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
+ with:
+ ref: ${{ env.RELEASE_COMMIT }}
+
+ - name: Restore sealed source code
+ uses: ./.github/actions/seal-restore
+ with:
+ integrity_hash: ${{ needs.seal.outputs.integrity_hash }}
+ artifact_name: ${{ needs.seal.outputs.artifact_name }}
+
+ - name: Debug cache restore
+ run: cat pyproject.toml
+
+ - name: Install poetry
+ run: pipx install git+https://github.com/python-poetry/poetry@68b88e5390720a3dd84f02940ec5200bfce39ac6 # v1.5.0
+ - name: Set up Python
+ uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0
+ with:
+ python-version: "3.12"
+ cache: "poetry"
+ - name: Install dependencies
+ run: make dev
+ - name: Run all tests, linting and baselines
+ run: make pr
+
+ # This job creates a release artifact (tar.gz, wheel)
+ # it checks out code from release commit for custom actions to work
+ # then restores the sealed source code (overwrites any potential tampering)
+ # it's done separately from release job to enforce least privilege.
+ # We export just the final build artifact for release
+ build:
+ runs-on: ubuntu-latest
+ needs: [quality_check, seal]
+ permissions:
+ contents: read
+ outputs:
+ integrity_hash: ${{ steps.seal_build.outputs.integrity_hash }}
+ artifact_name: ${{ steps.seal_build.outputs.artifact_name }}
+ attestation_hashes: ${{ steps.encoded_hash.outputs.attestation_hashes }}
+ steps:
+ # NOTE: we need actions/checkout to configure git first (pre-commit hooks in make dev)
+ - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
+ with:
+ ref: ${{ env.RELEASE_COMMIT }}
+
+ - name: Restore sealed source code
+ uses: ./.github/actions/seal-restore
+ with:
+ integrity_hash: ${{ needs.seal.outputs.integrity_hash }}
+ artifact_name: ${{ needs.seal.outputs.artifact_name }}
+
+ - name: Install poetry
+ run: pipx install git+https://github.com/python-poetry/poetry@68b88e5390720a3dd84f02940ec5200bfce39ac6 # v1.5.0
+ - name: Set up Python
+ uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0
+ with:
+ python-version: "3.12"
+ cache: "poetry"
+
+ - name: Build python package and wheel
+ run: poetry build
+
+ - name: Seal and upload
+ id: seal_build
+ uses: ./.github/actions/seal
+ with:
+ artifact_name_prefix: "build"
+ files: "dist/"
+
+ # NOTE: SLSA retraces our build to its artifact to ensure it wasn't tampered
+ # coupled with GitHub OIDC, SLSA can then confidently sign it came from this release pipeline+commit+branch+org+repo+actor+integrity hash
+ - name: Create attestation encoded hash for provenance
+ id: encoded_hash
+ working-directory: dist
+ run: echo "attestation_hashes=$(sha256sum ./* | base64 -w0)" >> "$GITHUB_OUTPUT"
+
+ # This job creates a provenance file that describes how our release was built (all steps)
+ # after it verifies our build is reproducible within the same pipeline
+ # it confirms that its own software and the CI build haven't been tampered with (Trust but verify)
+ # lastly, it creates and sign an attestation (multiple.intoto.jsonl) that confirms
+ # this build artifact came from this GitHub org, branch, actor, commit ID, inputs that triggered this pipeline, and matches its integrity hash
+ # NOTE: supply chain threats review (we protect against all of them now): https://slsa.dev/spec/v1.0/threats-overview
+ provenance:
+ needs: [seal, build]
+ permissions:
+ contents: write # nested job explicitly require despite upload assets being set to false
+ actions: read # To read the workflow path.
+ id-token: write # To sign the provenance.
+ # NOTE: provenance fails if we use action pinning... it's a Github limitation
+ # because SLSA needs to trace & attest it came from a given branch; pinning doesn't expose that information
+ # https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/generic/README.md#referencing-the-slsa-generator
+ uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.0.0
+ with:
+ base64-subjects: ${{ needs.build.outputs.attestation_hashes }}
+ upload-assets: false # we upload its attestation in create_tag job, otherwise it creates a new release
+
+ # This job uses release artifact to publish to PyPi
+ # it exchanges JWT tokens with GitHub to obtain PyPi credentials
+ # since it's already registered as a Trusted Publisher.
+ # It uses the sealed build artifact (.whl, .tar.gz) to release it
+ release:
+ needs: [build, seal, provenance]
+ environment: pre-release
+ runs-on: ubuntu-latest
+ permissions:
+ id-token: write # OIDC for PyPi Trusted Publisher feature
+ env:
+ RELEASE_VERSION: ${{ needs.seal.outputs.RELEASE_VERSION }}
+ steps:
+ # NOTE: we need actions/checkout in order to use our local actions (e.g., ./.github/actions)
+ - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
+ with:
+ ref: ${{ env.RELEASE_COMMIT }}
+
+ - name: Restore sealed source code
+ uses: ./.github/actions/seal-restore
+ with:
+ integrity_hash: ${{ needs.build.outputs.integrity_hash }}
+ artifact_name: ${{ needs.build.outputs.artifact_name }}
+
+ - name: Upload to PyPi prod
+ if: ${{ !inputs.skip_pypi }}
+ uses: pypa/gh-action-pypi-publish@0ab0b79471669eb3a4d647e625009c62f9f3b241 # v1.10.1
+
+ # Creates a PR with the latest version we've just released
+ # since our trunk is protected against any direct pushes from automation
+ bump_version:
+ needs: [release, seal, provenance]
+ permissions:
+ contents: write # create-pr action creates a temporary branch
+ pull-requests: write # create-pr action creates a PR using the temporary branch
+ runs-on: ubuntu-latest
+ steps:
+ # NOTE: we need actions/checkout to authenticate and configure git first
+ - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
+ with:
+ ref: ${{ env.RELEASE_COMMIT }}
+
+ - name: Restore sealed source code
+ uses: ./.github/actions/seal-restore
+ with:
+ integrity_hash: ${{ needs.seal.outputs.integrity_hash }}
+ artifact_name: ${{ needs.seal.outputs.artifact_name }}
+
+ - name: Download provenance
+ uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
+ with:
+ name: ${{needs.provenance.outputs.provenance-name}}
+
+ - name: Update provenance
+ run: mkdir -p "${PROVENANCE_DIR}" && mv "${PROVENANCE_FILE}" "${PROVENANCE_DIR}/"
+ env:
+ PROVENANCE_FILE: ${{ needs.provenance.outputs.provenance-name }}
+ PROVENANCE_DIR: provenance/${{ needs.seal.outputs.RELEASE_VERSION}}
+
+ - name: Create PR
+ id: create-pr
+ uses: ./.github/actions/create-pr
+ with:
+ files: "pyproject.toml aws_lambda_powertools/shared/version.py provenance/"
+ temp_branch_prefix: "ci-bump"
+ pull_request_title: "chore(ci): new pre-release ${{ needs.seal.outputs.RELEASE_VERSION }}"
+ github_token: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/publish_v2_layer.yml b/.github/workflows/publish_v2_layer.yml
index 38c02983103..64fabcf2f55 100644
--- a/.github/workflows/publish_v2_layer.yml
+++ b/.github/workflows/publish_v2_layer.yml
@@ -88,7 +88,7 @@ jobs:
working-directory: ./layer
steps:
- name: checkout
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
+ uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
ref: ${{ env.RELEASE_COMMIT }}
@@ -101,11 +101,11 @@ jobs:
- name: Install poetry
run: pipx install git+https://github.com/python-poetry/poetry@68b88e5390720a3dd84f02940ec5200bfce39ac6 # v1.5.0
- name: Setup Node.js
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
+ uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
with:
node-version: "16.12"
- name: Setup python
- uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0
+ uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0
with:
python-version: "3.12"
cache: "pip"
@@ -117,14 +117,14 @@ jobs:
pip install --require-hashes -r requirements.txt
- name: Set up QEMU
- uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v2.0.0
+ uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v2.0.0
with:
platforms: arm64
# NOTE: we need QEMU to build Layer against a different architecture (e.g., ARM)
- name: Set up Docker Buildx
id: builder
- uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0
+ uses: docker/setup-buildx-action@988b5a0280414f521da01fcc63a27aeeb4b104db # v3.6.1
with:
install: true
driver: docker
@@ -146,7 +146,7 @@ jobs:
- name: zip output
run: zip -r cdk.out.zip cdk.out
- name: Archive CDK artifacts
- uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
+ uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0
with:
name: cdk-layer-artefact
path: layer/cdk.out.zip
@@ -247,7 +247,7 @@ jobs:
pages: none
steps:
- name: Checkout repository # reusable workflows start clean, so we need to checkout again
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
+ uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
ref: ${{ env.RELEASE_COMMIT }}
@@ -258,7 +258,7 @@ jobs:
artifact_name: ${{ inputs.source_code_artifact_name }}
- name: Download CDK layer artifacts
- uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
+ uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
with:
path: cdk-layer-stack
pattern: cdk-layer-stack-* # merge all Layer artifacts created per region earlier (reusable_deploy_v2_layer_stack.yml; step "Save Layer ARN artifact")
diff --git a/.github/workflows/publish_v3_layer.yml b/.github/workflows/publish_v3_layer.yml
index 9bc7c7bad87..ff71735e5de 100644
--- a/.github/workflows/publish_v3_layer.yml
+++ b/.github/workflows/publish_v3_layer.yml
@@ -91,7 +91,7 @@ jobs:
working-directory: ./layer_v3
steps:
- name: checkout
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
+ uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
ref: ${{ env.RELEASE_COMMIT }}
@@ -104,11 +104,11 @@ jobs:
- name: Install poetry
run: pipx install git+https://github.com/python-poetry/poetry@68b88e5390720a3dd84f02940ec5200bfce39ac6 # v1.5.0
- name: Setup Node.js
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
+ uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
with:
node-version: "18.20.4"
- name: Setup python
- uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0
+ uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0
with:
python-version: ${{ matrix.python-version }}
cache: "pip"
@@ -127,7 +127,7 @@ jobs:
- name: Set up Docker Buildx
id: builder
- uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0
+ uses: docker/setup-buildx-action@988b5a0280414f521da01fcc63a27aeeb4b104db # v3.6.1
with:
install: true
driver: docker
diff --git a/.github/workflows/quality_check.yml b/.github/workflows/quality_check.yml
index bdac2576bcc..b3fc858d567 100644
--- a/.github/workflows/quality_check.yml
+++ b/.github/workflows/quality_check.yml
@@ -52,11 +52,11 @@ jobs:
permissions:
contents: read # checkout code only
steps:
- - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
+ - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Install poetry
run: pipx install poetry
- name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0
+ uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0
with:
python-version: ${{ matrix.python-version }}
cache: "poetry"
@@ -68,13 +68,16 @@ jobs:
run: make mypy
- name: Test with pytest
run: make test
+ - name: Test dependencies with Nox
+ run: make test-dependencies
- name: Security baseline
run: make security-baseline
- name: Complexity baseline
run: make complexity-baseline
- name: Upload coverage to Codecov
- uses: codecov/codecov-action@125fc84a9a348dbcf27191600683ec096ec9021c # 4.4.1
+ uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # 4.5.0
with:
+ token: ${{ secrets.CODECOV_TOKEN }}
file: ./coverage.xml
env_vars: PYTHON
name: aws-lambda-powertools-python-codecov
diff --git a/.github/workflows/quality_check_pydanticv2.yml b/.github/workflows/quality_check_pydanticv2.yml
deleted file mode 100644
index 0022de58bbc..00000000000
--- a/.github/workflows/quality_check_pydanticv2.yml
+++ /dev/null
@@ -1,67 +0,0 @@
-name: Code quality - Pydanticv2
-
-# PROCESS
-#
-# 1. Install all dependencies and spin off containers for all supported Python versions
-# 2. Run code formatters and linters (various checks) for code standard
-# 3. Run static typing checker for potential bugs
-# 4. Run entire test suite for regressions except end-to-end (unit, functional, performance)
-# 5. Run static analysis (in addition to CodeQL) for common insecure code practices
-# 6. Run complexity baseline to avoid error-prone bugs and keep maintenance lower
-# 7. Collect and report on test coverage
-
-# USAGE
-#
-# Always triggered on new PRs, PR changes and PR merge.
-
-on:
- pull_request:
- paths:
- - "aws_lambda_powertools/**"
- - "tests/**"
- - "pyproject.toml"
- - "poetry.lock"
- - "mypy.ini"
- branches:
- - develop
- push:
- paths:
- - "aws_lambda_powertools/**"
- - "tests/**"
- - "pyproject.toml"
- - "poetry.lock"
- - "mypy.ini"
- branches:
- - develop
-
-permissions:
- contents: read
-
-jobs:
- quality_check:
- runs-on: ubuntu-latest
- strategy:
- max-parallel: 4
- matrix:
- python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
- env:
- PYTHON: "${{ matrix.python-version }}"
- permissions:
- contents: read # checkout code only
- steps:
- - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- - name: Install poetry
- run: pipx install poetry
- - name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0
- with:
- python-version: ${{ matrix.python-version }}
- cache: "poetry"
- - name: Replacing Pydantic v1 with v2 > 2.0.3
- run: |
- rm -rf poetry.lock
- poetry add "pydantic=^2.0.3"
- - name: Install dependencies
- run: make dev
- - name: Test with pytest
- run: make test-pydanticv2
diff --git a/.github/workflows/record_pr.yml b/.github/workflows/record_pr.yml
index 386ddf666c9..b0921d6fba3 100644
--- a/.github/workflows/record_pr.yml
+++ b/.github/workflows/record_pr.yml
@@ -46,14 +46,14 @@ jobs:
permissions:
contents: read # NOTE: treat as untrusted location
steps:
- - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
+ - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: "Extract PR details"
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
script: |
const script = require('.github/scripts/save_pr_details.js')
await script({github, context, core})
- - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
+ - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0
with:
name: pr
path: pr.txt
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index e2e9d2b7bbd..b3790e445f2 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -80,7 +80,7 @@ jobs:
RELEASE_VERSION="${RELEASE_TAG_VERSION:1}"
echo "RELEASE_VERSION=${RELEASE_VERSION}" >> "$GITHUB_OUTPUT"
- - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
+ - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
ref: ${{ env.RELEASE_COMMIT }}
@@ -115,7 +115,7 @@ jobs:
contents: read
steps:
# NOTE: we need actions/checkout to configure git first (pre-commit hooks in make dev)
- - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
+ - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
ref: ${{ env.RELEASE_COMMIT }}
@@ -131,7 +131,7 @@ jobs:
- name: Install poetry
run: pipx install git+https://github.com/python-poetry/poetry@68b88e5390720a3dd84f02940ec5200bfce39ac6 # v1.5.0
- name: Set up Python
- uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0
+ uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0
with:
python-version: "3.12"
cache: "poetry"
@@ -156,7 +156,7 @@ jobs:
attestation_hashes: ${{ steps.encoded_hash.outputs.attestation_hashes }}
steps:
# NOTE: we need actions/checkout to configure git first (pre-commit hooks in make dev)
- - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
+ - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
ref: ${{ env.RELEASE_COMMIT }}
@@ -169,7 +169,7 @@ jobs:
- name: Install poetry
run: pipx install git+https://github.com/python-poetry/poetry@68b88e5390720a3dd84f02940ec5200bfce39ac6 # v1.5.0
- name: Set up Python
- uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0
+ uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0
with:
python-version: "3.12"
cache: "poetry"
@@ -225,7 +225,7 @@ jobs:
RELEASE_VERSION: ${{ needs.seal.outputs.RELEASE_VERSION }}
steps:
# NOTE: we need actions/checkout in order to use our local actions (e.g., ./.github/actions)
- - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
+ - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
ref: ${{ env.RELEASE_COMMIT }}
@@ -237,12 +237,12 @@ jobs:
- name: Upload to PyPi prod
if: ${{ !inputs.skip_pypi }}
- uses: pypa/gh-action-pypi-publish@81e9d935c883d0b210363ab89cf05f3894778450 # v1.8.14
+ uses: pypa/gh-action-pypi-publish@0ab0b79471669eb3a4d647e625009c62f9f3b241 # v1.10.1
# PyPi test maintenance affected us numerous times, leaving for history purposes
# - name: Upload to PyPi test
# if: ${{ !inputs.skip_pypi }}
- # uses: pypa/gh-action-pypi-publish@81e9d935c883d0b210363ab89cf05f3894778450 # v1.8.14
+ # uses: pypa/gh-action-pypi-publish@0ab0b79471669eb3a4d647e625009c62f9f3b241 # v1.10.1
# with:
# repository-url: https://test.pypi.org/legacy/
@@ -259,7 +259,7 @@ jobs:
contents: write
steps:
# NOTE: we need actions/checkout to authenticate and configure git first
- - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
+ - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
ref: ${{ env.RELEASE_COMMIT }}
@@ -303,7 +303,7 @@ jobs:
runs-on: ubuntu-latest
steps:
# NOTE: we need actions/checkout to authenticate and configure git first
- - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
+ - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
ref: ${{ env.RELEASE_COMMIT }}
@@ -357,7 +357,7 @@ jobs:
env:
RELEASE_VERSION: ${{ needs.seal.outputs.RELEASE_VERSION }}
steps:
- - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
+ - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
ref: ${{ env.RELEASE_COMMIT }}
diff --git a/.github/workflows/reusable_deploy_v2_layer_stack.yml b/.github/workflows/reusable_deploy_v2_layer_stack.yml
index d097214ff00..8366f20997b 100644
--- a/.github/workflows/reusable_deploy_v2_layer_stack.yml
+++ b/.github/workflows/reusable_deploy_v2_layer_stack.yml
@@ -105,7 +105,7 @@ jobs:
- region: "ca-central-1"
has_arm64_support: "true"
- region: "ca-west-1"
- has_arm64_support: "false"
+ has_arm64_support: "true"
- region: "eu-central-1"
has_arm64_support: "true"
- region: "eu-central-2"
@@ -140,7 +140,7 @@ jobs:
has_arm64_support: "true"
steps:
- name: checkout
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
+ uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
ref: ${{ env.RELEASE_COMMIT }}
@@ -158,11 +158,11 @@ jobs:
aws-region: ${{ matrix.region }}
role-to-assume: ${{ secrets.AWS_LAYERS_ROLE_ARN }}
- name: Setup Node.js
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
+ uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
with:
node-version: "16.12"
- name: Setup python
- uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0
+ uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0
with:
python-version: "3.12"
cache: "pip"
@@ -180,7 +180,7 @@ jobs:
- name: install deps
run: poetry install
- name: Download artifact
- uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
+ uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
with:
name: ${{ inputs.artefact-name }}
path: layer
@@ -197,7 +197,7 @@ jobs:
cat cdk-layer-stack/${{ matrix.region }}-layer-version.txt
- name: Save Layer ARN artifact
if: ${{ inputs.stage == 'PROD' }}
- uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
+ uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0
with:
name: cdk-layer-stack-${{ matrix.region }}
path: ./layer/cdk-layer-stack/* # NOTE: upload-artifact does not inherit working-directory setting.
diff --git a/.github/workflows/reusable_deploy_v2_sar.yml b/.github/workflows/reusable_deploy_v2_sar.yml
index bb36afed5b8..cbbe2c53d03 100644
--- a/.github/workflows/reusable_deploy_v2_sar.yml
+++ b/.github/workflows/reusable_deploy_v2_sar.yml
@@ -79,7 +79,7 @@ jobs:
architecture: ["x86_64", "arm64"]
steps:
- name: checkout
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
+ uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
ref: ${{ env.RELEASE_COMMIT }}
@@ -111,11 +111,11 @@ jobs:
aws-region: ${{ env.AWS_REGION }}
role-to-assume: ${{ secrets.AWS_SAR_V2_ROLE_ARN }}
- name: Setup Node.js
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
+ uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
with:
node-version: ${{ env.NODE_VERSION }}
- name: Download artifact
- uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
+ uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
with:
name: ${{ inputs.artefact-name }}
- name: Unzip artefact
diff --git a/.github/workflows/reusable_export_pr_details.yml b/.github/workflows/reusable_export_pr_details.yml
index a7fc6c94f93..bae94335844 100644
--- a/.github/workflows/reusable_export_pr_details.yml
+++ b/.github/workflows/reusable_export_pr_details.yml
@@ -76,7 +76,7 @@ jobs:
prLabels: ${{ steps.prLabels.outputs.prLabels }}
steps:
- name: Checkout repository # in case caller workflow doesn't checkout thus failing with file not found
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
+ uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: "Download previously saved PR"
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
env:
@@ -112,4 +112,4 @@ jobs:
run: echo prIsMerged="$(jq -c '.pull_request.merged' "${FILENAME}")" >> "$GITHUB_OUTPUT"
- name: "Export Pull Request labels"
id: prLabels
- run: echo prLabels="$(jq -c '.labels' "${FILENAME}")" >> "$GITHUB_OUTPUT"
\ No newline at end of file
+ run: echo prLabels="$(jq -c '.labels' "${FILENAME}")" >> "$GITHUB_OUTPUT"
diff --git a/.github/workflows/reusable_publish_changelog.yml b/.github/workflows/reusable_publish_changelog.yml
index 20108fbf9ee..599c035ff3b 100644
--- a/.github/workflows/reusable_publish_changelog.yml
+++ b/.github/workflows/reusable_publish_changelog.yml
@@ -26,7 +26,7 @@ jobs:
pull-requests: write # create PR
steps:
- name: Checkout repository # reusable workflows start clean, so we need to checkout again
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
+ uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
fetch-depth: 0
- name: "Generate latest changelog"
diff --git a/.github/workflows/reusable_publish_docs.yml b/.github/workflows/reusable_publish_docs.yml
index 93ec97aa795..5e0f18f8d4d 100644
--- a/.github/workflows/reusable_publish_docs.yml
+++ b/.github/workflows/reusable_publish_docs.yml
@@ -44,14 +44,14 @@ jobs:
id-token: write # trade JWT token for AWS credentials in AWS Docs account
pages: write # uncomment if mike fails as we migrated to S3 hosting
steps:
- - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
+ - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
fetch-depth: 0
ref: ${{ inputs.git_ref }}
- name: Install poetry
run: pipx install poetry
- name: Set up Python
- uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0
+ uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0
with:
python-version: "3.12"
cache: "poetry"
diff --git a/.github/workflows/run-e2e-tests.yml b/.github/workflows/run-e2e-tests.yml
index 26df50e50bf..dd908d1f2b1 100644
--- a/.github/workflows/run-e2e-tests.yml
+++ b/.github/workflows/run-e2e-tests.yml
@@ -52,17 +52,17 @@ jobs:
if: ${{ github.actor != 'dependabot[bot]' && github.repository == 'aws-powertools/powertools-lambda-python' }}
steps:
- name: "Checkout"
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
+ uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Install poetry
run: pipx install poetry
- name: "Use Python"
- uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0
+ uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0
with:
python-version: ${{ matrix.version }}
architecture: "x64"
cache: "poetry"
- name: Setup Node.js
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
+ uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
with:
node-version: "20.10.0"
- name: Install CDK CLI
diff --git a/.github/workflows/secure_workflows.yml b/.github/workflows/secure_workflows.yml
index ca7e0c2c982..97530536261 100644
--- a/.github/workflows/secure_workflows.yml
+++ b/.github/workflows/secure_workflows.yml
@@ -30,9 +30,9 @@ jobs:
contents: read # checkout code and subsequently GitHub action workflows
steps:
- name: Checkout code
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
+ uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Ensure 3rd party workflows have SHA pinned
- uses: zgosalvez/github-actions-ensure-sha-pinned-actions@40e45e738b3cad2729f599d8afc6ed02184e1dbd # v3.0.5
+ uses: zgosalvez/github-actions-ensure-sha-pinned-actions@0901cf7b71c7ea6261ec69a3dc2bd3f9264f893e # v3.0.12
with:
allowlist: |
slsa-framework/slsa-github-generator
diff --git a/.gitpod.Dockerfile b/.gitpod.Dockerfile
index efa414f9ac7..9fa927ddac6 100644
--- a/.gitpod.Dockerfile
+++ b/.gitpod.Dockerfile
@@ -1,11 +1,11 @@
-# See here all gitpod images available: https://hub.docker.com/r/gitpod/workspace-python-3.9/tags
-# Current python version: 3.9.13
-FROM gitpod/workspace-python-3.9@sha256:de87d4ebffe8daab2e8fef96ec20497ae4f39e8dcb9dec1483d0be61ea78e8cd
+# See here all gitpod images available: https://hub.docker.com/r/gitpod/workspace-python-3.11/tags
+# Current python version: 3.11.9
+FROM gitpod/workspace-python-3.11@sha256:2d9a242844bef5710ab4622899a5254a0c59f0ac58c0d3ac998f749323f43951
WORKDIR /app
ADD . /app
# Installing pre-commit as system package and not user package. Git needs this to execute pre-commit hooks.
RUN export PIP_USER=no
-# v3.3.3
+# pre-commit v3.7.1
RUN python3 -m pip install --require-hashes -r .gitpod_requirements.txt
\ No newline at end of file
diff --git a/.gitpod_requirements.in b/.gitpod_requirements.in
index e88cdf05e74..b427b003fa9 100644
--- a/.gitpod_requirements.in
+++ b/.gitpod_requirements.in
@@ -1 +1 @@
-pre-commit==3.3.3
\ No newline at end of file
+pre-commit==3.7.1
\ No newline at end of file
diff --git a/.gitpod_requirements.txt b/.gitpod_requirements.txt
index db6274738d3..a9643d7dfdf 100644
--- a/.gitpod_requirements.txt
+++ b/.gitpod_requirements.txt
@@ -1,5 +1,5 @@
#
-# This file is autogenerated by pip-compile with Python 3.9
+# This file is autogenerated by pip-compile with Python 3.11
# by the following command:
#
# pip-compile --generate-hashes --output-file=.gitpod_requirements.txt .gitpod_requirements.in
@@ -28,9 +28,9 @@ platformdirs==3.8.0 \
--hash=sha256:b0cabcb11063d21a0b261d557acb0a9d2126350e63b70cdf7db6347baea456dc \
--hash=sha256:ca9ed98ce73076ba72e092b23d3c93ea6c4e186b3f1c3dad6edd98ff6ffcca2e
# via virtualenv
-pre-commit==3.3.3 \
- --hash=sha256:10badb65d6a38caff29703362271d7dca483d01da88f9d7e05d0b97171c136cb \
- --hash=sha256:a2256f489cd913d575c145132ae196fe335da32d91a8294b7afe6622335dd023
+pre-commit==3.7.1 \
+ --hash=sha256:8ca3ad567bc78a4972a3f1a477e94a79d4597e8140a6e0b651c5e33899c3654a \
+ --hash=sha256:fae36fd1d7ad7d6a5a1c0b0d5adb2ed1a3bda5a21bf6c3e5372073d7a11cd4c5
# via -r .gitpod_requirements.in
pyyaml==6.0 \
--hash=sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf \
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 859e0cbe7f1..090f4f07cb2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,41 +6,587 @@
## Bug Fixes
+* **event_handler:** correct URL for OpenAPI spec in Swagger UI ([#4930](https://github.com/aws-powertools/powertools-lambda-python/issues/4930))
+
+## Code Refactoring
+
+* **event_handler:** correct typo in exception docstring ([#4948](https://github.com/aws-powertools/powertools-lambda-python/issues/4948))
+
+## Documentation
+
+* **logger:** fix typo for the INFO log_level example ([#5039](https://github.com/aws-powertools/powertools-lambda-python/issues/5039))
+* **maintainers:** update the maintainers table ([#5148](https://github.com/aws-powertools/powertools-lambda-python/issues/5148))
+* **public_reference:** add Pushpay as a public reference ([#5036](https://github.com/aws-powertools/powertools-lambda-python/issues/5036))
+
+## Features
+
+* **layers:** add ARM64 support for ca-west-1 ([#4949](https://github.com/aws-powertools/powertools-lambda-python/issues/4949))
+
+## Maintenance
+
+* **ci:** new pre-release 2.43.2a5 ([#5024](https://github.com/aws-powertools/powertools-lambda-python/issues/5024))
+* **ci:** new pre-release 2.43.2a0 ([#4946](https://github.com/aws-powertools/powertools-lambda-python/issues/4946))
+* **ci:** new pre-release 2.43.2a1 ([#4970](https://github.com/aws-powertools/powertools-lambda-python/issues/4970))
+* **ci:** new pre-release 2.43.2a2 ([#4978](https://github.com/aws-powertools/powertools-lambda-python/issues/4978))
+* **ci:** allow sar beta app ([#5109](https://github.com/aws-powertools/powertools-lambda-python/issues/5109))
+* **ci:** add workflow dispatch for SAR ([#5108](https://github.com/aws-powertools/powertools-lambda-python/issues/5108))
+* **ci:** new pre-release 2.43.1a2 ([#4933](https://github.com/aws-powertools/powertools-lambda-python/issues/4933))
+* **ci:** new pre-release 2.43.2a3 ([#5003](https://github.com/aws-powertools/powertools-lambda-python/issues/5003))
+* **ci:** new pre-release 2.43.2a6 ([#5035](https://github.com/aws-powertools/powertools-lambda-python/issues/5035))
+* **ci:** new pre-release 2.43.2a4 ([#5014](https://github.com/aws-powertools/powertools-lambda-python/issues/5014))
+* **ci:** add temporary pipeline for v3 ([#5026](https://github.com/aws-powertools/powertools-lambda-python/issues/5026))
+* **deps:** bump squidfunk/mkdocs-material from `9919d6e` to `a73e4bb` in /docs ([#5022](https://github.com/aws-powertools/powertools-lambda-python/issues/5022))
+* **deps:** bump actions/upload-artifact from 4.3.6 to 4.4.0 ([#5099](https://github.com/aws-powertools/powertools-lambda-python/issues/5099))
+* **deps:** bump actions/setup-python from 5.1.1 to 5.2.0 ([#5100](https://github.com/aws-powertools/powertools-lambda-python/issues/5100))
+* **deps:** bump github.com/aws/aws-sdk-go-v2/service/lambda from 1.56.4 to 1.57.0 in /layer/scripts/layer-balancer in the layer-balancer group ([#5019](https://github.com/aws-powertools/powertools-lambda-python/issues/5019))
+* **deps:** bump pypa/gh-action-pypi-publish from 1.9.0 to 1.10.0 ([#5110](https://github.com/aws-powertools/powertools-lambda-python/issues/5110))
+* **deps:** bump squidfunk/mkdocs-material from `7132ca3` to `a2e3a31` in /docs ([#5111](https://github.com/aws-powertools/powertools-lambda-python/issues/5111))
+* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 3 updates ([#4997](https://github.com/aws-powertools/powertools-lambda-python/issues/4997))
+* **deps:** bump github.com/aws/aws-sdk-go-v2/service/lambda from 1.57.0 to 1.58.0 in /layer/scripts/layer-balancer in the layer-balancer group ([#5052](https://github.com/aws-powertools/powertools-lambda-python/issues/5052))
+* **deps:** bump pypa/gh-action-pypi-publish from 1.10.0 to 1.10.1 ([#5115](https://github.com/aws-powertools/powertools-lambda-python/issues/5115))
+* **deps:** bump docker/setup-qemu-action from 3.0.0 to 3.2.0 ([#5047](https://github.com/aws-powertools/powertools-lambda-python/issues/5047))
+* **deps:** bump actions/download-artifact from 4.1.7 to 4.1.8 ([#5050](https://github.com/aws-powertools/powertools-lambda-python/issues/5050))
+* **deps:** bump actions/setup-node from 4.0.2 to 4.0.3 ([#5048](https://github.com/aws-powertools/powertools-lambda-python/issues/5048))
+* **deps:** bump squidfunk/mkdocs-material from `a73e4bb` to `7132ca3` in /docs ([#5065](https://github.com/aws-powertools/powertools-lambda-python/issues/5065))
+* **deps:** bump cryptography from 42.0.8 to 43.0.1 ([#5119](https://github.com/aws-powertools/powertools-lambda-python/issues/5119))
+* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 3.0.10 to 3.0.11 ([#5081](https://github.com/aws-powertools/powertools-lambda-python/issues/5081))
+* **deps:** bump github.com/aws/aws-sdk-go-v2/config from 1.27.30 to 1.27.31 in /layer/scripts/layer-balancer in the layer-balancer group ([#5080](https://github.com/aws-powertools/powertools-lambda-python/issues/5080))
+* **deps:** bump actions/checkout from 4.1.6 to 4.1.7 ([#5049](https://github.com/aws-powertools/powertools-lambda-python/issues/5049))
+* **deps:** bump actions/upload-artifact from 4.3.3 to 4.3.6 ([#5051](https://github.com/aws-powertools/powertools-lambda-python/issues/5051))
+* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 2 updates ([#5124](https://github.com/aws-powertools/powertools-lambda-python/issues/5124))
+* **deps:** bump datadog-lambda from 6.97.0 to 6.98.0 ([#4938](https://github.com/aws-powertools/powertools-lambda-python/issues/4938))
+* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 3.0.11 to 3.0.12 ([#5143](https://github.com/aws-powertools/powertools-lambda-python/issues/5143))
+* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 2 updates ([#5062](https://github.com/aws-powertools/powertools-lambda-python/issues/5062))
+* **deps:** bump pypa/gh-action-pypi-publish from 1.8.14 to 1.9.0 ([#5059](https://github.com/aws-powertools/powertools-lambda-python/issues/5059))
+* **deps:** bump pydantic from 1.10.17 to 1.10.18 ([#5067](https://github.com/aws-powertools/powertools-lambda-python/issues/5067))
+* **deps:** bump actions/setup-python from 5.1.0 to 5.1.1 ([#5058](https://github.com/aws-powertools/powertools-lambda-python/issues/5058))
+* **deps:** bump github.com/aws/aws-sdk-go-v2/config from 1.27.29 to 1.27.30 in /layer/scripts/layer-balancer in the layer-balancer group ([#5070](https://github.com/aws-powertools/powertools-lambda-python/issues/5070))
+* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 3 updates ([#5114](https://github.com/aws-powertools/powertools-lambda-python/issues/5114))
+* **deps:** bump docker/setup-buildx-action from 3.3.0 to 3.6.1 ([#5060](https://github.com/aws-powertools/powertools-lambda-python/issues/5060))
+* **deps-dev:** bump pytest-asyncio from 0.23.8 to 0.24.0 ([#5055](https://github.com/aws-powertools/powertools-lambda-python/issues/5055))
+* **deps-dev:** bump aws-cdk-lib from 2.153.0 to 2.154.1 ([#5063](https://github.com/aws-powertools/powertools-lambda-python/issues/5063))
+* **deps-dev:** bump mkdocs-material from 9.5.32 to 9.5.33 ([#5066](https://github.com/aws-powertools/powertools-lambda-python/issues/5066))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.254 to 0.1.256 ([#5073](https://github.com/aws-powertools/powertools-lambda-python/issues/5073))
+* **deps-dev:** bump aws-cdk from 2.153.0 to 2.154.0 ([#5061](https://github.com/aws-powertools/powertools-lambda-python/issues/5061))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.253 to 0.1.254 ([#5057](https://github.com/aws-powertools/powertools-lambda-python/issues/5057))
+* **deps-dev:** bump aws-cdk-aws-lambda-python-alpha from 2.153.0a0 to 2.154.1a0 ([#5069](https://github.com/aws-powertools/powertools-lambda-python/issues/5069))
+* **deps-dev:** bump ruff from 0.6.1 to 0.6.2 ([#5056](https://github.com/aws-powertools/powertools-lambda-python/issues/5056))
+* **deps-dev:** bump aws-cdk from 2.154.0 to 2.154.1 ([#5071](https://github.com/aws-powertools/powertools-lambda-python/issues/5071))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.252 to 0.1.253 ([#5045](https://github.com/aws-powertools/powertools-lambda-python/issues/5045))
+* **deps-dev:** bump aws-cdk-aws-lambda-python-alpha from 2.152.0a0 to 2.153.0a0 ([#5044](https://github.com/aws-powertools/powertools-lambda-python/issues/5044))
+* **deps-dev:** bump sentry-sdk from 2.13.0 to 2.14.0 ([#5146](https://github.com/aws-powertools/powertools-lambda-python/issues/5146))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.256 to 0.1.257 ([#5078](https://github.com/aws-powertools/powertools-lambda-python/issues/5078))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.257 to 0.1.260 ([#5084](https://github.com/aws-powertools/powertools-lambda-python/issues/5084))
+* **deps-dev:** bump httpx from 0.27.0 to 0.27.2 ([#5085](https://github.com/aws-powertools/powertools-lambda-python/issues/5085))
+* **deps-dev:** bump cfn-lint from 1.10.3 to 1.11.0 ([#5086](https://github.com/aws-powertools/powertools-lambda-python/issues/5086))
+* **deps-dev:** bump types-python-dateutil from 2.9.0.20240316 to 2.9.0.20240821 ([#5046](https://github.com/aws-powertools/powertools-lambda-python/issues/5046))
+* **deps-dev:** bump mypy-boto3-lambda from 1.35.1 to 1.35.3 in the boto-typing group ([#5043](https://github.com/aws-powertools/powertools-lambda-python/issues/5043))
+* **deps-dev:** bump mypy-boto3-appconfig from 1.35.0 to 1.35.8 in the boto-typing group ([#5090](https://github.com/aws-powertools/powertools-lambda-python/issues/5090))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.260 to 0.1.261 ([#5091](https://github.com/aws-powertools/powertools-lambda-python/issues/5091))
+* **deps-dev:** bump ruff from 0.6.2 to 0.6.3 ([#5094](https://github.com/aws-powertools/powertools-lambda-python/issues/5094))
+* **deps-dev:** bump aws-cdk-lib from 2.152.0 to 2.153.0 ([#5031](https://github.com/aws-powertools/powertools-lambda-python/issues/5031))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.251 to 0.1.252 ([#5032](https://github.com/aws-powertools/powertools-lambda-python/issues/5032))
+* **deps-dev:** bump aws-cdk from 2.152.0 to 2.153.0 ([#5033](https://github.com/aws-powertools/powertools-lambda-python/issues/5033))
+* **deps-dev:** bump the boto-typing group with 2 updates ([#5030](https://github.com/aws-powertools/powertools-lambda-python/issues/5030))
+* **deps-dev:** bump cfn-lint from 1.11.0 to 1.11.1 ([#5095](https://github.com/aws-powertools/powertools-lambda-python/issues/5095))
+* **deps-dev:** bump mypy-boto3-logs from 1.35.0 to 1.35.10 in the boto-typing group ([#5102](https://github.com/aws-powertools/powertools-lambda-python/issues/5102))
+* **deps-dev:** bump aws-cdk from 2.154.1 to 2.155.0 ([#5101](https://github.com/aws-powertools/powertools-lambda-python/issues/5101))
+* **deps-dev:** bump mkdocs-material from 9.5.31 to 9.5.32 ([#5020](https://github.com/aws-powertools/powertools-lambda-python/issues/5020))
+* **deps-dev:** bump filelock from 3.15.4 to 3.16.0 ([#5145](https://github.com/aws-powertools/powertools-lambda-python/issues/5145))
+* **deps-dev:** bump types-redis from 4.6.0.20240806 to 4.6.0.20240819 ([#5021](https://github.com/aws-powertools/powertools-lambda-python/issues/5021))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.250 to 0.1.251 ([#5018](https://github.com/aws-powertools/powertools-lambda-python/issues/5018))
+* **deps-dev:** bump mkdocs-material from 9.5.33 to 9.5.34 ([#5112](https://github.com/aws-powertools/powertools-lambda-python/issues/5112))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.261 to 0.1.262 ([#5103](https://github.com/aws-powertools/powertools-lambda-python/issues/5103))
+* **deps-dev:** bump aws-cdk-lib from 2.154.1 to 2.155.0 ([#5104](https://github.com/aws-powertools/powertools-lambda-python/issues/5104))
+* **deps-dev:** bump types-redis from 4.6.0.20240819 to 4.6.0.20240903 ([#5116](https://github.com/aws-powertools/powertools-lambda-python/issues/5116))
+* **deps-dev:** bump cfn-lint from 1.10.2 to 1.10.3 ([#5009](https://github.com/aws-powertools/powertools-lambda-python/issues/5009))
+* **deps-dev:** bump aws-cdk-aws-lambda-python-alpha from 2.151.0a0 to 2.152.0a0 ([#5006](https://github.com/aws-powertools/powertools-lambda-python/issues/5006))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.248 to 0.1.250 ([#5011](https://github.com/aws-powertools/powertools-lambda-python/issues/5011))
+* **deps-dev:** bump ruff from 0.6.0 to 0.6.1 ([#5007](https://github.com/aws-powertools/powertools-lambda-python/issues/5007))
+* **deps-dev:** bump the boto-typing group with 11 updates ([#5005](https://github.com/aws-powertools/powertools-lambda-python/issues/5005))
+* **deps-dev:** bump aws-cdk-aws-lambda-python-alpha from 2.154.1a0 to 2.155.0a0 ([#5117](https://github.com/aws-powertools/powertools-lambda-python/issues/5117))
+* **deps-dev:** bump cfn-lint from 1.11.1 to 1.12.1 ([#5118](https://github.com/aws-powertools/powertools-lambda-python/issues/5118))
+* **deps-dev:** bump aws-cdk-lib from 2.151.0 to 2.152.0 ([#4999](https://github.com/aws-powertools/powertools-lambda-python/issues/4999))
+* **deps-dev:** bump cfn-lint from 1.10.1 to 1.10.2 ([#5002](https://github.com/aws-powertools/powertools-lambda-python/issues/5002))
+* **deps-dev:** bump ruff from 0.5.7 to 0.6.0 ([#5001](https://github.com/aws-powertools/powertools-lambda-python/issues/5001))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.246 to 0.1.248 ([#5000](https://github.com/aws-powertools/powertools-lambda-python/issues/5000))
+* **deps-dev:** bump aws-cdk from 2.151.0 to 2.152.0 ([#4996](https://github.com/aws-powertools/powertools-lambda-python/issues/4996))
+* **deps-dev:** bump mypy-boto3-s3 from 1.34.160 to 1.34.162 in the boto-typing group ([#4998](https://github.com/aws-powertools/powertools-lambda-python/issues/4998))
+* **deps-dev:** bump mypy-boto3-logs from 1.35.10 to 1.35.12 in the boto-typing group ([#5121](https://github.com/aws-powertools/powertools-lambda-python/issues/5121))
+* **deps-dev:** bump cfn-lint from 1.12.1 to 1.12.3 ([#5126](https://github.com/aws-powertools/powertools-lambda-python/issues/5126))
+* **deps-dev:** bump ruff from 0.6.3 to 0.6.4 ([#5130](https://github.com/aws-powertools/powertools-lambda-python/issues/5130))
+* **deps-dev:** bump aws-cdk from 2.155.0 to 2.156.0 ([#5133](https://github.com/aws-powertools/powertools-lambda-python/issues/5133))
+* **deps-dev:** bump mypy-boto3-s3 from 1.34.158 to 1.34.160 in the boto-typing group ([#4972](https://github.com/aws-powertools/powertools-lambda-python/issues/4972))
+* **deps-dev:** bump types-python-dateutil from 2.9.0.20240821 to 2.9.0.20240906 ([#5134](https://github.com/aws-powertools/powertools-lambda-python/issues/5134))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.246 to 0.1.247 ([#4973](https://github.com/aws-powertools/powertools-lambda-python/issues/4973))
+* **deps-dev:** bump cfn-lint from 1.9.7 to 1.10.1 ([#4968](https://github.com/aws-powertools/powertools-lambda-python/issues/4968))
+* **deps-dev:** bump sentry-sdk from 2.12.0 to 2.13.0 ([#4969](https://github.com/aws-powertools/powertools-lambda-python/issues/4969))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.242 to 0.1.246 ([#4967](https://github.com/aws-powertools/powertools-lambda-python/issues/4967))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.263 to 0.1.264 ([#5135](https://github.com/aws-powertools/powertools-lambda-python/issues/5135))
+* **deps-dev:** bump aws-cdk-lib from 2.155.0 to 2.156.0 ([#5137](https://github.com/aws-powertools/powertools-lambda-python/issues/5137))
+* **deps-dev:** bump cfn-lint from 1.12.3 to 1.12.4 ([#5136](https://github.com/aws-powertools/powertools-lambda-python/issues/5136))
+* **deps-dev:** bump mypy-boto3-s3 from 1.34.138 to 1.34.158 in the boto-typing group ([#4936](https://github.com/aws-powertools/powertools-lambda-python/issues/4936))
+* **deps-dev:** bump aws-cdk-aws-lambda-python-alpha from 2.155.0a0 to 2.156.0a0 ([#5144](https://github.com/aws-powertools/powertools-lambda-python/issues/5144))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.262 to 0.1.263 ([#5122](https://github.com/aws-powertools/powertools-lambda-python/issues/5122))
+* **docs:** load self hosted mermaid.js ([#5077](https://github.com/aws-powertools/powertools-lambda-python/issues/5077))
+
+## Regression
+
+* **deps:** "chore(deps-dev): bump cdklabs-generative-ai-cdk-constructs from 0.1.246 to 0.1.247" ([#4974](https://github.com/aws-powertools/powertools-lambda-python/issues/4974))
+
+
+
+## [v2.43.1] - 2024-08-12
+## Bug Fixes
+
+* **event_source:** fix regression when working with zero numbers in DynamoDBStreamEvent ([#4932](https://github.com/aws-powertools/powertools-lambda-python/issues/4932))
+
+## Maintenance
+
+* version bump
+* **ci:** new pre-release 2.43.1a0 ([#4920](https://github.com/aws-powertools/powertools-lambda-python/issues/4920))
+* **ci:** new pre-release 2.43.1a1 ([#4926](https://github.com/aws-powertools/powertools-lambda-python/issues/4926))
+* **ci:** new pre-release 2.42.1a9 ([#4912](https://github.com/aws-powertools/powertools-lambda-python/issues/4912))
+* **deps-dev:** bump ruff from 0.5.6 to 0.5.7 ([#4918](https://github.com/aws-powertools/powertools-lambda-python/issues/4918))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.234 to 0.1.238 ([#4917](https://github.com/aws-powertools/powertools-lambda-python/issues/4917))
+* **deps-dev:** bump mypy-boto3-ssm from 1.34.132 to 1.34.158 in the boto-typing group ([#4921](https://github.com/aws-powertools/powertools-lambda-python/issues/4921))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.238 to 0.1.242 ([#4922](https://github.com/aws-powertools/powertools-lambda-python/issues/4922))
+* **deps-dev:** bump cfn-lint from 1.9.6 to 1.9.7 ([#4923](https://github.com/aws-powertools/powertools-lambda-python/issues/4923))
+* **deps-dev:** bump cfn-lint from 1.9.5 to 1.9.6 ([#4916](https://github.com/aws-powertools/powertools-lambda-python/issues/4916))
+
+
+
+## [v2.43.0] - 2024-08-08
+## Bug Fixes
+
+* **data_class:** ensure DynamoDBStreamEvent conforms to decimal limits ([#4863](https://github.com/aws-powertools/powertools-lambda-python/issues/4863))
+
+## Code Refactoring
+
+* **test:** make CORS test consistent with expected behavior ([#4882](https://github.com/aws-powertools/powertools-lambda-python/issues/4882))
+* **tracer:** make capture_lambda_handler type more generic ([#4796](https://github.com/aws-powertools/powertools-lambda-python/issues/4796))
+
+## Documentation
+
+* fix type vs. field in comment ([#4832](https://github.com/aws-powertools/powertools-lambda-python/issues/4832))
+* **public_reference:** add CHS Inc. as a public reference ([#4885](https://github.com/aws-powertools/powertools-lambda-python/issues/4885))
+* **public_reference:** add LocalStack as a public reference ([#4858](https://github.com/aws-powertools/powertools-lambda-python/issues/4858))
+* **public_reference:** add Caylent as a public reference ([#4822](https://github.com/aws-powertools/powertools-lambda-python/issues/4822))
+
+## Features
+
+* **metrics:** add unit None for CloudWatch EMF Metrics ([#4904](https://github.com/aws-powertools/powertools-lambda-python/issues/4904))
+* **validation:** returns output from validate function ([#4839](https://github.com/aws-powertools/powertools-lambda-python/issues/4839))
+
+## Maintenance
+
+* version bump
+* **ci:** new pre-release 2.42.1a5 ([#4868](https://github.com/aws-powertools/powertools-lambda-python/issues/4868))
+* **ci:** new pre-release 2.42.1a8 ([#4903](https://github.com/aws-powertools/powertools-lambda-python/issues/4903))
+* **ci:** new pre-release 2.42.1a0 ([#4827](https://github.com/aws-powertools/powertools-lambda-python/issues/4827))
+* **ci:** new pre-release 2.42.1a7 ([#4894](https://github.com/aws-powertools/powertools-lambda-python/issues/4894))
+* **ci:** new pre-release 2.42.1a1 ([#4837](https://github.com/aws-powertools/powertools-lambda-python/issues/4837))
+* **ci:** new pre-release 2.42.1a3 ([#4856](https://github.com/aws-powertools/powertools-lambda-python/issues/4856))
+* **ci:** new pre-release 2.42.1a4 ([#4864](https://github.com/aws-powertools/powertools-lambda-python/issues/4864))
+* **ci:** new pre-release 2.42.1a6 ([#4884](https://github.com/aws-powertools/powertools-lambda-python/issues/4884))
+* **ci:** new pre-release 2.42.1a2 ([#4847](https://github.com/aws-powertools/powertools-lambda-python/issues/4847))
+* **deps:** bump golang.org/x/sync from 0.7.0 to 0.8.0 in /layer/scripts/layer-balancer in the layer-balancer group ([#4892](https://github.com/aws-powertools/powertools-lambda-python/issues/4892))
+* **deps:** bump actions/upload-artifact from 4.3.5 to 4.3.6 ([#4901](https://github.com/aws-powertools/powertools-lambda-python/issues/4901))
+* **deps:** bump actions/upload-artifact from 4.3.4 to 4.3.5 ([#4871](https://github.com/aws-powertools/powertools-lambda-python/issues/4871))
+* **deps:** bump ossf/scorecard-action from 2.3.3 to 2.4.0 ([#4829](https://github.com/aws-powertools/powertools-lambda-python/issues/4829))
+* **deps:** bump squidfunk/mkdocs-material from `257eca8` to `9919d6e` in /docs ([#4878](https://github.com/aws-powertools/powertools-lambda-python/issues/4878))
+* **deps:** bump docker/setup-buildx-action from 3.5.0 to 3.6.1 ([#4844](https://github.com/aws-powertools/powertools-lambda-python/issues/4844))
+* **deps:** bump redis from 5.0.7 to 5.0.8 ([#4854](https://github.com/aws-powertools/powertools-lambda-python/issues/4854))
+* **deps-dev:** bump ruff from 0.5.5 to 0.5.6 ([#4874](https://github.com/aws-powertools/powertools-lambda-python/issues/4874))
+* **deps-dev:** bump mypy-boto3-cloudwatch from 1.34.83 to 1.34.153 in the boto-typing group ([#4887](https://github.com/aws-powertools/powertools-lambda-python/issues/4887))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.224 to 0.1.228 ([#4867](https://github.com/aws-powertools/powertools-lambda-python/issues/4867))
+* **deps-dev:** bump cfn-lint from 1.9.1 to 1.9.3 ([#4866](https://github.com/aws-powertools/powertools-lambda-python/issues/4866))
+* **deps-dev:** bump sentry-sdk from 2.11.0 to 2.12.0 ([#4861](https://github.com/aws-powertools/powertools-lambda-python/issues/4861))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.228 to 0.1.230 ([#4876](https://github.com/aws-powertools/powertools-lambda-python/issues/4876))
+* **deps-dev:** bump black from 24.4.2 to 24.8.0 ([#4873](https://github.com/aws-powertools/powertools-lambda-python/issues/4873))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.223 to 0.1.224 ([#4855](https://github.com/aws-powertools/powertools-lambda-python/issues/4855))
+* **deps-dev:** bump mypy-boto3-logs from 1.34.66 to 1.34.151 in the boto-typing group ([#4853](https://github.com/aws-powertools/powertools-lambda-python/issues/4853))
+* **deps-dev:** bump coverage from 7.6.0 to 7.6.1 ([#4888](https://github.com/aws-powertools/powertools-lambda-python/issues/4888))
+* **deps-dev:** bump cfn-lint from 1.8.2 to 1.9.1 ([#4851](https://github.com/aws-powertools/powertools-lambda-python/issues/4851))
+* **deps-dev:** bump aws-cdk-aws-lambda-python-alpha from 2.150.0a0 to 2.151.0a0 ([#4889](https://github.com/aws-powertools/powertools-lambda-python/issues/4889))
+* **deps-dev:** bump aws-cdk from 2.150.0 to 2.151.0 ([#4872](https://github.com/aws-powertools/powertools-lambda-python/issues/4872))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.219 to 0.1.222 ([#4836](https://github.com/aws-powertools/powertools-lambda-python/issues/4836))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.222 to 0.1.223 ([#4843](https://github.com/aws-powertools/powertools-lambda-python/issues/4843))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.233 to 0.1.234 ([#4909](https://github.com/aws-powertools/powertools-lambda-python/issues/4909))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.230 to 0.1.231 ([#4891](https://github.com/aws-powertools/powertools-lambda-python/issues/4891))
+* **deps-dev:** bump cfn-lint from 1.9.3 to 1.9.5 ([#4890](https://github.com/aws-powertools/powertools-lambda-python/issues/4890))
+* **deps-dev:** bump pytest from 8.3.1 to 8.3.2 ([#4824](https://github.com/aws-powertools/powertools-lambda-python/issues/4824))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.231 to 0.1.233 ([#4900](https://github.com/aws-powertools/powertools-lambda-python/issues/4900))
+* **deps-dev:** bump mkdocs-material from 9.5.30 to 9.5.31 ([#4877](https://github.com/aws-powertools/powertools-lambda-python/issues/4877))
+* **deps-dev:** bump types-redis from 4.6.0.20240425 to 4.6.0.20240726 ([#4831](https://github.com/aws-powertools/powertools-lambda-python/issues/4831))
+* **deps-dev:** bump ruff from 0.5.4 to 0.5.5 ([#4823](https://github.com/aws-powertools/powertools-lambda-python/issues/4823))
+* **deps-dev:** bump aws-cdk-lib from 2.150.0 to 2.151.0 ([#4875](https://github.com/aws-powertools/powertools-lambda-python/issues/4875))
+* **deps-dev:** bump types-redis from 4.6.0.20240726 to 4.6.0.20240806 ([#4899](https://github.com/aws-powertools/powertools-lambda-python/issues/4899))
+* **maintenance:** add Banxware customer refernece ([#4841](https://github.com/aws-powertools/powertools-lambda-python/issues/4841))
+
+
+
+## [v2.42.0] - 2024-07-25
+## Bug Fixes
+
+* **idempotency:** ensure in_progress_expiration field is set on Lambda timeout. ([#4773](https://github.com/aws-powertools/powertools-lambda-python/issues/4773))
+
+## Documentation
+
+* **idempotency:** improve navigation, wording, and new section on guarantees ([#4613](https://github.com/aws-powertools/powertools-lambda-python/issues/4613))
+
+## Features
+
+* **event_handler:** add OpenAPI extensions ([#4703](https://github.com/aws-powertools/powertools-lambda-python/issues/4703))
+
+## Maintenance
+
+* version bump
+* **ci:** new pre-release 2.41.1a4 ([#4772](https://github.com/aws-powertools/powertools-lambda-python/issues/4772))
+* **ci:** new pre-release 2.41.1a0 ([#4749](https://github.com/aws-powertools/powertools-lambda-python/issues/4749))
+* **ci:** new pre-release 2.41.1a1 ([#4756](https://github.com/aws-powertools/powertools-lambda-python/issues/4756))
+* **ci:** new pre-release 2.41.1a2 ([#4758](https://github.com/aws-powertools/powertools-lambda-python/issues/4758))
+* **ci:** new pre-release 2.41.1a9 ([#4808](https://github.com/aws-powertools/powertools-lambda-python/issues/4808))
+* **ci:** new pre-release 2.41.1a3 ([#4766](https://github.com/aws-powertools/powertools-lambda-python/issues/4766))
+* **ci:** new pre-release 2.41.1a8 ([#4802](https://github.com/aws-powertools/powertools-lambda-python/issues/4802))
+* **ci:** new pre-release 2.41.1a5 ([#4777](https://github.com/aws-powertools/powertools-lambda-python/issues/4777))
+* **ci:** new pre-release 2.41.1a6 ([#4783](https://github.com/aws-powertools/powertools-lambda-python/issues/4783))
+* **ci:** new pre-release 2.41.1a7 ([#4792](https://github.com/aws-powertools/powertools-lambda-python/issues/4792))
+* **deps:** bump github.com/aws/aws-sdk-go-v2/config from 1.27.26 to 1.27.27 in /layer/scripts/layer-balancer in the layer-balancer group ([#4779](https://github.com/aws-powertools/powertools-lambda-python/issues/4779))
+* **deps:** bump aws-actions/closed-issue-message from 8b6324312193476beecf11f8e8539d73a3553bf4 to 80edfc24bdf1283400eb04d20a8a605ae8bf7d48 ([#4786](https://github.com/aws-powertools/powertools-lambda-python/issues/4786))
+* **deps:** bump actions/dependency-review-action from 4.3.3 to 4.3.4 ([#4753](https://github.com/aws-powertools/powertools-lambda-python/issues/4753))
+* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 3 updates ([#4745](https://github.com/aws-powertools/powertools-lambda-python/issues/4745))
+* **deps:** bump datadog-lambda from 6.96.0 to 6.97.0 ([#4770](https://github.com/aws-powertools/powertools-lambda-python/issues/4770))
+* **deps:** bump docker/setup-buildx-action from 3.4.0 to 3.5.0 ([#4801](https://github.com/aws-powertools/powertools-lambda-python/issues/4801))
+* **deps:** bump docker/setup-qemu-action from 3.1.0 to 3.2.0 ([#4800](https://github.com/aws-powertools/powertools-lambda-python/issues/4800))
+* **deps-dev:** bump cfn-lint from 1.8.1 to 1.8.2 ([#4788](https://github.com/aws-powertools/powertools-lambda-python/issues/4788))
+* **deps-dev:** bump pytest-asyncio from 0.23.7 to 0.23.8 ([#4776](https://github.com/aws-powertools/powertools-lambda-python/issues/4776))
+* **deps-dev:** bump pytest from 8.2.2 to 8.3.1 ([#4799](https://github.com/aws-powertools/powertools-lambda-python/issues/4799))
+* **deps-dev:** bump aws-cdk-lib from 2.148.1 to 2.150.0 ([#4806](https://github.com/aws-powertools/powertools-lambda-python/issues/4806))
+* **deps-dev:** bump ruff from 0.5.3 to 0.5.4 ([#4798](https://github.com/aws-powertools/powertools-lambda-python/issues/4798))
+* **deps-dev:** bump cfn-lint from 1.6.1 to 1.8.1 ([#4780](https://github.com/aws-powertools/powertools-lambda-python/issues/4780))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.211 to 0.1.212 ([#4769](https://github.com/aws-powertools/powertools-lambda-python/issues/4769))
+* **deps-dev:** bump ruff from 0.5.2 to 0.5.3 ([#4781](https://github.com/aws-powertools/powertools-lambda-python/issues/4781))
+* **deps-dev:** bump mkdocs-material from 9.5.28 to 9.5.29 ([#4764](https://github.com/aws-powertools/powertools-lambda-python/issues/4764))
+* **deps-dev:** bump aws-cdk from 2.148.0 to 2.149.0 ([#4765](https://github.com/aws-powertools/powertools-lambda-python/issues/4765))
+* **deps-dev:** bump ruff from 0.5.1 to 0.5.2 ([#4762](https://github.com/aws-powertools/powertools-lambda-python/issues/4762))
+* **deps-dev:** bump sentry-sdk from 2.9.0 to 2.10.0 ([#4763](https://github.com/aws-powertools/powertools-lambda-python/issues/4763))
+* **deps-dev:** bump aws-cdk from 2.149.0 to 2.150.0 ([#4805](https://github.com/aws-powertools/powertools-lambda-python/issues/4805))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.207 to 0.1.211 ([#4760](https://github.com/aws-powertools/powertools-lambda-python/issues/4760))
+* **deps-dev:** bump mypy-boto3-dynamodb from 1.34.131 to 1.34.148 in the boto-typing group ([#4812](https://github.com/aws-powertools/powertools-lambda-python/issues/4812))
+* **deps-dev:** bump sentry-sdk from 2.10.0 to 2.11.0 ([#4815](https://github.com/aws-powertools/powertools-lambda-python/issues/4815))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.212 to 0.1.219 ([#4817](https://github.com/aws-powertools/powertools-lambda-python/issues/4817))
+* **deps-dev:** bump cfn-lint from 1.6.0 to 1.6.1 ([#4751](https://github.com/aws-powertools/powertools-lambda-python/issues/4751))
+* **deps-dev:** bump mkdocs-material from 9.5.29 to 9.5.30 ([#4807](https://github.com/aws-powertools/powertools-lambda-python/issues/4807))
+* **deps-dev:** bump aws-cdk-aws-lambda-python-alpha from 2.148.1a0 to 2.150.0a0 ([#4813](https://github.com/aws-powertools/powertools-lambda-python/issues/4813))
+* **deps-dev:** bump cfn-lint from 1.5.3 to 1.6.0 ([#4747](https://github.com/aws-powertools/powertools-lambda-python/issues/4747))
+* **deps-dev:** bump coverage from 7.5.4 to 7.6.0 ([#4746](https://github.com/aws-powertools/powertools-lambda-python/issues/4746))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.206 to 0.1.207 ([#4748](https://github.com/aws-powertools/powertools-lambda-python/issues/4748))
+* **deps-dev:** bump mypy-boto3-secretsmanager from 1.34.128 to 1.34.145 in the boto-typing group ([#4787](https://github.com/aws-powertools/powertools-lambda-python/issues/4787))
+* **docs:** Add lambda layer policy to versioning docs ([#4811](https://github.com/aws-powertools/powertools-lambda-python/issues/4811))
+* **logger:** use package logger over source logger to reduce noise ([#4793](https://github.com/aws-powertools/powertools-lambda-python/issues/4793))
+
+
+
+## [v2.41.0] - 2024-07-11
+## Bug Fixes
+
+* **event_handler:** make the max_age attribute comply with RFC specification ([#4731](https://github.com/aws-powertools/powertools-lambda-python/issues/4731))
+* **event_handler:** disable allow-credentials header when origin allow_origin is * ([#4638](https://github.com/aws-powertools/powertools-lambda-python/issues/4638))
+* **event_handler:** convert null body to empty string in ALBResolver to avoid HTTP 502 ([#4683](https://github.com/aws-powertools/powertools-lambda-python/issues/4683))
+* **event_handler:** custom serializer recursive values when using data validation ([#4664](https://github.com/aws-powertools/powertools-lambda-python/issues/4664))
+
+## Documentation
+
+* **i-made-this:** Bedrock agents with Powertools for AWS Lambda ([#4705](https://github.com/aws-powertools/powertools-lambda-python/issues/4705))
+* **public_reference:** add BusPatrol as a public reference ([#4713](https://github.com/aws-powertools/powertools-lambda-python/issues/4713))
+
+## Features
+
+* **batch:** add option to not raise `BatchProcessingError` exception when the entire batch fails ([#4719](https://github.com/aws-powertools/powertools-lambda-python/issues/4719))
+* **feature_flags:** allow customers to bring their own boto3 client and session ([#4717](https://github.com/aws-powertools/powertools-lambda-python/issues/4717))
+* **parser:** add support for API Gateway Lambda authorizer events ([#4718](https://github.com/aws-powertools/powertools-lambda-python/issues/4718))
+
+## Maintenance
+
+* version bump
+* Add token to codecov action ([#4682](https://github.com/aws-powertools/powertools-lambda-python/issues/4682))
+* **ci:** new pre-release 2.40.2a5 ([#4706](https://github.com/aws-powertools/powertools-lambda-python/issues/4706))
+* **ci:** new pre-release 2.40.2a0 ([#4665](https://github.com/aws-powertools/powertools-lambda-python/issues/4665))
+* **ci:** new pre-release 2.40.2a8 ([#4737](https://github.com/aws-powertools/powertools-lambda-python/issues/4737))
+* **ci:** new pre-release 2.40.2a7 ([#4726](https://github.com/aws-powertools/powertools-lambda-python/issues/4726))
+* **ci:** new pre-release 2.40.2a1 ([#4669](https://github.com/aws-powertools/powertools-lambda-python/issues/4669))
+* **ci:** new pre-release 2.40.2a2 ([#4679](https://github.com/aws-powertools/powertools-lambda-python/issues/4679))
+* **ci:** new pre-release 2.40.2a3 ([#4688](https://github.com/aws-powertools/powertools-lambda-python/issues/4688))
+* **ci:** new pre-release 2.40.2a6 ([#4715](https://github.com/aws-powertools/powertools-lambda-python/issues/4715))
+* **ci:** new pre-release 2.40.2a4 ([#4694](https://github.com/aws-powertools/powertools-lambda-python/issues/4694))
+* **deps:** bump docker/setup-qemu-action from 3.0.0 to 3.1.0 ([#4685](https://github.com/aws-powertools/powertools-lambda-python/issues/4685))
+* **deps:** bump actions/setup-python from 5.1.0 to 5.1.1 ([#4732](https://github.com/aws-powertools/powertools-lambda-python/issues/4732))
+* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 3 updates ([#4733](https://github.com/aws-powertools/powertools-lambda-python/issues/4733))
+* **deps:** bump actions/upload-artifact from 4.3.3 to 4.3.4 ([#4698](https://github.com/aws-powertools/powertools-lambda-python/issues/4698))
+* **deps:** bump actions/download-artifact from 4.1.7 to 4.1.8 ([#4699](https://github.com/aws-powertools/powertools-lambda-python/issues/4699))
+* **deps:** bump actions/setup-node from 4.0.2 to 4.0.3 ([#4725](https://github.com/aws-powertools/powertools-lambda-python/issues/4725))
+* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 3.0.9 to 3.0.10 ([#4678](https://github.com/aws-powertools/powertools-lambda-python/issues/4678))
+* **deps:** bump docker/setup-buildx-action from 3.3.0 to 3.4.0 ([#4693](https://github.com/aws-powertools/powertools-lambda-python/issues/4693))
+* **deps:** bump zipp from 3.17.0 to 3.19.1 in /docs ([#4720](https://github.com/aws-powertools/powertools-lambda-python/issues/4720))
+* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 3 updates ([#4659](https://github.com/aws-powertools/powertools-lambda-python/issues/4659))
+* **deps:** bump certifi from 2024.6.2 to 2024.7.4 ([#4700](https://github.com/aws-powertools/powertools-lambda-python/issues/4700))
+* **deps:** bump github.com/aws/aws-sdk-go-v2/config from 1.27.23 to 1.27.24 in /layer/scripts/layer-balancer in the layer-balancer group ([#4684](https://github.com/aws-powertools/powertools-lambda-python/issues/4684))
+* **deps-dev:** bump mkdocs-material from 9.5.27 to 9.5.28 ([#4676](https://github.com/aws-powertools/powertools-lambda-python/issues/4676))
+* **deps-dev:** bump cfn-lint from 1.4.2 to 1.5.0 ([#4675](https://github.com/aws-powertools/powertools-lambda-python/issues/4675))
+* **deps-dev:** bump aws-cdk-aws-lambda-python-alpha from 2.147.3a0 to 2.148.0a0 ([#4722](https://github.com/aws-powertools/powertools-lambda-python/issues/4722))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.200 to 0.1.201 ([#4687](https://github.com/aws-powertools/powertools-lambda-python/issues/4687))
+* **deps-dev:** bump aws-cdk-lib from 2.147.2 to 2.147.3 ([#4674](https://github.com/aws-powertools/powertools-lambda-python/issues/4674))
+* **deps-dev:** bump zipp from 3.17.0 to 3.19.1 in /layer ([#4721](https://github.com/aws-powertools/powertools-lambda-python/issues/4721))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.202 to 0.1.205 ([#4723](https://github.com/aws-powertools/powertools-lambda-python/issues/4723))
+* **deps-dev:** bump aws-cdk-aws-lambda-python-alpha from 2.147.2a0 to 2.147.3a0 ([#4686](https://github.com/aws-powertools/powertools-lambda-python/issues/4686))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.199 to 0.1.200 ([#4677](https://github.com/aws-powertools/powertools-lambda-python/issues/4677))
+* **deps-dev:** bump aws-cdk-lib from 2.147.3 to 2.148.0 ([#4710](https://github.com/aws-powertools/powertools-lambda-python/issues/4710))
+* **deps-dev:** bump aws-cdk from 2.147.2 to 2.147.3 ([#4672](https://github.com/aws-powertools/powertools-lambda-python/issues/4672))
+* **deps-dev:** bump mypy-boto3-s3 from 1.34.120 to 1.34.138 in the boto-typing group ([#4673](https://github.com/aws-powertools/powertools-lambda-python/issues/4673))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.201 to 0.1.202 ([#4696](https://github.com/aws-powertools/powertools-lambda-python/issues/4696))
+* **deps-dev:** bump cfn-lint from 1.5.1 to 1.5.2 ([#4724](https://github.com/aws-powertools/powertools-lambda-python/issues/4724))
+* **deps-dev:** bump ruff from 0.5.0 to 0.5.1 ([#4697](https://github.com/aws-powertools/powertools-lambda-python/issues/4697))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.198 to 0.1.199 ([#4668](https://github.com/aws-powertools/powertools-lambda-python/issues/4668))
+* **deps-dev:** bump aws-cdk-aws-lambda-python-alpha from 2.147.1a0 to 2.147.2a0 ([#4667](https://github.com/aws-powertools/powertools-lambda-python/issues/4667))
+* **deps-dev:** bump aws-cdk from 2.147.3 to 2.148.0 ([#4708](https://github.com/aws-powertools/powertools-lambda-python/issues/4708))
+* **deps-dev:** bump cfn-lint from 1.5.2 to 1.5.3 ([#4734](https://github.com/aws-powertools/powertools-lambda-python/issues/4734))
+* **deps-dev:** bump sentry-sdk from 2.8.0 to 2.9.0 ([#4735](https://github.com/aws-powertools/powertools-lambda-python/issues/4735))
+* **deps-dev:** bump cfn-lint from 1.4.1 to 1.4.2 ([#4660](https://github.com/aws-powertools/powertools-lambda-python/issues/4660))
+* **deps-dev:** bump aws-cdk-lib from 2.147.1 to 2.147.2 ([#4661](https://github.com/aws-powertools/powertools-lambda-python/issues/4661))
+* **deps-dev:** bump cfn-lint from 1.5.0 to 1.5.1 ([#4711](https://github.com/aws-powertools/powertools-lambda-python/issues/4711))
+* **deps-dev:** bump aws-cdk from 2.147.1 to 2.147.2 ([#4657](https://github.com/aws-powertools/powertools-lambda-python/issues/4657))
+* **deps-dev:** bump ruff from 0.4.10 to 0.5.0 ([#4644](https://github.com/aws-powertools/powertools-lambda-python/issues/4644))
+* **deps-dev:** bump sentry-sdk from 2.7.1 to 2.8.0 ([#4712](https://github.com/aws-powertools/powertools-lambda-python/issues/4712))
+* **layers:** downgrade aws cdk to 2.145.0 ([#4739](https://github.com/aws-powertools/powertools-lambda-python/issues/4739))
+
+
+
+## [v2.40.1] - 2024-06-28
+## Bug Fixes
+
+* **event_handler:** current_event regression AppSyncResolver Router ([#4652](https://github.com/aws-powertools/powertools-lambda-python/issues/4652))
+
+## Maintenance
+
+* version bump
+* **ci:** new pre-release 2.40.1a1 ([#4653](https://github.com/aws-powertools/powertools-lambda-python/issues/4653))
+* **ci:** new pre-release 2.40.1a0 ([#4648](https://github.com/aws-powertools/powertools-lambda-python/issues/4648))
+* **deps-dev:** bump cfn-lint from 1.3.7 to 1.4.1 ([#4646](https://github.com/aws-powertools/powertools-lambda-python/issues/4646))
+* **deps-dev:** bump sentry-sdk from 2.7.0 to 2.7.1 ([#4645](https://github.com/aws-powertools/powertools-lambda-python/issues/4645))
+
+
+
+## [v2.40.0] - 2024-06-27
+## Bug Fixes
+
+* **event_sources:** change partition and offset field types in KafkaEventRecord ([#4515](https://github.com/aws-powertools/powertools-lambda-python/issues/4515))
+
+## Documentation
+
+* **homepage:** Fix homepage link ([#4587](https://github.com/aws-powertools/powertools-lambda-python/issues/4587))
+* **i-made-this:** add new article about best practices for accelerating serverless development ([#4518](https://github.com/aws-powertools/powertools-lambda-python/issues/4518))
+* **public reference:** add Brsk as a public reference ([#4597](https://github.com/aws-powertools/powertools-lambda-python/issues/4597))
+
+## Features
+
+* **event-handler:** add appsync batch resolvers ([#1998](https://github.com/aws-powertools/powertools-lambda-python/issues/1998))
+* **validation:** support JSON Schema referencing in validation utils ([#4508](https://github.com/aws-powertools/powertools-lambda-python/issues/4508))
+
+## Maintenance
+
+* version bump
+* **ci:** add the Metrics feature to nox tests ([#4552](https://github.com/aws-powertools/powertools-lambda-python/issues/4552))
+* **ci:** new pre-release 2.39.2a5 ([#4636](https://github.com/aws-powertools/powertools-lambda-python/issues/4636))
+* **ci:** add the Streaming feature to nox tests ([#4575](https://github.com/aws-powertools/powertools-lambda-python/issues/4575))
+* **ci:** new pre-release 2.39.2a4 ([#4629](https://github.com/aws-powertools/powertools-lambda-python/issues/4629))
+* **ci:** new pre-release 2.39.2a3 ([#4620](https://github.com/aws-powertools/powertools-lambda-python/issues/4620))
+* **ci:** add the Event Handler feature to nox tests ([#4581](https://github.com/aws-powertools/powertools-lambda-python/issues/4581))
+* **ci:** add the Data Class feature to nox tests ([#4583](https://github.com/aws-powertools/powertools-lambda-python/issues/4583))
+* **ci:** add the Parser feature to nox tests ([#4584](https://github.com/aws-powertools/powertools-lambda-python/issues/4584))
+* **ci:** add the Idempotency feature to nox tests ([#4585](https://github.com/aws-powertools/powertools-lambda-python/issues/4585))
+* **ci:** new pre-release 2.39.2a2 ([#4610](https://github.com/aws-powertools/powertools-lambda-python/issues/4610))
+* **ci:** introduce tests with Nox ([#4537](https://github.com/aws-powertools/powertools-lambda-python/issues/4537))
+* **ci:** new pre-release 2.39.2a1 ([#4598](https://github.com/aws-powertools/powertools-lambda-python/issues/4598))
+* **ci:** add the Tracer feature to nox tests ([#4567](https://github.com/aws-powertools/powertools-lambda-python/issues/4567))
+* **ci:** add the Middleware Factory feature to nox tests ([#4568](https://github.com/aws-powertools/powertools-lambda-python/issues/4568))
+* **ci:** add the Parameters feature to nox tests ([#4569](https://github.com/aws-powertools/powertools-lambda-python/issues/4569))
+* **ci:** add the Batch Processor feature to nox tests ([#4586](https://github.com/aws-powertools/powertools-lambda-python/issues/4586))
+* **ci:** add the Feature Flags feature to nox tests ([#4570](https://github.com/aws-powertools/powertools-lambda-python/issues/4570))
+* **ci:** add the Validation feature to nox tests ([#4571](https://github.com/aws-powertools/powertools-lambda-python/issues/4571))
+* **ci:** introduce daily pre-releases ([#4535](https://github.com/aws-powertools/powertools-lambda-python/issues/4535))
+* **ci:** new pre-release 2.39.2a0 ([#4590](https://github.com/aws-powertools/powertools-lambda-python/issues/4590))
+* **ci:** add the Data Masking feature to nox tests ([#4574](https://github.com/aws-powertools/powertools-lambda-python/issues/4574))
+* **ci:** add the Typing feature to nox tests ([#4572](https://github.com/aws-powertools/powertools-lambda-python/issues/4572))
+* **deps:** bump pypa/gh-action-pypi-publish from 1.8.14 to 1.9.0 ([#4592](https://github.com/aws-powertools/powertools-lambda-python/issues/4592))
+* **deps:** bump pydantic from 1.10.16 to 1.10.17 ([#4595](https://github.com/aws-powertools/powertools-lambda-python/issues/4595))
+* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 3 updates ([#4565](https://github.com/aws-powertools/powertools-lambda-python/issues/4565))
+* **deps:** bump squidfunk/mkdocs-material from `96abcbb` to `257eca8` in /docs ([#4540](https://github.com/aws-powertools/powertools-lambda-python/issues/4540))
+* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 3.0.7 to 3.0.9 ([#4539](https://github.com/aws-powertools/powertools-lambda-python/issues/4539))
+* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 3 updates ([#4546](https://github.com/aws-powertools/powertools-lambda-python/issues/4546))
+* **deps:** bump redis from 5.0.5 to 5.0.6 ([#4527](https://github.com/aws-powertools/powertools-lambda-python/issues/4527))
+* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 3 updates ([#4580](https://github.com/aws-powertools/powertools-lambda-python/issues/4580))
+* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 2 updates ([#4635](https://github.com/aws-powertools/powertools-lambda-python/issues/4635))
+* **deps:** bump codecov/codecov-action from 4.4.1 to 4.5.0 ([#4514](https://github.com/aws-powertools/powertools-lambda-python/issues/4514))
+* **deps:** bump pypa/gh-action-pypi-publish from 1.8.14 to 1.9.0 ([#4538](https://github.com/aws-powertools/powertools-lambda-python/issues/4538))
+* **deps:** bump fastjsonschema from 2.19.1 to 2.20.0 ([#4543](https://github.com/aws-powertools/powertools-lambda-python/issues/4543))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.189 to 0.1.192 ([#4578](https://github.com/aws-powertools/powertools-lambda-python/issues/4578))
+* **deps-dev:** bump sentry-sdk from 2.5.1 to 2.6.0 ([#4579](https://github.com/aws-powertools/powertools-lambda-python/issues/4579))
+* **deps-dev:** bump cfn-lint from 0.87.7 to 1.3.0 ([#4577](https://github.com/aws-powertools/powertools-lambda-python/issues/4577))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.192 to 0.1.193 ([#4596](https://github.com/aws-powertools/powertools-lambda-python/issues/4596))
+* **deps-dev:** bump ruff from 0.4.9 to 0.4.10 ([#4594](https://github.com/aws-powertools/powertools-lambda-python/issues/4594))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.193 to 0.1.194 ([#4601](https://github.com/aws-powertools/powertools-lambda-python/issues/4601))
+* **deps-dev:** bump aws-cdk from 2.146.0 to 2.147.0 ([#4604](https://github.com/aws-powertools/powertools-lambda-python/issues/4604))
+* **deps-dev:** bump aws-cdk-lib from 2.146.0 to 2.147.0 ([#4603](https://github.com/aws-powertools/powertools-lambda-python/issues/4603))
+* **deps-dev:** bump filelock from 3.15.1 to 3.15.3 ([#4576](https://github.com/aws-powertools/powertools-lambda-python/issues/4576))
+* **deps-dev:** bump hvac from 2.2.0 to 2.3.0 ([#4563](https://github.com/aws-powertools/powertools-lambda-python/issues/4563))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.188 to 0.1.189 ([#4564](https://github.com/aws-powertools/powertools-lambda-python/issues/4564))
+* **deps-dev:** bump cfn-lint from 1.3.0 to 1.3.3 ([#4602](https://github.com/aws-powertools/powertools-lambda-python/issues/4602))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.194 to 0.1.198 ([#4627](https://github.com/aws-powertools/powertools-lambda-python/issues/4627))
+* **deps-dev:** bump aws-cdk-aws-lambda-python-alpha from 2.146.0a0 to 2.147.0a0 ([#4619](https://github.com/aws-powertools/powertools-lambda-python/issues/4619))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.184 to 0.1.188 ([#4550](https://github.com/aws-powertools/powertools-lambda-python/issues/4550))
+* **deps-dev:** bump mkdocs-material from 9.5.26 to 9.5.27 ([#4544](https://github.com/aws-powertools/powertools-lambda-python/issues/4544))
+* **deps-dev:** bump aws-cdk-aws-lambda-python-alpha from 2.145.0a0 to 2.146.0a0 ([#4542](https://github.com/aws-powertools/powertools-lambda-python/issues/4542))
+* **deps-dev:** bump urllib3 from 1.26.18 to 1.26.19 in /layer ([#4547](https://github.com/aws-powertools/powertools-lambda-python/issues/4547))
+* **deps-dev:** bump aws-cdk-lib from 2.145.0 to 2.146.0 ([#4526](https://github.com/aws-powertools/powertools-lambda-python/issues/4526))
+* **deps-dev:** bump aws-cdk from 2.147.0 to 2.147.1 ([#4614](https://github.com/aws-powertools/powertools-lambda-python/issues/4614))
+* **deps-dev:** bump coverage from 7.5.3 to 7.5.4 ([#4617](https://github.com/aws-powertools/powertools-lambda-python/issues/4617))
+* **deps-dev:** bump aws-cdk-lib from 2.147.0 to 2.147.1 ([#4615](https://github.com/aws-powertools/powertools-lambda-python/issues/4615))
+* **deps-dev:** bump mypy-boto3-secretsmanager from 1.34.125 to 1.34.128 in the boto-typing group ([#4541](https://github.com/aws-powertools/powertools-lambda-python/issues/4541))
+* **deps-dev:** bump pdoc3 from 0.10.0 to 0.11.0 ([#4618](https://github.com/aws-powertools/powertools-lambda-python/issues/4618))
+* **deps-dev:** bump mypy-boto3-secretsmanager from 1.34.109 to 1.34.125 in the boto-typing group ([#4509](https://github.com/aws-powertools/powertools-lambda-python/issues/4509))
+* **deps-dev:** bump mike from 2.1.1 to 2.1.2 ([#4616](https://github.com/aws-powertools/powertools-lambda-python/issues/4616))
+* **deps-dev:** bump mypy from 1.10.0 to 1.10.1 ([#4624](https://github.com/aws-powertools/powertools-lambda-python/issues/4624))
+* **deps-dev:** bump filelock from 3.15.3 to 3.15.4 ([#4626](https://github.com/aws-powertools/powertools-lambda-python/issues/4626))
+* **deps-dev:** bump ruff from 0.4.8 to 0.4.9 ([#4528](https://github.com/aws-powertools/powertools-lambda-python/issues/4528))
+* **deps-dev:** bump cfn-lint from 1.3.3 to 1.3.5 ([#4628](https://github.com/aws-powertools/powertools-lambda-python/issues/4628))
+* **deps-dev:** bump mypy-boto3-ssm from 1.34.91 to 1.34.132 in the boto-typing group ([#4623](https://github.com/aws-powertools/powertools-lambda-python/issues/4623))
+* **deps-dev:** bump aws-cdk from 2.145.0 to 2.146.0 ([#4525](https://github.com/aws-powertools/powertools-lambda-python/issues/4525))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.182 to 0.1.184 ([#4529](https://github.com/aws-powertools/powertools-lambda-python/issues/4529))
+* **deps-dev:** bump bandit from 1.7.8 to 1.7.9 ([#4511](https://github.com/aws-powertools/powertools-lambda-python/issues/4511))
+* **deps-dev:** bump cfn-lint from 0.87.6 to 0.87.7 ([#4513](https://github.com/aws-powertools/powertools-lambda-python/issues/4513))
+* **deps-dev:** bump filelock from 3.14.0 to 3.15.1 ([#4512](https://github.com/aws-powertools/powertools-lambda-python/issues/4512))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.179 to 0.1.182 ([#4510](https://github.com/aws-powertools/powertools-lambda-python/issues/4510))
+* **deps-dev:** bump cfn-lint from 1.3.5 to 1.3.7 ([#4634](https://github.com/aws-powertools/powertools-lambda-python/issues/4634))
+* **deps-dev:** bump mypy-boto3-dynamodb from 1.34.114 to 1.34.131 in the boto-typing group ([#4593](https://github.com/aws-powertools/powertools-lambda-python/issues/4593))
+* **governance:** fix errors when creating Gitpod environment ([#4532](https://github.com/aws-powertools/powertools-lambda-python/issues/4532))
+* **layers:** downgrade aws cdk to 2.145.0 ([#4640](https://github.com/aws-powertools/powertools-lambda-python/issues/4640))
+
+
+
+## [v2.39.1] - 2024-06-13
+## Bug Fixes
+
+* **event_handler:** regression making pydantic required (it should not) ([#4500](https://github.com/aws-powertools/powertools-lambda-python/issues/4500))
+
+## Maintenance
+
+* version bump
+
+
+
+## [v2.39.0] - 2024-06-13
+## Bug Fixes
+
+* **event_handler:** do not skip middleware and exception handlers on 404 error ([#4492](https://github.com/aws-powertools/powertools-lambda-python/issues/4492))
+* **event_handler:** raise more specific SerializationError exception for unsupported types in data validation ([#4415](https://github.com/aws-powertools/powertools-lambda-python/issues/4415))
+* **event_handler:** security scheme unhashable list when working with router ([#4421](https://github.com/aws-powertools/powertools-lambda-python/issues/4421))
* **event_handler:** CORS Origin for ALBResolver multi-headers ([#4385](https://github.com/aws-powertools/powertools-lambda-python/issues/4385))
+* **idempotency:** POWERTOOLS_IDEMPOTENCY_DISABLED should respect truthy values ([#4391](https://github.com/aws-powertools/powertools-lambda-python/issues/4391))
## Documentation
* **homepage:** Change installation to CDK v2 ([#4351](https://github.com/aws-powertools/powertools-lambda-python/issues/4351))
+* **public reference:** add Recast as a public reference ([#4491](https://github.com/aws-powertools/powertools-lambda-python/issues/4491))
## Features
* **event_source:** add CloudFormationCustomResourceEvent data class. ([#4342](https://github.com/aws-powertools/powertools-lambda-python/issues/4342))
+* **events:** Update and Add Cognito User Pool Events ([#4423](https://github.com/aws-powertools/powertools-lambda-python/issues/4423))
## Maintenance
+* version bump
+* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 3 updates ([#4369](https://github.com/aws-powertools/powertools-lambda-python/issues/4369))
+* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 3 updates ([#4468](https://github.com/aws-powertools/powertools-lambda-python/issues/4468))
+* **deps:** bump datadog-lambda from 5.94.0 to 6.95.0 ([#4471](https://github.com/aws-powertools/powertools-lambda-python/issues/4471))
+* **deps:** bump redis from 5.0.4 to 5.0.5 ([#4464](https://github.com/aws-powertools/powertools-lambda-python/issues/4464))
* **deps:** bump aws-encryption-sdk from 3.2.0 to 3.3.0 ([#4393](https://github.com/aws-powertools/powertools-lambda-python/issues/4393))
-* **deps:** bump squidfunk/mkdocs-material from `48d1914` to `5358893` in /docs ([#4377](https://github.com/aws-powertools/powertools-lambda-python/issues/4377))
+* **deps:** bump codecov/codecov-action from 4.4.0 to 4.4.1 ([#4376](https://github.com/aws-powertools/powertools-lambda-python/issues/4376))
+* **deps:** bump squidfunk/mkdocs-material from `8a87f05` to `96abcbb` in /docs ([#4461](https://github.com/aws-powertools/powertools-lambda-python/issues/4461))
+* **deps:** bump typing-extensions from 4.12.1 to 4.12.2 ([#4470](https://github.com/aws-powertools/powertools-lambda-python/issues/4470))
* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 2 updates ([#4396](https://github.com/aws-powertools/powertools-lambda-python/issues/4396))
-* **deps:** bump requests from 2.31.0 to 2.32.0 ([#4383](https://github.com/aws-powertools/powertools-lambda-python/issues/4383))
* **deps:** bump aws-xray-sdk from 2.13.0 to 2.13.1 ([#4379](https://github.com/aws-powertools/powertools-lambda-python/issues/4379))
-* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 3 updates ([#4369](https://github.com/aws-powertools/powertools-lambda-python/issues/4369))
-* **deps:** bump codecov/codecov-action from 4.4.0 to 4.4.1 ([#4376](https://github.com/aws-powertools/powertools-lambda-python/issues/4376))
-* **deps-dev:** bump mypy-boto3-secretsmanager from 1.34.107 to 1.34.109 in the boto-typing group ([#4378](https://github.com/aws-powertools/powertools-lambda-python/issues/4378))
-* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.152 to 0.1.154 ([#4382](https://github.com/aws-powertools/powertools-lambda-python/issues/4382))
-* **deps-dev:** bump sentry-sdk from 2.2.0 to 2.2.1 ([#4388](https://github.com/aws-powertools/powertools-lambda-python/issues/4388))
+* **deps:** bump actions/dependency-review-action from 4.3.2 to 4.3.3 ([#4456](https://github.com/aws-powertools/powertools-lambda-python/issues/4456))
+* **deps:** bump aws-xray-sdk from 2.13.1 to 2.14.0 ([#4453](https://github.com/aws-powertools/powertools-lambda-python/issues/4453))
+* **deps:** bump typing-extensions from 4.11.0 to 4.12.0 ([#4404](https://github.com/aws-powertools/powertools-lambda-python/issues/4404))
+* **deps:** bump squidfunk/mkdocs-material from `5358893` to `8a87f05` in /docs ([#4408](https://github.com/aws-powertools/powertools-lambda-python/issues/4408))
+* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 3.0.6 to 3.0.7 ([#4478](https://github.com/aws-powertools/powertools-lambda-python/issues/4478))
+* **deps:** bump squidfunk/mkdocs-material from `48d1914` to `5358893` in /docs ([#4377](https://github.com/aws-powertools/powertools-lambda-python/issues/4377))
+* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 3 updates ([#4444](https://github.com/aws-powertools/powertools-lambda-python/issues/4444))
+* **deps:** bump pydantic from 1.10.15 to 1.10.16 ([#4485](https://github.com/aws-powertools/powertools-lambda-python/issues/4485))
+* **deps:** bump datadog-lambda from 6.95.0 to 6.96.0 ([#4489](https://github.com/aws-powertools/powertools-lambda-python/issues/4489))
+* **deps:** bump actions/checkout from 4.1.6 to 4.1.7 ([#4493](https://github.com/aws-powertools/powertools-lambda-python/issues/4493))
+* **deps:** bump typing-extensions from 4.12.0 to 4.12.1 ([#4440](https://github.com/aws-powertools/powertools-lambda-python/issues/4440))
+* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 3.0.5 to 3.0.6 ([#4445](https://github.com/aws-powertools/powertools-lambda-python/issues/4445))
+* **deps:** bump requests from 2.31.0 to 2.32.0 ([#4383](https://github.com/aws-powertools/powertools-lambda-python/issues/4383))
+* **deps-dev:** bump aws-cdk from 2.143.1 to 2.144.0 ([#4443](https://github.com/aws-powertools/powertools-lambda-python/issues/4443))
+* **deps-dev:** bump aws-cdk-lib from 2.143.1 to 2.144.0 ([#4441](https://github.com/aws-powertools/powertools-lambda-python/issues/4441))
+* **deps-dev:** bump ruff from 0.4.6 to 0.4.7 ([#4435](https://github.com/aws-powertools/powertools-lambda-python/issues/4435))
+* **deps-dev:** bump aws-cdk-aws-lambda-python-alpha from 2.143.0a0 to 2.143.1a0 ([#4433](https://github.com/aws-powertools/powertools-lambda-python/issues/4433))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.164 to 0.1.169 ([#4442](https://github.com/aws-powertools/powertools-lambda-python/issues/4442))
+* **deps-dev:** bump pytest from 8.2.1 to 8.2.2 ([#4450](https://github.com/aws-powertools/powertools-lambda-python/issues/4450))
+* **deps-dev:** bump aws-cdk from 2.143.0 to 2.143.1 ([#4430](https://github.com/aws-powertools/powertools-lambda-python/issues/4430))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.163 to 0.1.164 ([#4428](https://github.com/aws-powertools/powertools-lambda-python/issues/4428))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.161 to 0.1.163 ([#4425](https://github.com/aws-powertools/powertools-lambda-python/issues/4425))
+* **deps-dev:** bump cfn-lint from 0.87.5 to 0.87.6 ([#4486](https://github.com/aws-powertools/powertools-lambda-python/issues/4486))
+* **deps-dev:** bump sentry-sdk from 2.3.1 to 2.4.0 ([#4449](https://github.com/aws-powertools/powertools-lambda-python/issues/4449))
+* **deps-dev:** bump ruff from 0.4.5 to 0.4.6 ([#4417](https://github.com/aws-powertools/powertools-lambda-python/issues/4417))
+* **deps-dev:** bump cfn-lint from 0.87.3 to 0.87.4 ([#4419](https://github.com/aws-powertools/powertools-lambda-python/issues/4419))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.159 to 0.1.161 ([#4420](https://github.com/aws-powertools/powertools-lambda-python/issues/4420))
+* **deps-dev:** bump coverage from 7.5.2 to 7.5.3 ([#4418](https://github.com/aws-powertools/powertools-lambda-python/issues/4418))
+* **deps-dev:** bump mypy-boto3-dynamodb from 1.34.113 to 1.34.114 in the boto-typing group ([#4416](https://github.com/aws-powertools/powertools-lambda-python/issues/4416))
+* **deps-dev:** bump mkdocs-material from 9.5.24 to 9.5.25 ([#4411](https://github.com/aws-powertools/powertools-lambda-python/issues/4411))
+* **deps-dev:** bump aws-cdk-lib from 2.143.0 to 2.143.1 ([#4429](https://github.com/aws-powertools/powertools-lambda-python/issues/4429))
+* **deps-dev:** bump aws-cdk-aws-lambda-python-alpha from 2.142.1a0 to 2.143.0a0 ([#4410](https://github.com/aws-powertools/powertools-lambda-python/issues/4410))
+* **deps-dev:** bump mypy-boto3-dynamodb from 1.34.97 to 1.34.113 in the boto-typing group ([#4409](https://github.com/aws-powertools/powertools-lambda-python/issues/4409))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.158 to 0.1.159 ([#4412](https://github.com/aws-powertools/powertools-lambda-python/issues/4412))
+* **deps-dev:** bump coverage from 7.5.1 to 7.5.2 ([#4413](https://github.com/aws-powertools/powertools-lambda-python/issues/4413))
+* **deps-dev:** bump aws-cdk-aws-lambda-python-alpha from 2.143.1a0 to 2.144.0a0 ([#4448](https://github.com/aws-powertools/powertools-lambda-python/issues/4448))
+* **deps-dev:** bump mypy-boto3-s3 from 1.34.105 to 1.34.120 in the boto-typing group ([#4452](https://github.com/aws-powertools/powertools-lambda-python/issues/4452))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.169 to 0.1.173 ([#4459](https://github.com/aws-powertools/powertools-lambda-python/issues/4459))
+* **deps-dev:** bump aws-cdk-lib from 2.142.1 to 2.143.0 ([#4403](https://github.com/aws-powertools/powertools-lambda-python/issues/4403))
+* **deps-dev:** bump aws-cdk from 2.142.1 to 2.143.0 ([#4402](https://github.com/aws-powertools/powertools-lambda-python/issues/4402))
+* **deps-dev:** bump ruff from 0.4.4 to 0.4.5 ([#4399](https://github.com/aws-powertools/powertools-lambda-python/issues/4399))
+* **deps-dev:** bump sentry-sdk from 2.2.1 to 2.3.1 ([#4398](https://github.com/aws-powertools/powertools-lambda-python/issues/4398))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.157 to 0.1.158 ([#4397](https://github.com/aws-powertools/powertools-lambda-python/issues/4397))
+* **deps-dev:** bump ruff from 0.4.7 to 0.4.8 ([#4455](https://github.com/aws-powertools/powertools-lambda-python/issues/4455))
+* **deps-dev:** bump sentry-sdk from 2.4.0 to 2.5.0 ([#4462](https://github.com/aws-powertools/powertools-lambda-python/issues/4462))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.155 to 0.1.157 ([#4394](https://github.com/aws-powertools/powertools-lambda-python/issues/4394))
+* **deps-dev:** bump mkdocs-material from 9.5.25 to 9.5.26 ([#4463](https://github.com/aws-powertools/powertools-lambda-python/issues/4463))
+* **deps-dev:** bump mypy-boto3-cloudformation from 1.34.84 to 1.34.111 in the boto-typing group ([#4392](https://github.com/aws-powertools/powertools-lambda-python/issues/4392))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.154 to 0.1.155 ([#4386](https://github.com/aws-powertools/powertools-lambda-python/issues/4386))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.173 to 0.1.174 ([#4466](https://github.com/aws-powertools/powertools-lambda-python/issues/4466))
* **deps-dev:** bump pytest-asyncio from 0.23.6 to 0.23.7 ([#4387](https://github.com/aws-powertools/powertools-lambda-python/issues/4387))
+* **deps-dev:** bump sentry-sdk from 2.2.0 to 2.2.1 ([#4388](https://github.com/aws-powertools/powertools-lambda-python/issues/4388))
+* **deps-dev:** bump ijson from 3.2.3 to 3.3.0 ([#4465](https://github.com/aws-powertools/powertools-lambda-python/issues/4465))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.152 to 0.1.154 ([#4382](https://github.com/aws-powertools/powertools-lambda-python/issues/4382))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.174 to 0.1.175 ([#4472](https://github.com/aws-powertools/powertools-lambda-python/issues/4472))
+* **deps-dev:** bump mypy-boto3-secretsmanager from 1.34.107 to 1.34.109 in the boto-typing group ([#4378](https://github.com/aws-powertools/powertools-lambda-python/issues/4378))
+* **deps-dev:** bump sentry-sdk from 2.5.0 to 2.5.1 ([#4469](https://github.com/aws-powertools/powertools-lambda-python/issues/4469))
+* **deps-dev:** bump cfn-lint from 0.87.4 to 0.87.5 ([#4479](https://github.com/aws-powertools/powertools-lambda-python/issues/4479))
* **deps-dev:** bump mkdocs-material from 9.5.23 to 9.5.24 ([#4380](https://github.com/aws-powertools/powertools-lambda-python/issues/4380))
* **deps-dev:** bump pytest from 8.2.0 to 8.2.1 ([#4381](https://github.com/aws-powertools/powertools-lambda-python/issues/4381))
-* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.154 to 0.1.155 ([#4386](https://github.com/aws-powertools/powertools-lambda-python/issues/4386))
-* **deps-dev:** bump mypy-boto3-cloudformation from 1.34.84 to 1.34.111 in the boto-typing group ([#4392](https://github.com/aws-powertools/powertools-lambda-python/issues/4392))
+* **deps-dev:** bump aws-cdk from 2.144.0 to 2.145.0 ([#4482](https://github.com/aws-powertools/powertools-lambda-python/issues/4482))
+* **deps-dev:** bump aws-cdk-lib from 2.144.0 to 2.145.0 ([#4481](https://github.com/aws-powertools/powertools-lambda-python/issues/4481))
* **deps-dev:** bump aws-cdk-aws-lambda-python-alpha from 2.141.0a0 to 2.142.1a0 ([#4367](https://github.com/aws-powertools/powertools-lambda-python/issues/4367))
-* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.155 to 0.1.157 ([#4394](https://github.com/aws-powertools/powertools-lambda-python/issues/4394))
+* **deps-dev:** bump aws-cdk-aws-lambda-python-alpha from 2.144.0a0 to 2.145.0a0 ([#4487](https://github.com/aws-powertools/powertools-lambda-python/issues/4487))
* **deps-dev:** bump aws-cdk from 2.142.0 to 2.142.1 ([#4366](https://github.com/aws-powertools/powertools-lambda-python/issues/4366))
* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.150 to 0.1.152 ([#4368](https://github.com/aws-powertools/powertools-lambda-python/issues/4368))
-* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.157 to 0.1.158 ([#4397](https://github.com/aws-powertools/powertools-lambda-python/issues/4397))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.176 to 0.1.179 ([#4488](https://github.com/aws-powertools/powertools-lambda-python/issues/4488))
* **deps-dev:** bump cfn-lint from 0.87.2 to 0.87.3 ([#4370](https://github.com/aws-powertools/powertools-lambda-python/issues/4370))
-* **deps-dev:** bump sentry-sdk from 2.2.1 to 2.3.1 ([#4398](https://github.com/aws-powertools/powertools-lambda-python/issues/4398))
-* **deps-dev:** bump ruff from 0.4.4 to 0.4.5 ([#4399](https://github.com/aws-powertools/powertools-lambda-python/issues/4399))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.175 to 0.1.176 ([#4480](https://github.com/aws-powertools/powertools-lambda-python/issues/4480))
+* **libraries:** add jmespath as a required dependency ([#4422](https://github.com/aws-powertools/powertools-lambda-python/issues/4422))
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index cc37371cb88..dc04db7ce4f 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -67,7 +67,7 @@ timeline
Pre-Pull Request
(make pr) : Code linting
: Docs linting
: Static typing analysis
- : Tests (unit|functional|perf)
+ : Tests (unit|functional|perf|dependencies)
: Security baseline
: Complexity baseline
: +pre-commit checks
diff --git a/Makefile b/Makefile
index a91464e5f56..114a817b1cd 100644
--- a/Makefile
+++ b/Makefile
@@ -13,7 +13,6 @@ dev:
dev-gitpod:
pip install --upgrade pip poetry
- @$(MAKE) dev-version-plugin
poetry install --extras "all redis datamasking"
pre-commit install
@@ -33,6 +32,9 @@ test:
poetry run pytest -m "not perf" --ignore tests/e2e --cov=aws_lambda_powertools --cov-report=xml
poetry run pytest --cache-clear tests/performance
+test-dependencies:
+ poetry run nox --error-on-external-run --reuse-venv=yes --non-interactive
+
test-pydanticv2:
poetry run pytest -m "not perf" --ignore tests/e2e
@@ -84,7 +86,7 @@ complexity-baseline:
$(info Maintenability index)
poetry run radon mi aws_lambda_powertools
$(info Cyclomatic complexity index)
- poetry run xenon --max-absolute C --max-modules A --max-average A aws_lambda_powertools --exclude aws_lambda_powertools/shared/json_encoder.py
+ poetry run xenon --max-absolute C --max-modules A --max-average A aws_lambda_powertools --exclude aws_lambda_powertools/shared/json_encoder.py,aws_lambda_powertools/utilities/validation/base.py
#
# Use `poetry version /` for version bump
diff --git a/README.md b/README.md
index 7ebe5ba0d6c..215e4bfe828 100644
--- a/README.md
+++ b/README.md
@@ -53,14 +53,23 @@ Knowing which companies are using this library is important to help prioritize t
The following companies, among others, use Powertools:
+* [Alma Media](https://www.almamedia.fi/en/)
+* [Banxware](https://www.banxware.com/)
+* [Brsk](https://www.brsk.co.uk/)
+* [BusPatrol](https://buspatrol.com/)
* [Capital One](https://www.capitalone.com/)
+* [Caylent](https://caylent.com/)
+* [CHS Inc.](https://www.chsinc.com/)
* [CPQi (Exadel Financial Services)](https://cpqi.com/)
* [CloudZero](https://www.cloudzero.com/)
* [CyberArk](https://www.cyberark.com/)
* [globaldatanet](https://globaldatanet.com/)
* [IMS](https://ims.tech/)
* [Jit Security](https://www.jit.io/)
+* [LocalStack](https://www.localstack.cloud/)
* [Propellor.ai](https://www.propellor.ai/)
+* [Pushpay](https://pushpay.com/)
+* [Recast](https://getrecast.com/)
* [TopSport](https://www.topsport.com.au/)
* [Transformity](https://transformity.tech/)
* [Trek10](https://www.trek10.com/)
diff --git a/aws_lambda_powertools/event_handler/api_gateway.py b/aws_lambda_powertools/event_handler/api_gateway.py
index 372a4704944..fcb22addf6b 100644
--- a/aws_lambda_powertools/event_handler/api_gateway.py
+++ b/aws_lambda_powertools/event_handler/api_gateway.py
@@ -14,10 +14,12 @@
from pathlib import Path
from typing import TYPE_CHECKING, Any, Callable, Generic, Literal, Mapping, Match, Pattern, Sequence, TypeVar, cast
+from typing_extensions import override
+
from aws_lambda_powertools.event_handler import content_types
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.exceptions import RequestValidationError, SchemaValidationError
from aws_lambda_powertools.event_handler.openapi.types import (
COMPONENT_REF_PREFIX,
METHODS_WITH_BODY,
@@ -27,7 +29,13 @@
validation_error_definition,
validation_error_response_definition,
)
-from aws_lambda_powertools.event_handler.util import _FrozenDict, extract_origin_header
+from aws_lambda_powertools.event_handler.util import (
+ _FrozenDict,
+ _FrozenListDict,
+ _validate_openapi_security_parameters,
+ extract_origin_header,
+)
+from aws_lambda_powertools.shared.cookies import Cookie
from aws_lambda_powertools.shared.functions import powertools_dev_is_set
from aws_lambda_powertools.shared.json_encoder import Encoder
from aws_lambda_powertools.utilities.data_classes import (
@@ -96,20 +104,22 @@ class CORSConfig:
Examples
--------
- Simple cors example using the default permissive cors, not this should only be used during early prototyping
+ Simple CORS example using the default permissive CORS, note that this should only be used during early prototyping.
```python
- from aws_lambda_powertools.event_handler import APIGatewayRestResolver
+ from aws_lambda_powertools.event_handler.api_gateway import (
+ APIGatewayRestResolver, CORSConfig
+ )
- app = APIGatewayRestResolver()
+ app = APIGatewayRestResolver(cors=CORSConfig())
- @app.get("/my/path", cors=True)
+ @app.get("/my/path")
def with_cors():
return {"message": "Foo"}
```
Using a custom CORSConfig where `with_cors` used the custom provided CORSConfig and `without_cors`
- do not include any cors headers.
+ do not include any CORS headers.
```python
from aws_lambda_powertools.event_handler.api_gateway import (
@@ -166,9 +176,12 @@ def __init__(
allow_credentials: bool
A boolean value that sets the value of `Access-Control-Allow-Credentials`
"""
+
self._allowed_origins = [allow_origin]
+
if extra_origins:
self._allowed_origins.extend(extra_origins)
+
self.allow_headers = set(self._REQUIRED_HEADERS + (allow_headers or []))
self.expose_headers = expose_headers or []
self.max_age = max_age
@@ -189,17 +202,42 @@ def to_dict(self, origin: str | None) -> dict[str, str]:
# The origin matched an allowed origin, so return the CORS headers
headers = {
"Access-Control-Allow-Origin": origin,
- "Access-Control-Allow-Headers": ",".join(sorted(self.allow_headers)),
+ "Access-Control-Allow-Headers": CORSConfig.build_allow_methods(self.allow_headers),
}
if self.expose_headers:
headers["Access-Control-Expose-Headers"] = ",".join(self.expose_headers)
if self.max_age is not None:
headers["Access-Control-Max-Age"] = str(self.max_age)
- if self.allow_credentials is True:
+ if origin != "*" and self.allow_credentials is True:
headers["Access-Control-Allow-Credentials"] = "true"
return headers
+ def allowed_origin(self, extracted_origin: str) -> str | None:
+ if extracted_origin in self._allowed_origins:
+ return extracted_origin
+ if extracted_origin is not None and "*" in self._allowed_origins:
+ return "*"
+
+ return None
+
+ @staticmethod
+ def build_allow_methods(methods: set[str]) -> str:
+ """Build sorted comma delimited methods for Access-Control-Allow-Methods header
+
+ Parameters
+ ----------
+ methods : set[str]
+ Set of HTTP Methods
+
+ Returns
+ -------
+ set[str]
+ Formatted string with all HTTP Methods allowed for CORS e.g., `GET, OPTIONS`
+
+ """
+ return ",".join(sorted(methods))
+
class Response(Generic[ResponseT]):
"""Response data class that provides greater control over what is returned from the proxy event"""
@@ -260,16 +298,17 @@ def __init__(
func: Callable,
cors: bool,
compress: bool,
- cache_control: str | None,
- summary: str | None,
- description: str | None,
- responses: dict[int, OpenAPIResponse] | None,
- response_description: str | None,
- tags: list[str] | None,
- operation_id: str | None,
- include_in_schema: bool,
- security: list[dict[str, list[str]]] | None,
- middlewares: list[Callable[..., Response]] | None,
+ cache_control: str | None = None,
+ summary: str | None = None,
+ description: str | None = None,
+ responses: dict[int, OpenAPIResponse] | None = None,
+ response_description: str | None = None,
+ tags: list[str] | None = None,
+ operation_id: str | None = None,
+ include_in_schema: bool = True,
+ security: list[dict[str, list[str]]] | None = None,
+ openapi_extensions: dict[str, Any] | None = None,
+ middlewares: list[Callable[..., Response]] | None = None,
):
"""
@@ -306,6 +345,8 @@ def __init__(
Whether or not to include this route in the OpenAPI schema
security: list[dict[str, list[str]]], optional
The OpenAPI security for this route
+ openapi_extensions: dict[str, Any], optional
+ Additional OpenAPI extensions as a dictionary.
middlewares: list[Callable[..., Response]] | None
The list of route middlewares to be called in order.
"""
@@ -329,6 +370,7 @@ def __init__(
self.tags = tags or []
self.include_in_schema = include_in_schema
self.security = security
+ self.openapi_extensions = openapi_extensions
self.middlewares = middlewares or []
self.operation_id = operation_id or self._generate_operation_id()
@@ -480,6 +522,10 @@ def _get_openapi_path(
if self.security:
operation["security"] = self.security
+ # Add OpenAPI extensions if present
+ if self.openapi_extensions:
+ operation.update(self.openapi_extensions)
+
# Add the parameters to the OpenAPI operation
if parameters:
all_parameters = {(param["in"], param["name"]): param for param in parameters}
@@ -762,7 +808,10 @@ def __init__(
def _add_cors(self, event: ResponseEventT, cors: CORSConfig):
"""Update headers to include the configured Access-Control headers"""
extracted_origin_header = extract_origin_header(event.resolved_headers_field)
- self.response.headers.update(cors.to_dict(extracted_origin_header))
+
+ origin = cors.allowed_origin(extracted_origin_header)
+ if origin is not None:
+ self.response.headers.update(cors.to_dict(origin))
def _add_cache_control(self, cache_control: str):
"""Set the specified cache control headers for 200 http responses. For non-200 `no-cache` is used."""
@@ -873,6 +922,7 @@ def route(
operation_id: str | None = None,
include_in_schema: bool = True,
security: list[dict[str, list[str]]] | None = None,
+ openapi_extensions: dict[str, Any] | None = None,
middlewares: list[Callable[..., Any]] | None = None,
):
raise NotImplementedError()
@@ -932,6 +982,7 @@ def get(
operation_id: str | None = None,
include_in_schema: bool = True,
security: list[dict[str, list[str]]] | None = None,
+ openapi_extensions: dict[str, Any] | None = None,
middlewares: list[Callable[..., Any]] | None = None,
):
"""Get route decorator with GET `method`
@@ -970,6 +1021,7 @@ def lambda_handler(event, context):
operation_id,
include_in_schema,
security,
+ openapi_extensions,
middlewares,
)
@@ -987,6 +1039,7 @@ def post(
operation_id: str | None = None,
include_in_schema: bool = True,
security: list[dict[str, list[str]]] | None = None,
+ openapi_extensions: dict[str, Any] | None = None,
middlewares: list[Callable[..., Any]] | None = None,
):
"""Post route decorator with POST `method`
@@ -1026,6 +1079,7 @@ def lambda_handler(event, context):
operation_id,
include_in_schema,
security,
+ openapi_extensions,
middlewares,
)
@@ -1043,6 +1097,7 @@ def put(
operation_id: str | None = None,
include_in_schema: bool = True,
security: list[dict[str, list[str]]] | None = None,
+ openapi_extensions: dict[str, Any] | None = None,
middlewares: list[Callable[..., Any]] | None = None,
):
"""Put route decorator with PUT `method`
@@ -1082,6 +1137,7 @@ def lambda_handler(event, context):
operation_id,
include_in_schema,
security,
+ openapi_extensions,
middlewares,
)
@@ -1099,6 +1155,7 @@ def delete(
operation_id: str | None = None,
include_in_schema: bool = True,
security: list[dict[str, list[str]]] | None = None,
+ openapi_extensions: dict[str, Any] | None = None,
middlewares: list[Callable[..., Any]] | None = None,
):
"""Delete route decorator with DELETE `method`
@@ -1137,6 +1194,7 @@ def lambda_handler(event, context):
operation_id,
include_in_schema,
security,
+ openapi_extensions,
middlewares,
)
@@ -1154,6 +1212,7 @@ def patch(
operation_id: str | None = None,
include_in_schema: bool = True,
security: list[dict[str, list[str]]] | None = None,
+ openapi_extensions: dict[str, Any] | None = None,
middlewares: list[Callable] | None = None,
):
"""Patch route decorator with PATCH `method`
@@ -1195,6 +1254,7 @@ def lambda_handler(event, context):
operation_id,
include_in_schema,
security,
+ openapi_extensions,
middlewares,
)
@@ -1212,6 +1272,7 @@ def head(
operation_id: str | None = None,
include_in_schema: bool = True,
security: list[dict[str, list[str]]] | None = None,
+ openapi_extensions: dict[str, Any] | None = None,
middlewares: list[Callable] | None = None,
):
"""Head route decorator with HEAD `method`
@@ -1252,6 +1313,7 @@ def lambda_handler(event, context):
operation_id,
include_in_schema,
security,
+ openapi_extensions,
middlewares,
)
@@ -1372,7 +1434,6 @@ def _registered_api_adapter(app: ApiGatewayResolver, next_middleware: Callable[.
"""
route_args: dict = app.context.get("_route_args", {})
logger.debug(f"Calling API Route Handler: {route_args}")
-
return app._to_response(next_middleware(**route_args))
@@ -1473,6 +1534,7 @@ def get_openapi_schema(
license_info: License | None = None,
security_schemes: dict[str, SecurityScheme] | None = None,
security: list[dict[str, list[str]]] | None = None,
+ openapi_extensions: dict[str, Any] | None = None,
) -> OpenAPI:
"""
Returns the OpenAPI schema as a pydantic model.
@@ -1503,6 +1565,8 @@ def get_openapi_schema(
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.
+ openapi_extensions: Dict[str, Any], optional
+ Additional OpenAPI extensions as a dictionary.
Returns
-------
@@ -1535,11 +1599,15 @@ def get_openapi_schema(
info.update({field: value for field, value in optional_fields.items() if value})
+ if not isinstance(openapi_extensions, dict):
+ openapi_extensions = {}
+
output: dict[str, Any] = {
"openapi": openapi_version,
"info": info,
"servers": self._get_openapi_servers(servers),
"security": self._get_openapi_security(security, security_schemes),
+ **openapi_extensions,
}
components: dict[str, dict[str, Any]] = {}
@@ -1560,6 +1628,16 @@ def get_openapi_schema(
# Add routes to the OpenAPI schema
for route in all_routes:
+
+ if route.security and not _validate_openapi_security_parameters(
+ security=route.security,
+ security_schemes=security_schemes,
+ ):
+ raise SchemaValidationError(
+ "Security configuration was not found in security_schemas or security_schema was not defined. "
+ "See: https://docs.powertools.aws.dev/lambda/python/latest/core/event_handler/api_gateway/#security-schemes",
+ )
+
if not route.include_in_schema:
continue
@@ -1605,12 +1683,11 @@ def _get_openapi_security(
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")
+ if not _validate_openapi_security_parameters(security=security, security_schemes=security_schemes):
+ raise SchemaValidationError(
+ "Security configuration was not found in security_schemas or security_schema was not defined. "
+ "See: https://docs.powertools.aws.dev/lambda/python/latest/core/event_handler/api_gateway/#security-schemes",
+ )
return security
@@ -1641,6 +1718,7 @@ def get_openapi_json_schema(
license_info: License | None = None,
security_schemes: dict[str, SecurityScheme] | None = None,
security: list[dict[str, list[str]]] | None = None,
+ openapi_extensions: dict[str, Any] | None = None,
) -> str:
"""
Returns the OpenAPI schema as a JSON serializable dict
@@ -1671,6 +1749,8 @@ def get_openapi_json_schema(
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.
+ openapi_extensions: Dict[str, Any], optional
+ Additional OpenAPI extensions as a dictionary.
Returns
-------
@@ -1693,6 +1773,7 @@ def get_openapi_json_schema(
license_info=license_info,
security_schemes=security_schemes,
security=security,
+ openapi_extensions=openapi_extensions,
),
by_alias=True,
exclude_none=True,
@@ -1720,6 +1801,7 @@ def enable_swagger(
security: list[dict[str, list[str]]] | None = None,
oauth2_config: OAuth2Config | None = None,
persist_authorization: bool = False,
+ openapi_extensions: dict[str, Any] | None = None,
):
"""
Returns the OpenAPI schema as a JSON serializable dict
@@ -1762,6 +1844,8 @@ def enable_swagger(
The OAuth2 configuration for the Swagger UI.
persist_authorization: bool, optional
Whether to persist authorization data on browser close/refresh.
+ openapi_extensions: dict[str, Any], optional
+ Additional OpenAPI extensions as a dictionary.
"""
from aws_lambda_powertools.event_handler.openapi.compat import model_json
from aws_lambda_powertools.event_handler.openapi.models import Server
@@ -1811,6 +1895,7 @@ def swagger_handler():
license_info=license_info,
security_schemes=security_schemes,
security=security,
+ openapi_extensions=openapi_extensions,
)
# The .replace('', '<\\/') part is necessary to prevent a potential issue where the JSON string contains
@@ -1835,7 +1920,6 @@ def swagger_handler():
body = generate_swagger_html(
escaped_spec,
- f"{base_path}{path}",
swagger_js,
swagger_css,
swagger_base_url,
@@ -1864,6 +1948,7 @@ def route(
operation_id: str | None = None,
include_in_schema: bool = True,
security: list[dict[str, list[str]]] | None = None,
+ openapi_extensions: dict[str, Any] | None = None,
middlewares: list[Callable[..., Any]] | None = None,
):
"""Route decorator includes parameter `method`"""
@@ -1891,6 +1976,7 @@ def register_resolver(func: Callable):
operation_id,
include_in_schema,
security,
+ openapi_extensions,
middlewares,
)
@@ -1916,6 +2002,36 @@ def register_resolver(func: Callable):
def resolve(self, event, context) -> dict[str, Any]:
"""Resolves the response based on the provide event and decorator routes
+ ## Internals
+
+ Request processing chain is triggered by a Route object being called _(`_call_route` -> `__call__`)_:
+
+ 1. **When a route is matched**
+ 1.1. Exception handlers _(if any exception bubbled up and caught)_
+ 1.2. Global middlewares _(before, and after on the way back)_
+ 1.3. Path level middleware _(before, and after on the way back)_
+ 1.4. Middleware adapter to ensure Response is homogenous (_registered_api_adapter)
+ 1.5. Run actual route
+ 2. **When a route is NOT matched**
+ 2.1. Exception handlers _(if any exception bubbled up and caught)_
+ 2.2. Global middlewares _(before, and after on the way back)_
+ 2.3. Path level middleware _(before, and after on the way back)_
+ 2.4. Middleware adapter to ensure Response is homogenous (_registered_api_adapter)
+ 2.5. Run 404 route handler
+ 3. **When a route is a pre-flight CORS (often not matched)**
+ 3.1. Exception handlers _(if any exception bubbled up and caught)_
+ 3.2. Global middlewares _(before, and after on the way back)_
+ 3.3. Path level middleware _(before, and after on the way back)_
+ 3.4. Middleware adapter to ensure Response is homogenous (_registered_api_adapter)
+ 3.5. Return 204 with appropriate CORS headers
+ 4. **When a route is matched with Data Validation enabled**
+ 4.1. Exception handlers _(if any exception bubbled up and caught)_
+ 4.2. Data Validation middleware _(before, and after on the way back)_
+ 4.3. Global middlewares _(before, and after on the way back)_
+ 4.4. Path level middleware _(before, and after on the way back)_
+ 4.5. Middleware adapter to ensure Response is homogenous (_registered_api_adapter)
+ 4.6. Run actual route
+
Parameters
----------
event: dict[str, Any]
@@ -2039,7 +2155,9 @@ def _resolve(self) -> ResponseBuilder:
method = self.current_event.http_method.upper()
path = self._remove_prefix(self.current_event.path)
- for route in self._static_routes + self._dynamic_routes:
+ registered_routes = self._static_routes + self._dynamic_routes
+
+ for route in registered_routes:
if method != route.method:
continue
match_results: Match | None = route.rule.match(path)
@@ -2051,8 +2169,7 @@ def _resolve(self) -> ResponseBuilder:
route_keys = self._convert_matches_into_route_keys(match_results)
return self._call_route(route, route_keys) # pass fn args
- logger.debug(f"No match found for path {path} and method {method}")
- return self._not_found(method)
+ return self._handle_not_found(method=method, path=path)
def _remove_prefix(self, path: str) -> str:
"""Remove the configured prefix from the path"""
@@ -2090,36 +2207,65 @@ def _path_starts_with(path: str, prefix: str):
return path.startswith(prefix + "/")
- def _not_found(self, method: str) -> ResponseBuilder:
+ def _handle_not_found(self, method: str, path: str) -> ResponseBuilder:
"""Called when no matching route was found and includes support for the cors preflight response"""
- headers = {}
- if self._cors:
- logger.debug("CORS is enabled, updating headers.")
- extracted_origin_header = extract_origin_header(self.current_event.resolved_headers_field)
- headers.update(self._cors.to_dict(extracted_origin_header))
-
- if method == "OPTIONS":
- logger.debug("Pre-flight request detected. Returning CORS with null response")
- headers["Access-Control-Allow-Methods"] = ",".join(sorted(self._cors_methods))
- return ResponseBuilder(
- response=Response(status_code=204, content_type=None, headers=headers, body=""),
- serializer=self._serializer,
- )
+ logger.debug(f"No match found for path {path} and method {method}")
- handler = self._lookup_exception_handler(NotFoundError)
- if handler:
- return self._response_builder_class(response=handler(NotFoundError()), serializer=self._serializer)
+ def not_found_handler():
+ """Route handler for 404s
+
+ It handles in the following order:
+
+ 1. Pre-flight CORS requests (OPTIONS)
+ 2. Detects and calls custom HTTP 404 handler
+ 3. Returns standard 404 along with CORS headers
+
+ Returns
+ -------
+ Response
+ HTTP 404 response
+ """
+ _headers: dict[str, Any] = {}
+
+ # Pre-flight request? Return immediately to avoid browser error
+ if self._cors and method == "OPTIONS":
+ logger.debug("Pre-flight request detected. Returning CORS with empty response")
+ _headers["Access-Control-Allow-Methods"] = CORSConfig.build_allow_methods(self._cors_methods)
+
+ return Response(status_code=204, content_type=None, headers=_headers, body="")
+
+ # Customer registered 404 route? Call it.
+ custom_not_found_handler = self._lookup_exception_handler(NotFoundError)
+ if custom_not_found_handler:
+ return custom_not_found_handler(NotFoundError())
- return self._response_builder_class(
- response=Response(
+ # No CORS and no custom 404 fn? Default response
+ return Response(
status_code=HTTPStatus.NOT_FOUND.value,
content_type=content_types.APPLICATION_JSON,
- headers=headers,
+ headers=_headers,
body={"statusCode": HTTPStatus.NOT_FOUND.value, "message": "Not found"},
- ),
- serializer=self._serializer,
+ )
+
+ # We create a route to trigger entire request chain (middleware+exception handlers)
+ route = Route(
+ rule=self._compile_regex(r".*"),
+ method=method,
+ path=path,
+ func=not_found_handler,
+ cors=self._cors_enabled,
+ compress=False,
)
+ # Add matched Route reference into the Resolver context
+ self.append_context(_route=route, _path=path)
+
+ # Kick-off request chain:
+ # -> exception_handlers()
+ # --> middlewares()
+ # ---> not_found_route()
+ return self._call_route(route=route, route_arguments={})
+
def _call_route(self, route: Route, route_arguments: dict[str, str]) -> ResponseBuilder:
"""Actually call the matching route with any provided keyword arguments."""
try:
@@ -2344,6 +2490,7 @@ def route(
operation_id: str | None = None,
include_in_schema: bool = True,
security: list[dict[str, list[str]]] | None = None,
+ openapi_extensions: dict[str, Any] | None = None,
middlewares: list[Callable[..., Any]] | None = None,
):
def register_route(func: Callable):
@@ -2351,6 +2498,8 @@ def register_route(func: Callable):
methods = (method,) if isinstance(method, str) else tuple(method)
frozen_responses = _FrozenDict(responses) if responses else None
frozen_tags = frozenset(tags) if tags else None
+ frozen_security = _FrozenListDict(security) if security else None
+ fronzen_openapi_extensions = _FrozenDict(openapi_extensions) if openapi_extensions else None
route_key = (
rule,
@@ -2365,7 +2514,8 @@ def register_route(func: Callable):
frozen_tags,
operation_id,
include_in_schema,
- security,
+ frozen_security,
+ fronzen_openapi_extensions,
)
# Collate Middleware for routes
@@ -2446,6 +2596,7 @@ def route(
operation_id: str | None = None,
include_in_schema: bool = True,
security: list[dict[str, list[str]]] | None = None,
+ openapi_extensions: dict[str, Any] | None = None,
middlewares: list[Callable[..., Any]] | None = None,
):
# NOTE: see #1552 for more context.
@@ -2463,6 +2614,7 @@ def route(
operation_id,
include_in_schema,
security,
+ openapi_extensions,
middlewares,
)
@@ -2524,3 +2676,24 @@ def __init__(
def _get_base_path(self) -> str:
# ALB doesn't have a stage variable, so we just return an empty string
return ""
+
+ @override
+ def _to_response(self, result: dict | tuple | Response) -> Response:
+ """Convert the route's result to a Response
+
+ ALB requires a non-null body otherwise it converts as HTTP 5xx
+
+ 3 main result types are supported:
+
+ - Dict[str, Any]: Rest api response with just the Dict to json stringify and content-type is set to
+ application/json
+ - Tuple[dict, int]: Same dict handling as above but with the option of including a status code
+ - Response: returned as is, and allows for more flexibility
+ """
+
+ # NOTE: Minor override for early return on Response with null body for ALB
+ if isinstance(result, Response) and result.body is None:
+ logger.debug("ALB doesn't allow None responses; converting to empty string")
+ result.body = ""
+
+ return super()._to_response(result)
diff --git a/aws_lambda_powertools/event_handler/appsync.py b/aws_lambda_powertools/event_handler/appsync.py
index 0cb4daa7510..c60256ca706 100644
--- a/aws_lambda_powertools/event_handler/appsync.py
+++ b/aws_lambda_powertools/event_handler/appsync.py
@@ -1,100 +1,80 @@
from __future__ import annotations
+import asyncio
import logging
-from typing import TYPE_CHECKING, Any, Callable, TypeVar
+import warnings
+from typing import TYPE_CHECKING, Any, Callable
+from aws_lambda_powertools.event_handler.graphql_appsync.exceptions import InvalidBatchResponse, ResolverNotFoundError
+from aws_lambda_powertools.event_handler.graphql_appsync.router import Router
from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent
if TYPE_CHECKING:
from aws_lambda_powertools.utilities.typing import LambdaContext
-logger = logging.getLogger(__name__)
-
-AppSyncResolverEventT = TypeVar("AppSyncResolverEventT", bound=AppSyncResolverEvent)
-
+from aws_lambda_powertools.warnings import PowertoolsUserWarning
-class BaseRouter:
- current_event: AppSyncResolverEventT # type: ignore[valid-type]
- lambda_context: LambdaContext
- context: dict
-
- def __init__(self):
- self._resolvers: dict = {}
-
- def resolver(self, type_name: str = "*", field_name: str | None = None):
- """Registers the resolver for field_name
-
- Parameters
- ----------
- type_name : str
- Type name
- field_name : str
- Field name
- """
-
- def register_resolver(func):
- logger.debug(f"Adding resolver `{func.__name__}` for field `{type_name}.{field_name}`")
- self._resolvers[f"{type_name}.{field_name}"] = {"func": func}
- return func
-
- return register_resolver
-
- def append_context(self, **additional_context):
- """Append key=value data as routing context"""
- self.context.update(**additional_context)
-
- def clear_context(self):
- """Resets routing context"""
- self.context.clear()
+logger = logging.getLogger(__name__)
-class AppSyncResolver(BaseRouter):
+class AppSyncResolver(Router):
"""
- AppSync resolver decorator
+ AppSync GraphQL API Resolver
Example
-------
-
- **Sample usage**
-
- from aws_lambda_powertools.event_handler import AppSyncResolver
-
- app = AppSyncResolver()
-
- @app.resolver(type_name="Query", field_name="listLocations")
- def list_locations(page: int = 0, size: int = 10) -> list:
- # Your logic to fetch locations with arguments passed in
- return [{"id": 100, "name": "Smooth Grooves"}]
-
- @app.resolver(type_name="Merchant", field_name="extraInfo")
- def get_extra_info() -> dict:
- # Can use "app.current_event.source" to filter within the parent context
- account_type = app.current_event.source["accountType"]
- method = "BTC" if account_type == "NEW" else "USD"
- return {"preferredPaymentMethod": method}
-
- @app.resolver(field_name="commonField")
- def common_field() -> str:
- # Would match all fieldNames matching 'commonField'
- return str(uuid.uuid4())
+ ```python
+ from aws_lambda_powertools.event_handler import AppSyncResolver
+
+ app = AppSyncResolver()
+
+ @app.resolver(type_name="Query", field_name="listLocations")
+ def list_locations(page: int = 0, size: int = 10) -> list:
+ # Your logic to fetch locations with arguments passed in
+ return [{"id": 100, "name": "Smooth Grooves"}]
+
+ @app.resolver(type_name="Merchant", field_name="extraInfo")
+ def get_extra_info() -> dict:
+ # Can use "app.current_event.source" to filter within the parent context
+ account_type = app.current_event.source["accountType"]
+ method = "BTC" if account_type == "NEW" else "USD"
+ return {"preferredPaymentMethod": method}
+
+ @app.resolver(field_name="commonField")
+ def common_field() -> str:
+ # Would match all fieldNames matching 'commonField'
+ return str(uuid.uuid4())
+ ```
"""
def __init__(self):
+ """
+ Initialize a new instance of the AppSyncResolver.
+ """
super().__init__()
self.context = {} # early init as customers might add context before event resolution
- def resolve(
+ def __call__(
self,
event: dict,
context: LambdaContext,
data_model: type[AppSyncResolverEvent] = AppSyncResolverEvent,
) -> Any:
- """Resolve field_name
+ """Implicit lambda handler which internally calls `resolve`"""
+ return self.resolve(event, context, data_model)
+
+ def resolve(
+ self,
+ event: dict | list[dict],
+ context: LambdaContext,
+ data_model: type[AppSyncResolverEvent] = AppSyncResolverEvent,
+ ) -> Any:
+ """Resolves the response based on the provide event and decorator routes
Parameters
----------
- event : dict
- Lambda event
+ event : dict | list[Dict]
+ Lambda event either coming from batch processing endpoint or from standard processing endpoint
context : LambdaContext
Lambda context
data_model:
@@ -158,45 +138,214 @@ def lambda_handler(event, context):
ValueError
If we could not find a field resolver
"""
- # Maintenance: revisit generics/overload to fix [attr-defined] in mypy usage
- BaseRouter.current_event = data_model(event)
- BaseRouter.lambda_context = context
- resolver = self._get_resolver(BaseRouter.current_event.type_name, BaseRouter.current_event.field_name)
- response = resolver(**BaseRouter.current_event.arguments)
+ self.lambda_context = context
+ Router.lambda_context = context
+
+ if isinstance(event, list):
+ Router.current_batch_event = [data_model(e) for e in event]
+ response = self._call_batch_resolver(event=event, data_model=data_model)
+ else:
+ Router.current_event = data_model(event)
+ response = self._call_single_resolver(event=event, data_model=data_model)
+
self.clear_context()
return response
- def _get_resolver(self, type_name: str, field_name: str) -> Callable:
- """Get resolver for field_name
+ def _call_single_resolver(self, event: dict, data_model: type[AppSyncResolverEvent]) -> Any:
+ """Call single event resolver
Parameters
----------
- type_name : str
- Type name
- field_name : str
- Field name
+ event : dict
+ Event
+ data_model : type[AppSyncResolverEvent]
+ Data_model to decode AppSync event, by default it is of AppSyncResolverEvent type or subclass of it
+ """
+
+ logger.debug("Processing direct resolver event")
+
+ self.current_event = data_model(event)
+ resolver = self._resolver_registry.find_resolver(self.current_event.type_name, self.current_event.field_name)
+ if not resolver:
+ raise ValueError(f"No resolver found for '{self.current_event.type_name}.{self.current_event.field_name}'")
+ return resolver["func"](**self.current_event.arguments)
+
+ def _call_sync_batch_resolver(
+ self,
+ resolver: Callable,
+ raise_on_error: bool = False,
+ aggregate: bool = True,
+ ) -> list[Any]:
+ """
+ Calls a synchronous batch resolver function for each event in the current batch.
+
+ Parameters
+ ----------
+ resolver: Callable
+ The callable function to resolve events.
+ raise_on_error: bool
+ A flag indicating whether to raise an error when processing batches
+ with failed items. Defaults to False, which means errors are handled without raising exceptions.
+ aggregate: bool
+ A flag indicating whether the batch items should be processed at once or individually.
+ If True (default), the batch resolver will process all items in the batch as a single event.
+ If False, the batch resolver will process each item in the batch individually.
Returns
-------
- Callable
- callable function and configuration
+ list[Any]
+ A list of results corresponding to the resolved events.
"""
- full_name = f"{type_name}.{field_name}"
- resolver = self._resolvers.get(full_name, self._resolvers.get(f"*.{field_name}"))
- if not resolver:
- raise ValueError(f"No resolver found for '{full_name}'")
- return resolver["func"]
- def __call__(
+ logger.debug(f"Graceful error handling flag {raise_on_error=}")
+
+ # Checks whether the entire batch should be processed at once
+ if aggregate:
+ # Process the entire batch
+ response = resolver(event=self.current_batch_event)
+
+ if not isinstance(response, list):
+ raise InvalidBatchResponse("The response must be a List when using batch resolvers")
+
+ return response
+
+ # Non aggregated events, so we call this event list x times
+ # Stop on first exception we encounter
+ if raise_on_error:
+ return [
+ resolver(event=appconfig_event, **appconfig_event.arguments)
+ for appconfig_event in self.current_batch_event
+ ]
+
+ # By default, we gracefully append `None` for any records that failed processing
+ results = []
+ for idx, event in enumerate(self.current_batch_event):
+ try:
+ results.append(resolver(event=event, **event.arguments))
+ except Exception:
+ logger.debug(f"Failed to process event number {idx} from field '{event.info.field_name}'")
+ results.append(None)
+
+ return results
+
+ async def _call_async_batch_resolver(
self,
- event: dict,
- context: LambdaContext,
- data_model: type[AppSyncResolverEvent] = AppSyncResolverEvent,
- ) -> Any:
- """Implicit lambda handler which internally calls `resolve`"""
- return self.resolve(event, context, data_model)
+ resolver: Callable,
+ raise_on_error: bool = False,
+ aggregate: bool = True,
+ ) -> list[Any]:
+ """
+ Asynchronously call a batch resolver for each event in the current batch.
+
+ Parameters
+ ----------
+ resolver: Callable
+ The asynchronous resolver function.
+ raise_on_error: bool
+ A flag indicating whether to raise an error when processing batches
+ with failed items. Defaults to False, which means errors are handled without raising exceptions.
+ aggregate: bool
+ A flag indicating whether the batch items should be processed at once or individually.
+ If True (default), the batch resolver will process all items in the batch as a single event.
+ If False, the batch resolver will process each item in the batch individually.
+
+ Returns
+ -------
+ list[Any]
+ A list of results corresponding to the resolved events.
+ """
+
+ logger.debug(f"Graceful error handling flag {raise_on_error=}")
+
+ # Checks whether the entire batch should be processed at once
+ if aggregate:
+ # Process the entire batch
+ ret = await resolver(event=self.current_batch_event)
+ if not isinstance(ret, list):
+ raise InvalidBatchResponse("The response must be a List when using batch resolvers")
+
+ return ret
+
+ response: list = []
+
+ # Prime coroutines
+ tasks = [resolver(event=e, **e.arguments) for e in self.current_batch_event]
+
+ # Aggregate results or raise at first error
+ if raise_on_error:
+ response.extend(await asyncio.gather(*tasks))
+ return response
+
+ # Aggregate results and exceptions, then filter them out
+ # Use `None` upon exception for graceful error handling at GraphQL engine level
+ #
+ # NOTE: asyncio.gather(return_exceptions=True) catches and includes exceptions in the results
+ # this will become useful when we support exception handling in AppSync resolver
+ results = await asyncio.gather(*tasks, return_exceptions=True)
+ response.extend(None if isinstance(ret, Exception) else ret for ret in results)
+
+ return response
+
+ def _call_batch_resolver(self, event: list[dict], data_model: type[AppSyncResolverEvent]) -> list[Any]:
+ """Call batch event resolver for sync and async methods
+
+ Parameters
+ ----------
+ event : list[dict]
+ Batch event
+ data_model : type[AppSyncResolverEvent]
+ Data_model to decode AppSync event, by default AppSyncResolverEvent or a subclass
+
+ Returns
+ -------
+ list[Any]
+ Results of the resolver execution.
+
+ Raises
+ ------
+ InconsistentPayloadError:
+ When all events in the batch do not have the same fieldName.
+
+ ResolverNotFoundError:
+ When no resolver is found for the specified type and field.
+ """
+ logger.debug("Processing batch resolver event")
+
+ self.current_batch_event = [data_model(e) for e in event]
+ type_name, field_name = self.current_batch_event[0].type_name, self.current_batch_event[0].field_name
+
+ resolver = self._batch_resolver_registry.find_resolver(type_name, field_name)
+ async_resolver = self._async_batch_resolver_registry.find_resolver(type_name, field_name)
+
+ if resolver and async_resolver:
+ warnings.warn(
+ f"Both synchronous and asynchronous resolvers found for the same event and field."
+ f"The synchronous resolver takes precedence. Executing: {resolver['func'].__name__}",
+ stacklevel=2,
+ category=PowertoolsUserWarning,
+ )
+
+ if resolver:
+ logger.debug(f"Found sync resolver. {resolver=}, {field_name=}")
+ return self._call_sync_batch_resolver(
+ resolver=resolver["func"],
+ raise_on_error=resolver["raise_on_error"],
+ aggregate=resolver["aggregate"],
+ )
+
+ if async_resolver:
+ logger.debug(f"Found async resolver. {resolver=}, {field_name=}")
+ return asyncio.run(
+ self._call_async_batch_resolver(
+ resolver=async_resolver["func"],
+ raise_on_error=async_resolver["raise_on_error"],
+ aggregate=async_resolver["aggregate"],
+ ),
+ )
+
+ raise ResolverNotFoundError(f"No resolver found for '{type_name}.{field_name}'")
def include_router(self, router: Router) -> None:
"""Adds all resolvers defined in a router
@@ -206,15 +355,112 @@ def include_router(self, router: Router) -> None:
router : Router
A router containing a dict of field resolvers
"""
+
# Merge app and router context
+ logger.debug("Merging router and app context")
self.context.update(**router.context)
+
# use pointer to allow context clearance after event is processed e.g., resolve(evt, ctx)
router.context = self.context
- self._resolvers.update(router._resolvers)
+ logger.debug("Merging router resolver registries")
+ self._resolver_registry.merge(router._resolver_registry)
+ self._batch_resolver_registry.merge(router._batch_resolver_registry)
+ self._async_batch_resolver_registry.merge(router._async_batch_resolver_registry)
+ def resolver(self, type_name: str = "*", field_name: str | None = None) -> Callable:
+ """Registers direct resolver function for GraphQL type and field name.
-class Router(BaseRouter):
- def __init__(self):
- super().__init__()
- self.context = {} # early init as customers might add context before event resolution
+ Parameters
+ ----------
+ type_name : str, optional
+ GraphQL type e.g., Query, Mutation, by default "*" meaning any
+ field_name : Optional[str], optional
+ GraphQL field e.g., getTodo, createTodo, by default None
+
+ Returns
+ -------
+ Callable
+ Registered resolver
+
+ Example
+ -------
+
+ ```python
+ from aws_lambda_powertools.event_handler import AppSyncResolver
+
+ from typing import TypedDict
+
+ app = AppSyncResolver()
+
+ class Todo(TypedDict, total=False):
+ id: str
+ userId: str
+ title: str
+ completed: bool
+
+ # resolve any GraphQL `getTodo` queries
+ # arguments are injected as function arguments as-is
+ @app.resolver(type_name="Query", field_name="getTodo")
+ def get_todo(id: str = "", status: str = "open") -> Todo:
+ todos: Response = requests.get(f"https://jsonplaceholder.typicode.com/todos/{id}")
+ todos.raise_for_status()
+
+ return todos.json()
+
+ def lambda_handler(event, context):
+ return app.resolve(event, context)
+ ```
+ """
+ return self._resolver_registry.register(field_name=field_name, type_name=type_name)
+
+ def batch_resolver(
+ self,
+ type_name: str = "*",
+ field_name: str | None = None,
+ raise_on_error: bool = False,
+ aggregate: bool = True,
+ ) -> Callable:
+ """Registers batch resolver function for GraphQL type and field name.
+
+ By default, we handle errors gracefully by returning `None`. If you want
+ to short-circuit and fail the entire batch use `raise_on_error=True`.
+
+ Parameters
+ ----------
+ type_name : str, optional
+ GraphQL type e.g., Query, Mutation, by default "*" meaning any
+ field_name : Optional[str], optional
+ GraphQL field e.g., getTodo, createTodo, by default None
+ raise_on_error : bool, optional
+ Whether to fail entire batch upon error, or handle errors gracefully (None), by default False
+ aggregate: bool
+ A flag indicating whether the batch items should be processed at once or individually.
+ If True (default), the batch resolver will process all items in the batch as a single event.
+ If False, the batch resolver will process each item in the batch individually.
+
+ Returns
+ -------
+ Callable
+ Registered resolver
+ """
+ return self._batch_resolver_registry.register(
+ field_name=field_name,
+ type_name=type_name,
+ raise_on_error=raise_on_error,
+ aggregate=aggregate,
+ )
+
+ def async_batch_resolver(
+ self,
+ type_name: str = "*",
+ field_name: str | None = None,
+ raise_on_error: bool = False,
+ aggregate: bool = True,
+ ) -> Callable:
+ return self._async_batch_resolver_registry.register(
+ field_name=field_name,
+ type_name=type_name,
+ raise_on_error=raise_on_error,
+ aggregate=aggregate,
+ )
diff --git a/aws_lambda_powertools/event_handler/bedrock_agent.py b/aws_lambda_powertools/event_handler/bedrock_agent.py
index 1c305cd4197..8af5520a188 100644
--- a/aws_lambda_powertools/event_handler/bedrock_agent.py
+++ b/aws_lambda_powertools/event_handler/bedrock_agent.py
@@ -110,6 +110,8 @@ def get( # type: ignore[override]
include_in_schema: bool = True,
middlewares: list[Callable[..., Any]] | None = None,
) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
+
+ openapi_extensions = None
security = None
return super().get(
@@ -125,6 +127,7 @@ def get( # type: ignore[override]
operation_id,
include_in_schema,
security,
+ openapi_extensions,
middlewares,
)
@@ -145,6 +148,7 @@ def post( # type: ignore[override]
include_in_schema: bool = True,
middlewares: list[Callable[..., Any]] | None = None,
):
+ openapi_extensions = None
security = None
return super().post(
@@ -160,6 +164,7 @@ def post( # type: ignore[override]
operation_id,
include_in_schema,
security,
+ openapi_extensions,
middlewares,
)
@@ -180,6 +185,7 @@ def put( # type: ignore[override]
include_in_schema: bool = True,
middlewares: list[Callable[..., Any]] | None = None,
):
+ openapi_extensions = None
security = None
return super().put(
@@ -195,6 +201,7 @@ def put( # type: ignore[override]
operation_id,
include_in_schema,
security,
+ openapi_extensions,
middlewares,
)
@@ -215,6 +222,7 @@ def patch( # type: ignore[override]
include_in_schema: bool = True,
middlewares: list[Callable] | None = None,
):
+ openapi_extensions = None
security = None
return super().patch(
@@ -230,6 +238,7 @@ def patch( # type: ignore[override]
operation_id,
include_in_schema,
security,
+ openapi_extensions,
middlewares,
)
@@ -250,6 +259,7 @@ def delete( # type: ignore[override]
include_in_schema: bool = True,
middlewares: list[Callable[..., Any]] | None = None,
):
+ openapi_extensions = None
security = None
return super().delete(
@@ -265,6 +275,7 @@ def delete( # type: ignore[override]
operation_id,
include_in_schema,
security,
+ openapi_extensions,
middlewares,
)
@@ -278,7 +289,7 @@ def _convert_matches_into_route_keys(self, match: Match) -> dict[str, str]:
return parameters
@override
- def get_openapi_json_schema(
+ def get_openapi_json_schema( # type: ignore[override]
self,
*,
title: str = "Powertools API",
@@ -333,6 +344,8 @@ def get_openapi_json_schema(
"""
from aws_lambda_powertools.event_handler.openapi.compat import model_json
+ openapi_extensions = None
+
schema = super().get_openapi_schema(
title=title,
version=version,
@@ -346,6 +359,7 @@ def get_openapi_json_schema(
license_info=license_info,
security_schemes=security_schemes,
security=security,
+ openapi_extensions=openapi_extensions,
)
schema.openapi = "3.0.3"
diff --git a/aws_lambda_powertools/event_handler/exceptions.py b/aws_lambda_powertools/event_handler/exceptions.py
index 4a2838275b1..ca5dbbc9830 100644
--- a/aws_lambda_powertools/event_handler/exceptions.py
+++ b/aws_lambda_powertools/event_handler/exceptions.py
@@ -39,7 +39,7 @@ def __init__(self, msg: str = "Not found"):
class InternalServerError(ServiceError):
- """API Gateway and ALB Not Found Internal Server Error (500)"""
+ """API Gateway and ALB Internal Server Error (500)"""
def __init__(self, message: str):
super().__init__(HTTPStatus.INTERNAL_SERVER_ERROR, message)
diff --git a/tests/functional/feature_flags/__init__.py b/aws_lambda_powertools/event_handler/graphql_appsync/__init__.py
similarity index 100%
rename from tests/functional/feature_flags/__init__.py
rename to aws_lambda_powertools/event_handler/graphql_appsync/__init__.py
diff --git a/aws_lambda_powertools/event_handler/graphql_appsync/_registry.py b/aws_lambda_powertools/event_handler/graphql_appsync/_registry.py
new file mode 100644
index 00000000000..9c8dd395a9f
--- /dev/null
+++ b/aws_lambda_powertools/event_handler/graphql_appsync/_registry.py
@@ -0,0 +1,78 @@
+from __future__ import annotations
+
+import logging
+from typing import Any, Callable
+
+logger = logging.getLogger(__name__)
+
+
+class ResolverRegistry:
+ def __init__(self):
+ self.resolvers: dict[str, dict[str, Any]] = {}
+
+ def register(
+ self,
+ type_name: str = "*",
+ field_name: str | None = None,
+ raise_on_error: bool = False,
+ aggregate: bool = True,
+ ) -> Callable:
+ """Registers the resolver for field_name
+
+ Parameters
+ ----------
+ type_name : str
+ Type name
+ field_name : str
+ Field name
+ raise_on_error: bool
+ A flag indicating whether to raise an error when processing batches
+ with failed items. Defaults to False, which means errors are handled without raising exceptions.
+ aggregate: bool
+ A flag indicating whether the batch items should be processed at once or individually.
+ If True (default), the batch resolver will process all items in the batch as a single event.
+ If False, the batch resolver will process each item in the batch individually.
+
+ Return
+ ----------
+ Callable
+ A Callable
+ """
+
+ def _register(func) -> Callable:
+ logger.debug(f"Adding resolver `{func.__name__}` for field `{type_name}.{field_name}`")
+ self.resolvers[f"{type_name}.{field_name}"] = {
+ "func": func,
+ "raise_on_error": raise_on_error,
+ "aggregate": aggregate,
+ }
+ return func
+
+ return _register
+
+ def find_resolver(self, type_name: str, field_name: str) -> dict | None:
+ """Find resolver based on type_name and field_name
+
+ Parameters
+ ----------
+ type_name : str
+ Type name
+ field_name : str
+ Field name
+ Return
+ ----------
+ Optional[Dict]
+ A dictionary with the resolver and if raise exception on error
+ """
+ logger.debug(f"Looking for resolver for type={type_name}, field={field_name}.")
+ return self.resolvers.get(f"{type_name}.{field_name}", self.resolvers.get(f"*.{field_name}"))
+
+ def merge(self, other_registry: ResolverRegistry):
+ """Update current registry with incoming registry
+
+ Parameters
+ ----------
+ other_registry : ResolverRegistry
+ Registry to merge from
+ """
+ self.resolvers.update(**other_registry.resolvers)
diff --git a/aws_lambda_powertools/event_handler/graphql_appsync/base.py b/aws_lambda_powertools/event_handler/graphql_appsync/base.py
new file mode 100644
index 00000000000..f0fe4d78d19
--- /dev/null
+++ b/aws_lambda_powertools/event_handler/graphql_appsync/base.py
@@ -0,0 +1,160 @@
+from __future__ import annotations
+
+from abc import ABC, abstractmethod
+from typing import Callable
+
+
+class BaseRouter(ABC):
+ """Abstract base class for Router (resolvers)"""
+
+ @abstractmethod
+ def resolver(self, type_name: str = "*", field_name: str | None = None) -> Callable:
+ """
+ Retrieve a resolver function for a specific type and field.
+
+ Parameters
+ -----------
+ type_name: str
+ The name of the type.
+ field_name: str, optional
+ The name of the field (default is None).
+
+ Examples
+ --------
+ ```python
+ from typing import Optional
+
+ from aws_lambda_powertools.event_handler import AppSyncResolver
+ from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent
+ from aws_lambda_powertools.utilities.typing import LambdaContext
+
+ app = AppSyncResolver()
+
+ @app.resolver(type_name="Query", field_name="getPost")
+ def related_posts(event: AppSyncResolverEvent) -> Optional[list]:
+ return {"success": "ok"}
+
+ def lambda_handler(event, context: LambdaContext) -> dict:
+ return app.resolve(event, context)
+ ```
+
+ Returns
+ -------
+ Callable
+ The resolver function.
+ """
+ raise NotImplementedError
+
+ @abstractmethod
+ def batch_resolver(
+ self,
+ type_name: str = "*",
+ field_name: str | None = None,
+ raise_on_error: bool = False,
+ aggregate: bool = True,
+ ) -> Callable:
+ """
+ Retrieve a batch resolver function for a specific type and field.
+
+ Parameters
+ -----------
+ type_name: str
+ The name of the type.
+ field_name: str, optional
+ The name of the field (default is None).
+ raise_on_error: bool
+ A flag indicating whether to raise an error when processing batches
+ with failed items. Defaults to False, which means errors are handled without raising exceptions.
+ aggregate: bool
+ A flag indicating whether the batch items should be processed at once or individually.
+ If True (default), the batch resolver will process all items in the batch as a single event.
+ If False, the batch resolver will process each item in the batch individually.
+
+ Examples
+ --------
+ ```python
+ from typing import Optional
+
+ from aws_lambda_powertools.event_handler import AppSyncResolver
+ from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent
+ from aws_lambda_powertools.utilities.typing import LambdaContext
+
+ app = AppSyncResolver()
+
+ @app.batch_resolver(type_name="Query", field_name="getPost")
+ def related_posts(event: AppSyncResolverEvent, id) -> Optional[list]:
+ return {"post_id": id}
+
+ def lambda_handler(event, context: LambdaContext) -> dict:
+ return app.resolve(event, context)
+ ```
+
+ Returns
+ -------
+ Callable
+ The batch resolver function.
+ """
+ raise NotImplementedError
+
+ @abstractmethod
+ def async_batch_resolver(
+ self,
+ type_name: str = "*",
+ field_name: str | None = None,
+ raise_on_error: bool = False,
+ aggregate: bool = True,
+ ) -> Callable:
+ """
+ Retrieve a batch resolver function for a specific type and field and runs async.
+
+ Parameters
+ -----------
+ type_name: str
+ The name of the type.
+ field_name: str, optional
+ The name of the field (default is None).
+ raise_on_error: bool
+ A flag indicating whether to raise an error when processing batches
+ with failed items. Defaults to False, which means errors are handled without raising exceptions.
+ aggregate: bool
+ A flag indicating whether the batch items should be processed at once or individually.
+ If True (default), the batch resolver will process all items in the batch as a single event.
+ If False, the batch resolver will process each item in the batch individually.
+
+ Examples
+ --------
+ ```python
+ from typing import Optional
+
+ from aws_lambda_powertools.event_handler import AppSyncResolver
+ from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent
+ from aws_lambda_powertools.utilities.typing import LambdaContext
+
+ app = AppSyncResolver()
+
+ @app.async_batch_resolver(type_name="Query", field_name="getPost")
+ async def related_posts(event: AppSyncResolverEvent, id) -> Optional[list]:
+ return {"post_id": id}
+
+ def lambda_handler(event, context: LambdaContext) -> dict:
+ return app.resolve(event, context)
+ ```
+
+ Returns
+ -------
+ Callable
+ The batch resolver function.
+ """
+ raise NotImplementedError
+
+ @abstractmethod
+ def append_context(self, **additional_context) -> None:
+ """
+ Appends context information available under any route.
+
+ Parameters
+ -----------
+ **additional_context: dict
+ Additional context key-value pairs to append.
+ """
+ raise NotImplementedError
diff --git a/aws_lambda_powertools/event_handler/graphql_appsync/exceptions.py b/aws_lambda_powertools/event_handler/graphql_appsync/exceptions.py
new file mode 100644
index 00000000000..f98a75b6f17
--- /dev/null
+++ b/aws_lambda_powertools/event_handler/graphql_appsync/exceptions.py
@@ -0,0 +1,10 @@
+class ResolverNotFoundError(Exception):
+ """
+ When a resolver is not found during a lookup.
+ """
+
+
+class InvalidBatchResponse(Exception):
+ """
+ When a batch response something different from a List
+ """
diff --git a/aws_lambda_powertools/event_handler/graphql_appsync/router.py b/aws_lambda_powertools/event_handler/graphql_appsync/router.py
new file mode 100644
index 00000000000..cb0dce1adc7
--- /dev/null
+++ b/aws_lambda_powertools/event_handler/graphql_appsync/router.py
@@ -0,0 +1,62 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Callable
+
+from aws_lambda_powertools.event_handler.graphql_appsync._registry import ResolverRegistry
+from aws_lambda_powertools.event_handler.graphql_appsync.base import BaseRouter
+
+if TYPE_CHECKING:
+ from aws_lambda_powertools.utilities.data_classes.appsync_resolver_event import AppSyncResolverEvent
+ from aws_lambda_powertools.utilities.typing.lambda_context import LambdaContext
+
+
+class Router(BaseRouter):
+ context: dict
+ current_batch_event: list[AppSyncResolverEvent] = []
+ current_event: AppSyncResolverEvent | None = None
+ lambda_context: LambdaContext | None = None
+
+ def __init__(self):
+ self.context = {} # early init as customers might add context before event resolution
+ self._resolver_registry = ResolverRegistry()
+ self._batch_resolver_registry = ResolverRegistry()
+ self._async_batch_resolver_registry = ResolverRegistry()
+
+ def resolver(self, type_name: str = "*", field_name: str | None = None) -> Callable:
+ return self._resolver_registry.register(field_name=field_name, type_name=type_name)
+
+ def batch_resolver(
+ self,
+ type_name: str = "*",
+ field_name: str | None = None,
+ raise_on_error: bool = False,
+ aggregate: bool = True,
+ ) -> Callable:
+ return self._batch_resolver_registry.register(
+ field_name=field_name,
+ type_name=type_name,
+ raise_on_error=raise_on_error,
+ aggregate=aggregate,
+ )
+
+ def async_batch_resolver(
+ self,
+ type_name: str = "*",
+ field_name: str | None = None,
+ raise_on_error: bool = False,
+ aggregate: bool = True,
+ ) -> Callable:
+ return self._async_batch_resolver_registry.register(
+ field_name=field_name,
+ type_name=type_name,
+ raise_on_error=raise_on_error,
+ aggregate=aggregate,
+ )
+
+ def append_context(self, **additional_context):
+ """Append key=value data as routing context"""
+ self.context.update(**additional_context)
+
+ def clear_context(self):
+ """Resets routing context"""
+ self.context.clear()
diff --git a/aws_lambda_powertools/event_handler/middlewares/openapi_validation.py b/aws_lambda_powertools/event_handler/middlewares/openapi_validation.py
index eaed5083ab7..93ae91e7bd3 100644
--- a/aws_lambda_powertools/event_handler/middlewares/openapi_validation.py
+++ b/aws_lambda_powertools/event_handler/middlewares/openapi_validation.py
@@ -232,7 +232,7 @@ def _prepare_response_content(
for k, v in res.items()
}
elif dataclasses.is_dataclass(res):
- return dataclasses.asdict(res)
+ return dataclasses.asdict(res) # type: ignore[call-overload]
return res
def _get_body(self, app: EventHandlerInstance) -> dict[str, Any]:
diff --git a/aws_lambda_powertools/event_handler/openapi/encoders.py b/aws_lambda_powertools/event_handler/openapi/encoders.py
index 9279e6f27c2..4de53c5e1de 100644
--- a/aws_lambda_powertools/event_handler/openapi/encoders.py
+++ b/aws_lambda_powertools/event_handler/openapi/encoders.py
@@ -19,6 +19,8 @@
if TYPE_CHECKING:
from aws_lambda_powertools.event_handler.openapi.types import IncEx
+from aws_lambda_powertools.event_handler.openapi.exceptions import SerializationError
+
"""
This module contains the encoders used by jsonable_encoder to convert Python objects to JSON serializable data types.
"""
@@ -72,88 +74,98 @@ def jsonable_encoder( # noqa: PLR0911
if exclude is not None and not isinstance(exclude, (set, dict)):
exclude = set(exclude)
- # Pydantic models
- if isinstance(obj, BaseModel):
- return _dump_base_model(
- obj=obj,
- include=include,
- exclude=exclude,
- by_alias=by_alias,
- exclude_unset=exclude_unset,
- exclude_none=exclude_none,
- exclude_defaults=exclude_defaults,
- )
+ try:
+ # Pydantic models
+ if isinstance(obj, BaseModel):
+ return _dump_base_model(
+ obj=obj,
+ include=include,
+ exclude=exclude,
+ by_alias=by_alias,
+ exclude_unset=exclude_unset,
+ exclude_none=exclude_none,
+ exclude_defaults=exclude_defaults,
+ )
- # Dataclasses
- if dataclasses.is_dataclass(obj):
- obj_dict = dataclasses.asdict(obj)
- return jsonable_encoder(
- obj_dict,
- include=include,
- exclude=exclude,
- by_alias=by_alias,
- exclude_unset=exclude_unset,
- exclude_defaults=exclude_defaults,
- exclude_none=exclude_none,
- )
+ # Dataclasses
+ if dataclasses.is_dataclass(obj):
+ obj_dict = dataclasses.asdict(obj) # type: ignore[call-overload]
+ return jsonable_encoder(
+ obj_dict,
+ include=include,
+ exclude=exclude,
+ by_alias=by_alias,
+ exclude_unset=exclude_unset,
+ exclude_defaults=exclude_defaults,
+ exclude_none=exclude_none,
+ custom_serializer=custom_serializer,
+ )
- # Enums
- if isinstance(obj, Enum):
- return obj.value
+ # Enums
+ if isinstance(obj, Enum):
+ return obj.value
- # Paths
- if isinstance(obj, PurePath):
- return str(obj)
+ # Paths
+ if isinstance(obj, PurePath):
+ return str(obj)
- # Scalars
- if isinstance(obj, (str, int, float, type(None))):
- return obj
+ # Scalars
+ if isinstance(obj, (str, int, float, type(None))):
+ return obj
- # Dictionaries
- if isinstance(obj, dict):
- return _dump_dict(
- obj=obj,
- include=include,
- exclude=exclude,
- by_alias=by_alias,
- exclude_none=exclude_none,
- exclude_unset=exclude_unset,
- )
+ # Dictionaries
+ if isinstance(obj, dict):
+ return _dump_dict(
+ obj=obj,
+ include=include,
+ exclude=exclude,
+ by_alias=by_alias,
+ exclude_unset=exclude_unset,
+ exclude_none=exclude_none,
+ custom_serializer=custom_serializer,
+ )
- # Sequences
- if isinstance(obj, (list, set, frozenset, GeneratorType, tuple, deque)):
- return _dump_sequence(
+ # Sequences
+ if isinstance(obj, (list, set, frozenset, GeneratorType, tuple, deque)):
+ return _dump_sequence(
+ obj=obj,
+ include=include,
+ exclude=exclude,
+ by_alias=by_alias,
+ exclude_none=exclude_none,
+ exclude_defaults=exclude_defaults,
+ exclude_unset=exclude_unset,
+ custom_serializer=custom_serializer,
+ )
+
+ # Other types
+ if type(obj) in ENCODERS_BY_TYPE:
+ return ENCODERS_BY_TYPE[type(obj)](obj)
+
+ for encoder, classes_tuple in encoders_by_class_tuples.items():
+ if isinstance(obj, classes_tuple):
+ return encoder(obj)
+
+ # Use custom serializer if present
+ if custom_serializer:
+ return custom_serializer(obj)
+
+ # Default
+ return _dump_other(
obj=obj,
include=include,
exclude=exclude,
by_alias=by_alias,
exclude_none=exclude_none,
- exclude_defaults=exclude_defaults,
exclude_unset=exclude_unset,
+ exclude_defaults=exclude_defaults,
+ custom_serializer=custom_serializer,
)
-
- # Other types
- if type(obj) in ENCODERS_BY_TYPE:
- return ENCODERS_BY_TYPE[type(obj)](obj)
-
- for encoder, classes_tuple in encoders_by_class_tuples.items():
- if isinstance(obj, classes_tuple):
- return encoder(obj)
-
- # Use custom serializer if present
- if custom_serializer:
- return custom_serializer(obj)
-
- # Default
- return _dump_other(
- obj=obj,
- include=include,
- exclude=exclude,
- by_alias=by_alias,
- exclude_none=exclude_none,
- exclude_unset=exclude_unset,
- exclude_defaults=exclude_defaults,
- )
+ except ValueError as exc:
+ raise SerializationError(
+ f"Unable to serialize the object {obj} as it is not a supported type. Error details: {exc}",
+ "See: https://docs.powertools.aws.dev/lambda/python/latest/core/event_handler/api_gateway/#serializing-objects",
+ ) from exc
def _dump_base_model(
@@ -197,9 +209,15 @@ def _dump_dict(
by_alias: bool = True,
exclude_unset: bool = False,
exclude_none: bool = False,
+ custom_serializer: Callable[[Any], str] | None = None,
) -> dict[str, Any]:
"""
Dump a dict to a dict, using the same parameters as jsonable_encoder
+
+ Parameters
+ ----------
+ custom_serializer : Callable, optional
+ A custom serializer to use for encoding the object, when everything else fails.
"""
encoded_dict = {}
allowed_keys = set(obj.keys())
@@ -218,12 +236,14 @@ def _dump_dict(
by_alias=by_alias,
exclude_unset=exclude_unset,
exclude_none=exclude_none,
+ custom_serializer=custom_serializer,
)
encoded_value = jsonable_encoder(
value,
by_alias=by_alias,
exclude_unset=exclude_unset,
exclude_none=exclude_none,
+ custom_serializer=custom_serializer,
)
encoded_dict[encoded_key] = encoded_value
return encoded_dict
@@ -238,9 +258,10 @@ def _dump_sequence(
exclude_unset: bool = False,
exclude_none: bool = False,
exclude_defaults: bool = False,
+ custom_serializer: Callable[[Any], str] | None = None,
) -> list[Any]:
"""
- Dump a sequence to a list, using the same parameters as jsonable_encoder
+ Dump a sequence to a list, using the same parameters as jsonable_encoder.
"""
encoded_list = []
for item in obj:
@@ -253,6 +274,7 @@ def _dump_sequence(
exclude_unset=exclude_unset,
exclude_defaults=exclude_defaults,
exclude_none=exclude_none,
+ custom_serializer=custom_serializer,
),
)
return encoded_list
@@ -267,6 +289,7 @@ def _dump_other(
exclude_unset: bool = False,
exclude_none: bool = False,
exclude_defaults: bool = False,
+ custom_serializer: Callable[[Any], str] | None = None,
) -> Any:
"""
Dump an object to a hashable object, using the same parameters as jsonable_encoder
@@ -288,6 +311,7 @@ def _dump_other(
exclude_unset=exclude_unset,
exclude_defaults=exclude_defaults,
exclude_none=exclude_none,
+ custom_serializer=custom_serializer,
)
diff --git a/aws_lambda_powertools/event_handler/openapi/exceptions.py b/aws_lambda_powertools/event_handler/openapi/exceptions.py
index fdd829ba9b1..e1ed33e67fd 100644
--- a/aws_lambda_powertools/event_handler/openapi/exceptions.py
+++ b/aws_lambda_powertools/event_handler/openapi/exceptions.py
@@ -21,3 +21,15 @@ class RequestValidationError(ValidationException):
def __init__(self, errors: Sequence[Any], *, body: Any = None) -> None:
super().__init__(errors)
self.body = body
+
+
+class SerializationError(Exception):
+ """
+ Base exception for all encoding errors
+ """
+
+
+class SchemaValidationError(ValidationException):
+ """
+ Raised when the OpenAPI schema validation fails
+ """
diff --git a/aws_lambda_powertools/event_handler/openapi/models.py b/aws_lambda_powertools/event_handler/openapi/models.py
index d1bc1bce386..9420cd4afbc 100644
--- a/aws_lambda_powertools/event_handler/openapi/models.py
+++ b/aws_lambda_powertools/event_handler/openapi/models.py
@@ -2,10 +2,11 @@
from enum import Enum
from typing import Any, Dict, List, Literal, Optional, Set, Union
-from pydantic import AnyUrl, BaseModel, ConfigDict, Field
+from pydantic import AnyUrl, BaseModel, ConfigDict, Field, model_validator
from typing_extensions import Annotated
from aws_lambda_powertools.event_handler.openapi.compat import model_rebuild
+from aws_lambda_powertools.event_handler.openapi.exceptions import SchemaValidationError
MODEL_CONFIG_ALLOW = ConfigDict(extra="allow")
MODEL_CONFIG_IGNORE = ConfigDict(extra="ignore")
@@ -16,6 +17,38 @@
"""
+class OpenAPIExtensions(BaseModel):
+ """
+ This class serves as a Pydantic proxy model to add OpenAPI extensions.
+
+ OpenAPI extensions are arbitrary fields, so we remove openapi_extensions when dumping
+ and add only the provided value in the schema.
+ """
+
+ openapi_extensions: Optional[Dict[str, Any]] = None
+
+ # If the 'openapi_extensions' field is present in the 'values' dictionary,
+ # And if the extension starts with x- (must respect the RFC)
+ # update the 'values' dictionary with the contents of 'openapi_extensions',
+ # and then remove the 'openapi_extensions' field from the 'values' dictionary
+ model_config = {"extra": "allow"}
+
+ @model_validator(mode="before")
+ def serialize_openapi_extension_v2(self):
+ if isinstance(self, dict) and self.get("openapi_extensions"):
+
+ openapi_extension_value = self.get("openapi_extensions")
+
+ for extension_key in openapi_extension_value:
+ if not str(extension_key).startswith("x-"):
+ raise SchemaValidationError("An OpenAPI extension key must start with x-")
+
+ self.update(openapi_extension_value)
+ self.pop("openapi_extensions", None)
+
+ return self
+
+
# https://swagger.io/specification/#contact-object
class Contact(BaseModel):
name: Optional[str] = None
@@ -57,7 +90,7 @@ class ServerVariable(BaseModel):
# https://swagger.io/specification/#server-object
-class Server(BaseModel):
+class Server(OpenAPIExtensions):
url: Union[AnyUrl, str]
description: Optional[str] = None
variables: Optional[Dict[str, ServerVariable]] = None
@@ -287,7 +320,7 @@ class Tag(BaseModel):
# https://swagger.io/specification/#operation-object
-class Operation(BaseModel):
+class Operation(OpenAPIExtensions):
tags: Optional[List[str]] = None
summary: Optional[str] = None
description: Optional[str] = None
@@ -332,7 +365,7 @@ class SecuritySchemeType(Enum):
openIdConnect = "openIdConnect"
-class SecurityBase(BaseModel):
+class SecurityBase(OpenAPIExtensions):
type_: SecuritySchemeType = Field(alias="type")
description: Optional[str] = None
@@ -428,7 +461,7 @@ class Components(BaseModel):
# https://swagger.io/specification/#openapi-object
-class OpenAPI(BaseModel):
+class OpenAPI(OpenAPIExtensions):
openapi: str
info: Info
jsonSchemaDialect: Optional[str] = None
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 953e55fd3ad..70d98743bcf 100644
--- a/aws_lambda_powertools/event_handler/openapi/swagger_ui/html.py
+++ b/aws_lambda_powertools/event_handler/openapi/swagger_ui/html.py
@@ -8,7 +8,6 @@
def generate_swagger_html(
spec: str,
- path: str,
swagger_js: str,
swagger_css: str,
swagger_base_url: str,
@@ -22,8 +21,6 @@ def generate_swagger_html(
----------
spec: str
The OpenAPI spec
- path: str
- The path to the Swagger documentation
swagger_js: str
Swagger UI JavaScript source code or URL
swagger_css: str
@@ -97,7 +94,7 @@ def generate_swagger_html(
}}
var ui = SwaggerUIBundle(swaggerUIOptions)
- ui.specActions.updateUrl('{path}?format=json');
+ ui.specActions.updateUrl(currentUrl.pathname + "?format=json");
{oauth2_content}