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/pre-release.yml b/.github/workflows/pre-release.yml
new file mode 100644
index 00000000000..29ff2691f2a
--- /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@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.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@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.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@81e9d935c883d0b210363ab89cf05f3894778450 # v1.8.14
+
+ # 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@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
+ 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/docs/index.md b/docs/index.md
index ed799932ff3..eaec5e9f015 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -63,14 +63,98 @@ You can install Powertools for AWS Lambda (Python) using your favorite dependenc
=== "Lambda Layer"
- You can add our layer both in the [AWS Lambda Console _(under `Layers`)_](https://eu-west-1.console.aws.amazon.com/lambda/home#/add/layer){target="_blank"}, or via your favorite infrastructure as code framework with the ARN value.
-
- For the latter, make sure to replace `{region}` with your AWS region, e.g., `eu-west-1`.
-
- * x86 architecture: __arn:aws:lambda:{region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:73__{: .copyMe}:clipboard:
- * ARM architecture: __arn:aws:lambda:{region}:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:73__{: .copyMe}:clipboard:
-
- ???+ note "Code snippets for popular infrastructure as code frameworks"
+ [Lambda Layer](https://docs.aws.amazon.com/lambda/latest/dg/configuration-layers.html){target="_blank"} is a .zip file archive that can contain additional code, pre-packaged dependencies, data, or configuration files. We compile and optimize [all dependencies](#install), and remove duplicate dependencies [already available in the Lambda runtime](https://github.com/aws-powertools/powertools-lambda-layer-cdk/blob/d24716744f7d1f37617b4998c992c4c067e19e64/layer/Python/Dockerfile#L36){target="_blank"} to achieve the most optimal size.
+
+ | Architecture | Layer ARN |
+ | ------------ | --------------------------------------------------------------------------------------------------------- |
+ | x86 | __arn:aws:lambda:{region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:73__{: .copyMe}:clipboard: |
+ | ARM | __arn:aws:lambda:{region}:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:73__{: .copyMe}:clipboard: |
+
+ === "AWS Console"
+
+ You can add our layer using the [AWS Lambda Console _(direct link)_](https://console.aws.amazon.com/lambda/home#/add/layer){target="_blank"}:
+
+ * Under Layers, choose `Specify an ARN`
+ * Click to copy the correct ARN value based on your AWS Lambda function architecture and region
+
+ ??? info "Click to expand and copy any regional Lambda Layer ARN"
+
+ === "x86_64"
+
+
+ | Region name | Region code | Layer ARN |
+ | ------------------------- | -------------------- | --------------------------------------------------------------------------------------------------------- |
+ | Africa (Cape Town) | **`af-south-1`** | **arn:aws:lambda:af-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:73**{: .copyMe}:clipboard: |
+ | Asia Pacific (Hong Kong) | **`ap-east-1`** | **arn:aws:lambda:ap-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:73**{: .copyMe}:clipboard: |
+ | Asia Pacific (Tokyo) | **`ap-northeast-1`** | **arn:aws:lambda:ap-northeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:73**{: .copyMe}:clipboard: |
+ | Asia Pacific (Seoul) | **`ap-northeast-2`** | **arn:aws:lambda:ap-northeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:73**{: .copyMe}:clipboard: |
+ | Asia Pacific (Osaka) | **`ap-northeast-3`** | **arn:aws:lambda:ap-northeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV2:73**{: .copyMe}:clipboard: |
+ | Asia Pacific (Mumbai) | **`ap-south-1`** | **arn:aws:lambda:ap-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:73**{: .copyMe}:clipboard: |
+ | Asia Pacific (Hyderabad) | **`ap-south-2`** | **arn:aws:lambda:ap-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:73**{: .copyMe}:clipboard: |
+ | Asia Pacific (Singapore) | **`ap-southeast-1`** | **arn:aws:lambda:ap-southeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:73**{: .copyMe}:clipboard: |
+ | Asia Pacific (Sydney) | **`ap-southeast-2`** | **arn:aws:lambda:ap-southeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:73**{: .copyMe}:clipboard: |
+ | Asia Pacific (Jakarta) | **`ap-southeast-3`** | **arn:aws:lambda:ap-southeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV2:73**{: .copyMe}:clipboard: |
+ | Asia Pacific (Melbourne) | **`ap-southeast-4`** | **arn:aws:lambda:ap-southeast-4:017000801446:layer:AWSLambdaPowertoolsPythonV2:73**{: .copyMe}:clipboard: |
+ | Canada (Central) | **`ca-central-1`** | **arn:aws:lambda:ca-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:73**{: .copyMe}:clipboard: |
+ | Canada (West) | **`ca-west-1`** | **arn:aws:lambda:ca-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:73**{: .copyMe}:clipboard: |
+ | Europe (Frankfurt) | **`eu-central-1`** | **arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:73**{: .copyMe}:clipboard: |
+ | Europe (Zurich) | **`eu-central-2`** | **arn:aws:lambda:eu-central-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:73**{: .copyMe}:clipboard: |
+ | Europe (Stockholm) | **`eu-north-1`** | **arn:aws:lambda:eu-north-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:73**{: .copyMe}:clipboard: |
+ | Europe (Milan) | **`eu-south-1`** | **arn:aws:lambda:eu-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:73**{: .copyMe}:clipboard: |
+ | Europe (Spain) | **`eu-south-2`** | **arn:aws:lambda:eu-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:73**{: .copyMe}:clipboard: |
+ | Europe (Ireland) | **`eu-west-1`** | **arn:aws:lambda:eu-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:73**{: .copyMe}:clipboard: |
+ | Europe (London) | **`eu-west-2`** | **arn:aws:lambda:eu-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:73**{: .copyMe}:clipboard: |
+ | Europe (Paris) | **`eu-west-3`** | **arn:aws:lambda:eu-west-3:017000801446:layer:AWSLambdaPowertoolsPythonV2:73**{: .copyMe}:clipboard: |
+ | Middle East (Israel) | **`il-central-1`** | **arn:aws:lambda:il-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:73**{: .copyMe}:clipboard: |
+ | Middle East (UAE) | **`me-central-1`** | **arn:aws:lambda:me-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:73**{: .copyMe}:clipboard: |
+ | Middle East (Bahrain) | **`me-south-1`** | **arn:aws:lambda:me-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:73**{: .copyMe}:clipboard: |
+ | South America (São Paulo) | **`sa-east-1`** | **arn:aws:lambda:sa-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:73**{: .copyMe}:clipboard: |
+ | US East (N. Virginia) | **`us-east-1`** | **arn:aws:lambda:us-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:73**{: .copyMe}:clipboard: |
+ | US East (Ohio) | **`us-east-2`** | **arn:aws:lambda:us-east-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:73**{: .copyMe}:clipboard: |
+ | US West (N. California) | **`us-west-1`** | **arn:aws:lambda:us-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:73**{: .copyMe}:clipboard: |
+ | US West (Oregon) | **`us-west-2`** | **arn:aws:lambda:us-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:73**{: .copyMe}:clipboard: |
+
+
+ === "arm64"
+
+ | Region name | Region code | Layer ARN |
+ | ------------------------- | -------------------- | --------------------------------------------------------------------------------------------------------------- |
+ | Africa (Cape Town) | **`af-south-1`** | **arn:aws:lambda:af-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:73**{: .copyMe}:clipboard: |
+ | Asia Pacific (Hong Kong) | **`ap-east-1`** | **arn:aws:lambda:ap-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:73**{: .copyMe}:clipboard: |
+ | Asia Pacific (Tokyo) | **`ap-northeast-1`** | **arn:aws:lambda:ap-northeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:73**{: .copyMe}:clipboard: |
+ | Asia Pacific (Seoul) | **`ap-northeast-2`** | **arn:aws:lambda:ap-northeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:73**{: .copyMe}:clipboard: |
+ | Asia Pacific (Osaka) | **`ap-northeast-3`** | **arn:aws:lambda:ap-northeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:73**{: .copyMe}:clipboard: |
+ | Asia Pacific (Mumbai) | **`ap-south-1`** | **arn:aws:lambda:ap-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:73**{: .copyMe}:clipboard: |
+ | Asia Pacific (Hyderabad) | **`ap-south-2`** | **arn:aws:lambda:ap-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:73**{: .copyMe}:clipboard: |
+ | Asia Pacific (Singapore) | **`ap-southeast-1`** | **arn:aws:lambda:ap-southeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:73**{: .copyMe}:clipboard: |
+ | Asia Pacific (Sydney) | **`ap-southeast-2`** | **arn:aws:lambda:ap-southeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:73**{: .copyMe}:clipboard: |
+ | Asia Pacific (Jakarta) | **`ap-southeast-3`** | **arn:aws:lambda:ap-southeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:73**{: .copyMe}:clipboard: |
+ | Asia Pacific (Melbourne) | **`ap-southeast-4`** | **arn:aws:lambda:ap-southeast-4:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:73**{: .copyMe}:clipboard: |
+ | Canada (Central) | **`ca-central-1`** | **arn:aws:lambda:ca-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:73**{: .copyMe}:clipboard: |
+ | Canada (West) | **`ca-west-1`** | **arn:aws:lambda:ca-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:73**{: .copyMe}:clipboard: |
+ | Europe (Frankfurt) | **`eu-central-1`** | **arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:73**{: .copyMe}:clipboard: |
+ | Europe (Zurich) | **`eu-central-2`** | **arn:aws:lambda:eu-central-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:73**{: .copyMe}:clipboard: |
+ | Europe (Stockholm) | **`eu-north-1`** | **arn:aws:lambda:eu-north-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:73**{: .copyMe}:clipboard: |
+ | Europe (Milan) | **`eu-south-1`** | **arn:aws:lambda:eu-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:73**{: .copyMe}:clipboard: |
+ | Europe (Spain) | **`eu-south-2`** | **arn:aws:lambda:eu-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:73**{: .copyMe}:clipboard: |
+ | Europe (Ireland) | **`eu-west-1`** | **arn:aws:lambda:eu-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:73**{: .copyMe}:clipboard: |
+ | Europe (London) | **`eu-west-2`** | **arn:aws:lambda:eu-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:73**{: .copyMe}:clipboard: |
+ | Europe (Paris) | **`eu-west-3`** | **arn:aws:lambda:eu-west-3:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:73**{: .copyMe}:clipboard: |
+ | Middle East (Israel) | **`il-central-1`** | **arn:aws:lambda:il-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:73**{: .copyMe}:clipboard: |
+ | Middle East (UAE) | **`me-central-1`** | **arn:aws:lambda:me-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:73**{: .copyMe}:clipboard: |
+ | Middle East (Bahrain) | **`me-south-1`** | **arn:aws:lambda:me-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:73**{: .copyMe}:clipboard: |
+ | South America (São Paulo) | **`sa-east-1`** | **arn:aws:lambda:sa-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:73**{: .copyMe}:clipboard: |
+ | US East (N. Virginia) | **`us-east-1`** | **arn:aws:lambda:us-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:73**{: .copyMe}:clipboard: |
+ | US East (Ohio) | **`us-east-2`** | **arn:aws:lambda:us-east-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:73**{: .copyMe}:clipboard: |
+ | US West (N. California) | **`us-west-1`** | **arn:aws:lambda:us-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:73**{: .copyMe}:clipboard: |
+ | US West (Oregon) | **`us-west-2`** | **arn:aws:lambda:us-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:73**{: .copyMe}:clipboard: |
+
+
+ === "Infrastructure as Code (IaC)"
+
+ > Are we missing a framework? please create [a documentation request](https://github.com/aws-powertools/powertools-lambda-python/issues/new?assignees=&labels=documentation%2Ctriage&projects=&template=documentation_improvements.yml&title=Docs%3A+TITLE){target="_blank" rel="nofollow"}.
+
+ Thanks to the community, we've covered most popular frameworks on how to add a Lambda Layer to an existing function.
=== "x86_64"
@@ -148,117 +232,36 @@ You can install Powertools for AWS Lambda (Python) using your favorite dependenc
--8<-- "examples/homepage/install/arm64/amplify.txt"
```
-### Local development
-
-!!! info "Using Lambda Layer? Simply add [**`"aws-lambda-powertools[all]"`**](#){: .copyMe}:clipboard: as a development dependency."
-
-Powertools for AWS Lambda (Python) relies on the [AWS SDK bundled in the Lambda runtime](https://docs.aws.amazon.com/lambda/latest/dg/lambda-python.html){target="_blank"}. This helps us achieve an optimal package size and initialization. However, when developing locally, you need to install AWS SDK as a development dependency to support IDE auto-completion and to run your tests locally:
+ === "Inspect Lambda Layer contents"
-- __Pip__: [**`pip install "aws-lambda-powertools[aws-sdk]"`**](#){: .copyMe}:clipboard:
-- __Poetry__: [**`poetry add "aws-lambda-powertools[aws-sdk]" --group dev`**](#){: .copyMe}:clipboard:
-- __Pdm__: [**`pdm add -dG "aws-lambda-powertools[aws-sdk]"`**](#){: .copyMe}:clipboard:
+ You can use AWS CLI to generate a pre-signed URL to download the contents of our Lambda Layer.
-__A word about dependency resolution__
+ ```bash title="AWS CLI command to download Lambda Layer content"
+ aws lambda get-layer-version-by-arn --arn arn:aws:lambda:eu-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:73 --region eu-west-1
+ ```
-In this context, `[aws-sdk]` is an alias to the `boto3` package. Due to dependency resolution, it'll either install:
+ You'll find the pre-signed URL under `Location` key as part of the CLI command output.
-- __(A)__ the SDK version available in [Lambda runtime](https://docs.aws.amazon.com/lambda/latest/dg/lambda-python.html){target="_blank"}
-- __(B)__ a more up-to-date version if another package you use also depends on `boto3`, for example [Powertools for AWS Lambda (Python) Tracer](core/tracer.md){target="_blank"}
+=== "Serverless Application Repository (SAR)"
-### Lambda Layer
-
-[Lambda Layer](https://docs.aws.amazon.com/lambda/latest/dg/configuration-layers.html){target="_blank"} is a .zip file archive that can contain additional code, pre-packaged dependencies, data, or configuration files. We compile and optimize [all dependencies](#install), and remove duplicate dependencies [already available in the Lambda runtime](https://github.com/aws-powertools/powertools-lambda-layer-cdk/blob/d24716744f7d1f37617b4998c992c4c067e19e64/layer/Python/Dockerfile#L36){target="_blank"} to achieve the most optimal size.
-
-??? note "Click to expand and copy any regional Lambda Layer ARN"
-
- === "x86_64"
-
- | Region | Layer ARN |
- | -------------------- | --------------------------------------------------------------------------------------------------------- |
- | **`af-south-1`** | **arn:aws:lambda:af-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:73**{: .copyMe}:clipboard: |
- | **`ap-east-1`** | **arn:aws:lambda:ap-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:73**{: .copyMe}:clipboard: |
- | **`ap-northeast-1`** | **arn:aws:lambda:ap-northeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:73**{: .copyMe}:clipboard: |
- | **`ap-northeast-2`** | **arn:aws:lambda:ap-northeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:73**{: .copyMe}:clipboard: |
- | **`ap-northeast-3`** | **arn:aws:lambda:ap-northeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV2:73**{: .copyMe}:clipboard: |
- | **`ap-south-1`** | **arn:aws:lambda:ap-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:73**{: .copyMe}:clipboard: |
- | **`ap-south-2`** | **arn:aws:lambda:ap-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:73**{: .copyMe}:clipboard: |
- | **`ap-southeast-1`** | **arn:aws:lambda:ap-southeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:73**{: .copyMe}:clipboard: |
- | **`ap-southeast-2`** | **arn:aws:lambda:ap-southeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:73**{: .copyMe}:clipboard: |
- | **`ap-southeast-3`** | **arn:aws:lambda:ap-southeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV2:73**{: .copyMe}:clipboard: |
- | **`ap-southeast-4`** | **arn:aws:lambda:ap-southeast-4:017000801446:layer:AWSLambdaPowertoolsPythonV2:73**{: .copyMe}:clipboard: |
- | **`ca-central-1`** | **arn:aws:lambda:ca-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:73**{: .copyMe}:clipboard: |
- | **`ca-west-1`** | **arn:aws:lambda:ca-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:73**{: .copyMe}:clipboard: |
- | **`eu-central-1`** | **arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:73**{: .copyMe}:clipboard: |
- | **`eu-central-2`** | **arn:aws:lambda:eu-central-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:73**{: .copyMe}:clipboard: |
- | **`eu-north-1`** | **arn:aws:lambda:eu-north-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:73**{: .copyMe}:clipboard: |
- | **`eu-south-1`** | **arn:aws:lambda:eu-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:73**{: .copyMe}:clipboard: |
- | **`eu-south-2`** | **arn:aws:lambda:eu-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:73**{: .copyMe}:clipboard: |
- | **`eu-west-1`** | **arn:aws:lambda:eu-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:73**{: .copyMe}:clipboard: |
- | **`eu-west-2`** | **arn:aws:lambda:eu-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:73**{: .copyMe}:clipboard: |
- | **`eu-west-3`** | **arn:aws:lambda:eu-west-3:017000801446:layer:AWSLambdaPowertoolsPythonV2:73**{: .copyMe}:clipboard: |
- | **`il-central-1`** | **arn:aws:lambda:il-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:73**{: .copyMe}:clipboard: |
- | **`me-central-1`** | **arn:aws:lambda:me-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:73**{: .copyMe}:clipboard: |
- | **`me-south-1`** | **arn:aws:lambda:me-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:73**{: .copyMe}:clipboard: |
- | **`sa-east-1`** | **arn:aws:lambda:sa-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:73**{: .copyMe}:clipboard: |
- | **`us-east-1`** | **arn:aws:lambda:us-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:73**{: .copyMe}:clipboard: |
- | **`us-east-2`** | **arn:aws:lambda:us-east-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:73**{: .copyMe}:clipboard: |
- | **`us-west-1`** | **arn:aws:lambda:us-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:73**{: .copyMe}:clipboard: |
- | **`us-west-2`** | **arn:aws:lambda:us-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:73**{: .copyMe}:clipboard: |
-
- === "arm64"
-
- | Region | Layer ARN |
- | -------------------- | --------------------------------------------------------------------------------------------------------------- |
- | **`af-south-1`** | **arn:aws:lambda:af-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:73**{: .copyMe}:clipboard: |
- | **`ap-east-1`** | **arn:aws:lambda:ap-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:73**{: .copyMe}:clipboard: |
- | **`ap-northeast-1`** | **arn:aws:lambda:ap-northeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:73**{: .copyMe}:clipboard: |
- | **`ap-northeast-2`** | **arn:aws:lambda:ap-northeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:73**{: .copyMe}:clipboard: |
- | **`ap-northeast-3`** | **arn:aws:lambda:ap-northeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:73**{: .copyMe}:clipboard: |
- | **`ap-south-1`** | **arn:aws:lambda:ap-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:73**{: .copyMe}:clipboard: |
- | **`ap-south-2`** | **arn:aws:lambda:ap-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:73**{: .copyMe}:clipboard: |
- | **`ap-southeast-1`** | **arn:aws:lambda:ap-southeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:73**{: .copyMe}:clipboard: |
- | **`ap-southeast-2`** | **arn:aws:lambda:ap-southeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:73**{: .copyMe}:clipboard: |
- | **`ap-southeast-3`** | **arn:aws:lambda:ap-southeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:73**{: .copyMe}:clipboard: |
- | **`ca-central-1`** | **arn:aws:lambda:ca-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:73**{: .copyMe}:clipboard: |
- | **`eu-central-1`** | **arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:73**{: .copyMe}:clipboard: |
- | **`eu-central-2`** | **arn:aws:lambda:eu-central-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:73**{: .copyMe}:clipboard: |
- | **`eu-north-1`** | **arn:aws:lambda:eu-north-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:73**{: .copyMe}:clipboard: |
- | **`eu-south-1`** | **arn:aws:lambda:eu-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:73**{: .copyMe}:clipboard: |
- | **`eu-south-2`** | **arn:aws:lambda:eu-south-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:73**{: .copyMe}:clipboard: |
- | **`eu-west-1`** | **arn:aws:lambda:eu-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:73**{: .copyMe}:clipboard: |
- | **`eu-west-2`** | **arn:aws:lambda:eu-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:73**{: .copyMe}:clipboard: |
- | **`eu-west-3`** | **arn:aws:lambda:eu-west-3:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:73**{: .copyMe}:clipboard: |
- | **`il-central-1`** | **arn:aws:lambda:il-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:73**{: .copyMe}:clipboard: |
- | **`me-central-1`** | **arn:aws:lambda:me-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:73**{: .copyMe}:clipboard: |
- | **`me-south-1`** | **arn:aws:lambda:me-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:73**{: .copyMe}:clipboard: |
- | **`sa-east-1`** | **arn:aws:lambda:sa-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:73**{: .copyMe}:clipboard: |
- | **`us-east-1`** | **arn:aws:lambda:us-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:73**{: .copyMe}:clipboard: |
- | **`us-east-2`** | **arn:aws:lambda:us-east-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:73**{: .copyMe}:clipboard: |
- | **`us-west-1`** | **arn:aws:lambda:us-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:73**{: .copyMe}:clipboard: |
- | **`us-west-2`** | **arn:aws:lambda:us-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:73**{: .copyMe}:clipboard: |
-
-**Want to inspect the contents of the Layer?**
-
-The pre-signed URL to download this Lambda Layer will be within `Location` key in the CLI output. The CLI output will also contain the Powertools for AWS Lambda version it contains.
-
-```bash title="AWS CLI command to download Lambda Layer content"
-aws lambda get-layer-version-by-arn --arn arn:aws:lambda:eu-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:73 --region eu-west-1
-```
+ We provide a SAR App that deploys a CloudFormation stack with a copy of our Lambda Layer in your AWS account and region.
-#### SAR
+ Compared with the [public Layer ARN](#lambda-layer) option, the advantage is being able to use a semantic version.
-Serverless Application Repository (SAR) App deploys a CloudFormation stack with a copy of our Lambda Layer in your AWS account and region.
+ | App | | | ARN |
+ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --- | --- | ----------------------------------------------------------------------------------------------------------------------------- |
+ | [**aws-lambda-powertools-python-layer**](https://serverlessrepo.aws.amazon.com/applications/eu-west-1/057560766410/aws-lambda-powertools-python-layer){target="_blank"} | | | __arn:aws:serverlessrepo:eu-west-1:057560766410:applications/aws-lambda-powertools-python-layer__{: .copyMe}:clipboard: |
+ | [**aws-lambda-powertools-python-layer-arm64**](https://serverlessrepo.aws.amazon.com/applications/eu-west-1/057560766410/aws-lambda-powertools-python-layer-arm64){target="_blank"} | | | __arn:aws:serverlessrepo:eu-west-1:057560766410:applications/aws-lambda-powertools-python-layer-arm64__{: .copyMe}:clipboard: |
-Compared with the [public Layer ARN](#lambda-layer) option, SAR allows you to choose a semantic version and deploys a Layer in your target account.
+ ??? question "Don't have enough permissions? Expand for a least-privilege IAM policy example"
-| App | ARN | Description |
-| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------- |
-| [**aws-lambda-powertools-python-layer**](https://serverlessrepo.aws.amazon.com/applications/eu-west-1/057560766410/aws-lambda-powertools-python-layer){target="_blank"} | [arn:aws:serverlessrepo:eu-west-1:057560766410:applications/aws-lambda-powertools-python-layer](#){: .copyMe}:clipboard: | Contains all extra dependencies (e.g: pydantic). |
-| [**aws-lambda-powertools-python-layer-arm64**](https://serverlessrepo.aws.amazon.com/applications/eu-west-1/057560766410/aws-lambda-powertools-python-layer-arm64){target="_blank"} | [arn:aws:serverlessrepo:eu-west-1:057560766410:applications/aws-lambda-powertools-python-layer-arm64](#){: .copyMe}:clipboard: | Contains all extra dependencies (e.g: pydantic). For arm64 functions. |
+ Credits to [mwarkentin](https://github.com/mwarkentin){target="_blank" rel="nofollow"} for providing the scoped down IAM permissions.
-??? note "Click to expand and copy SAR code snippets for popular frameworks"
+ ```yaml hl_lines="21-52" title="Least-privileged IAM permissions SAM example"
+ --8<-- "examples/homepage/install/sar/scoped_down_iam.yaml"
+ ```
- You can create a shared Lambda Layers stack and make this along with other account level layers stack.
+ If you're using Infrastructure as Code, here are some excerpts on how to use SAR:
=== "SAM"
@@ -280,17 +283,38 @@ Compared with the [public Layer ARN](#lambda-layer) option, SAR allows you to ch
=== "Terraform"
- > Credits to [Dani Comnea](https://github.com/DanyC97){target="_blank" rel="nofollow"} for providing the Terraform equivalent.
+ > Credits to [Dani Comnea](https://github.com/DanyC97){target="_blank" rel="nofollow"} for providing the Terraform equivalent.
```terraform hl_lines="12-13 15-20 23-25 40"
--8<-- "examples/homepage/install/sar/terraform.tf"
```
- Credits to [mwarkentin](https://github.com/mwarkentin){target="_blank" rel="nofollow"} for providing the scoped down IAM permissions below.
+=== "Alpha releases"
+
+ Every morning during business days _(~8am UTC)_, we publish a `prerelease` to PyPi to accelerate customer feedback on **unstable** releases / bugfixes until they become production ready.
+
+ Here's how you can use them:
- ```yaml hl_lines="21-52" title="Least-privileged IAM permissions SAM example"
- --8<-- "examples/homepage/install/sar/scoped_down_iam.yaml"
- ```
+ - __Pip__: [**`pip install --pre "aws-lambda-powertools"`**](#){: .copyMe}:clipboard:
+ - __Poetry__: [**`poetry add --allow-prereleases "aws-lambda-powertools" --group dev`**](#){: .copyMe}:clipboard:
+ - __Pdm__: [**`pdm add -dG --prerelease "aws-lambda-powertools"`**](#){: .copyMe}:clipboard:
+
+### Local development
+
+!!! info "Using Lambda Layer? Simply add [**`"aws-lambda-powertools[all]"`**](#){: .copyMe}:clipboard: as a development dependency."
+
+Powertools for AWS Lambda (Python) relies on the [AWS SDK bundled in the Lambda runtime](https://docs.aws.amazon.com/lambda/latest/dg/lambda-python.html){target="_blank"}. This helps us achieve an optimal package size and initialization. However, when developing locally, you need to install AWS SDK as a development dependency to support IDE auto-completion and to run your tests locally:
+
+- __Pip__: [**`pip install "aws-lambda-powertools[aws-sdk]"`**](#){: .copyMe}:clipboard:
+- __Poetry__: [**`poetry add "aws-lambda-powertools[aws-sdk]" --group dev`**](#){: .copyMe}:clipboard:
+- __Pdm__: [**`pdm add -dG "aws-lambda-powertools[aws-sdk]"`**](#){: .copyMe}:clipboard:
+
+__A word about dependency resolution__
+
+In this context, `[aws-sdk]` is an alias to the `boto3` package. Due to dependency resolution, it'll either install:
+
+- __(A)__ the SDK version available in [Lambda runtime](https://docs.aws.amazon.com/lambda/latest/dg/lambda-python.html){target="_blank"}
+- __(B)__ a more up-to-date version if another package you use also depends on `boto3`, for example [Powertools for AWS Lambda (Python) Tracer](core/tracer.md){target="_blank"}
## Quick getting started
@@ -448,8 +472,12 @@ Knowing which companies are using this library is important to help prioritize t
!!! note "Layers help us understand who uses Powertools for AWS Lambda (Python) in a non-intrusive way."
+
+
When [using Layers](#lambda-layer), you can add Powertools for AWS Lambda (Python) as a dev dependency to not impact the development process. For Layers, we pre-package all dependencies, compile and optimize for storage and both x86 and ARM architecture.
+
+
## Tenets
These are our core principles to guide our decision making.
diff --git a/docs/maintainers.md b/docs/maintainers.md
index 4fd4f109a33..c2d811e0a8a 100644
--- a/docs/maintainers.md
+++ b/docs/maintainers.md
@@ -154,9 +154,9 @@ Firstly, make sure the commit history in the `develop` branch **(1)** it's up to
**Looks good, what's next?**
-Kickoff the `Release` workflow with the intended version - this might take around 25m-30m to complete.
+Kickoff the [`Release` workflow](https://github.com/aws-powertools/powertools-lambda-python/blob/6db9079d21698b72f5d36d72c993c1aad7276db6/.github/workflows/release.yml#L3) with the intended version - this might take around 25m-30m to complete.
-Once complete, you can start drafting the release notes to let customers know **what changed and what's in it for them (a.k.a why they should care)**. We have guidelines in the release notes section so you know what good looks like.
+Once complete, you can start drafting the release notes to let customers know **what changed and what's in it for them (a.k.a why they should care)**. We have guidelines in the [release notes section](#drafting-release-notes) so you know what good looks like.
> **NOTE**: Documentation might take a few minutes to reflect the latest version due to caching and CDN invalidations.
@@ -231,33 +231,43 @@ Release complete : milestone, m6, 10:31,2m
#### Drafting release notes
+!!! info "Make sure the release workflow completed before you edit release notes."
+
Visit the [Releases page](https://github.com/aws-powertools/powertools-lambda-python/releases) and choose the edit pencil button.
Make sure the `tag` field reflects the new version you're releasing, the target branch field is set to `develop`, and `release title` matches your tag e.g., `v1.26.0`.
You'll notice we group all changes based on their [labels](#labels) like `feature`, `bug`, `documentation`, etc.
+!!! question inline end "Spotted a typo?"
-**I spotted a typo or incorrect grouping - how do I fix it?**
+ Edit the respective PR title/labels and run the [Release Drafter workflow](https://github.com/aws-powertools/powertools-lambda-python/actions/workflows/release-drafter.yml).
-Edit the respective PR title and update their [labels](#labels). Then run the [Release Drafter workflow](https://github.com/aws-powertools/powertools-lambda-python/actions/workflows/release-drafter.yml) to update the Draft release.
+!!! question "All good, what's next?"
-> **NOTE**: This won't change the CHANGELOG as the merge commit is immutable. Don't worry about it. We'd only rewrite git history only if this can lead to confusion and we'd pair with another maintainer.
+The best part comes now!
-**All looking good, what's next?**
+Replace the placeholder `[Human readable summary of changes]` with what you'd like to communicate to customers what this release is all about.
-The best part comes now. Replace the placeholder `[Human readable summary of changes]` with what you'd like to communicate to customers what this release is all about. Rule of thumb: always put yourself in the customers shoes.
+!!! tip inline end "Always put yourself in the customers shoes. Most read the first sentence only to know whether this is for them."
These are some questions to keep in mind when drafting your first or future release notes:
-- Can customers understand at a high level what changed in this release?
-- Is there a link to the documentation where they can read more about each main change?
-- Are there any graphics or [code snippets](https://carbon.now.sh/) that can enhance readability?
-- Are we calling out any key contributor(s) to this release?
- - All contributors are automatically credited, use this as an exceptional case to feature them
+- **Can customers briefly understand the main changes in less than 30s?**
+ - _tip: first paragraph is punchy and optimizes for dependabot-like notifications._
+- **Are we calling out key contributor(s) to this release?**
+- **Is it clear what each change enables/unlocks and before?**
+ - _tip: use present and active voice; lead with the answer._
+- **Does it include a link to the documentation for each main change?**
+ - _tip: release explains what a change unblocks/enables (before/after), docs go in details_
+- **Is code snippet better in text or [graphic](https://carbon.now.sh)?**
+- **Does code snippet focus on the change only?**
+ - _tip: release snippets highlight functionality, no need to be functional (that's docs)_
Once you're happy, hit `Publish release` 🎉🎉🎉.
-This will kick off the [Publishing workflow](https://github.com/aws-powertools/powertools-lambda-python/actions/workflows/release.yml) and within a few minutes you should see the latest version in PyPi, and all issues labeled as `pending-release` will be closed and notified.
+### Releasing an alpha release
+
+We publish alpha releases _(`prerelease`)_ every morning during business days (~8am UTC). You can also manually trigger `pre-release` workflow when needed.
### Run end to end tests
diff --git a/examples/homepage/install/sar/cdk_sar.py b/examples/homepage/install/sar/cdk_sar.py
index ff7c8cc40f5..49fb146df6f 100644
--- a/examples/homepage/install/sar/cdk_sar.py
+++ b/examples/homepage/install/sar/cdk_sar.py
@@ -3,7 +3,7 @@
POWERTOOLS_BASE_NAME = "AWSLambdaPowertools"
# Find latest from github.com/aws-powertools/powertools-lambda-python/releases
-POWERTOOLS_VER = "2.37.0"
+POWERTOOLS_VER = "2.39.1"
POWERTOOLS_ARN = "arn:aws:serverlessrepo:eu-west-1:057560766410:applications/aws-lambda-powertools-python-layer"