From 65312cac7e1b75b0dd22dd824f83779a190a060e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 29 Mar 2023 21:00:49 +0000 Subject: [PATCH 01/34] chore(deps-dev): bump aws-cdk-lib from 2.70.0 to 2.71.0 (#2065) Bumps [aws-cdk-lib](https://github.com/aws/aws-cdk) from 2.70.0 to 2.71.0. - [Release notes](https://github.com/aws/aws-cdk/releases) - [Changelog](https://github.com/aws/aws-cdk/blob/main/CHANGELOG.v2.md) - [Commits](https://github.com/aws/aws-cdk/compare/v2.70.0...v2.71.0) --- updated-dependencies: - dependency-name: aws-cdk-lib dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 8 ++++---- pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index 82f45436255..2aadbbb8226 100644 --- a/poetry.lock +++ b/poetry.lock @@ -153,14 +153,14 @@ typeguard = ">=2.13.3,<2.14.0" [[package]] name = "aws-cdk-lib" -version = "2.70.0" +version = "2.71.0" description = "Version 2 of the AWS Cloud Development Kit library" category = "dev" optional = false python-versions = "~=3.7" files = [ - {file = "aws-cdk-lib-2.70.0.tar.gz", hash = "sha256:1eff41bf88d12653538bc0119725d6e64f74a02ca9e79c03e893984543363c95"}, - {file = "aws_cdk_lib-2.70.0-py3-none-any.whl", hash = "sha256:cd74cdbc3b2a544a0979b73b4b875f541db886b61f933da0420a542d9333fade"}, + {file = "aws-cdk-lib-2.71.0.tar.gz", hash = "sha256:86405be9740a9678deb5a37821b38b6acaf158577e988d85990cfba6e8a6d8b3"}, + {file = "aws_cdk_lib-2.71.0-py3-none-any.whl", hash = "sha256:e87fad1469c699618a9d41354ac8c680d44231336600bb8c7341b86f7979aa4a"}, ] [package.dependencies] @@ -3053,4 +3053,4 @@ validation = ["fastjsonschema"] [metadata] lock-version = "2.0" python-versions = "^3.7.4" -content-hash = "acf4342dadf128c32b93fd8ee5832f7d37cc45e216c9aee6cc715e358dd5a961" +content-hash = "f48e2b2472561642e2f091fc51ebc6a4b3ecaa431d7fec24677e539f4478f09f" diff --git a/pyproject.toml b/pyproject.toml index 9058acc244f..86bcf54d3d4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,7 +63,7 @@ mkdocs-git-revision-date-plugin = "^0.3.2" mike = "^1.1.2" retry = "^0.9.2" pytest-xdist = "^3.2.1" -aws-cdk-lib = "^2.70.0" +aws-cdk-lib = "^2.71.0" "aws-cdk.aws-apigatewayv2-alpha" = "^2.38.1-alpha.0" "aws-cdk.aws-apigatewayv2-integrations-alpha" = "^2.38.1-alpha.0" "aws-cdk.aws-apigatewayv2-authorizers-alpha" = "^2.38.1-alpha.0" From 00ac71b6a5bacbec9c7310389a8233c1bfa8ebcf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 30 Mar 2023 14:10:34 +0200 Subject: [PATCH 02/34] chore(deps-dev): bump aws-cdk from 2.70.0 to 2.71.0 (#2067) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index c26d7aa83b0..ecdd30d0955 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,13 +8,13 @@ "name": "aws-lambda-powertools-python-e2e", "version": "1.0.0", "devDependencies": { - "aws-cdk": "^2.70.0" + "aws-cdk": "^2.71.0" } }, "node_modules/aws-cdk": { - "version": "2.70.0", - "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.70.0.tgz", - "integrity": "sha512-B12e+h8FNNS2BGgBKzKrU541shC/9CWeC1Z/CwX2NKxPgbeP2eGtgR1hCfT/VaotcfJ8+dSd4I32nNNc+wz+QA==", + "version": "2.71.0", + "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.71.0.tgz", + "integrity": "sha512-vanN/suvCVBp1Qux7m72dTLkY+L+DIwRBZeb7PUcoic3CUeYeTNAl47sdlm/KMH+mAK4LOumecWVX2m56caHtA==", "dev": true, "bin": { "cdk": "bin/cdk" @@ -43,9 +43,9 @@ }, "dependencies": { "aws-cdk": { - "version": "2.70.0", - "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.70.0.tgz", - "integrity": "sha512-B12e+h8FNNS2BGgBKzKrU541shC/9CWeC1Z/CwX2NKxPgbeP2eGtgR1hCfT/VaotcfJ8+dSd4I32nNNc+wz+QA==", + "version": "2.71.0", + "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.71.0.tgz", + "integrity": "sha512-vanN/suvCVBp1Qux7m72dTLkY+L+DIwRBZeb7PUcoic3CUeYeTNAl47sdlm/KMH+mAK4LOumecWVX2m56caHtA==", "dev": true, "requires": { "fsevents": "2.3.2" diff --git a/package.json b/package.json index 81341382258..de8b3f98aa9 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,6 @@ "name": "aws-lambda-powertools-python-e2e", "version": "1.0.0", "devDependencies": { - "aws-cdk": "^2.70.0" + "aws-cdk": "^2.71.0" } } From 4005c15e935c9e052b0e57a5017335fda0bfac35 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 30 Mar 2023 12:13:12 +0000 Subject: [PATCH 03/34] chore(deps-dev): bump black from 23.1.0 to 23.3.0 (#2066) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 54 +++++++++++++++++++++++++------------------------- pyproject.toml | 2 +- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/poetry.lock b/poetry.lock index 2aadbbb8226..31e2c2a4c61 100644 --- a/poetry.lock +++ b/poetry.lock @@ -250,37 +250,37 @@ yaml = ["PyYAML"] [[package]] name = "black" -version = "23.1.0" +version = "23.3.0" description = "The uncompromising code formatter." category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "black-23.1.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:b6a92a41ee34b883b359998f0c8e6eb8e99803aa8bf3123bf2b2e6fec505a221"}, - {file = "black-23.1.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:57c18c5165c1dbe291d5306e53fb3988122890e57bd9b3dcb75f967f13411a26"}, - {file = "black-23.1.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:9880d7d419bb7e709b37e28deb5e68a49227713b623c72b2b931028ea65f619b"}, - {file = "black-23.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e6663f91b6feca5d06f2ccd49a10f254f9298cc1f7f49c46e498a0771b507104"}, - {file = "black-23.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:9afd3f493666a0cd8f8df9a0200c6359ac53940cbde049dcb1a7eb6ee2dd7074"}, - {file = "black-23.1.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:bfffba28dc52a58f04492181392ee380e95262af14ee01d4bc7bb1b1c6ca8d27"}, - {file = "black-23.1.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:c1c476bc7b7d021321e7d93dc2cbd78ce103b84d5a4cf97ed535fbc0d6660648"}, - {file = "black-23.1.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:382998821f58e5c8238d3166c492139573325287820963d2f7de4d518bd76958"}, - {file = "black-23.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bf649fda611c8550ca9d7592b69f0637218c2369b7744694c5e4902873b2f3a"}, - {file = "black-23.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:121ca7f10b4a01fd99951234abdbd97728e1240be89fde18480ffac16503d481"}, - {file = "black-23.1.0-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:a8471939da5e824b891b25751955be52ee7f8a30a916d570a5ba8e0f2eb2ecad"}, - {file = "black-23.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8178318cb74f98bc571eef19068f6ab5613b3e59d4f47771582f04e175570ed8"}, - {file = "black-23.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:a436e7881d33acaf2536c46a454bb964a50eff59b21b51c6ccf5a40601fbef24"}, - {file = "black-23.1.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:a59db0a2094d2259c554676403fa2fac3473ccf1354c1c63eccf7ae65aac8ab6"}, - {file = "black-23.1.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:0052dba51dec07ed029ed61b18183942043e00008ec65d5028814afaab9a22fd"}, - {file = "black-23.1.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:49f7b39e30f326a34b5c9a4213213a6b221d7ae9d58ec70df1c4a307cf2a1580"}, - {file = "black-23.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:162e37d49e93bd6eb6f1afc3e17a3d23a823042530c37c3c42eeeaf026f38468"}, - {file = "black-23.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:8b70eb40a78dfac24842458476135f9b99ab952dd3f2dab738c1881a9b38b753"}, - {file = "black-23.1.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:a29650759a6a0944e7cca036674655c2f0f63806ddecc45ed40b7b8aa314b651"}, - {file = "black-23.1.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:bb460c8561c8c1bec7824ecbc3ce085eb50005883a6203dcfb0122e95797ee06"}, - {file = "black-23.1.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:c91dfc2c2a4e50df0026f88d2215e166616e0c80e86004d0003ece0488db2739"}, - {file = "black-23.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a951cc83ab535d248c89f300eccbd625e80ab880fbcfb5ac8afb5f01a258ac9"}, - {file = "black-23.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:0680d4380db3719ebcfb2613f34e86c8e6d15ffeabcf8ec59355c5e7b85bb555"}, - {file = "black-23.1.0-py3-none-any.whl", hash = "sha256:7a0f701d314cfa0896b9001df70a530eb2472babb76086344e688829efd97d32"}, - {file = "black-23.1.0.tar.gz", hash = "sha256:b0bd97bea8903f5a2ba7219257a44e3f1f9d00073d6cc1add68f0beec69692ac"}, + {file = "black-23.3.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:0945e13506be58bf7db93ee5853243eb368ace1c08a24c65ce108986eac65915"}, + {file = "black-23.3.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:67de8d0c209eb5b330cce2469503de11bca4085880d62f1628bd9972cc3366b9"}, + {file = "black-23.3.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:7c3eb7cea23904399866c55826b31c1f55bbcd3890ce22ff70466b907b6775c2"}, + {file = "black-23.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32daa9783106c28815d05b724238e30718f34155653d4d6e125dc7daec8e260c"}, + {file = "black-23.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:35d1381d7a22cc5b2be2f72c7dfdae4072a3336060635718cc7e1ede24221d6c"}, + {file = "black-23.3.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:a8a968125d0a6a404842fa1bf0b349a568634f856aa08ffaff40ae0dfa52e7c6"}, + {file = "black-23.3.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:c7ab5790333c448903c4b721b59c0d80b11fe5e9803d8703e84dcb8da56fec1b"}, + {file = "black-23.3.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:a6f6886c9869d4daae2d1715ce34a19bbc4b95006d20ed785ca00fa03cba312d"}, + {file = "black-23.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f3c333ea1dd6771b2d3777482429864f8e258899f6ff05826c3a4fcc5ce3f70"}, + {file = "black-23.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:11c410f71b876f961d1de77b9699ad19f939094c3a677323f43d7a29855fe326"}, + {file = "black-23.3.0-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:1d06691f1eb8de91cd1b322f21e3bfc9efe0c7ca1f0e1eb1db44ea367dff656b"}, + {file = "black-23.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50cb33cac881766a5cd9913e10ff75b1e8eb71babf4c7104f2e9c52da1fb7de2"}, + {file = "black-23.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e114420bf26b90d4b9daa597351337762b63039752bdf72bf361364c1aa05925"}, + {file = "black-23.3.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:48f9d345675bb7fbc3dd85821b12487e1b9a75242028adad0333ce36ed2a6d27"}, + {file = "black-23.3.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:714290490c18fb0126baa0fca0a54ee795f7502b44177e1ce7624ba1c00f2331"}, + {file = "black-23.3.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:064101748afa12ad2291c2b91c960be28b817c0c7eaa35bec09cc63aa56493c5"}, + {file = "black-23.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:562bd3a70495facf56814293149e51aa1be9931567474993c7942ff7d3533961"}, + {file = "black-23.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:e198cf27888ad6f4ff331ca1c48ffc038848ea9f031a3b40ba36aced7e22f2c8"}, + {file = "black-23.3.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:3238f2aacf827d18d26db07524e44741233ae09a584273aa059066d644ca7b30"}, + {file = "black-23.3.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:f0bd2f4a58d6666500542b26354978218a9babcdc972722f4bf90779524515f3"}, + {file = "black-23.3.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:92c543f6854c28a3c7f39f4d9b7694f9a6eb9d3c5e2ece488c327b6e7ea9b266"}, + {file = "black-23.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a150542a204124ed00683f0db1f5cf1c2aaaa9cc3495b7a3b5976fb136090ab"}, + {file = "black-23.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:6b39abdfb402002b8a7d030ccc85cf5afff64ee90fa4c5aebc531e3ad0175ddb"}, + {file = "black-23.3.0-py3-none-any.whl", hash = "sha256:ec751418022185b0c1bb7d7736e6933d40bbb14c14a0abcf9123d1b159f98dd4"}, + {file = "black-23.3.0.tar.gz", hash = "sha256:1c7b8d606e728a41ea1ccbd7264677e494e87cf630e399262ced92d4a8dac940"}, ] [package.dependencies] @@ -3053,4 +3053,4 @@ validation = ["fastjsonschema"] [metadata] lock-version = "2.0" python-versions = "^3.7.4" -content-hash = "f48e2b2472561642e2f091fc51ebc6a4b3ecaa431d7fec24677e539f4478f09f" +content-hash = "bc84330c1e9b5d7305b8fe573f725f2101896c5cdc9560ad712d787c360bece9" diff --git a/pyproject.toml b/pyproject.toml index 86bcf54d3d4..71da57bffed 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,7 +36,7 @@ typing-extensions = "^4.4.0" [tool.poetry.dev-dependencies] coverage = {extras = ["toml"], version = "^7.2"} pytest = "^7.2.2" -black = "^23.1" +black = "^23.3" boto3 = "^1.18" flake8 = [ # https://github.com/python/importlib_metadata/issues/406 From 2f5eff0f5083f5654c8d2e761b910fea625bd82d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 30 Mar 2023 21:01:37 +0000 Subject: [PATCH 04/34] chore(deps-dev): bump aws-cdk-lib from 2.71.0 to 2.72.0 (#2070) Bumps [aws-cdk-lib](https://github.com/aws/aws-cdk) from 2.71.0 to 2.72.0. - [Release notes](https://github.com/aws/aws-cdk/releases) - [Changelog](https://github.com/aws/aws-cdk/blob/main/CHANGELOG.v2.md) - [Commits](https://github.com/aws/aws-cdk/compare/v2.71.0...v2.72.0) --- updated-dependencies: - dependency-name: aws-cdk-lib dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 10 +++++----- pyproject.toml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/poetry.lock b/poetry.lock index 31e2c2a4c61..4b8ba44b905 100644 --- a/poetry.lock +++ b/poetry.lock @@ -153,14 +153,14 @@ typeguard = ">=2.13.3,<2.14.0" [[package]] name = "aws-cdk-lib" -version = "2.71.0" +version = "2.72.0" description = "Version 2 of the AWS Cloud Development Kit library" category = "dev" optional = false python-versions = "~=3.7" files = [ - {file = "aws-cdk-lib-2.71.0.tar.gz", hash = "sha256:86405be9740a9678deb5a37821b38b6acaf158577e988d85990cfba6e8a6d8b3"}, - {file = "aws_cdk_lib-2.71.0-py3-none-any.whl", hash = "sha256:e87fad1469c699618a9d41354ac8c680d44231336600bb8c7341b86f7979aa4a"}, + {file = "aws-cdk-lib-2.72.0.tar.gz", hash = "sha256:52aad005f1c7142682cb75362dfd27981d95678615c604c763e1c89d758ed557"}, + {file = "aws_cdk_lib-2.72.0-py3-none-any.whl", hash = "sha256:22e327f05eee7484ae2122192ae90e66b9d23e641f61c9d6e50bff17c6d7382c"}, ] [package.dependencies] @@ -168,7 +168,7 @@ files = [ "aws-cdk.asset-kubectl-v20" = ">=2.1.1,<3.0.0" "aws-cdk.asset-node-proxy-agent-v5" = ">=2.0.77,<3.0.0" constructs = ">=10.0.0,<11.0.0" -jsii = ">=1.77.0,<2.0.0" +jsii = ">=1.78.1,<2.0.0" publication = ">=0.0.3" typeguard = ">=2.13.3,<2.14.0" @@ -3053,4 +3053,4 @@ validation = ["fastjsonschema"] [metadata] lock-version = "2.0" python-versions = "^3.7.4" -content-hash = "bc84330c1e9b5d7305b8fe573f725f2101896c5cdc9560ad712d787c360bece9" +content-hash = "16c9a382adaae01963762950cc49d0fe004aed6f6d45800c1888b73d1ebd6643" diff --git a/pyproject.toml b/pyproject.toml index 71da57bffed..625ab93ce13 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,7 +63,7 @@ mkdocs-git-revision-date-plugin = "^0.3.2" mike = "^1.1.2" retry = "^0.9.2" pytest-xdist = "^3.2.1" -aws-cdk-lib = "^2.71.0" +aws-cdk-lib = "^2.72.0" "aws-cdk.aws-apigatewayv2-alpha" = "^2.38.1-alpha.0" "aws-cdk.aws-apigatewayv2-integrations-alpha" = "^2.38.1-alpha.0" "aws-cdk.aws-apigatewayv2-authorizers-alpha" = "^2.38.1-alpha.0" From a9813b7b9e716f4a2ed852692c30cdd61b9723bd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 31 Mar 2023 00:43:14 +0100 Subject: [PATCH 05/34] chore(deps): bump peaceiris/actions-gh-pages from 3.9.2 to 3.9.3 (#2069) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/reusable_publish_docs.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/reusable_publish_docs.yml b/.github/workflows/reusable_publish_docs.yml index 162b216dce2..aad83c0cddb 100644 --- a/.github/workflows/reusable_publish_docs.yml +++ b/.github/workflows/reusable_publish_docs.yml @@ -65,7 +65,7 @@ jobs: poetry run mike set-default --push latest - name: Release API docs - uses: peaceiris/actions-gh-pages@bd8c6b06eba6b3d25d72b7a1767993c0aeee42e7 # v3.9.2 + uses: peaceiris/actions-gh-pages@373f7f263a76c20808c831209c920827a82a2847 # v3.9.3 env: VERSION: ${{ inputs.version }} with: @@ -75,7 +75,7 @@ jobs: destination_dir: ${{ env.VERSION }}/api - name: Release API docs to latest if: ${{ inputs.alias == 'latest' }} - uses: peaceiris/actions-gh-pages@bd8c6b06eba6b3d25d72b7a1767993c0aeee42e7 # v3.9.2 + uses: peaceiris/actions-gh-pages@373f7f263a76c20808c831209c920827a82a2847 # v3.9.3 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./api From cd45bb9b552489b8fb8d724d3cf46b8a8ebc1283 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 31 Mar 2023 00:43:27 +0100 Subject: [PATCH 06/34] chore(deps-dev): bump aws-cdk from 2.71.0 to 2.72.0 (#2071) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index ecdd30d0955..cea67309511 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,13 +8,13 @@ "name": "aws-lambda-powertools-python-e2e", "version": "1.0.0", "devDependencies": { - "aws-cdk": "^2.71.0" + "aws-cdk": "^2.72.0" } }, "node_modules/aws-cdk": { - "version": "2.71.0", - "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.71.0.tgz", - "integrity": "sha512-vanN/suvCVBp1Qux7m72dTLkY+L+DIwRBZeb7PUcoic3CUeYeTNAl47sdlm/KMH+mAK4LOumecWVX2m56caHtA==", + "version": "2.72.0", + "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.72.0.tgz", + "integrity": "sha512-Dpqw3qBob//8QuNquzxlfAKrTXA5oDj25GptigDq2X6bh5Ho1WBiN1C71zngwiR3OPEC5CXcYKiqxBXtXd9JiQ==", "dev": true, "bin": { "cdk": "bin/cdk" @@ -43,9 +43,9 @@ }, "dependencies": { "aws-cdk": { - "version": "2.71.0", - "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.71.0.tgz", - "integrity": "sha512-vanN/suvCVBp1Qux7m72dTLkY+L+DIwRBZeb7PUcoic3CUeYeTNAl47sdlm/KMH+mAK4LOumecWVX2m56caHtA==", + "version": "2.72.0", + "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.72.0.tgz", + "integrity": "sha512-Dpqw3qBob//8QuNquzxlfAKrTXA5oDj25GptigDq2X6bh5Ho1WBiN1C71zngwiR3OPEC5CXcYKiqxBXtXd9JiQ==", "dev": true, "requires": { "fsevents": "2.3.2" diff --git a/package.json b/package.json index de8b3f98aa9..1320c6b99ca 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,6 @@ "name": "aws-lambda-powertools-python-e2e", "version": "1.0.0", "devDependencies": { - "aws-cdk": "^2.71.0" + "aws-cdk": "^2.72.0" } } From 3873f53dc5562f7cddbfc02833f43c115464da6a Mon Sep 17 00:00:00 2001 From: Heitor Lessa Date: Fri, 31 Mar 2023 17:01:59 +0200 Subject: [PATCH 07/34] docs(idempotency): new sequence diagrams, fix idempotency record vs DynamoDB TTL confusion (#2074) --- docs/utilities/idempotency.md | 316 ++++++++++++++++++++++++---------- 1 file changed, 225 insertions(+), 91 deletions(-) diff --git a/docs/utilities/idempotency.md b/docs/utilities/idempotency.md index 49a028168b3..466058dacef 100644 --- a/docs/utilities/idempotency.md +++ b/docs/utilities/idempotency.md @@ -16,6 +16,32 @@ times with the same parameters**. This makes idempotent operations safe to retry **Idempotency key** is a hash representation of either the entire event or a specific configured subset of the event, and invocation results are **JSON serialized** and stored in your persistence storage layer. +**Idempotency record** is the data representation of an idempotent request saved in your preferred storage layer. We use it to coordinate whether a request is idempotent, whether it's still valid or expired based on timestamps, etc. + +
+```mermaid +classDiagram + direction LR + class IdempotencyRecord { + idempotency_key str + status Status + expiry_timestamp int + in_progress_expiry_timestamp int + response_data Json~str~ + payload_hash str + } + class Status { + <> + INPROGRESS + COMPLETE + EXPIRED internal_only + } + IdempotencyRecord -- Status +``` + +Idempotency record representation +
+ ## Key features * Prevent Lambda handler from executing more than once on the same event payload during a time window @@ -70,7 +96,7 @@ Resources: HelloWorldFunction: Type: AWS::Serverless::Function Properties: - Runtime: python3.8 + Runtime: python3.9 ... Policies: - DynamoDBCrudPolicy: @@ -92,6 +118,11 @@ Resources: You can quickly start by initializing the `DynamoDBPersistenceLayer` class and using it with the `idempotent` decorator on your lambda handler. +???+ note + In this example, the entire Lambda handler is treated as a single idempotent operation. If your Lambda handler can cause multiple side effects, or you're only interested in making a specific logic idempotent, use [`idempotent_function`](#idempotent_function-decorator) instead. + +!!! tip "See [Choosing a payload subset for idempotency](#choosing-a-payload-subset-for-idempotency) for more elaborate use cases." + === "app.py" ```python hl_lines="1-3 5 7 14" @@ -124,13 +155,17 @@ You can quickly start by initializing the `DynamoDBPersistenceLayer` class and u } ``` +After processing this request successfully, a second request containing the exact same payload above will now return the same response, ensuring our customer isn't charged twice. + +!!! question "New to idempotency concept? Please review our [Terminology](#terminology) section if you haven't yet." + ### Idempotent_function decorator Similar to [idempotent decorator](#idempotent-decorator), you can use `idempotent_function` decorator for any synchronous Python function. When using `idempotent_function`, you must tell us which keyword parameter in your function signature has the data we should use via **`data_keyword_argument`**. -!!! info "We support JSON serializable data, [Python Dataclasses](https://docs.python.org/3.7/library/dataclasses.html){target="_blank"}, [Parser/Pydantic Models](parser.md){target="_blank"}, and our [Event Source Data Classes](./data_classes.md){target="_blank"}." +!!! tip "We support JSON serializable data, [Python Dataclasses](https://docs.python.org/3.7/library/dataclasses.html){target="_blank"}, [Parser/Pydantic Models](parser.md){target="_blank"}, and our [Event Source Data Classes](./data_classes.md){target="_blank"}." ???+ warning "Limitation" Make sure to call your decorated function using keyword arguments. @@ -215,7 +250,7 @@ When using `idempotent_function`, you must tell us which keyword parameter in yo You can can easily integrate with [Batch utility](batch.md) via context manager. This ensures that you process each record in an idempotent manner, and guard against a [Lambda timeout](#lambda-timeouts) idempotent situation. ???+ "Choosing an unique batch record attribute" - In this example, we choose `messageId` as our idempotency token since we know it'll be unique. + In this example, we choose `messageId` as our idempotency key since we know it'll be unique. Depending on your use case, it might be more accurate [to choose another field](#choosing-a-payload-subset-for-idempotency) your producer intentionally set to define uniqueness. @@ -299,8 +334,10 @@ In this example, we have a Lambda handler that creates a payment for a user subs Imagine the function executes successfully, but the client never receives the response due to a connection issue. It is safe to retry in this instance, as the idempotent decorator will return a previously saved response. -???+ warning "Warning: Idempotency for JSON payloads" - The payload extracted by the `event_key_jmespath` is treated as a string by default, so will be sensitive to differences in whitespace even when the JSON payload itself is identical. +**What we want here** is to instruct Idempotency to use `user` and `product_id` fields from our incoming payload as our idempotency key. If we were to treat the entire request as our idempotency key, a simple HTTP header change would cause our customer to be charged twice. + +???+ tip "Deserializing JSON strings in payloads for increased accuracy." + The payload extracted by the `event_key_jmespath` is treated as a string by default. This means there could be differences in whitespace even when the JSON payload itself is identical. To alter this behaviour, we can use the [JMESPath built-in function](jmespath_functions.md#powertools_json-function) `powertools_json()` to treat the payload as a JSON object (dict) rather than a string. @@ -314,9 +351,9 @@ Imagine the function executes successfully, but the client never receives the re persistence_layer = DynamoDBPersistenceLayer(table_name="IdempotencyTable") - # Treat everything under the "body" key - # in the event json object as our payload - config = IdempotencyConfig(event_key_jmespath="powertools_json(body)") + # Deserialize JSON string under the "body" key + # then extract "user" and "product_id" data + config = IdempotencyConfig(event_key_jmespath="powertools_json(body).[user, product_id]") @idempotent(config=config, persistence_store=persistence_layer) def handler(event, context): @@ -368,42 +405,7 @@ Imagine the function executes successfully, but the client never receives the re } ``` -### Idempotency request flow - -This sequence diagram shows an example flow of what happens in the payment scenario: - -
-```mermaid -sequenceDiagram - participant Client - participant Lambda - participant Persistence Layer - alt initial request - Client->>Lambda: Invoke (event) - Lambda->>Persistence Layer: Get or set (id=event.search(payload)) - activate Persistence Layer - Note right of Persistence Layer: Locked to prevent concurrent
invocations with
the same payload. - Lambda-->>Lambda: Call handler (event) - Lambda->>Persistence Layer: Update record with result - deactivate Persistence Layer - Persistence Layer-->>Persistence Layer: Update record with result - Lambda-->>Client: Response sent to client - else retried request - Client->>Lambda: Invoke (event) - Lambda->>Persistence Layer: Get or set (id=event.search(payload)) - Persistence Layer-->>Lambda: Already exists in persistence layer. Return result - Lambda-->>Client: Response sent to client - end -``` -Idempotent sequence -
- -The client was successful in receiving the result after the retry. Since the Lambda handler was only executed once, our customer hasn't been charged twice. - -???+ note - Bear in mind that the entire Lambda handler is treated as a single idempotent operation. If your Lambda handler can cause multiple side effects, consider splitting it into separate functions. - -#### Lambda timeouts +### Lambda timeouts ???+ note This is automatically done when you decorate your Lambda handler with [@idempotent decorator](#idempotent-decorator). @@ -441,45 +443,6 @@ def lambda_handler(event, context): return record_handler(event) ``` -#### Lambda timeout sequence diagram - -This sequence diagram shows an example flow of what happens if a Lambda function times out: - -
-```mermaid -sequenceDiagram - participant Client - participant Lambda - participant Persistence Layer - alt initial request - Client->>Lambda: Invoke (event) - Lambda->>Persistence Layer: Get or set (id=event.search(payload)) - activate Persistence Layer - Note right of Persistence Layer: Locked to prevent concurrent
invocations with
the same payload. - Note over Lambda: Time out - Lambda--xLambda: Call handler (event) - Lambda-->>Client: Return error response - deactivate Persistence Layer - else concurrent request before timeout - Client->>Lambda: Invoke (event) - Lambda->>Persistence Layer: Get or set (id=event.search(payload)) - Persistence Layer-->>Lambda: Request already INPROGRESS - Lambda--xClient: Return IdempotencyAlreadyInProgressError - else retry after Lambda timeout - Client->>Lambda: Invoke (event) - Lambda->>Persistence Layer: Get or set (id=event.search(payload)) - activate Persistence Layer - Note right of Persistence Layer: Locked to prevent concurrent
invocations with
the same payload. - Lambda-->>Lambda: Call handler (event) - Lambda->>Persistence Layer: Update record with result - deactivate Persistence Layer - Persistence Layer-->>Persistence Layer: Update record with result - Lambda-->>Client: Response sent to client - end -``` -Idempotent sequence for Lambda timeouts -
- ### Handling exceptions If you are using the `idempotent` decorator on your Lambda handler, any unhandled exceptions that are raised during the code execution will cause **the record in the persistence layer to be deleted**. @@ -531,6 +494,172 @@ def call_external_service(data: dict, **kwargs): As this happens outside the scope of your decorated function, you are not able to catch it if you're using the `idempotent` decorator on your Lambda handler. +### Idempotency request flow + +The following sequence diagrams explain how the Idempotency feature behaves under different scenarios. + +#### Successful request + +
+```mermaid +sequenceDiagram + participant Client + participant Lambda + participant Persistence Layer + alt initial request + Client->>Lambda: Invoke (event) + Lambda->>Persistence Layer: Get or set idempotency_key=hash(payload) + activate Persistence Layer + Note over Lambda,Persistence Layer: Set record status to INPROGRESS.
Prevents concurrent invocations
with the same payload + Lambda-->>Lambda: Call your function + Lambda->>Persistence Layer: Update record with result + deactivate Persistence Layer + Persistence Layer-->>Persistence Layer: Update record + Note over Lambda,Persistence Layer: Set record status to COMPLETE.
New invocations with the same payload
now return the same result + Lambda-->>Client: Response sent to client + else retried request + Client->>Lambda: Invoke (event) + Lambda->>Persistence Layer: Get or set idempotency_key=hash(payload) + activate Persistence Layer + Persistence Layer-->>Lambda: Already exists in persistence layer. + deactivate Persistence Layer + Note over Lambda,Persistence Layer: Record status is COMPLETE and not expired + Lambda-->>Client: Same response sent to client + end +``` +Idempotent successful request +
+ +#### Successful request with cache enabled + +!!! note "[In-memory cache is disabled by default](#using-in-memory-cache)." + +
+```mermaid +sequenceDiagram + participant Client + participant Lambda + participant Persistence Layer + alt initial request + Client->>Lambda: Invoke (event) + Lambda->>Persistence Layer: Get or set idempotency_key=hash(payload) + activate Persistence Layer + Note over Lambda,Persistence Layer: Set record status to INPROGRESS.
Prevents concurrent invocations
with the same payload + Lambda-->>Lambda: Call your function + Lambda->>Persistence Layer: Update record with result + deactivate Persistence Layer + Persistence Layer-->>Persistence Layer: Update record + Note over Lambda,Persistence Layer: Set record status to COMPLETE.
New invocations with the same payload
now return the same result + Lambda-->>Lambda: Save record and result in memory + Lambda-->>Client: Response sent to client + else retried request + Client->>Lambda: Invoke (event) + Lambda-->>Lambda: Get idempotency_key=hash(payload) + Note over Lambda,Persistence Layer: Record status is COMPLETE and not expired + Lambda-->>Client: Same response sent to client + end +``` +Idempotent successful request cached +
+ +#### Expired idempotency records + +
+```mermaid +sequenceDiagram + participant Client + participant Lambda + participant Persistence Layer + alt initial request + Client->>Lambda: Invoke (event) + Lambda->>Persistence Layer: Get or set idempotency_key=hash(payload) + activate Persistence Layer + Note over Lambda,Persistence Layer: Set record status to INPROGRESS.
Prevents concurrent invocations
with the same payload + Lambda-->>Lambda: Call your function + Lambda->>Persistence Layer: Update record with result + deactivate Persistence Layer + Persistence Layer-->>Persistence Layer: Update record + Note over Lambda,Persistence Layer: Set record status to COMPLETE.
New invocations with the same payload
now return the same result + Lambda-->>Client: Response sent to client + else retried request + Client->>Lambda: Invoke (event) + Lambda->>Persistence Layer: Get or set idempotency_key=hash(payload) + activate Persistence Layer + Persistence Layer-->>Lambda: Already exists in persistence layer. + deactivate Persistence Layer + Note over Lambda,Persistence Layer: Record status is COMPLETE but expired hours ago + loop Repeat initial request process + Note over Lambda,Persistence Layer: 1. Set record to INPROGRESS,
2. Call your function,
3. Set record to COMPLETE + end + Lambda-->>Client: Same response sent to client + end +``` +Previous Idempotent request expired +
+ +#### Concurrent identical in-flight requests + +
+```mermaid +sequenceDiagram + participant Client + participant Lambda + participant Persistence Layer + Client->>Lambda: Invoke (event) + Lambda->>Persistence Layer: Get or set idempotency_key=hash(payload) + activate Persistence Layer + Note over Lambda,Persistence Layer: Set record status to INPROGRESS.
Prevents concurrent invocations
with the same payload + par Second request + Client->>Lambda: Invoke (event) + Lambda->>Persistence Layer: Get or set idempotency_key=hash(payload) + Lambda--xLambda: IdempotencyAlreadyInProgressError + Lambda->>Client: Error sent to client if unhandled + end + Lambda-->>Lambda: Call your function + Lambda->>Persistence Layer: Update record with result + deactivate Persistence Layer + Persistence Layer-->>Persistence Layer: Update record + Note over Lambda,Persistence Layer: Set record status to COMPLETE.
New invocations with the same payload
now return the same result + Lambda-->>Client: Response sent to client +``` +Concurrent identical in-flight requests +
+ +#### Lambda request timeout + +
+```mermaid +sequenceDiagram + participant Client + participant Lambda + participant Persistence Layer + alt initial request + Client->>Lambda: Invoke (event) + Lambda->>Persistence Layer: Get or set idempotency_key=hash(payload) + activate Persistence Layer + Note over Lambda,Persistence Layer: Set record status to INPROGRESS.
Prevents concurrent invocations
with the same payload + Lambda-->>Lambda: Call your function + Note right of Lambda: Time out + Lambda--xLambda: Time out error + Lambda-->>Client: Return error response + deactivate Persistence Layer + else retry after Lambda timeout elapses + Client->>Lambda: Invoke (event) + Lambda->>Persistence Layer: Get or set idempotency_key=hash(payload) + activate Persistence Layer + Note over Lambda,Persistence Layer: Set record status to INPROGRESS.
Reset in_progress_expiry attribute + Lambda-->>Lambda: Call your function + Lambda->>Persistence Layer: Update record with result + deactivate Persistence Layer + Persistence Layer-->>Persistence Layer: Update record + Lambda-->>Client: Response sent to client + end +``` +Idempotent request during and after Lambda timeouts +
+ +## Advanced + ### Persistence layers #### DynamoDBPersistenceLayer @@ -565,8 +694,6 @@ When using DynamoDB as a persistence layer, you can alter the attribute names by | **sort_key_attr** | | | Sort key of the table (if table is configured with a sort key). | | **static_pk_value** | | `idempotency#{LAMBDA_FUNCTION_NAME}` | Static value to use as the partition key. Only used when **sort_key_attr** is set. | -## Advanced - ### Customizing the default behavior Idempotent decorator can be further configured with **`IdempotencyConfig`** as seen in the previous example. These are the available options for further configuration @@ -619,14 +746,13 @@ When enabled, the default is to cache a maximum of 256 records in each Lambda ex ### Expiring idempotency records -???+ note - By default, we expire idempotency records after **an hour** (3600 seconds). +!!! note "By default, we expire idempotency records after **an hour** (3600 seconds)." In most cases, it is not desirable to store the idempotency records forever. Rather, you want to guarantee that the same payload won't be executed within a period of time. You can change this window with the **`expires_after_seconds`** parameter: -```python hl_lines="8 11" title="Adjusting cache TTL" +```python hl_lines="8 11" title="Adjusting idempotency record expiration" from aws_lambda_powertools.utilities.idempotency import ( IdempotencyConfig, DynamoDBPersistenceLayer, idempotent ) @@ -642,10 +768,18 @@ def handler(event, context): ... ``` -This will mark any records older than 5 minutes as expired, and the lambda handler will be executed as normal if it is invoked with a matching payload. +This will mark any records older than 5 minutes as expired, and [your function will be executed as normal if it is invoked with a matching payload](#expired-idempotency-records). + +???+ important "Idempotency record expiration vs DynamoDB time-to-live (TTL)" + [DynamoDB TTL is a feature](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/howitworks-ttl.html){target="_blank"} to remove items after a certain period of time, it may occur within 48 hours of expiration. + + We don't rely on DynamoDB or any persistence storage layer to determine whether a record is expired to avoid eventual inconsistency states. + + Instead, Idempotency records saved in the storage layer contain timestamps that can be verified upon retrieval and double checked within Idempotency feature. + + **Why?** -???+ note "Note: DynamoDB time-to-live field" - This utility uses **`expiration`** as the TTL field in DynamoDB, as [demonstrated in the SAM example earlier](#required-resources). + A record might still be valid (`COMPLETE`) when we retrieved, but in some rare cases it might expire a second later. A record could also be [cached in memory](#using-in-memory-cache). You might also want to have idempotent transactions that should expire in seconds. ### Payload validation From 8446866f5f4e8c115a1339e986bc7e682924904f Mon Sep 17 00:00:00 2001 From: Release bot Date: Fri, 31 Mar 2023 15:02:28 +0000 Subject: [PATCH 08/34] update changelog with latest changes --- CHANGELOG.md | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e20b97c56f2..7264ea034e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,22 @@ # Unreleased +## Documentation + +* **idempotency:** new sequence diagrams, fix idempotency record vs DynamoDB TTL confusion ([#2074](https://github.com/awslabs/aws-lambda-powertools-python/issues/2074)) + +## Maintenance + +* **deps:** bump peaceiris/actions-gh-pages from 3.9.2 to 3.9.3 ([#2069](https://github.com/awslabs/aws-lambda-powertools-python/issues/2069)) +* **deps-dev:** bump aws-cdk from 2.71.0 to 2.72.0 ([#2071](https://github.com/awslabs/aws-lambda-powertools-python/issues/2071)) +* **deps-dev:** bump aws-cdk-lib from 2.71.0 to 2.72.0 ([#2070](https://github.com/awslabs/aws-lambda-powertools-python/issues/2070)) +* **deps-dev:** bump black from 23.1.0 to 23.3.0 ([#2066](https://github.com/awslabs/aws-lambda-powertools-python/issues/2066)) +* **deps-dev:** bump aws-cdk from 2.70.0 to 2.71.0 ([#2067](https://github.com/awslabs/aws-lambda-powertools-python/issues/2067)) +* **deps-dev:** bump aws-cdk-lib from 2.70.0 to 2.71.0 ([#2065](https://github.com/awslabs/aws-lambda-powertools-python/issues/2065)) + + + +## [v2.11.0] - 2023-03-29 ## Bug Fixes * **feature_flags:** make test conditions deterministic ([#2059](https://github.com/awslabs/aws-lambda-powertools-python/issues/2059)) @@ -19,8 +35,9 @@ ## Maintenance +* update v2 layer ARN on documentation * **deps:** bump pydantic from 1.10.6 to 1.10.7 ([#2034](https://github.com/awslabs/aws-lambda-powertools-python/issues/2034)) -* **deps-dev:** bump types-requests from 2.28.11.16 to 2.28.11.17 ([#2061](https://github.com/awslabs/aws-lambda-powertools-python/issues/2061)) +* **deps-dev:** bump mypy-boto3-s3 from 1.26.97 to 1.26.97.post2 ([#2043](https://github.com/awslabs/aws-lambda-powertools-python/issues/2043)) * **deps-dev:** bump cfn-lint from 0.75.1 to 0.76.1 ([#2056](https://github.com/awslabs/aws-lambda-powertools-python/issues/2056)) * **deps-dev:** bump types-python-dateutil from 2.8.19.10 to 2.8.19.11 ([#2057](https://github.com/awslabs/aws-lambda-powertools-python/issues/2057)) * **deps-dev:** bump mypy-boto3-s3 from 1.26.97.post2 to 1.26.99 ([#2054](https://github.com/awslabs/aws-lambda-powertools-python/issues/2054)) @@ -29,14 +46,14 @@ * **deps-dev:** bump mypy-boto3-cloudwatch from 1.26.52 to 1.26.99 ([#2049](https://github.com/awslabs/aws-lambda-powertools-python/issues/2049)) * **deps-dev:** bump filelock from 3.10.1 to 3.10.2 ([#2045](https://github.com/awslabs/aws-lambda-powertools-python/issues/2045)) * **deps-dev:** bump types-requests from 2.28.11.15 to 2.28.11.16 ([#2044](https://github.com/awslabs/aws-lambda-powertools-python/issues/2044)) -* **deps-dev:** bump mypy-boto3-s3 from 1.26.97 to 1.26.97.post2 ([#2043](https://github.com/awslabs/aws-lambda-powertools-python/issues/2043)) +* **deps-dev:** bump filelock from 3.10.4 to 3.10.7 ([#2055](https://github.com/awslabs/aws-lambda-powertools-python/issues/2055)) * **deps-dev:** bump mypy-boto3-dynamodb from 1.26.97 to 1.26.97.post1 ([#2042](https://github.com/awslabs/aws-lambda-powertools-python/issues/2042)) * **deps-dev:** bump filelock from 3.10.0 to 3.10.1 ([#2036](https://github.com/awslabs/aws-lambda-powertools-python/issues/2036)) * **deps-dev:** bump aws-cdk from 2.69.0 to 2.70.0 ([#2039](https://github.com/awslabs/aws-lambda-powertools-python/issues/2039)) * **deps-dev:** bump mypy-boto3-dynamodb from 1.26.87 to 1.26.97 ([#2035](https://github.com/awslabs/aws-lambda-powertools-python/issues/2035)) * **deps-dev:** bump mypy-boto3-s3 from 1.26.62 to 1.26.97 ([#2037](https://github.com/awslabs/aws-lambda-powertools-python/issues/2037)) * **deps-dev:** bump aws-cdk-lib from 2.69.0 to 2.70.0 ([#2038](https://github.com/awslabs/aws-lambda-powertools-python/issues/2038)) -* **deps-dev:** bump filelock from 3.10.4 to 3.10.7 ([#2055](https://github.com/awslabs/aws-lambda-powertools-python/issues/2055)) +* **deps-dev:** bump types-requests from 2.28.11.16 to 2.28.11.17 ([#2061](https://github.com/awslabs/aws-lambda-powertools-python/issues/2061)) * **deps-dev:** bump mypy-boto3-ssm from 1.26.77 to 1.26.97 ([#2033](https://github.com/awslabs/aws-lambda-powertools-python/issues/2033)) * **deps-dev:** bump flake8-comprehensions from 3.11.0 to 3.11.1 ([#2029](https://github.com/awslabs/aws-lambda-powertools-python/issues/2029)) * **deps-dev:** bump cfn-lint from 0.75.0 to 0.75.1 ([#2027](https://github.com/awslabs/aws-lambda-powertools-python/issues/2027)) @@ -3023,7 +3040,8 @@ * Merge pull request [#5](https://github.com/awslabs/aws-lambda-powertools-python/issues/5) from jfuss/feat/python38 -[Unreleased]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v2.10.0...HEAD +[Unreleased]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v2.11.0...HEAD +[v2.11.0]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v2.10.0...v2.11.0 [v2.10.0]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v2.9.1...v2.10.0 [v2.9.1]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v2.9.0...v2.9.1 [v2.9.0]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v2.8.0...v2.9.0 From b3d0962adcda66089e1967ba8f6cb50fd5cb5021 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 31 Mar 2023 21:04:23 +0000 Subject: [PATCH 09/34] chore(deps-dev): bump mypy-boto3-s3 from 1.26.99 to 1.26.104 (#2075) Bumps [mypy-boto3-s3](https://github.com/youtype/mypy_boto3_builder) from 1.26.99 to 1.26.104. - [Release notes](https://github.com/youtype/mypy_boto3_builder/releases) - [Commits](https://github.com/youtype/mypy_boto3_builder/commits) --- updated-dependencies: - dependency-name: mypy-boto3-s3 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 10 +++++----- pyproject.toml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/poetry.lock b/poetry.lock index 4b8ba44b905..e54492848f6 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1779,14 +1779,14 @@ typing-extensions = ">=4.1.0" [[package]] name = "mypy-boto3-s3" -version = "1.26.99" -description = "Type annotations for boto3.S3 1.26.99 service generated with mypy-boto3-builder 7.14.4" +version = "1.26.104" +description = "Type annotations for boto3.S3 1.26.104 service generated with mypy-boto3-builder 7.14.5" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "mypy-boto3-s3-1.26.99.tar.gz", hash = "sha256:8889a4c62aec85906bdb5f2727560bbd4c41cb5874ba067e250f24e09e0d9b24"}, - {file = "mypy_boto3_s3-1.26.99-py3-none-any.whl", hash = "sha256:1a9c3e758fa255fe48b2f6c3c48004cae7a92c31b457421fe1116989e9ea882d"}, + {file = "mypy-boto3-s3-1.26.104.tar.gz", hash = "sha256:a58d342d72d58fefa2ecd24d926dbc92ae8b97205c59c3144d186857d5f30814"}, + {file = "mypy_boto3_s3-1.26.104-py3-none-any.whl", hash = "sha256:24ff5ec193659abb0415925e73787a64631de46e59fd0f261a840e6c0611a38e"}, ] [package.dependencies] @@ -3053,4 +3053,4 @@ validation = ["fastjsonschema"] [metadata] lock-version = "2.0" python-versions = "^3.7.4" -content-hash = "16c9a382adaae01963762950cc49d0fe004aed6f6d45800c1888b73d1ebd6643" +content-hash = "de41899598706ac4082375c42100eb9f404650259329eaddb996f6ace4c31cbb" diff --git a/pyproject.toml b/pyproject.toml index 625ab93ce13..235acee5f3a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -77,7 +77,7 @@ mypy-boto3-lambda = "^1.26.80" mypy-boto3-logs = "^1.26.53" mypy-boto3-secretsmanager = "^1.26.89" mypy-boto3-ssm = "^1.26.97" -mypy-boto3-s3 = "^1.26.99" +mypy-boto3-s3 = "^1.26.104" mypy-boto3-xray = "^1.26.11" types-requests = "^2.28.11" typing-extensions = "^4.4.0" From 466f60acb14f6749161d9039311c9092e778f8c0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Apr 2023 07:53:14 +0000 Subject: [PATCH 10/34] chore(deps-dev): bump aws-cdk-lib from 2.72.0 to 2.72.1 (#2076) Bumps [aws-cdk-lib](https://github.com/aws/aws-cdk) from 2.72.0 to 2.72.1. - [Release notes](https://github.com/aws/aws-cdk/releases) - [Changelog](https://github.com/aws/aws-cdk/blob/v2.72.1/CHANGELOG.v2.md) - [Commits](https://github.com/aws/aws-cdk/compare/v2.72.0...v2.72.1) --- updated-dependencies: - dependency-name: aws-cdk-lib dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 8 ++++---- pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index e54492848f6..5ac9361bbcb 100644 --- a/poetry.lock +++ b/poetry.lock @@ -153,14 +153,14 @@ typeguard = ">=2.13.3,<2.14.0" [[package]] name = "aws-cdk-lib" -version = "2.72.0" +version = "2.72.1" description = "Version 2 of the AWS Cloud Development Kit library" category = "dev" optional = false python-versions = "~=3.7" files = [ - {file = "aws-cdk-lib-2.72.0.tar.gz", hash = "sha256:52aad005f1c7142682cb75362dfd27981d95678615c604c763e1c89d758ed557"}, - {file = "aws_cdk_lib-2.72.0-py3-none-any.whl", hash = "sha256:22e327f05eee7484ae2122192ae90e66b9d23e641f61c9d6e50bff17c6d7382c"}, + {file = "aws-cdk-lib-2.72.1.tar.gz", hash = "sha256:a0aeaf0e0d0dcc36fe52a1df09708028a8f71f54116bc3f2afec546b0d90c256"}, + {file = "aws_cdk_lib-2.72.1-py3-none-any.whl", hash = "sha256:0d7001b0f507dcd435c6c20688e61d6c45c297e54bae2bf36256e10520668a8a"}, ] [package.dependencies] @@ -3053,4 +3053,4 @@ validation = ["fastjsonschema"] [metadata] lock-version = "2.0" python-versions = "^3.7.4" -content-hash = "de41899598706ac4082375c42100eb9f404650259329eaddb996f6ace4c31cbb" +content-hash = "3b9d4f7a57ffb24e33c0f194c208ef40b3f993f98f9b61472f333e5a4f281bcf" diff --git a/pyproject.toml b/pyproject.toml index 235acee5f3a..12aa69fdbad 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,7 +63,7 @@ mkdocs-git-revision-date-plugin = "^0.3.2" mike = "^1.1.2" retry = "^0.9.2" pytest-xdist = "^3.2.1" -aws-cdk-lib = "^2.72.0" +aws-cdk-lib = "^2.72.1" "aws-cdk.aws-apigatewayv2-alpha" = "^2.38.1-alpha.0" "aws-cdk.aws-apigatewayv2-integrations-alpha" = "^2.38.1-alpha.0" "aws-cdk.aws-apigatewayv2-authorizers-alpha" = "^2.38.1-alpha.0" From d55f218a8b50a85d7b270073c3077e4c92f43562 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Apr 2023 07:56:28 +0000 Subject: [PATCH 11/34] chore(deps-dev): bump mkdocs-material from 9.1.4 to 9.1.5 (#2077) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 8 ++++---- pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index 5ac9361bbcb..165d2c1a777 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1573,14 +1573,14 @@ mkdocs = ">=0.17" [[package]] name = "mkdocs-material" -version = "9.1.4" +version = "9.1.5" description = "Documentation that simply works" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "mkdocs_material-9.1.4-py3-none-any.whl", hash = "sha256:4c92dcf9365068259bef3eed8e0dd5410056b6f7187bdea2d52848c0f94cd94c"}, - {file = "mkdocs_material-9.1.4.tar.gz", hash = "sha256:c3a8943e9e4a7d2624291da365bbccf0b9f88688aa6947a46260d8c165cd4389"}, + {file = "mkdocs_material-9.1.5-py3-none-any.whl", hash = "sha256:981e1ef0250e2fbcc23610e9b20e5f242fe1f808079b9bcfbb6c49aa2999343c"}, + {file = "mkdocs_material-9.1.5.tar.gz", hash = "sha256:744519bca52b1e8fe7c2e80e15ed59baf8948111ec763ae6ae629c409bd16d6e"}, ] [package.dependencies] @@ -3053,4 +3053,4 @@ validation = ["fastjsonschema"] [metadata] lock-version = "2.0" python-versions = "^3.7.4" -content-hash = "3b9d4f7a57ffb24e33c0f194c208ef40b3f993f98f9b61472f333e5a4f281bcf" +content-hash = "9711a25bda181e76ad773eb53f3f1497d203621a1f96c83b96228d0cfd8cfb21" diff --git a/pyproject.toml b/pyproject.toml index 12aa69fdbad..c9950ca01d7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -81,7 +81,7 @@ mypy-boto3-s3 = "^1.26.104" mypy-boto3-xray = "^1.26.11" types-requests = "^2.28.11" typing-extensions = "^4.4.0" -mkdocs-material = "^9.1.4" +mkdocs-material = "^9.1.5" filelock = "^3.10.7" checksumdir = "^1.2.0" mypy-boto3-appconfigdata = "^1.26.70" From 490191982d31b3ac2e37f6fc780b75f49f886831 Mon Sep 17 00:00:00 2001 From: Michael K Date: Mon, 3 Apr 2023 09:35:27 +0000 Subject: [PATCH 12/34] docs(parser): fix highlighted line (#2064) Co-authored-by: Ruben Fonseca --- docs/utilities/parser.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/utilities/parser.md b/docs/utilities/parser.md index 8610cb63c15..66103ad474b 100644 --- a/docs/utilities/parser.md +++ b/docs/utilities/parser.md @@ -63,7 +63,7 @@ Use the decorator for fail fast scenarios where you want your Lambda function to ???+ note **This decorator will replace the `event` object with the parsed model if successful**. This means you might be careful when nesting other decorators that expect `event` to be a `dict`. -```python hl_lines="18" title="Parsing and validating upon invocation with event_parser decorator" +```python hl_lines="19" title="Parsing and validating upon invocation with event_parser decorator" from aws_lambda_powertools.utilities.parser import event_parser, BaseModel from aws_lambda_powertools.utilities.typing import LambdaContext from typing import List, Optional @@ -111,7 +111,7 @@ handler(event=json.dumps(payload), context=LambdaContext()) # also works if even Use this standalone function when you want more control over the data validation process, for example returning a 400 error for malformed payloads. -```python hl_lines="21 30" title="Using standalone parse function for more flexibility" +```python hl_lines="21 31" title="Using standalone parse function for more flexibility" from aws_lambda_powertools.utilities.parser import parse, BaseModel, ValidationError from typing import List, Optional From a53956c32987cf1a4c8e9bd310eac3bd8aa96541 Mon Sep 17 00:00:00 2001 From: Release bot Date: Mon, 3 Apr 2023 09:35:55 +0000 Subject: [PATCH 13/34] update changelog with latest changes --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7264ea034e1..390220d5d21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +7,14 @@ ## Documentation * **idempotency:** new sequence diagrams, fix idempotency record vs DynamoDB TTL confusion ([#2074](https://github.com/awslabs/aws-lambda-powertools-python/issues/2074)) +* **parser:** fix highlighted line ([#2064](https://github.com/awslabs/aws-lambda-powertools-python/issues/2064)) ## Maintenance * **deps:** bump peaceiris/actions-gh-pages from 3.9.2 to 3.9.3 ([#2069](https://github.com/awslabs/aws-lambda-powertools-python/issues/2069)) +* **deps-dev:** bump mkdocs-material from 9.1.4 to 9.1.5 ([#2077](https://github.com/awslabs/aws-lambda-powertools-python/issues/2077)) +* **deps-dev:** bump aws-cdk-lib from 2.72.0 to 2.72.1 ([#2076](https://github.com/awslabs/aws-lambda-powertools-python/issues/2076)) +* **deps-dev:** bump mypy-boto3-s3 from 1.26.99 to 1.26.104 ([#2075](https://github.com/awslabs/aws-lambda-powertools-python/issues/2075)) * **deps-dev:** bump aws-cdk from 2.71.0 to 2.72.0 ([#2071](https://github.com/awslabs/aws-lambda-powertools-python/issues/2071)) * **deps-dev:** bump aws-cdk-lib from 2.71.0 to 2.72.0 ([#2070](https://github.com/awslabs/aws-lambda-powertools-python/issues/2070)) * **deps-dev:** bump black from 23.1.0 to 23.3.0 ([#2066](https://github.com/awslabs/aws-lambda-powertools-python/issues/2066)) From ec1b3466b8f513e235711916c63556671f89aedf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Apr 2023 08:04:08 +0200 Subject: [PATCH 14/34] chore(deps): bump aws-xray-sdk from 2.11.0 to 2.12.0 (#2080) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 165d2c1a777..4fe60374f5b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -210,14 +210,14 @@ dev = ["black (==23.1.0)", "boto3 (>=1.23,<2)", "boto3-stubs[appconfig,serverles [[package]] name = "aws-xray-sdk" -version = "2.11.0" +version = "2.12.0" description = "The AWS X-Ray SDK for Python (the SDK) enables Python developers to record and emit information from within their applications to the AWS X-Ray service." category = "main" optional = true python-versions = "*" files = [ - {file = "aws-xray-sdk-2.11.0.tar.gz", hash = "sha256:78835fc841f03e550858f18a9973eab8618f47f22d2f59edf130578fa545a867"}, - {file = "aws_xray_sdk-2.11.0-py2.py3-none-any.whl", hash = "sha256:693fa3a4c790e131fe1e20814ede415a9eeeab5c3b7c868686d3e3c696b8524d"}, + {file = "aws-xray-sdk-2.12.0.tar.gz", hash = "sha256:295afc237073a80956d7d4f27c31830edcb9a8ccca9ef8aa44990badef15e5b7"}, + {file = "aws_xray_sdk-2.12.0-py2.py3-none-any.whl", hash = "sha256:30886e23cc2daadc1c06a76f25b071205e84848419d1ddf097b62a565e156542"}, ] [package.dependencies] From bac3939336519e7f51eab636de629e7bcb924cf7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Apr 2023 08:04:25 +0200 Subject: [PATCH 15/34] chore(deps-dev): bump aws-cdk from 2.72.0 to 2.72.1 (#2081) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index cea67309511..c9605508e40 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,13 +8,13 @@ "name": "aws-lambda-powertools-python-e2e", "version": "1.0.0", "devDependencies": { - "aws-cdk": "^2.72.0" + "aws-cdk": "^2.72.1" } }, "node_modules/aws-cdk": { - "version": "2.72.0", - "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.72.0.tgz", - "integrity": "sha512-Dpqw3qBob//8QuNquzxlfAKrTXA5oDj25GptigDq2X6bh5Ho1WBiN1C71zngwiR3OPEC5CXcYKiqxBXtXd9JiQ==", + "version": "2.72.1", + "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.72.1.tgz", + "integrity": "sha512-Noihlxyurq9ecz/aTx+j3raeedI0hcPaYFKqS1CkFDoOEvLHIuIEB6regoJHWy9GER/yYYgKu68c7xYo3LaZPA==", "dev": true, "bin": { "cdk": "bin/cdk" @@ -43,9 +43,9 @@ }, "dependencies": { "aws-cdk": { - "version": "2.72.0", - "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.72.0.tgz", - "integrity": "sha512-Dpqw3qBob//8QuNquzxlfAKrTXA5oDj25GptigDq2X6bh5Ho1WBiN1C71zngwiR3OPEC5CXcYKiqxBXtXd9JiQ==", + "version": "2.72.1", + "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.72.1.tgz", + "integrity": "sha512-Noihlxyurq9ecz/aTx+j3raeedI0hcPaYFKqS1CkFDoOEvLHIuIEB6regoJHWy9GER/yYYgKu68c7xYo3LaZPA==", "dev": true, "requires": { "fsevents": "2.3.2" diff --git a/package.json b/package.json index 1320c6b99ca..8fefe4656da 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,6 @@ "name": "aws-lambda-powertools-python-e2e", "version": "1.0.0", "devDependencies": { - "aws-cdk": "^2.72.0" + "aws-cdk": "^2.72.1" } } From dfee4e57f7410944f44eb4d4435a00deac3342d2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Apr 2023 21:22:03 +0100 Subject: [PATCH 16/34] chore(deps-dev): bump cfn-lint from 0.76.1 to 0.76.2 (#2084) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 8 ++++---- pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index 4fe60374f5b..0fe21255b57 100644 --- a/poetry.lock +++ b/poetry.lock @@ -370,14 +370,14 @@ files = [ [[package]] name = "cfn-lint" -version = "0.76.1" +version = "0.76.2" description = "Checks CloudFormation templates for practices and behaviour that could potentially be improved" category = "dev" optional = false python-versions = ">=3.7, <=4.0, !=4.0" files = [ - {file = "cfn-lint-0.76.1.tar.gz", hash = "sha256:9aa952f13f6383e902a2aa6f5b88f058fc274a691917fb31ee95fc62f0acaa0d"}, - {file = "cfn_lint-0.76.1-py3-none-any.whl", hash = "sha256:7408a6849e700539923a209a16bf74c0dd78a10ec52d26ed08889b23140d9abd"}, + {file = "cfn-lint-0.76.2.tar.gz", hash = "sha256:997be30acb914c19f4df570bd53811dd8f0f3781f6864725bd07c1b5c3144439"}, + {file = "cfn_lint-0.76.2-py3-none-any.whl", hash = "sha256:1f38f88203097ff6dc80e57b71c9e33d620310ec11944f49e68b10208c342181"}, ] [package.dependencies] @@ -3053,4 +3053,4 @@ validation = ["fastjsonschema"] [metadata] lock-version = "2.0" python-versions = "^3.7.4" -content-hash = "9711a25bda181e76ad773eb53f3f1497d203621a1f96c83b96228d0cfd8cfb21" +content-hash = "c7bce703660fd5e1b6487bbefe71076715daacba5b28302b058c570dd81658d5" diff --git a/pyproject.toml b/pyproject.toml index c9950ca01d7..5a04f8aa7ee 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -100,7 +100,7 @@ all = ["pydantic", "aws-xray-sdk", "fastjsonschema"] aws-sdk = ["boto3"] [tool.poetry.group.dev.dependencies] -cfn-lint = "0.76.1" +cfn-lint = "0.76.2" mypy = "^1.1.1" types-python-dateutil = "^2.8.19.6" httpx = "^0.23.3" From 99bcf80829d234a3dc63f06812f4143fae68e666 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Apr 2023 21:23:29 +0100 Subject: [PATCH 17/34] chore(deps-dev): bump types-python-dateutil from 2.8.19.11 to 2.8.19.12 (#2085) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 0fe21255b57..8b86cdfb10d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2804,14 +2804,14 @@ test = ["mypy", "pytest", "typing-extensions"] [[package]] name = "types-python-dateutil" -version = "2.8.19.11" +version = "2.8.19.12" description = "Typing stubs for python-dateutil" category = "dev" optional = false python-versions = "*" files = [ - {file = "types-python-dateutil-2.8.19.11.tar.gz", hash = "sha256:de66222c54318c2e05ceb4956976d16696240a45fc2c98e54bfe9a56ce5e1eff"}, - {file = "types_python_dateutil-2.8.19.11-py3-none-any.whl", hash = "sha256:357553f8056cfbb8ce8ea0ca4a6a3480268596748360df73a94c2b8c113a5b06"}, + {file = "types-python-dateutil-2.8.19.12.tar.gz", hash = "sha256:355b2cb82b31e556fd18e7b074de7c350c680ab80608f0cc55ba6770d986d67d"}, + {file = "types_python_dateutil-2.8.19.12-py3-none-any.whl", hash = "sha256:fe5b545e678ec13e3ddc83a0eee1545c1b5e2fba4cfc39b276ab6f4e7604a923"}, ] [[package]] From 50ac176575ec1e9f52dfa645041b9a7a3b9f43b3 Mon Sep 17 00:00:00 2001 From: Heitor Lessa Date: Wed, 5 Apr 2023 18:13:26 +0200 Subject: [PATCH 18/34] feat(idempotency): allow custom sdk clients in DynamoDBPersistenceLayer (#2087) --- .../utilities/idempotency/persistence/base.py | 8 ++-- .../idempotency/persistence/dynamodb.py | 46 +++++++++++------- .../idempotency/test_idempotency.py | 48 +++++++++---------- tests/unit/idempotency/__init__.py | 0 .../idempotency/test_dynamodb_persistence.py | 21 ++++++++ 5 files changed, 77 insertions(+), 46 deletions(-) create mode 100644 tests/unit/idempotency/__init__.py create mode 100644 tests/unit/idempotency/test_dynamodb_persistence.py diff --git a/aws_lambda_powertools/utilities/idempotency/persistence/base.py b/aws_lambda_powertools/utilities/idempotency/persistence/base.py index 28b284b8e5e..cad60cbe2b7 100644 --- a/aws_lambda_powertools/utilities/idempotency/persistence/base.py +++ b/aws_lambda_powertools/utilities/idempotency/persistence/base.py @@ -41,8 +41,8 @@ def __init__( status: str = "", expiry_timestamp: Optional[int] = None, in_progress_expiry_timestamp: Optional[int] = None, - response_data: Optional[str] = "", - payload_hash: Optional[str] = None, + response_data: str = "", + payload_hash: str = "", ) -> None: """ @@ -117,7 +117,7 @@ def __init__(self): """Initialize the defaults""" self.function_name = "" self.configured = False - self.event_key_jmespath: Optional[str] = None + self.event_key_jmespath: str = "" self.event_key_compiled_jmespath = None self.jmespath_options: Optional[dict] = None self.payload_validation_enabled = False @@ -125,7 +125,7 @@ def __init__(self): self.raise_on_no_idempotency_key = False self.expires_after_seconds: int = 60 * 60 # 1 hour default self.use_local_cache = False - self.hash_function = None + self.hash_function = hashlib.md5 def configure(self, config: IdempotencyConfig, function_name: Optional[str] = None) -> None: """ diff --git a/aws_lambda_powertools/utilities/idempotency/persistence/dynamodb.py b/aws_lambda_powertools/utilities/idempotency/persistence/dynamodb.py index ce3aac2425e..654f8ca99d4 100644 --- a/aws_lambda_powertools/utilities/idempotency/persistence/dynamodb.py +++ b/aws_lambda_powertools/utilities/idempotency/persistence/dynamodb.py @@ -1,7 +1,9 @@ +from __future__ import annotations + import datetime import logging import os -from typing import Any, Dict, Optional +from typing import TYPE_CHECKING, Any, Dict, Optional import boto3 from boto3.dynamodb.types import TypeDeserializer @@ -19,6 +21,10 @@ DataRecord, ) +if TYPE_CHECKING: + from mypy_boto3_dynamodb import DynamoDBClient + from mypy_boto3_dynamodb.type_defs import AttributeValueTypeDef + logger = logging.getLogger(__name__) @@ -36,6 +42,7 @@ def __init__( validation_key_attr: str = "validation", boto_config: Optional[Config] = None, boto3_session: Optional[boto3.session.Session] = None, + boto3_client: "DynamoDBClient" | None = None, ): """ Initialize the DynamoDB client @@ -61,8 +68,10 @@ def __init__( DynamoDB attribute name for response data, by default "data" boto_config: botocore.config.Config, optional Botocore configuration to pass during client initialization - boto3_session : boto3.session.Session, optional + boto3_session : boto3.Session, optional Boto3 session to use for AWS API communication + boto3_client : DynamoDBClient, optional + Boto3 DynamoDB Client to use, boto3_session and boto_config will be ignored if both are provided Examples -------- @@ -78,10 +87,12 @@ def __init__( >>> def handler(event, context): >>> return {"StatusCode": 200} """ - - self._boto_config = boto_config or Config() - self._boto3_session = boto3_session or boto3.session.Session() - self._client = self._boto3_session.client("dynamodb", config=self._boto_config) + if boto3_client is None: + self._boto_config = boto_config or Config() + self._boto3_session: boto3.Session = boto3_session or boto3.session.Session() + self.client: "DynamoDBClient" = self._boto3_session.client("dynamodb", config=self._boto_config) + else: + self.client = boto3_client if sort_key_attr == key_attr: raise ValueError(f"key_attr [{key_attr}] and sort_key_attr [{sort_key_attr}] cannot be the same!") @@ -149,7 +160,7 @@ def _item_to_data_record(self, item: Dict[str, Any]) -> DataRecord: ) def _get_record(self, idempotency_key) -> DataRecord: - response = self._client.get_item( + response = self.client.get_item( TableName=self.table_name, Key=self._get_key(idempotency_key), ConsistentRead=True ) try: @@ -204,7 +215,7 @@ def _put_record(self, data_record: DataRecord) -> None: condition_expression = ( f"{idempotency_key_not_exist} OR {idempotency_expiry_expired} OR ({inprogress_expiry_expired})" ) - self._client.put_item( + self.client.put_item( TableName=self.table_name, Item=item, ConditionExpression=condition_expression, @@ -233,7 +244,7 @@ def _put_record(self, data_record: DataRecord) -> None: def _update_record(self, data_record: DataRecord): logger.debug(f"Updating record for idempotency key: {data_record.idempotency_key}") update_expression = "SET #response_data = :response_data, #expiry = :expiry, " "#status = :status" - expression_attr_values = { + expression_attr_values: Dict[str, "AttributeValueTypeDef"] = { ":expiry": {"N": str(data_record.expiry_timestamp)}, ":response_data": {"S": data_record.response_data}, ":status": {"S": data_record.status}, @@ -249,15 +260,14 @@ def _update_record(self, data_record: DataRecord): expression_attr_values[":validation_key"] = {"S": data_record.payload_hash} expression_attr_names["#validation_key"] = self.validation_key_attr - kwargs = { - "Key": self._get_key(data_record.idempotency_key), - "UpdateExpression": update_expression, - "ExpressionAttributeValues": expression_attr_values, - "ExpressionAttributeNames": expression_attr_names, - } - - self._client.update_item(TableName=self.table_name, **kwargs) + self.client.update_item( + TableName=self.table_name, + Key=self._get_key(data_record.idempotency_key), + UpdateExpression=update_expression, + ExpressionAttributeNames=expression_attr_names, + ExpressionAttributeValues=expression_attr_values, + ) def _delete_record(self, data_record: DataRecord) -> None: logger.debug(f"Deleting record for idempotency key: {data_record.idempotency_key}") - self._client.delete_item(TableName=self.table_name, Key={**self._get_key(data_record.idempotency_key)}) + self.client.delete_item(TableName=self.table_name, Key={**self._get_key(data_record.idempotency_key)}) diff --git a/tests/functional/idempotency/test_idempotency.py b/tests/functional/idempotency/test_idempotency.py index 68aeabeb50a..23d0537e533 100644 --- a/tests/functional/idempotency/test_idempotency.py +++ b/tests/functional/idempotency/test_idempotency.py @@ -76,7 +76,7 @@ def test_idempotent_lambda_already_completed( Test idempotent decorator where event with matching event key has already been successfully processed """ - stubber = stub.Stubber(persistence_store._client) + stubber = stub.Stubber(persistence_store.client) ddb_response = { "Item": { "id": {"S": hashed_idempotency_key}, @@ -120,7 +120,7 @@ def test_idempotent_lambda_in_progress( Test idempotent decorator where lambda_handler is already processing an event with matching event key """ - stubber = stub.Stubber(persistence_store._client) + stubber = stub.Stubber(persistence_store.client) expected_params = { "TableName": TABLE_NAME, @@ -172,7 +172,7 @@ def test_idempotent_lambda_in_progress_with_cache( """ save_to_cache_spy = mocker.spy(persistence_store, "_save_to_cache") retrieve_from_cache_spy = mocker.spy(persistence_store, "_retrieve_from_cache") - stubber = stub.Stubber(persistence_store._client) + stubber = stub.Stubber(persistence_store.client) expected_params = { "TableName": TABLE_NAME, @@ -234,7 +234,7 @@ def test_idempotent_lambda_first_execution( Test idempotent decorator when lambda is executed with an event with a previously unknown event key """ - stubber = stub.Stubber(persistence_store._client) + stubber = stub.Stubber(persistence_store.client) ddb_response = {} stubber.add_response("put_item", ddb_response, expected_params_put_item) @@ -269,7 +269,7 @@ def test_idempotent_lambda_first_execution_cached( """ save_to_cache_spy = mocker.spy(persistence_store, "_save_to_cache") retrieve_from_cache_spy = mocker.spy(persistence_store, "_retrieve_from_cache") - stubber = stub.Stubber(persistence_store._client) + stubber = stub.Stubber(persistence_store.client) ddb_response = {} stubber.add_response("put_item", ddb_response, expected_params_put_item) @@ -310,7 +310,7 @@ def test_idempotent_lambda_first_execution_event_mutation( Ensures we're passing data by value, not reference. """ event = copy.deepcopy(lambda_apigw_event) - stubber = stub.Stubber(persistence_store._client) + stubber = stub.Stubber(persistence_store.client) ddb_response = {} stubber.add_response( "put_item", @@ -350,7 +350,7 @@ def test_idempotent_lambda_expired( expiry window """ - stubber = stub.Stubber(persistence_store._client) + stubber = stub.Stubber(persistence_store.client) ddb_response = {} @@ -385,7 +385,7 @@ def test_idempotent_lambda_exception( # Create a new provider # Stub the boto3 client - stubber = stub.Stubber(persistence_store._client) + stubber = stub.Stubber(persistence_store.client) ddb_response = {} expected_params_delete_item = {"TableName": TABLE_NAME, "Key": {"id": {"S": hashed_idempotency_key}}} @@ -427,7 +427,7 @@ def test_idempotent_lambda_already_completed_with_validation_bad_payload( Test idempotent decorator where event with matching event key has already been successfully processed """ - stubber = stub.Stubber(persistence_store._client) + stubber = stub.Stubber(persistence_store.client) ddb_response = { "Item": { "id": {"S": hashed_idempotency_key}, @@ -471,7 +471,7 @@ def test_idempotent_lambda_expired_during_request( returns inconsistent/rapidly changing result between put_item and get_item calls. """ - stubber = stub.Stubber(persistence_store._client) + stubber = stub.Stubber(persistence_store.client) ddb_response_get_item = { "Item": { @@ -524,7 +524,7 @@ def test_idempotent_persistence_exception_deleting( Test idempotent decorator when lambda is executed with an event with a previously unknown event key, but lambda_handler raises an exception which is retryable. """ - stubber = stub.Stubber(persistence_store._client) + stubber = stub.Stubber(persistence_store.client) ddb_response = {} @@ -556,7 +556,7 @@ def test_idempotent_persistence_exception_updating( Test idempotent decorator when lambda is executed with an event with a previously unknown event key, but lambda_handler raises an exception which is retryable. """ - stubber = stub.Stubber(persistence_store._client) + stubber = stub.Stubber(persistence_store.client) ddb_response = {} @@ -587,7 +587,7 @@ def test_idempotent_persistence_exception_getting( Test idempotent decorator when lambda is executed with an event with a previously unknown event key, but lambda_handler raises an exception which is retryable. """ - stubber = stub.Stubber(persistence_store._client) + stubber = stub.Stubber(persistence_store.client) stubber.add_client_error("put_item", "ConditionalCheckFailedException") stubber.add_client_error("get_item", "UnexpectedException") @@ -625,7 +625,7 @@ def test_idempotent_lambda_first_execution_with_validation( """ Test idempotent decorator when lambda is executed with an event with a previously unknown event key """ - stubber = stub.Stubber(persistence_store._client) + stubber = stub.Stubber(persistence_store.client) ddb_response = {} stubber.add_response("put_item", ddb_response, expected_params_put_item_with_validation) @@ -661,7 +661,7 @@ def test_idempotent_lambda_with_validator_util( validator utility to unwrap the event """ - stubber = stub.Stubber(persistence_store._client) + stubber = stub.Stubber(persistence_store.client) ddb_response = { "Item": { "id": {"S": hashed_idempotency_key_with_envelope}, @@ -704,7 +704,7 @@ def test_idempotent_lambda_expires_in_progress_before_expire( hashed_idempotency_key, lambda_context, ): - stubber = stub.Stubber(persistence_store._client) + stubber = stub.Stubber(persistence_store.client) stubber.add_client_error("put_item", "ConditionalCheckFailedException") @@ -751,7 +751,7 @@ def test_idempotent_lambda_expires_in_progress_after_expire( hashed_idempotency_key, lambda_context, ): - stubber = stub.Stubber(persistence_store._client) + stubber = stub.Stubber(persistence_store.client) for _ in range(MAX_RETRIES + 1): stubber.add_client_error("put_item", "ConditionalCheckFailedException") @@ -1070,7 +1070,7 @@ def test_custom_jmespath_function_overrides_builtin_functions( def test_idempotent_lambda_save_inprogress_error(persistence_store: DynamoDBPersistenceLayer, lambda_context): # GIVEN a miss configured persistence layer # like no table was created for the idempotency persistence layer - stubber = stub.Stubber(persistence_store._client) + stubber = stub.Stubber(persistence_store.client) service_error_code = "ResourceNotFoundException" service_message = "Custom message" @@ -1327,7 +1327,7 @@ def test_idempotency_disabled_envvar(monkeypatch, lambda_context, persistence_st # Scenario to validate no requests sent to dynamodb table when 'POWERTOOLS_IDEMPOTENCY_DISABLED' is set mock_event = {"data": "value"} - persistence_store._client = MagicMock() + persistence_store.client = MagicMock() monkeypatch.setenv("POWERTOOLS_IDEMPOTENCY_DISABLED", "1") @@ -1342,7 +1342,7 @@ def dummy_handler(event, context): dummy(data=mock_event) dummy_handler(mock_event, lambda_context) - assert len(persistence_store._client.method_calls) == 0 + assert len(persistence_store.client.method_calls) == 0 @pytest.mark.parametrize("idempotency_config", [{"use_local_cache": True}], indirect=True) @@ -1351,7 +1351,7 @@ def test_idempotent_function_duplicates( ): # Scenario to validate the both methods are called mock_event = {"data": "value"} - persistence_store._client = MagicMock() + persistence_store.client = MagicMock() @idempotent_function(data_keyword_argument="data", persistence_store=persistence_store, config=idempotency_config) def one(data): @@ -1363,7 +1363,7 @@ def two(data): assert one(data=mock_event) == "one" assert two(data=mock_event) == "two" - assert len(persistence_store._client.method_calls) == 4 + assert len(persistence_store.client.method_calls) == 4 def test_invalid_dynamodb_persistence_layer(): @@ -1475,7 +1475,7 @@ def test_idempotent_lambda_compound_already_completed( Test idempotent decorator having a DynamoDBPersistenceLayer with a compound key """ - stubber = stub.Stubber(persistence_store_compound._client) + stubber = stub.Stubber(persistence_store_compound.client) stubber.add_client_error("put_item", "ConditionalCheckFailedException") ddb_response = { "Item": { @@ -1520,7 +1520,7 @@ def test_idempotent_lambda_compound_static_pk_value_has_correct_pk( Test idempotent decorator having a DynamoDBPersistenceLayer with a compound key and a static PK value """ - stubber = stub.Stubber(persistence_store_compound_static_pk_value._client) + stubber = stub.Stubber(persistence_store_compound_static_pk_value.client) ddb_response = {} stubber.add_response("put_item", ddb_response, expected_params_put_item_compound_key_static_pk_value) diff --git a/tests/unit/idempotency/__init__.py b/tests/unit/idempotency/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/unit/idempotency/test_dynamodb_persistence.py b/tests/unit/idempotency/test_dynamodb_persistence.py new file mode 100644 index 00000000000..9455c41ad8d --- /dev/null +++ b/tests/unit/idempotency/test_dynamodb_persistence.py @@ -0,0 +1,21 @@ +from dataclasses import dataclass + +from aws_lambda_powertools.utilities.idempotency import DynamoDBPersistenceLayer +from tests.e2e.utils.data_builder.common import build_random_value + + +def test_custom_sdk_client_injection(): + # GIVEN + @dataclass + class DummyClient: + table_name: str + + table_name = build_random_value() + fake_client = DummyClient(table_name) + + # WHEN + persistence_layer = DynamoDBPersistenceLayer(table_name, boto3_client=fake_client) + + # THEN + assert persistence_layer.table_name == table_name + assert persistence_layer.client == fake_client From 1f92c59e59be4c47e280c193d5bb86dc25e04c0c Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Wed, 5 Apr 2023 17:30:10 +0100 Subject: [PATCH 19/34] docs(idempotency): fixes to testing your code section (#2073) Co-authored-by: heitorlessa --- docs/utilities/idempotency.md | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/docs/utilities/idempotency.md b/docs/utilities/idempotency.md index 466058dacef..81afa8b0117 100644 --- a/docs/utilities/idempotency.md +++ b/docs/utilities/idempotency.md @@ -1221,7 +1221,7 @@ with a truthy value. If you prefer setting this for specific tests, and are usin ### Testing with DynamoDB Local -To test with [DynamoDB Local](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DynamoDBLocal.DownloadingAndRunning.html), you can replace the `Table` resource used by the persistence layer with one you create inside your tests. This allows you to set the endpoint_url. +To test with [DynamoDB Local](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DynamoDBLocal.DownloadingAndRunning.html), you can replace the `DynamoDB client` used by the persistence layer with one you create inside your tests. This allows you to set the endpoint_url. === "tests.py" @@ -1249,10 +1249,12 @@ To test with [DynamoDB Local](https://docs.aws.amazon.com/amazondynamodb/latest/ return LambdaContext() def test_idempotent_lambda(lambda_context): - # Create our own Table resource using the endpoint for our DynamoDB Local instance - resource = boto3.resource("dynamodb", endpoint_url='http://localhost:8000') - table = resource.Table(app.persistence_layer.table_name) - app.persistence_layer.table = table + # Configure the boto3 to use the endpoint for the DynamoDB Local instance + dynamodb_local_client = boto3.client("dynamodb", endpoint_url='http://localhost:8000') + app.persistence_layer.client = dynamodb_local_client + + # If desired, you can use a different DynamoDB Local table name than what your code already uses + # app.persistence_layer.table_name = "another table name" result = app.handler({'testkey': 'testvalue'}, lambda_context) assert result['payment_id'] == 12345 @@ -1310,10 +1312,10 @@ This means it is possible to pass a mocked Table resource, or stub various metho def test_idempotent_lambda(lambda_context): - table = MagicMock() - app.persistence_layer.table = table + mock_client = MagicMock() + app.persistence_layer.client = mock_client result = app.handler({'testkey': 'testvalue'}, lambda_context) - table.put_item.assert_called() + mock_client.put_item.assert_called() ... ``` From e41e0d754c751fdc3574ef03ad2bd897bcbd8ffe Mon Sep 17 00:00:00 2001 From: Release bot Date: Wed, 5 Apr 2023 16:30:31 +0000 Subject: [PATCH 20/34] update changelog with latest changes --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 390220d5d21..d0e79fb1fc1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,16 +6,25 @@ ## Documentation +* **idempotency:** fixes to testing your code section ([#2073](https://github.com/awslabs/aws-lambda-powertools-python/issues/2073)) * **idempotency:** new sequence diagrams, fix idempotency record vs DynamoDB TTL confusion ([#2074](https://github.com/awslabs/aws-lambda-powertools-python/issues/2074)) * **parser:** fix highlighted line ([#2064](https://github.com/awslabs/aws-lambda-powertools-python/issues/2064)) +## Features + +* **idempotency:** allow custom sdk clients in DynamoDBPersistenceLayer ([#2087](https://github.com/awslabs/aws-lambda-powertools-python/issues/2087)) + ## Maintenance +* **deps:** bump aws-xray-sdk from 2.11.0 to 2.12.0 ([#2080](https://github.com/awslabs/aws-lambda-powertools-python/issues/2080)) * **deps:** bump peaceiris/actions-gh-pages from 3.9.2 to 3.9.3 ([#2069](https://github.com/awslabs/aws-lambda-powertools-python/issues/2069)) +* **deps-dev:** bump types-python-dateutil from 2.8.19.11 to 2.8.19.12 ([#2085](https://github.com/awslabs/aws-lambda-powertools-python/issues/2085)) +* **deps-dev:** bump aws-cdk from 2.72.0 to 2.72.1 ([#2081](https://github.com/awslabs/aws-lambda-powertools-python/issues/2081)) * **deps-dev:** bump mkdocs-material from 9.1.4 to 9.1.5 ([#2077](https://github.com/awslabs/aws-lambda-powertools-python/issues/2077)) * **deps-dev:** bump aws-cdk-lib from 2.72.0 to 2.72.1 ([#2076](https://github.com/awslabs/aws-lambda-powertools-python/issues/2076)) * **deps-dev:** bump mypy-boto3-s3 from 1.26.99 to 1.26.104 ([#2075](https://github.com/awslabs/aws-lambda-powertools-python/issues/2075)) * **deps-dev:** bump aws-cdk from 2.71.0 to 2.72.0 ([#2071](https://github.com/awslabs/aws-lambda-powertools-python/issues/2071)) +* **deps-dev:** bump cfn-lint from 0.76.1 to 0.76.2 ([#2084](https://github.com/awslabs/aws-lambda-powertools-python/issues/2084)) * **deps-dev:** bump aws-cdk-lib from 2.71.0 to 2.72.0 ([#2070](https://github.com/awslabs/aws-lambda-powertools-python/issues/2070)) * **deps-dev:** bump black from 23.1.0 to 23.3.0 ([#2066](https://github.com/awslabs/aws-lambda-powertools-python/issues/2066)) * **deps-dev:** bump aws-cdk from 2.70.0 to 2.71.0 ([#2067](https://github.com/awslabs/aws-lambda-powertools-python/issues/2067)) From 415629e88e03d44952cfa36f273b6f9e8f2478c0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 6 Apr 2023 21:07:59 +0000 Subject: [PATCH 21/34] chore(deps-dev): bump mypy-boto3-cloudformation from 1.26.60 to 1.26.108 (#2095) Bumps [mypy-boto3-cloudformation](https://github.com/youtype/mypy_boto3_builder) from 1.26.60 to 1.26.108. - [Release notes](https://github.com/youtype/mypy_boto3_builder/releases) - [Commits](https://github.com/youtype/mypy_boto3_builder/commits) --- updated-dependencies: - dependency-name: mypy-boto3-cloudformation dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 12 ++++++------ pyproject.toml | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/poetry.lock b/poetry.lock index 8b86cdfb10d..2c927dc9a58 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1704,18 +1704,18 @@ typing-extensions = ">=4.1.0" [[package]] name = "mypy-boto3-cloudformation" -version = "1.26.60" -description = "Type annotations for boto3.CloudFormation 1.26.60 service generated with mypy-boto3-builder 7.12.3" +version = "1.26.108" +description = "Type annotations for boto3.CloudFormation 1.26.108 service generated with mypy-boto3-builder 7.14.5" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "mypy-boto3-cloudformation-1.26.60.tar.gz", hash = "sha256:1966b0f521e73d8d043e2499f33e252c71a7dba79bf79d89cb4a0bd8a79180a0"}, - {file = "mypy_boto3_cloudformation-1.26.60-py3-none-any.whl", hash = "sha256:30305e6055076acc48e4346f599b6b24e1b1281d95aabb4d8b335f3915a001fa"}, + {file = "mypy-boto3-cloudformation-1.26.108.tar.gz", hash = "sha256:feeb7cae3d2b850c8fc848585e1c4410bddc9860198edd14401a1c030789994d"}, + {file = "mypy_boto3_cloudformation-1.26.108-py3-none-any.whl", hash = "sha256:566487e5037170f58229af7f9fb2c62b0e98e5a1aa5bcd5eef54afb38a214561"}, ] [package.dependencies] -typing-extensions = ">=4.1.0" +typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.9\""} [[package]] name = "mypy-boto3-cloudwatch" @@ -3053,4 +3053,4 @@ validation = ["fastjsonschema"] [metadata] lock-version = "2.0" python-versions = "^3.7.4" -content-hash = "c7bce703660fd5e1b6487bbefe71076715daacba5b28302b058c570dd81658d5" +content-hash = "818303115e911bfc761902f8f455bbd16d1d88f9a0ace81feb1a14247f334b8e" diff --git a/pyproject.toml b/pyproject.toml index 5a04f8aa7ee..2b360a08e7c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -70,7 +70,7 @@ aws-cdk-lib = "^2.72.1" pytest-benchmark = "^4.0.0" python-snappy = "^0.6.1" mypy-boto3-appconfig = "^1.26.71" -mypy-boto3-cloudformation = "^1.26.57" +mypy-boto3-cloudformation = "^1.26.108" mypy-boto3-cloudwatch = "^1.26.99" mypy-boto3-dynamodb = "^1.26.97" mypy-boto3-lambda = "^1.26.80" From d20e69902d5a740573367744bf7a7b8e08c25859 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 6 Apr 2023 22:08:57 +0100 Subject: [PATCH 22/34] chore(deps-dev): bump aws-cdk from 2.72.1 to 2.73.0 (#2093) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index c9605508e40..45fcd4fbfa6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,13 +8,13 @@ "name": "aws-lambda-powertools-python-e2e", "version": "1.0.0", "devDependencies": { - "aws-cdk": "^2.72.1" + "aws-cdk": "^2.73.0" } }, "node_modules/aws-cdk": { - "version": "2.72.1", - "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.72.1.tgz", - "integrity": "sha512-Noihlxyurq9ecz/aTx+j3raeedI0hcPaYFKqS1CkFDoOEvLHIuIEB6regoJHWy9GER/yYYgKu68c7xYo3LaZPA==", + "version": "2.73.0", + "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.73.0.tgz", + "integrity": "sha512-4ZnY+OS83goCzv+1sCEpNTNiXWjY6KBzic2RNUObzpHjUskRSwUCtaeiv6OyZ55DZoP0tneAmWIBXHfixJ7iQw==", "dev": true, "bin": { "cdk": "bin/cdk" @@ -43,9 +43,9 @@ }, "dependencies": { "aws-cdk": { - "version": "2.72.1", - "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.72.1.tgz", - "integrity": "sha512-Noihlxyurq9ecz/aTx+j3raeedI0hcPaYFKqS1CkFDoOEvLHIuIEB6regoJHWy9GER/yYYgKu68c7xYo3LaZPA==", + "version": "2.73.0", + "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.73.0.tgz", + "integrity": "sha512-4ZnY+OS83goCzv+1sCEpNTNiXWjY6KBzic2RNUObzpHjUskRSwUCtaeiv6OyZ55DZoP0tneAmWIBXHfixJ7iQw==", "dev": true, "requires": { "fsevents": "2.3.2" diff --git a/package.json b/package.json index 8fefe4656da..369f6854693 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,6 @@ "name": "aws-lambda-powertools-python-e2e", "version": "1.0.0", "devDependencies": { - "aws-cdk": "^2.72.1" + "aws-cdk": "^2.73.0" } } From c051d02a535d4b43fd6b56297649cff9af24a930 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 6 Apr 2023 22:10:41 +0100 Subject: [PATCH 23/34] chore(deps-dev): bump coverage from 7.2.2 to 7.2.3 (#2092) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 104 ++++++++++++++++++++++++++-------------------------- 1 file changed, 52 insertions(+), 52 deletions(-) diff --git a/poetry.lock b/poetry.lock index 2c927dc9a58..86716ec3e1f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -535,63 +535,63 @@ typeguard = ">=2.13.3,<2.14.0" [[package]] name = "coverage" -version = "7.2.2" +version = "7.2.3" description = "Code coverage measurement for Python" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "coverage-7.2.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c90e73bdecb7b0d1cea65a08cb41e9d672ac6d7995603d6465ed4914b98b9ad7"}, - {file = "coverage-7.2.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e2926b8abedf750c2ecf5035c07515770944acf02e1c46ab08f6348d24c5f94d"}, - {file = "coverage-7.2.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57b77b9099f172804e695a40ebaa374f79e4fb8b92f3e167f66facbf92e8e7f5"}, - {file = "coverage-7.2.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:efe1c0adad110bf0ad7fb59f833880e489a61e39d699d37249bdf42f80590169"}, - {file = "coverage-7.2.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2199988e0bc8325d941b209f4fd1c6fa007024b1442c5576f1a32ca2e48941e6"}, - {file = "coverage-7.2.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:81f63e0fb74effd5be736cfe07d710307cc0a3ccb8f4741f7f053c057615a137"}, - {file = "coverage-7.2.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:186e0fc9cf497365036d51d4d2ab76113fb74f729bd25da0975daab2e107fd90"}, - {file = "coverage-7.2.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:420f94a35e3e00a2b43ad5740f935358e24478354ce41c99407cddd283be00d2"}, - {file = "coverage-7.2.2-cp310-cp310-win32.whl", hash = "sha256:38004671848b5745bb05d4d621526fca30cee164db42a1f185615f39dc997292"}, - {file = "coverage-7.2.2-cp310-cp310-win_amd64.whl", hash = "sha256:0ce383d5f56d0729d2dd40e53fe3afeb8f2237244b0975e1427bfb2cf0d32bab"}, - {file = "coverage-7.2.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3eb55b7b26389dd4f8ae911ba9bc8c027411163839dea4c8b8be54c4ee9ae10b"}, - {file = "coverage-7.2.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d2b96123a453a2d7f3995ddb9f28d01fd112319a7a4d5ca99796a7ff43f02af5"}, - {file = "coverage-7.2.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:299bc75cb2a41e6741b5e470b8c9fb78d931edbd0cd009c58e5c84de57c06731"}, - {file = "coverage-7.2.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5e1df45c23d4230e3d56d04414f9057eba501f78db60d4eeecfcb940501b08fd"}, - {file = "coverage-7.2.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:006ed5582e9cbc8115d2e22d6d2144a0725db542f654d9d4fda86793832f873d"}, - {file = "coverage-7.2.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d683d230b5774816e7d784d7ed8444f2a40e7a450e5720d58af593cb0b94a212"}, - {file = "coverage-7.2.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:8efb48fa743d1c1a65ee8787b5b552681610f06c40a40b7ef94a5b517d885c54"}, - {file = "coverage-7.2.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4c752d5264053a7cf2fe81c9e14f8a4fb261370a7bb344c2a011836a96fb3f57"}, - {file = "coverage-7.2.2-cp311-cp311-win32.whl", hash = "sha256:55272f33da9a5d7cccd3774aeca7a01e500a614eaea2a77091e9be000ecd401d"}, - {file = "coverage-7.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:92ebc1619650409da324d001b3a36f14f63644c7f0a588e331f3b0f67491f512"}, - {file = "coverage-7.2.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5afdad4cc4cc199fdf3e18088812edcf8f4c5a3c8e6cb69127513ad4cb7471a9"}, - {file = "coverage-7.2.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0484d9dd1e6f481b24070c87561c8d7151bdd8b044c93ac99faafd01f695c78e"}, - {file = "coverage-7.2.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d530191aa9c66ab4f190be8ac8cc7cfd8f4f3217da379606f3dd4e3d83feba69"}, - {file = "coverage-7.2.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ac0f522c3b6109c4b764ffec71bf04ebc0523e926ca7cbe6c5ac88f84faced0"}, - {file = "coverage-7.2.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ba279aae162b20444881fc3ed4e4f934c1cf8620f3dab3b531480cf602c76b7f"}, - {file = "coverage-7.2.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:53d0fd4c17175aded9c633e319360d41a1f3c6e352ba94edcb0fa5167e2bad67"}, - {file = "coverage-7.2.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c99cb7c26a3039a8a4ee3ca1efdde471e61b4837108847fb7d5be7789ed8fd9"}, - {file = "coverage-7.2.2-cp37-cp37m-win32.whl", hash = "sha256:5cc0783844c84af2522e3a99b9b761a979a3ef10fb87fc4048d1ee174e18a7d8"}, - {file = "coverage-7.2.2-cp37-cp37m-win_amd64.whl", hash = "sha256:817295f06eacdc8623dc4df7d8b49cea65925030d4e1e2a7c7218380c0072c25"}, - {file = "coverage-7.2.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6146910231ece63facfc5984234ad1b06a36cecc9fd0c028e59ac7c9b18c38c6"}, - {file = "coverage-7.2.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:387fb46cb8e53ba7304d80aadca5dca84a2fbf6fe3faf6951d8cf2d46485d1e5"}, - {file = "coverage-7.2.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:046936ab032a2810dcaafd39cc4ef6dd295df1a7cbead08fe996d4765fca9fe4"}, - {file = "coverage-7.2.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e627dee428a176ffb13697a2c4318d3f60b2ccdde3acdc9b3f304206ec130ccd"}, - {file = "coverage-7.2.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4fa54fb483decc45f94011898727802309a109d89446a3c76387d016057d2c84"}, - {file = "coverage-7.2.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3668291b50b69a0c1ef9f462c7df2c235da3c4073f49543b01e7eb1dee7dd540"}, - {file = "coverage-7.2.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:7c20b731211261dc9739bbe080c579a1835b0c2d9b274e5fcd903c3a7821cf88"}, - {file = "coverage-7.2.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5764e1f7471cb8f64b8cda0554f3d4c4085ae4b417bfeab236799863703e5de2"}, - {file = "coverage-7.2.2-cp38-cp38-win32.whl", hash = "sha256:4f01911c010122f49a3e9bdc730eccc66f9b72bd410a3a9d3cb8448bb50d65d3"}, - {file = "coverage-7.2.2-cp38-cp38-win_amd64.whl", hash = "sha256:c448b5c9e3df5448a362208b8d4b9ed85305528313fca1b479f14f9fe0d873b8"}, - {file = "coverage-7.2.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bfe7085783cda55e53510482fa7b5efc761fad1abe4d653b32710eb548ebdd2d"}, - {file = "coverage-7.2.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9d22e94e6dc86de981b1b684b342bec5e331401599ce652900ec59db52940005"}, - {file = "coverage-7.2.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:507e4720791977934bba016101579b8c500fb21c5fa3cd4cf256477331ddd988"}, - {file = "coverage-7.2.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bc4803779f0e4b06a2361f666e76f5c2e3715e8e379889d02251ec911befd149"}, - {file = "coverage-7.2.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db8c2c5ace167fd25ab5dd732714c51d4633f58bac21fb0ff63b0349f62755a8"}, - {file = "coverage-7.2.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4f68ee32d7c4164f1e2c8797535a6d0a3733355f5861e0f667e37df2d4b07140"}, - {file = "coverage-7.2.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d52f0a114b6a58305b11a5cdecd42b2e7f1ec77eb20e2b33969d702feafdd016"}, - {file = "coverage-7.2.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:797aad79e7b6182cb49c08cc5d2f7aa7b2128133b0926060d0a8889ac43843be"}, - {file = "coverage-7.2.2-cp39-cp39-win32.whl", hash = "sha256:db45eec1dfccdadb179b0f9ca616872c6f700d23945ecc8f21bb105d74b1c5fc"}, - {file = "coverage-7.2.2-cp39-cp39-win_amd64.whl", hash = "sha256:8dbe2647bf58d2c5a6c5bcc685f23b5f371909a5624e9f5cd51436d6a9f6c6ef"}, - {file = "coverage-7.2.2-pp37.pp38.pp39-none-any.whl", hash = "sha256:872d6ce1f5be73f05bea4df498c140b9e7ee5418bfa2cc8204e7f9b817caa968"}, - {file = "coverage-7.2.2.tar.gz", hash = "sha256:36dd42da34fe94ed98c39887b86db9d06777b1c8f860520e21126a75507024f2"}, + {file = "coverage-7.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e58c0d41d336569d63d1b113bd573db8363bc4146f39444125b7f8060e4e04f5"}, + {file = "coverage-7.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:344e714bd0fe921fc72d97404ebbdbf9127bac0ca1ff66d7b79efc143cf7c0c4"}, + {file = "coverage-7.2.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:974bc90d6f6c1e59ceb1516ab00cf1cdfbb2e555795d49fa9571d611f449bcb2"}, + {file = "coverage-7.2.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0743b0035d4b0e32bc1df5de70fba3059662ace5b9a2a86a9f894cfe66569013"}, + {file = "coverage-7.2.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d0391fb4cfc171ce40437f67eb050a340fdbd0f9f49d6353a387f1b7f9dd4fa"}, + {file = "coverage-7.2.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4a42e1eff0ca9a7cb7dc9ecda41dfc7cbc17cb1d02117214be0561bd1134772b"}, + {file = "coverage-7.2.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:be19931a8dcbe6ab464f3339966856996b12a00f9fe53f346ab3be872d03e257"}, + {file = "coverage-7.2.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:72fcae5bcac3333a4cf3b8f34eec99cea1187acd55af723bcbd559adfdcb5535"}, + {file = "coverage-7.2.3-cp310-cp310-win32.whl", hash = "sha256:aeae2aa38395b18106e552833f2a50c27ea0000122bde421c31d11ed7e6f9c91"}, + {file = "coverage-7.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:83957d349838a636e768251c7e9979e899a569794b44c3728eaebd11d848e58e"}, + {file = "coverage-7.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dfd393094cd82ceb9b40df4c77976015a314b267d498268a076e940fe7be6b79"}, + {file = "coverage-7.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:182eb9ac3f2b4874a1f41b78b87db20b66da6b9cdc32737fbbf4fea0c35b23fc"}, + {file = "coverage-7.2.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bb1e77a9a311346294621be905ea8a2c30d3ad371fc15bb72e98bfcfae532df"}, + {file = "coverage-7.2.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca0f34363e2634deffd390a0fef1aa99168ae9ed2af01af4a1f5865e362f8623"}, + {file = "coverage-7.2.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55416d7385774285b6e2a5feca0af9652f7f444a4fa3d29d8ab052fafef9d00d"}, + {file = "coverage-7.2.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:06ddd9c0249a0546997fdda5a30fbcb40f23926df0a874a60a8a185bc3a87d93"}, + {file = "coverage-7.2.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:fff5aaa6becf2c6a1699ae6a39e2e6fb0672c2d42eca8eb0cafa91cf2e9bd312"}, + {file = "coverage-7.2.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ea53151d87c52e98133eb8ac78f1206498c015849662ca8dc246255265d9c3c4"}, + {file = "coverage-7.2.3-cp311-cp311-win32.whl", hash = "sha256:8f6c930fd70d91ddee53194e93029e3ef2aabe26725aa3c2753df057e296b925"}, + {file = "coverage-7.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:fa546d66639d69aa967bf08156eb8c9d0cd6f6de84be9e8c9819f52ad499c910"}, + {file = "coverage-7.2.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b2317d5ed777bf5a033e83d4f1389fd4ef045763141d8f10eb09a7035cee774c"}, + {file = "coverage-7.2.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be9824c1c874b73b96288c6d3de793bf7f3a597770205068c6163ea1f326e8b9"}, + {file = "coverage-7.2.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2c3b2803e730dc2797a017335827e9da6da0e84c745ce0f552e66400abdfb9a1"}, + {file = "coverage-7.2.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f69770f5ca1994cb32c38965e95f57504d3aea96b6c024624fdd5bb1aa494a1"}, + {file = "coverage-7.2.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1127b16220f7bfb3f1049ed4a62d26d81970a723544e8252db0efde853268e21"}, + {file = "coverage-7.2.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:aa784405f0c640940595fa0f14064d8e84aff0b0f762fa18393e2760a2cf5841"}, + {file = "coverage-7.2.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:3146b8e16fa60427e03884301bf8209221f5761ac754ee6b267642a2fd354c48"}, + {file = "coverage-7.2.3-cp37-cp37m-win32.whl", hash = "sha256:1fd78b911aea9cec3b7e1e2622c8018d51c0d2bbcf8faaf53c2497eb114911c1"}, + {file = "coverage-7.2.3-cp37-cp37m-win_amd64.whl", hash = "sha256:0f3736a5d34e091b0a611964c6262fd68ca4363df56185902528f0b75dbb9c1f"}, + {file = "coverage-7.2.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:981b4df72c93e3bc04478153df516d385317628bd9c10be699c93c26ddcca8ab"}, + {file = "coverage-7.2.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c0045f8f23a5fb30b2eb3b8a83664d8dc4fb58faddf8155d7109166adb9f2040"}, + {file = "coverage-7.2.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f760073fcf8f3d6933178d67754f4f2d4e924e321f4bb0dcef0424ca0215eba1"}, + {file = "coverage-7.2.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c86bd45d1659b1ae3d0ba1909326b03598affbc9ed71520e0ff8c31a993ad911"}, + {file = "coverage-7.2.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:172db976ae6327ed4728e2507daf8a4de73c7cc89796483e0a9198fd2e47b462"}, + {file = "coverage-7.2.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d2a3a6146fe9319926e1d477842ca2a63fe99af5ae690b1f5c11e6af074a6b5c"}, + {file = "coverage-7.2.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:f649dd53833b495c3ebd04d6eec58479454a1784987af8afb77540d6c1767abd"}, + {file = "coverage-7.2.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7c4ed4e9f3b123aa403ab424430b426a1992e6f4c8fd3cb56ea520446e04d152"}, + {file = "coverage-7.2.3-cp38-cp38-win32.whl", hash = "sha256:eb0edc3ce9760d2f21637766c3aa04822030e7451981ce569a1b3456b7053f22"}, + {file = "coverage-7.2.3-cp38-cp38-win_amd64.whl", hash = "sha256:63cdeaac4ae85a179a8d6bc09b77b564c096250d759eed343a89d91bce8b6367"}, + {file = "coverage-7.2.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:20d1a2a76bb4eb00e4d36b9699f9b7aba93271c9c29220ad4c6a9581a0320235"}, + {file = "coverage-7.2.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ea748802cc0de4de92ef8244dd84ffd793bd2e7be784cd8394d557a3c751e21"}, + {file = "coverage-7.2.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21b154aba06df42e4b96fc915512ab39595105f6c483991287021ed95776d934"}, + {file = "coverage-7.2.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd214917cabdd6f673a29d708574e9fbdb892cb77eb426d0eae3490d95ca7859"}, + {file = "coverage-7.2.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c2e58e45fe53fab81f85474e5d4d226eeab0f27b45aa062856c89389da2f0d9"}, + {file = "coverage-7.2.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:87ecc7c9a1a9f912e306997ffee020297ccb5ea388421fe62a2a02747e4d5539"}, + {file = "coverage-7.2.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:387065e420aed3c71b61af7e82c7b6bc1c592f7e3c7a66e9f78dd178699da4fe"}, + {file = "coverage-7.2.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ea3f5bc91d7d457da7d48c7a732beaf79d0c8131df3ab278e6bba6297e23c6c4"}, + {file = "coverage-7.2.3-cp39-cp39-win32.whl", hash = "sha256:ae7863a1d8db6a014b6f2ff9c1582ab1aad55a6d25bac19710a8df68921b6e30"}, + {file = "coverage-7.2.3-cp39-cp39-win_amd64.whl", hash = "sha256:3f04becd4fcda03c0160d0da9c8f0c246bc78f2f7af0feea1ec0930e7c93fa4a"}, + {file = "coverage-7.2.3-pp37.pp38.pp39-none-any.whl", hash = "sha256:965ee3e782c7892befc25575fa171b521d33798132692df428a09efacaffe8d0"}, + {file = "coverage-7.2.3.tar.gz", hash = "sha256:d298c2815fa4891edd9abe5ad6e6cb4207104c7dd9fd13aea3fdebf6f9b91259"}, ] [package.dependencies] From 06f5a93fef0c3c07dd4a8b696383d7159b442387 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 6 Apr 2023 22:13:54 +0100 Subject: [PATCH 24/34] chore(deps-dev): bump filelock from 3.10.7 to 3.11.0 (#2094) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 10 +++++----- pyproject.toml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/poetry.lock b/poetry.lock index 86716ec3e1f..735a64d6f65 100644 --- a/poetry.lock +++ b/poetry.lock @@ -671,18 +671,18 @@ devel = ["colorama", "json-spec", "jsonschema", "pylint", "pytest", "pytest-benc [[package]] name = "filelock" -version = "3.10.7" +version = "3.11.0" description = "A platform independent file lock." category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "filelock-3.10.7-py3-none-any.whl", hash = "sha256:bde48477b15fde2c7e5a0713cbe72721cb5a5ad32ee0b8f419907960b9d75536"}, - {file = "filelock-3.10.7.tar.gz", hash = "sha256:892be14aa8efc01673b5ed6589dbccb95f9a8596f0507e232626155495c18105"}, + {file = "filelock-3.11.0-py3-none-any.whl", hash = "sha256:f08a52314748335c6460fc8fe40cd5638b85001225db78c2aa01c8c0db83b318"}, + {file = "filelock-3.11.0.tar.gz", hash = "sha256:3618c0da67adcc0506b015fd11ef7faf1b493f0b40d87728e19986b536890c37"}, ] [package.extras] -docs = ["furo (>=2022.12.7)", "sphinx (>=6.1.3)", "sphinx-autodoc-typehints (>=1.22,!=1.23.4)"] +docs = ["furo (>=2023.3.27)", "sphinx (>=6.1.3)", "sphinx-autodoc-typehints (>=1.22,!=1.23.4)"] testing = ["covdefaults (>=2.3)", "coverage (>=7.2.2)", "diff-cover (>=7.5)", "pytest (>=7.2.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)", "pytest-timeout (>=2.1)"] [[package]] @@ -3053,4 +3053,4 @@ validation = ["fastjsonschema"] [metadata] lock-version = "2.0" python-versions = "^3.7.4" -content-hash = "818303115e911bfc761902f8f455bbd16d1d88f9a0ace81feb1a14247f334b8e" +content-hash = "9a4a1fbd0853251fd870d59c453fb6992dab6904916ddf209092c95362508532" diff --git a/pyproject.toml b/pyproject.toml index 2b360a08e7c..72281aeebfe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -82,7 +82,7 @@ mypy-boto3-xray = "^1.26.11" types-requests = "^2.28.11" typing-extensions = "^4.4.0" mkdocs-material = "^9.1.5" -filelock = "^3.10.7" +filelock = "^3.11.0" checksumdir = "^1.2.0" mypy-boto3-appconfigdata = "^1.26.70" importlib-metadata = "^6.0" From 8d0c22ec609b5534db7bfb25d91c2e9ac41ce6a8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 6 Apr 2023 21:17:09 +0000 Subject: [PATCH 25/34] chore(deps-dev): bump aws-cdk-lib from 2.72.1 to 2.73.0 (#2097) Bumps [aws-cdk-lib](https://github.com/aws/aws-cdk) from 2.72.1 to 2.73.0. - [Release notes](https://github.com/aws/aws-cdk/releases) - [Changelog](https://github.com/aws/aws-cdk/blob/main/CHANGELOG.v2.md) - [Commits](https://github.com/aws/aws-cdk/compare/v2.72.1...v2.73.0) --- updated-dependencies: - dependency-name: aws-cdk-lib dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 8 ++++---- pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index 735a64d6f65..9dde59bb526 100644 --- a/poetry.lock +++ b/poetry.lock @@ -153,14 +153,14 @@ typeguard = ">=2.13.3,<2.14.0" [[package]] name = "aws-cdk-lib" -version = "2.72.1" +version = "2.73.0" description = "Version 2 of the AWS Cloud Development Kit library" category = "dev" optional = false python-versions = "~=3.7" files = [ - {file = "aws-cdk-lib-2.72.1.tar.gz", hash = "sha256:a0aeaf0e0d0dcc36fe52a1df09708028a8f71f54116bc3f2afec546b0d90c256"}, - {file = "aws_cdk_lib-2.72.1-py3-none-any.whl", hash = "sha256:0d7001b0f507dcd435c6c20688e61d6c45c297e54bae2bf36256e10520668a8a"}, + {file = "aws-cdk-lib-2.73.0.tar.gz", hash = "sha256:9e93044d19ae26ef4303b39cbd4b083f8b183bb9211bc2f9b76698a8c765d8ad"}, + {file = "aws_cdk_lib-2.73.0-py3-none-any.whl", hash = "sha256:60271826f4d53267b39c43aaba93eecbcd2b85c1a1ae206ce56f347344903554"}, ] [package.dependencies] @@ -3053,4 +3053,4 @@ validation = ["fastjsonschema"] [metadata] lock-version = "2.0" python-versions = "^3.7.4" -content-hash = "9a4a1fbd0853251fd870d59c453fb6992dab6904916ddf209092c95362508532" +content-hash = "62cbe15eb95d680d73c8359cf4c967f1dc27e99904b44a7c8adeb04a0032736e" diff --git a/pyproject.toml b/pyproject.toml index 72281aeebfe..148e7f994e5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,7 +63,7 @@ mkdocs-git-revision-date-plugin = "^0.3.2" mike = "^1.1.2" retry = "^0.9.2" pytest-xdist = "^3.2.1" -aws-cdk-lib = "^2.72.1" +aws-cdk-lib = "^2.73.0" "aws-cdk.aws-apigatewayv2-alpha" = "^2.38.1-alpha.0" "aws-cdk.aws-apigatewayv2-integrations-alpha" = "^2.38.1-alpha.0" "aws-cdk.aws-apigatewayv2-authorizers-alpha" = "^2.38.1-alpha.0" From 9f430b1b6449d1a4f9b3680932ed25068b263f02 Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Fri, 7 Apr 2023 13:02:49 +0100 Subject: [PATCH 26/34] docs(homepage): remove banner for end-of-support v1 (#2098) --- docs/overrides/main.html | 5 ----- docs/upgrade.md | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/docs/overrides/main.html b/docs/overrides/main.html index 7f4f2000d9a..0af326afb24 100644 --- a/docs/overrides/main.html +++ b/docs/overrides/main.html @@ -1,10 +1,5 @@ {% extends "base.html" %} -{% block announce %} -👋 Powertools for Python v1 will no longer receive updates or releases after 31/03/2023! -We encourage you to read our upgrade guide on how to migrate to v2. -{% endblock %} - {% block outdated %} You're not viewing the latest version. diff --git a/docs/upgrade.md b/docs/upgrade.md index 369daca1832..948ce6fc873 100644 --- a/docs/upgrade.md +++ b/docs/upgrade.md @@ -7,7 +7,7 @@ description: Guide to update between major Powertools versions ## End of support v1 -On March 31st, AWS Lambda Powertools for Python v1 will reach end of support. After that, Powertools v1 will no longer receive updates or releases. If you are still using v1, we encourage you to read our upgrade guide and update to the latest version. +!!! warning "On March 31st, 2023, AWS Lambda Powertools for Python v1 reached end of support and will no longer receive updates or releases. If you are still using v1, we strongly recommend you to read our upgrade guide and update to the latest version." Given our commitment to all of our customers using AWS Lambda Powertools for Python, we will keep [Pypi](https://pypi.org/project/aws-lambda-powertools/) v1 releases and documentation 1.x versions to prevent any disruption. From c58f474efd671c56535d85feebd0075c281b4f89 Mon Sep 17 00:00:00 2001 From: Release bot Date: Fri, 7 Apr 2023 12:03:15 +0000 Subject: [PATCH 27/34] update changelog with latest changes --- CHANGELOG.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d0e79fb1fc1..74ee0d15b13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ ## Documentation +* **homepage:** remove banner for end-of-support v1 ([#2098](https://github.com/awslabs/aws-lambda-powertools-python/issues/2098)) * **idempotency:** fixes to testing your code section ([#2073](https://github.com/awslabs/aws-lambda-powertools-python/issues/2073)) * **idempotency:** new sequence diagrams, fix idempotency record vs DynamoDB TTL confusion ([#2074](https://github.com/awslabs/aws-lambda-powertools-python/issues/2074)) * **parser:** fix highlighted line ([#2064](https://github.com/awslabs/aws-lambda-powertools-python/issues/2064)) @@ -18,13 +19,18 @@ * **deps:** bump aws-xray-sdk from 2.11.0 to 2.12.0 ([#2080](https://github.com/awslabs/aws-lambda-powertools-python/issues/2080)) * **deps:** bump peaceiris/actions-gh-pages from 3.9.2 to 3.9.3 ([#2069](https://github.com/awslabs/aws-lambda-powertools-python/issues/2069)) +* **deps-dev:** bump aws-cdk-lib from 2.72.1 to 2.73.0 ([#2097](https://github.com/awslabs/aws-lambda-powertools-python/issues/2097)) +* **deps-dev:** bump aws-cdk from 2.72.1 to 2.73.0 ([#2093](https://github.com/awslabs/aws-lambda-powertools-python/issues/2093)) +* **deps-dev:** bump mypy-boto3-cloudformation from 1.26.60 to 1.26.108 ([#2095](https://github.com/awslabs/aws-lambda-powertools-python/issues/2095)) * **deps-dev:** bump types-python-dateutil from 2.8.19.11 to 2.8.19.12 ([#2085](https://github.com/awslabs/aws-lambda-powertools-python/issues/2085)) +* **deps-dev:** bump cfn-lint from 0.76.1 to 0.76.2 ([#2084](https://github.com/awslabs/aws-lambda-powertools-python/issues/2084)) * **deps-dev:** bump aws-cdk from 2.72.0 to 2.72.1 ([#2081](https://github.com/awslabs/aws-lambda-powertools-python/issues/2081)) +* **deps-dev:** bump coverage from 7.2.2 to 7.2.3 ([#2092](https://github.com/awslabs/aws-lambda-powertools-python/issues/2092)) * **deps-dev:** bump mkdocs-material from 9.1.4 to 9.1.5 ([#2077](https://github.com/awslabs/aws-lambda-powertools-python/issues/2077)) * **deps-dev:** bump aws-cdk-lib from 2.72.0 to 2.72.1 ([#2076](https://github.com/awslabs/aws-lambda-powertools-python/issues/2076)) * **deps-dev:** bump mypy-boto3-s3 from 1.26.99 to 1.26.104 ([#2075](https://github.com/awslabs/aws-lambda-powertools-python/issues/2075)) * **deps-dev:** bump aws-cdk from 2.71.0 to 2.72.0 ([#2071](https://github.com/awslabs/aws-lambda-powertools-python/issues/2071)) -* **deps-dev:** bump cfn-lint from 0.76.1 to 0.76.2 ([#2084](https://github.com/awslabs/aws-lambda-powertools-python/issues/2084)) +* **deps-dev:** bump filelock from 3.10.7 to 3.11.0 ([#2094](https://github.com/awslabs/aws-lambda-powertools-python/issues/2094)) * **deps-dev:** bump aws-cdk-lib from 2.71.0 to 2.72.0 ([#2070](https://github.com/awslabs/aws-lambda-powertools-python/issues/2070)) * **deps-dev:** bump black from 23.1.0 to 23.3.0 ([#2066](https://github.com/awslabs/aws-lambda-powertools-python/issues/2066)) * **deps-dev:** bump aws-cdk from 2.70.0 to 2.71.0 ([#2067](https://github.com/awslabs/aws-lambda-powertools-python/issues/2067)) From c3e25d60d5183f22c33937e348551d75b5fb476c Mon Sep 17 00:00:00 2001 From: Ilya <34696956+LuckIlNe@users.noreply.github.com> Date: Fri, 7 Apr 2023 16:05:32 +0400 Subject: [PATCH 28/34] fix(batch): handle early validation errors for pydantic models (poison pill) #2091 (#2099) Co-authored-by: heitorlessa --- aws_lambda_powertools/utilities/batch/base.py | 43 ++- .../utilities/parser/models/dynamodb.py | 4 +- .../utilities/parser/models/kinesis.py | 2 +- .../utilities/parser/models/sqs.py | 2 +- .../utilities/parser/types.py | 8 +- tests/functional/batch/__init__.py | 0 tests/functional/batch/sample_models.py | 47 +++ tests/functional/test_utilities_batch.py | 294 +++++++++++++----- 8 files changed, 312 insertions(+), 88 deletions(-) create mode 100644 tests/functional/batch/__init__.py create mode 100644 tests/functional/batch/sample_models.py diff --git a/aws_lambda_powertools/utilities/batch/base.py b/aws_lambda_powertools/utilities/batch/base.py index 3aea2b70fa4..4cdea6d28f5 100644 --- a/aws_lambda_powertools/utilities/batch/base.py +++ b/aws_lambda_powertools/utilities/batch/base.py @@ -37,6 +37,7 @@ KinesisStreamRecord, ) from aws_lambda_powertools.utilities.data_classes.sqs_event import SQSRecord +from aws_lambda_powertools.utilities.parser import ValidationError from aws_lambda_powertools.utilities.typing import LambdaContext logger = logging.getLogger(__name__) @@ -316,21 +317,36 @@ def _get_messages_to_report(self) -> List[Dict[str, str]]: def _collect_sqs_failures(self): failures = [] for msg in self.fail_messages: - msg_id = msg.messageId if self.model else msg.message_id + # If a message failed due to model validation (e.g., poison pill) + # we convert to an event source data class...but self.model is still true + # therefore, we do an additional check on whether the failed message is still a model + # see https://github.com/awslabs/aws-lambda-powertools-python/issues/2091 + if self.model and getattr(msg, "parse_obj", None): + msg_id = msg.messageId + else: + msg_id = msg.message_id failures.append({"itemIdentifier": msg_id}) return failures def _collect_kinesis_failures(self): failures = [] for msg in self.fail_messages: - msg_id = msg.kinesis.sequenceNumber if self.model else msg.kinesis.sequence_number + # # see https://github.com/awslabs/aws-lambda-powertools-python/issues/2091 + if self.model and getattr(msg, "parse_obj", None): + msg_id = msg.kinesis.sequenceNumber + else: + msg_id = msg.kinesis.sequence_number failures.append({"itemIdentifier": msg_id}) return failures def _collect_dynamodb_failures(self): failures = [] for msg in self.fail_messages: - msg_id = msg.dynamodb.SequenceNumber if self.model else msg.dynamodb.sequence_number + # see https://github.com/awslabs/aws-lambda-powertools-python/issues/2091 + if self.model and getattr(msg, "parse_obj", None): + msg_id = msg.dynamodb.SequenceNumber + else: + msg_id = msg.dynamodb.sequence_number failures.append({"itemIdentifier": msg_id}) return failures @@ -347,6 +363,17 @@ def _to_batch_type(self, record: dict, event_type: EventType, model: Optional["B return model.parse_obj(record) return self._DATA_CLASS_MAPPING[event_type](record) + def _register_model_validation_error_record(self, record: dict): + """Convert and register failure due to poison pills where model failed validation early""" + # Parser will fail validation if record is a poison pill (malformed input) + # this means we can't collect the message id if we try transforming again + # so we convert into to the equivalent batch type model (e.g., SQS, Kinesis, DynamoDB Stream) + # and downstream we can correctly collect the correct message id identifier and make the failed record available + # see https://github.com/awslabs/aws-lambda-powertools-python/issues/2091 + logger.debug("Record cannot be converted to customer's model; converting without model") + failed_record: "EventSourceDataClassTypes" = self._to_batch_type(record=record, event_type=self.event_type) + return self.failure_handler(record=failed_record, exception=sys.exc_info()) + class BatchProcessor(BasePartialBatchProcessor): # Keep old name for compatibility """Process native partial responses from SQS, Kinesis Data Streams, and DynamoDB. @@ -471,14 +498,17 @@ def _process_record(self, record: dict) -> Union[SuccessResponse, FailureRespons record: dict A batch record to be processed. """ - data = self._to_batch_type(record=record, event_type=self.event_type, model=self.model) + data: Optional["BatchTypeModels"] = None try: + data = self._to_batch_type(record=record, event_type=self.event_type, model=self.model) if self._handler_accepts_lambda_context: result = self.handler(record=data, lambda_context=self.lambda_context) else: result = self.handler(record=data) return self.success_handler(record=record, result=result) + except ValidationError: + return self._register_model_validation_error_record(record) except Exception: return self.failure_handler(record=data, exception=sys.exc_info()) @@ -651,14 +681,17 @@ async def _async_process_record(self, record: dict) -> Union[SuccessResponse, Fa record: dict A batch record to be processed. """ - data = self._to_batch_type(record=record, event_type=self.event_type, model=self.model) + data: Optional["BatchTypeModels"] = None try: + data = self._to_batch_type(record=record, event_type=self.event_type, model=self.model) if self._handler_accepts_lambda_context: result = await self.handler(record=data, lambda_context=self.lambda_context) else: result = await self.handler(record=data) return self.success_handler(record=record, result=result) + except ValidationError: + return self._register_model_validation_error_record(record) except Exception: return self.failure_handler(record=data, exception=sys.exc_info()) diff --git a/aws_lambda_powertools/utilities/parser/models/dynamodb.py b/aws_lambda_powertools/utilities/parser/models/dynamodb.py index 772b8fb580f..4c85c72d438 100644 --- a/aws_lambda_powertools/utilities/parser/models/dynamodb.py +++ b/aws_lambda_powertools/utilities/parser/models/dynamodb.py @@ -9,8 +9,8 @@ class DynamoDBStreamChangedRecordModel(BaseModel): ApproximateCreationDateTime: Optional[date] Keys: Dict[str, Dict[str, Any]] - NewImage: Optional[Union[Dict[str, Any], Type[BaseModel]]] - OldImage: Optional[Union[Dict[str, Any], Type[BaseModel]]] + NewImage: Optional[Union[Dict[str, Any], Type[BaseModel], BaseModel]] + OldImage: Optional[Union[Dict[str, Any], Type[BaseModel], BaseModel]] SequenceNumber: str SizeBytes: int StreamViewType: Literal["NEW_AND_OLD_IMAGES", "KEYS_ONLY", "NEW_IMAGE", "OLD_IMAGE"] diff --git a/aws_lambda_powertools/utilities/parser/models/kinesis.py b/aws_lambda_powertools/utilities/parser/models/kinesis.py index 6fb9a7076b5..bb6d6b5318f 100644 --- a/aws_lambda_powertools/utilities/parser/models/kinesis.py +++ b/aws_lambda_powertools/utilities/parser/models/kinesis.py @@ -15,7 +15,7 @@ class KinesisDataStreamRecordPayload(BaseModel): kinesisSchemaVersion: str partitionKey: str sequenceNumber: str - data: Union[bytes, Type[BaseModel]] # base64 encoded str is parsed into bytes + data: Union[bytes, Type[BaseModel], BaseModel] # base64 encoded str is parsed into bytes approximateArrivalTimestamp: float @validator("data", pre=True, allow_reuse=True) diff --git a/aws_lambda_powertools/utilities/parser/models/sqs.py b/aws_lambda_powertools/utilities/parser/models/sqs.py index 1d56c4f8e34..c92a8361b7c 100644 --- a/aws_lambda_powertools/utilities/parser/models/sqs.py +++ b/aws_lambda_powertools/utilities/parser/models/sqs.py @@ -52,7 +52,7 @@ class SqsMsgAttributeModel(BaseModel): class SqsRecordModel(BaseModel): messageId: str receiptHandle: str - body: Union[str, Type[BaseModel]] + body: Union[str, Type[BaseModel], BaseModel] attributes: SqsAttributesModel messageAttributes: Dict[str, SqsMsgAttributeModel] md5OfBody: str diff --git a/aws_lambda_powertools/utilities/parser/types.py b/aws_lambda_powertools/utilities/parser/types.py index e9acceb8963..d3f00646d52 100644 --- a/aws_lambda_powertools/utilities/parser/types.py +++ b/aws_lambda_powertools/utilities/parser/types.py @@ -3,16 +3,18 @@ import sys from typing import Any, Dict, Type, TypeVar, Union -from pydantic import BaseModel +from pydantic import BaseModel, Json # We only need typing_extensions for python versions <3.8 if sys.version_info >= (3, 8): - from typing import Literal # noqa: F401 + from typing import Literal else: - from typing_extensions import Literal # noqa: F401 + from typing_extensions import Literal Model = TypeVar("Model", bound=BaseModel) EnvelopeModel = TypeVar("EnvelopeModel") EventParserReturnType = TypeVar("EventParserReturnType") AnyInheritedModel = Union[Type[BaseModel], BaseModel] RawDictOrModel = Union[Dict[str, Any], AnyInheritedModel] + +__all__ = ["Json", "Literal"] diff --git a/tests/functional/batch/__init__.py b/tests/functional/batch/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/functional/batch/sample_models.py b/tests/functional/batch/sample_models.py new file mode 100644 index 00000000000..556ff0ebf8a --- /dev/null +++ b/tests/functional/batch/sample_models.py @@ -0,0 +1,47 @@ +import json +from typing import Dict, Optional + +from aws_lambda_powertools.utilities.parser import BaseModel, validator +from aws_lambda_powertools.utilities.parser.models import ( + DynamoDBStreamChangedRecordModel, + DynamoDBStreamRecordModel, + KinesisDataStreamRecord, + KinesisDataStreamRecordPayload, + SqsRecordModel, +) +from aws_lambda_powertools.utilities.parser.types import Json, Literal + + +class Order(BaseModel): + item: dict + + +class OrderSqs(SqsRecordModel): + body: Json[Order] + + +class OrderKinesisPayloadRecord(KinesisDataStreamRecordPayload): + data: Json[Order] + + +class OrderKinesisRecord(KinesisDataStreamRecord): + kinesis: OrderKinesisPayloadRecord + + +class OrderDynamoDB(BaseModel): + Message: Order + + # auto transform json string + # so Pydantic can auto-initialize nested Order model + @validator("Message", pre=True) + def transform_message_to_dict(cls, value: Dict[Literal["S"], str]): + return json.loads(value["S"]) + + +class OrderDynamoDBChangeRecord(DynamoDBStreamChangedRecordModel): + NewImage: Optional[OrderDynamoDB] + OldImage: Optional[OrderDynamoDB] + + +class OrderDynamoDBRecord(DynamoDBStreamRecordModel): + dynamodb: OrderDynamoDBChangeRecord diff --git a/tests/functional/test_utilities_batch.py b/tests/functional/test_utilities_batch.py index c98d59a7042..2205d47660c 100644 --- a/tests/functional/test_utilities_batch.py +++ b/tests/functional/test_utilities_batch.py @@ -27,14 +27,12 @@ DynamoDBStreamChangedRecordModel, DynamoDBStreamRecordModel, ) -from aws_lambda_powertools.utilities.parser.models import ( - KinesisDataStreamRecord as KinesisDataStreamRecordModel, -) -from aws_lambda_powertools.utilities.parser.models import ( - KinesisDataStreamRecordPayload, - SqsRecordModel, -) from aws_lambda_powertools.utilities.parser.types import Literal +from tests.functional.batch.sample_models import ( + OrderDynamoDBRecord, + OrderKinesisRecord, + OrderSqs, +) from tests.functional.utils import b64_to_str, str_to_b64 @@ -119,6 +117,16 @@ def handler(record): return handler +@pytest.fixture(scope="module") +def record_handler_model() -> Callable: + def record_handler(record: OrderSqs): + if "fail" in record.body.item["type"]: + raise Exception("Failed to process record.") + return record.body.item + + return record_handler + + @pytest.fixture(scope="module") def async_record_handler() -> Callable[..., Awaitable[Any]]: async def handler(record): @@ -130,6 +138,16 @@ async def handler(record): return handler +@pytest.fixture(scope="module") +def async_record_handler_model() -> Callable[..., Awaitable[Any]]: + async def async_record_handler(record: OrderSqs): + if "fail" in record.body.item["type"]: + raise ValueError("Failed to process record.") + return record.body.item + + return async_record_handler + + @pytest.fixture(scope="module") def kinesis_record_handler() -> Callable: def handler(record: KinesisStreamRecord): @@ -141,17 +159,57 @@ def handler(record: KinesisStreamRecord): return handler +@pytest.fixture(scope="module") +def kinesis_record_handler_model() -> Callable: + def record_handler(record: OrderKinesisRecord): + if "fail" in record.kinesis.data.item["type"]: + raise ValueError("Failed to process record.") + return record.kinesis.data.item + + return record_handler + + +@pytest.fixture(scope="module") +def async_kinesis_record_handler_model() -> Callable[..., Awaitable[Any]]: + async def record_handler(record: OrderKinesisRecord): + if "fail" in record.kinesis.data.item["type"]: + raise Exception("Failed to process record.") + return record.kinesis.data.item + + return record_handler + + @pytest.fixture(scope="module") def dynamodb_record_handler() -> Callable: def handler(record: DynamoDBRecord): body = record.dynamodb.new_image.get("Message") if "fail" in body: - raise Exception("Failed to process record.") + raise ValueError("Failed to process record.") return body return handler +@pytest.fixture(scope="module") +def dynamodb_record_handler_model() -> Callable: + def record_handler(record: OrderDynamoDBRecord): + if "fail" in record.dynamodb.NewImage.Message.item["type"]: + raise ValueError("Failed to process record.") + return record.dynamodb.NewImage.Message.item + + return record_handler + + +@pytest.fixture(scope="module") +def async_dynamodb_record_handler() -> Callable[..., Awaitable[Any]]: + async def record_handler(record: OrderDynamoDBRecord): + if "fail" in record.dynamodb.NewImage.Message.item["type"]: + raise ValueError("Failed to process record.") + return record.dynamodb.NewImage.Message.item + + return record_handler + + @pytest.fixture(scope="module") def config() -> Config: return Config(region_name="us-east-1") @@ -374,18 +432,6 @@ def lambda_handler(event, context): def test_batch_processor_context_model(sqs_event_factory, order_event_factory): # GIVEN - class Order(BaseModel): - item: dict - - class OrderSqs(SqsRecordModel): - body: Order - - # auto transform json string - # so Pydantic can auto-initialize nested Order model - @validator("body", pre=True) - def transform_body_to_dict(cls, value: str): - return json.loads(value) - def record_handler(record: OrderSqs): return record.body.item @@ -411,18 +457,6 @@ def record_handler(record: OrderSqs): def test_batch_processor_context_model_with_failure(sqs_event_factory, order_event_factory): # GIVEN - class Order(BaseModel): - item: dict - - class OrderSqs(SqsRecordModel): - body: Order - - # auto transform json string - # so Pydantic can auto-initialize nested Order model - @validator("body", pre=True) - def transform_body_to_dict(cls, value: str): - return json.loads(value) - def record_handler(record: OrderSqs): if "fail" in record.body.item["type"]: raise Exception("Failed to process record.") @@ -542,27 +576,10 @@ def record_handler(record: OrderDynamoDBRecord): } -def test_batch_processor_kinesis_context_parser_model(kinesis_event_factory, order_event_factory): +def test_batch_processor_kinesis_context_parser_model( + kinesis_record_handler_model: Callable, kinesis_event_factory, order_event_factory +): # GIVEN - class Order(BaseModel): - item: dict - - class OrderKinesisPayloadRecord(KinesisDataStreamRecordPayload): - data: Order - - # auto transform json string - # so Pydantic can auto-initialize nested Order model - @validator("data", pre=True) - def transform_message_to_dict(cls, value: str): - # Powertools KinesisDataStreamRecordModel already decodes b64 to str here - return json.loads(value) - - class OrderKinesisRecord(KinesisDataStreamRecordModel): - kinesis: OrderKinesisPayloadRecord - - def record_handler(record: OrderKinesisRecord): - return record.kinesis.data.item - order_event = order_event_factory({"type": "success"}) first_record = kinesis_event_factory(order_event) second_record = kinesis_event_factory(order_event) @@ -570,7 +587,7 @@ def record_handler(record: OrderKinesisRecord): # WHEN processor = BatchProcessor(event_type=EventType.KinesisDataStreams, model=OrderKinesisRecord) - with processor(records, record_handler) as batch: + with processor(records, kinesis_record_handler_model) as batch: processed_messages = batch.process() # THEN @@ -583,29 +600,10 @@ def record_handler(record: OrderKinesisRecord): assert batch.response() == {"batchItemFailures": []} -def test_batch_processor_kinesis_context_parser_model_with_failure(kinesis_event_factory, order_event_factory): +def test_batch_processor_kinesis_context_parser_model_with_failure( + kinesis_record_handler_model: Callable, kinesis_event_factory, order_event_factory +): # GIVEN - class Order(BaseModel): - item: dict - - class OrderKinesisPayloadRecord(KinesisDataStreamRecordPayload): - data: Order - - # auto transform json string - # so Pydantic can auto-initialize nested Order model - @validator("data", pre=True) - def transform_message_to_dict(cls, value: str): - # Powertools KinesisDataStreamRecordModel - return json.loads(value) - - class OrderKinesisRecord(KinesisDataStreamRecordModel): - kinesis: OrderKinesisPayloadRecord - - def record_handler(record: OrderKinesisRecord): - if "fail" in record.kinesis.data.item["type"]: - raise Exception("Failed to process record.") - return record.kinesis.data.item - order_event = order_event_factory({"type": "success"}) order_event_fail = order_event_factory({"type": "fail"}) @@ -616,7 +614,7 @@ def record_handler(record: OrderKinesisRecord): # WHEN processor = BatchProcessor(event_type=EventType.KinesisDataStreams, model=OrderKinesisRecord) - with processor(records, record_handler) as batch: + with processor(records, kinesis_record_handler_model) as batch: batch.process() # THEN @@ -775,3 +773,147 @@ def test_async_batch_processor_context_with_failure(sqs_event_factory, async_rec assert batch.response() == { "batchItemFailures": [{"itemIdentifier": first_record.message_id}, {"itemIdentifier": third_record.message_id}] } + + +def test_batch_processor_model_with_partial_validation_error( + record_handler_model: Callable, sqs_event_factory, order_event_factory +): + # GIVEN + order_event = order_event_factory({"type": "success"}) + first_record = sqs_event_factory(order_event) + second_record = sqs_event_factory(order_event) + malformed_record = sqs_event_factory({"poison": "pill"}) + records = [first_record, malformed_record, second_record] + + # WHEN + processor = BatchProcessor(event_type=EventType.SQS, model=OrderSqs) + with processor(records, record_handler_model) as batch: + batch.process() + + # THEN + assert len(batch.fail_messages) == 1 + assert batch.response() == { + "batchItemFailures": [ + {"itemIdentifier": malformed_record["messageId"]}, + ] + } + + +def test_batch_processor_dynamodb_context_model_with_partial_validation_error( + dynamodb_record_handler_model: Callable, dynamodb_event_factory, order_event_factory +): + # GIVEN + order_event = order_event_factory({"type": "success"}) + first_record = dynamodb_event_factory(order_event) + second_record = dynamodb_event_factory(order_event) + malformed_record = dynamodb_event_factory({"poison": "pill"}) + records = [first_record, malformed_record, second_record] + + # WHEN + processor = BatchProcessor(event_type=EventType.DynamoDBStreams, model=OrderDynamoDBRecord) + with processor(records, dynamodb_record_handler_model) as batch: + batch.process() + + # THEN + assert len(batch.fail_messages) == 1 + assert batch.response() == { + "batchItemFailures": [ + {"itemIdentifier": malformed_record["dynamodb"]["SequenceNumber"]}, + ] + } + + +def test_batch_processor_kinesis_context_parser_model_with_partial_validation_error( + kinesis_record_handler_model: Callable, kinesis_event_factory, order_event_factory +): + # GIVEN + order_event = order_event_factory({"type": "success"}) + first_record = kinesis_event_factory(order_event) + second_record = kinesis_event_factory(order_event) + malformed_record = kinesis_event_factory('{"poison": "pill"}') + records = [first_record, malformed_record, second_record] + + # WHEN + processor = BatchProcessor(event_type=EventType.KinesisDataStreams, model=OrderKinesisRecord) + with processor(records, kinesis_record_handler_model) as batch: + batch.process() + + # THEN + assert len(batch.fail_messages) == 1 + assert batch.response() == { + "batchItemFailures": [ + {"itemIdentifier": malformed_record["kinesis"]["sequenceNumber"]}, + ] + } + + +def test_async_batch_processor_model_with_partial_validation_error( + async_record_handler_model: Callable, sqs_event_factory, order_event_factory +): + # GIVEN + order_event = order_event_factory({"type": "success"}) + first_record = sqs_event_factory(order_event) + second_record = sqs_event_factory(order_event) + malformed_record = sqs_event_factory({"poison": "pill"}) + records = [first_record, malformed_record, second_record] + + # WHEN + processor = AsyncBatchProcessor(event_type=EventType.SQS, model=OrderSqs) + with processor(records, async_record_handler_model) as batch: + batch.async_process() + + # THEN + assert len(batch.fail_messages) == 1 + assert batch.response() == { + "batchItemFailures": [ + {"itemIdentifier": malformed_record["messageId"]}, + ] + } + + +def test_async_batch_processor_dynamodb_context_model_with_partial_validation_error( + async_dynamodb_record_handler: Callable, dynamodb_event_factory, order_event_factory +): + # GIVEN + order_event = order_event_factory({"type": "success"}) + first_record = dynamodb_event_factory(order_event) + second_record = dynamodb_event_factory(order_event) + malformed_record = dynamodb_event_factory({"poison": "pill"}) + records = [first_record, malformed_record, second_record] + + # WHEN + processor = AsyncBatchProcessor(event_type=EventType.DynamoDBStreams, model=OrderDynamoDBRecord) + with processor(records, async_dynamodb_record_handler) as batch: + batch.async_process() + + # THEN + assert len(batch.fail_messages) == 1 + assert batch.response() == { + "batchItemFailures": [ + {"itemIdentifier": malformed_record["dynamodb"]["SequenceNumber"]}, + ] + } + + +def test_async_batch_processor_kinesis_context_parser_model_with_partial_validation_error( + async_kinesis_record_handler_model: Callable, kinesis_event_factory, order_event_factory +): + # GIVEN + order_event = order_event_factory({"type": "success"}) + first_record = kinesis_event_factory(order_event) + second_record = kinesis_event_factory(order_event) + malformed_record = kinesis_event_factory('{"poison": "pill"}') + records = [first_record, malformed_record, second_record] + + # WHEN + processor = AsyncBatchProcessor(event_type=EventType.KinesisDataStreams, model=OrderKinesisRecord) + with processor(records, async_kinesis_record_handler_model) as batch: + batch.async_process() + + # THEN + assert len(batch.fail_messages) == 1 + assert batch.response() == { + "batchItemFailures": [ + {"itemIdentifier": malformed_record["kinesis"]["sequenceNumber"]}, + ] + } From 60d0363e3ce972f4480052ebf5c78df104134086 Mon Sep 17 00:00:00 2001 From: Heitor Lessa Date: Fri, 7 Apr 2023 15:36:47 +0200 Subject: [PATCH 29/34] feat(batch): reduce boilerplate with process_partial_response (#2090) --- .../utilities/batch/__init__.py | 10 +- aws_lambda_powertools/utilities/batch/base.py | 107 +------- .../utilities/batch/decorators.py | 249 ++++++++++++++++++ .../utilities/batch/types.py | 12 +- docs/utilities/batch.md | 200 +++++++------- .../src/advanced_accessing_lambda_context.py | 30 +++ ..._processor.py => getting_started_async.py} | 7 +- .../src/getting_started_dynamodb.py | 30 +++ .../src/getting_started_kinesis.py | 28 ++ .../src/getting_started_sqs.py | 29 ++ .../src/getting_started_sqs_fifo.py | 22 ++ ...tting_started_sqs_fifo_context_manager.py} | 0 ... => getting_started_sqs_fifo_decorator.py} | 0 tests/functional/test_utilities_batch.py | 66 +++++ 14 files changed, 589 insertions(+), 201 deletions(-) create mode 100644 aws_lambda_powertools/utilities/batch/decorators.py create mode 100644 examples/batch_processing/src/advanced_accessing_lambda_context.py rename examples/batch_processing/src/{getting_started_async_batch_processor.py => getting_started_async.py} (79%) create mode 100644 examples/batch_processing/src/getting_started_dynamodb.py create mode 100644 examples/batch_processing/src/getting_started_kinesis.py create mode 100644 examples/batch_processing/src/getting_started_sqs.py create mode 100644 examples/batch_processing/src/getting_started_sqs_fifo.py rename examples/batch_processing/src/{sqs_fifo_batch_processor_context_manager.py => getting_started_sqs_fifo_context_manager.py} (100%) rename examples/batch_processing/src/{sqs_fifo_batch_processor.py => getting_started_sqs_fifo_decorator.py} (100%) diff --git a/aws_lambda_powertools/utilities/batch/__init__.py b/aws_lambda_powertools/utilities/batch/__init__.py index 0e2637cc358..e135655ef61 100644 --- a/aws_lambda_powertools/utilities/batch/__init__.py +++ b/aws_lambda_powertools/utilities/batch/__init__.py @@ -12,8 +12,12 @@ EventType, FailureResponse, SuccessResponse, +) +from aws_lambda_powertools.utilities.batch.decorators import ( async_batch_processor, + async_process_partial_response, batch_processor, + process_partial_response, ) from aws_lambda_powertools.utilities.batch.exceptions import ExceptionInfo from aws_lambda_powertools.utilities.batch.sqs_fifo_partial_processor import ( @@ -22,6 +26,10 @@ from aws_lambda_powertools.utilities.batch.types import BatchTypeModels __all__ = ( + "async_batch_processor", + "async_process_partial_response", + "batch_processor", + "process_partial_response", "BatchProcessor", "AsyncBatchProcessor", "BasePartialProcessor", @@ -32,6 +40,4 @@ "FailureResponse", "SuccessResponse", "SqsFifoPartialProcessor", - "batch_processor", - "async_batch_processor", ) diff --git a/aws_lambda_powertools/utilities/batch/base.py b/aws_lambda_powertools/utilities/batch/base.py index 4cdea6d28f5..210caf2bb14 100644 --- a/aws_lambda_powertools/utilities/batch/base.py +++ b/aws_lambda_powertools/utilities/batch/base.py @@ -11,19 +11,8 @@ import sys from abc import ABC, abstractmethod from enum import Enum -from typing import ( - Any, - Awaitable, - Callable, - Dict, - List, - Optional, - Tuple, - Union, - overload, -) +from typing import Any, Callable, Dict, List, Optional, Tuple, Union, overload -from aws_lambda_powertools.middleware_factory import lambda_handler_decorator from aws_lambda_powertools.shared import constants from aws_lambda_powertools.utilities.batch.exceptions import ( BatchProcessingError, @@ -513,51 +502,6 @@ def _process_record(self, record: dict) -> Union[SuccessResponse, FailureRespons return self.failure_handler(record=data, exception=sys.exc_info()) -@lambda_handler_decorator -def batch_processor( - handler: Callable, event: Dict, context: LambdaContext, record_handler: Callable, processor: BatchProcessor -): - """ - Middleware to handle batch event processing - - Parameters - ---------- - handler: Callable - Lambda's handler - event: Dict - Lambda's Event - context: LambdaContext - Lambda's Context - record_handler: Callable - Callable or corutine to process each record from the batch - processor: BatchProcessor - Batch Processor to handle partial failure cases - - Examples - -------- - **Processes Lambda's event with a BasePartialProcessor** - - >>> from aws_lambda_powertools.utilities.batch import batch_processor, BatchProcessor - >>> - >>> def record_handler(record): - >>> return record["body"] - >>> - >>> @batch_processor(record_handler=record_handler, processor=BatchProcessor()) - >>> def handler(event, context): - >>> return {"StatusCode": 200} - - Limitations - ----------- - * Async batch processors. Use `async_batch_processor` instead. - """ - records = event["Records"] - - with processor(records, record_handler, lambda_context=context): - processor.process() - - return handler(event, context) - - class AsyncBatchProcessor(BasePartialBatchProcessor): """Process native partial responses from SQS, Kinesis Data Streams, and DynamoDB asynchronously. @@ -694,52 +638,3 @@ async def _async_process_record(self, record: dict) -> Union[SuccessResponse, Fa return self._register_model_validation_error_record(record) except Exception: return self.failure_handler(record=data, exception=sys.exc_info()) - - -@lambda_handler_decorator -def async_batch_processor( - handler: Callable, - event: Dict, - context: LambdaContext, - record_handler: Callable[..., Awaitable[Any]], - processor: AsyncBatchProcessor, -): - """ - Middleware to handle batch event processing - Parameters - ---------- - handler: Callable - Lambda's handler - event: Dict - Lambda's Event - context: LambdaContext - Lambda's Context - record_handler: Callable[..., Awaitable[Any]] - Callable to process each record from the batch - processor: AsyncBatchProcessor - Batch Processor to handle partial failure cases - Examples - -------- - **Processes Lambda's event with a BasePartialProcessor** - >>> from aws_lambda_powertools.utilities.batch import async_batch_processor, AsyncBatchProcessor - >>> - >>> async def async_record_handler(record): - >>> payload: str = record.body - >>> return payload - >>> - >>> processor = AsyncBatchProcessor(event_type=EventType.SQS) - >>> - >>> @async_batch_processor(record_handler=async_record_handler, processor=processor) - >>> async def lambda_handler(event, context: LambdaContext): - >>> return processor.response() - - Limitations - ----------- - * Sync batch processors. Use `batch_processor` instead. - """ - records = event["Records"] - - with processor(records, record_handler, lambda_context=context): - processor.async_process() - - return handler(event, context) diff --git a/aws_lambda_powertools/utilities/batch/decorators.py b/aws_lambda_powertools/utilities/batch/decorators.py new file mode 100644 index 00000000000..8f594c1479e --- /dev/null +++ b/aws_lambda_powertools/utilities/batch/decorators.py @@ -0,0 +1,249 @@ +from __future__ import annotations + +from typing import Any, Awaitable, Callable, Dict, List + +from aws_lambda_powertools.middleware_factory import lambda_handler_decorator +from aws_lambda_powertools.utilities.batch import ( + AsyncBatchProcessor, + BasePartialBatchProcessor, + BatchProcessor, + EventType, +) +from aws_lambda_powertools.utilities.batch.types import PartialItemFailureResponse +from aws_lambda_powertools.utilities.typing import LambdaContext + + +@lambda_handler_decorator +def async_batch_processor( + handler: Callable, + event: Dict, + context: LambdaContext, + record_handler: Callable[..., Awaitable[Any]], + processor: AsyncBatchProcessor, +): + """ + Middleware to handle batch event processing + + Notes + ----- + Consider using async_process_partial_response function for an easier experience. + + Parameters + ---------- + handler: Callable + Lambda's handler + event: Dict + Lambda's Event + context: LambdaContext + Lambda's Context + record_handler: Callable[..., Awaitable[Any]] + Callable to process each record from the batch + processor: AsyncBatchProcessor + Batch Processor to handle partial failure cases + + Examples + -------- + **Processes Lambda's event with a BasePartialProcessor** + >>> from aws_lambda_powertools.utilities.batch import async_batch_processor, AsyncBatchProcessor + >>> from aws_lambda_powertools.utilities.data_classes.sqs_event import SQSRecord + >>> + >>> processor = AsyncBatchProcessor(event_type=EventType.SQS) + >>> + >>> async def async_record_handler(record: SQSRecord): + >>> payload: str = record.body + >>> return payload + >>> + >>> @async_batch_processor(record_handler=async_record_handler, processor=processor) + >>> def lambda_handler(event, context): + >>> return processor.response() + + Limitations + ----------- + * Sync batch processors. Use `batch_processor` instead. + """ + records = event["Records"] + + with processor(records, record_handler, lambda_context=context): + processor.async_process() + + return handler(event, context) + + +@lambda_handler_decorator +def batch_processor( + handler: Callable, event: Dict, context: LambdaContext, record_handler: Callable, processor: BatchProcessor +): + """ + Middleware to handle batch event processing + + Notes + ----- + Consider using process_partial_response function for an easier experience. + + Parameters + ---------- + handler: Callable + Lambda's handler + event: Dict + Lambda's Event + context: LambdaContext + Lambda's Context + record_handler: Callable + Callable or corutine to process each record from the batch + processor: BatchProcessor + Batch Processor to handle partial failure cases + + Examples + -------- + **Processes Lambda's event with a BatchProcessor** + + >>> from aws_lambda_powertools.utilities.batch import batch_processor, BatchProcessor, EventType + >>> from aws_lambda_powertools.utilities.data_classes.sqs_event import SQSRecord + >>> + >>> processor = BatchProcessor(EventType.SQS) + >>> + >>> def record_handler(record): + >>> return record["body"] + >>> + >>> @batch_processor(record_handler=record_handler, processor=BatchProcessor()) + >>> def handler(event, context): + >>> return processor.response() + + Limitations + ----------- + * Async batch processors. Use `async_batch_processor` instead. + """ + records = event["Records"] + + with processor(records, record_handler, lambda_context=context): + processor.process() + + return handler(event, context) + + +def process_partial_response( + event: Dict, + record_handler: Callable, + processor: BasePartialBatchProcessor, + context: LambdaContext | None = None, +) -> PartialItemFailureResponse: + """ + Higher level function to handle batch event processing. + + Parameters + ---------- + event: Dict + Lambda's original event + record_handler: Callable + Callable to process each record from the batch + processor: BasePartialBatchProcessor + Batch Processor to handle partial failure cases + context: LambdaContext + Lambda's context, used to optionally inject in record handler + + Returns + ------- + result: PartialItemFailureResponse + Lambda Partial Batch Response + + Examples + -------- + **Processes Lambda's SQS event** + + ```python + from aws_lambda_powertools.utilities.batch import BatchProcessor, EventType, process_partial_response + from aws_lambda_powertools.utilities.data_classes.sqs_event import SQSRecord + + processor = BatchProcessor(EventType.SQS) + + def record_handler(record: SQSRecord): + return record.body + + def handler(event, context): + return process_partial_response( + event=event, record_handler=record_handler, processor=processor, context=context + ) + ``` + + Limitations + ----------- + * Async batch processors. Use `async_process_partial_response` instead. + """ + try: + records: List[Dict] = event.get("Records", []) + except AttributeError: + event_types = ", ".join(list(EventType.__members__)) + docs = "https://awslabs.github.io/aws-lambda-powertools-python/latest/utilities/batch/#processing-messages-from-sqs" # noqa: E501 # long-line + raise ValueError( + f"Invalid event format. Please ensure batch event is a valid {processor.event_type.value} event. \n" + f"See sample events in our documentation for either {event_types}: \n {docs}" + ) + + with processor(records, record_handler, context): + processor.process() + + return processor.response() + + +def async_process_partial_response( + event: Dict, + record_handler: Callable, + processor: AsyncBatchProcessor, + context: LambdaContext | None = None, +) -> PartialItemFailureResponse: + """ + Higher level function to handle batch event processing asynchronously. + + Parameters + ---------- + event: Dict + Lambda's original event + record_handler: Callable + Callable to process each record from the batch + processor: AsyncBatchProcessor + Batch Processor to handle partial failure cases + context: LambdaContext + Lambda's context, used to optionally inject in record handler + + Returns + ------- + result: PartialItemFailureResponse + Lambda Partial Batch Response + + Examples + -------- + **Processes Lambda's SQS event** + + ```python + from aws_lambda_powertools.utilities.batch import AsyncBatchProcessor, EventType, process_partial_response + from aws_lambda_powertools.utilities.data_classes.sqs_event import SQSRecord + + processor = BatchProcessor(EventType.SQS) + + async def record_handler(record: SQSRecord): + return record.body + + def handler(event, context): + return async_process_partial_response( + event=event, record_handler=record_handler, processor=processor, context=context + ) + ``` + + Limitations + ----------- + * Sync batch processors. Use `process_partial_response` instead. + """ + try: + records: List[Dict] = event.get("Records", []) + except AttributeError: + event_types = ", ".join(list(EventType.__members__)) + docs = "https://awslabs.github.io/aws-lambda-powertools-python/latest/utilities/batch/#processing-messages-from-sqs" # noqa: E501 # long-line + raise ValueError( + f"Invalid event format. Please ensure batch event is a valid {processor.event_type.value} event. \n" + f"See sample events in our documentation for either {event_types}: \n {docs}" + ) + + with processor(records, record_handler, context): + processor.async_process() + + return processor.response() diff --git a/aws_lambda_powertools/utilities/batch/types.py b/aws_lambda_powertools/utilities/batch/types.py index 1fc5aba4fc4..89a5e4e3486 100644 --- a/aws_lambda_powertools/utilities/batch/types.py +++ b/aws_lambda_powertools/utilities/batch/types.py @@ -2,7 +2,9 @@ # type specifics # import sys -from typing import Optional, Type, Union +from typing import List, Optional, Type, Union + +from typing_extensions import TypedDict has_pydantic = "pydantic" in sys.modules @@ -22,3 +24,11 @@ else: BatchTypeModels = "BatchTypeModels" # type: ignore BatchSqsTypeModel = "BatchSqsTypeModel" # type: ignore + + +class PartialItemFailures(TypedDict): + itemIdentifier: str + + +class PartialItemFailureResponse(TypedDict): + batchItemFailures: List[PartialItemFailures] diff --git a/docs/utilities/batch.md b/docs/utilities/batch.md index 0f899673c2e..47cc2147978 100644 --- a/docs/utilities/batch.md +++ b/docs/utilities/batch.md @@ -219,23 +219,28 @@ The remaining sections of the documentation will rely on these samples. For comp ### Processing messages from SQS -Processing batches from SQS works in four stages: +Processing batches from SQS works in three stages: 1. Instantiate **`BatchProcessor`** and choose **`EventType.SQS`** for the event type 2. Define your function to handle each batch record, and use [`SQSRecord`](data_classes.md#sqs){target="_blank"} type annotation for autocompletion -3. Use either **`batch_processor`** decorator or your instantiated processor as a context manager to kick off processing -4. Return the appropriate response contract to Lambda via **`.response()`** processor method +3. Use **`process_partial_response`** to kick off processing ???+ info This code example optionally uses Tracer and Logger for completion. -=== "As a decorator" +=== "Recommended" - ```python hl_lines="4-5 9 15 23 25" + ```python hl_lines="4 9 12 18 29" + --8<-- "examples/batch_processing/src/getting_started_sqs.py" + ``` + +=== "As a context manager" + + ```python hl_lines="4-5 9 15 24-26 28" import json from aws_lambda_powertools import Logger, Tracer - from aws_lambda_powertools.utilities.batch import BatchProcessor, EventType, batch_processor + from aws_lambda_powertools.utilities.batch import BatchProcessor, EventType from aws_lambda_powertools.utilities.data_classes.sqs_event import SQSRecord from aws_lambda_powertools.utilities.typing import LambdaContext @@ -254,14 +259,17 @@ Processing batches from SQS works in four stages: @logger.inject_lambda_context @tracer.capture_lambda_handler - @batch_processor(record_handler=record_handler, processor=processor) def lambda_handler(event, context: LambdaContext): + batch = event["Records"] + with processor(records=batch, handler=record_handler): + processed_messages = processor.process() # kick off processing, return list[tuple] + return processor.response() ``` -=== "As a context manager" +=== "As a decorator (legacy)" - ```python hl_lines="4-5 9 15 24-26 28" + ```python hl_lines="4-5 9 15 23 25" import json from aws_lambda_powertools import Logger, Tracer @@ -284,11 +292,8 @@ Processing batches from SQS works in four stages: @logger.inject_lambda_context @tracer.capture_lambda_handler + @batch_processor(record_handler=record_handler, processor=processor) def lambda_handler(event, context: LambdaContext): - batch = event["Records"] - with processor(records=batch, handler=record_handler): - processed_messages = processor.process() # kick off processing, return list[tuple] - return processor.response() ``` @@ -352,37 +357,48 @@ Processing batches from SQS works in four stages: When using [SQS FIFO queues](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/FIFO-queues.html){target="_blank"}, we will stop processing messages after the first failure, and return all failed and unprocessed messages in `batchItemFailures`. This helps preserve the ordering of messages in your queue. -=== "As a decorator" +=== "Recommended" - ```python hl_lines="5 11" - --8<-- "examples/batch_processing/src/sqs_fifo_batch_processor.py" + ```python hl_lines="3 9" + --8<-- "examples/batch_processing/src/getting_started_sqs_fifo.py" ``` === "As a context manager" - ```python hl_lines="4 8" - --8<-- "examples/batch_processing/src/sqs_fifo_batch_processor_context_manager.py" + ```python hl_lines="2 6" + --8<-- "examples/batch_processing/src/getting_started_sqs_fifo_context_manager.py" + ``` + +=== "As a decorator (legacy)" + + ```python hl_lines="3 9" + --8<-- "examples/batch_processing/src/getting_started_sqs_fifo_decorator.py" ``` ### Processing messages from Kinesis -Processing batches from Kinesis works in four stages: +Processing batches from Kinesis works in three stages: 1. Instantiate **`BatchProcessor`** and choose **`EventType.KinesisDataStreams`** for the event type 2. Define your function to handle each batch record, and use [`KinesisStreamRecord`](data_classes.md#kinesis-streams){target="_blank"} type annotation for autocompletion -3. Use either **`batch_processor`** decorator or your instantiated processor as a context manager to kick off processing -4. Return the appropriate response contract to Lambda via **`.response()`** processor method +3. Use **`process_partial_response`** to kick off processing ???+ info This code example optionally uses Tracer and Logger for completion. -=== "As a decorator" +=== "Recommended" - ```python hl_lines="4-5 9 15 22 24" + ```python hl_lines="2 7 12 18 28" + --8<-- "examples/batch_processing/src/getting_started_kinesis.py" + ``` + +=== "As a context manager" + + ```python hl_lines="4-5 9 15 23-25 27" import json from aws_lambda_powertools import Logger, Tracer - from aws_lambda_powertools.utilities.batch import BatchProcessor, EventType, batch_processor + from aws_lambda_powertools.utilities.batch import BatchProcessor, EventType from aws_lambda_powertools.utilities.data_classes.kinesis_stream_event import KinesisStreamRecord from aws_lambda_powertools.utilities.typing import LambdaContext @@ -400,16 +416,17 @@ Processing batches from Kinesis works in four stages: @logger.inject_lambda_context @tracer.capture_lambda_handler - @batch_processor(record_handler=record_handler, processor=processor) def lambda_handler(event, context: LambdaContext): + batch = event["Records"] + with processor(records=batch, handler=record_handler): + processed_messages = processor.process() # kick off processing, return list[tuple] + return processor.response() ``` -=== "As a context manager" - - ```python hl_lines="4-5 9 15 23-25 27" - import json +=== "As a decorator (legacy)" + ```python hl_lines="2-3 7 20 22" from aws_lambda_powertools import Logger, Tracer from aws_lambda_powertools.utilities.batch import BatchProcessor, EventType, batch_processor from aws_lambda_powertools.utilities.data_classes.kinesis_stream_event import KinesisStreamRecord @@ -429,11 +446,8 @@ Processing batches from Kinesis works in four stages: @logger.inject_lambda_context @tracer.capture_lambda_handler + @batch_processor(record_handler=record_handler, processor=processor) def lambda_handler(event, context: LambdaContext): - batch = event["Records"] - with processor(records=batch, handler=record_handler): - processed_messages = processor.process() # kick off processing, return list[tuple] - return processor.response() ``` @@ -494,23 +508,28 @@ Processing batches from Kinesis works in four stages: ### Processing messages from DynamoDB -Processing batches from Kinesis works in four stages: +Processing batches from Kinesis works in three stages: 1. Instantiate **`BatchProcessor`** and choose **`EventType.DynamoDBStreams`** for the event type 2. Define your function to handle each batch record, and use [`DynamoDBRecord`](data_classes.md#dynamodb-streams){target="_blank"} type annotation for autocompletion -3. Use either **`batch_processor`** decorator or your instantiated processor as a context manager to kick off processing -4. Return the appropriate response contract to Lambda via **`.response()`** processor method +3. Use **`process_partial_response`** to kick off processing ???+ info This code example optionally uses Tracer and Logger for completion. -=== "As a decorator" +=== "Recommended" - ```python hl_lines="4-5 9 15 25 27" + ```python hl_lines="4 9 14 20 30" + --8<-- "examples/batch_processing/src/getting_started_dynamodb.py" + ``` + +=== "As a context manager" + + ```python hl_lines="4-5 9 15 23-27" import json from aws_lambda_powertools import Logger, Tracer - from aws_lambda_powertools.utilities.batch import BatchProcessor, EventType, batch_processor + from aws_lambda_powertools.utilities.batch import BatchProcessor, EventType from aws_lambda_powertools.utilities.data_classes.dynamo_db_stream_event import DynamoDBRecord from aws_lambda_powertools.utilities.typing import LambdaContext @@ -524,21 +543,21 @@ Processing batches from Kinesis works in four stages: def record_handler(record: DynamoDBRecord): logger.info(record.dynamodb.new_image) payload: dict = json.loads(record.dynamodb.new_image.get("Message")) - # alternatively: - # changes: Dict[str, Any] = record.dynamodb.new_image - # payload = change.get("Message").raw_event -> {"S": ""} ... @logger.inject_lambda_context @tracer.capture_lambda_handler - @batch_processor(record_handler=record_handler, processor=processor) def lambda_handler(event, context: LambdaContext): + batch = event["Records"] + with processor(records=batch, handler=record_handler): + processed_messages = processor.process() # kick off processing, return list[tuple] + return processor.response() ``` -=== "As a context manager" +=== "As a decorator (legacy)" - ```python hl_lines="4-5 9 15 26-28 30" + ```python hl_lines="4-5 9 15 22 24" import json from aws_lambda_powertools import Logger, Tracer @@ -555,19 +574,13 @@ Processing batches from Kinesis works in four stages: @tracer.capture_method def record_handler(record: DynamoDBRecord): logger.info(record.dynamodb.new_image) - payload: dict = json.loads(record.dynamodb.new_image.get("item")) - # alternatively: - # changes: Dict[str, Any] = record.dynamodb.new_image - # payload = change.get("Message") -> "" + payload: dict = json.loads(record.dynamodb.new_image.get("Message")) ... @logger.inject_lambda_context @tracer.capture_lambda_handler + @batch_processor(record_handler=record_handler, processor=processor) def lambda_handler(event, context: LambdaContext): - batch = event["Records"] - with processor(records=batch, handler=record_handler): - processed_messages = processor.process() # kick off processing, return list[tuple] - return processor.response() ``` @@ -648,16 +661,11 @@ All records in the batch will be passed to this handler for processing, even if * **Partial success with some exceptions**. We will return a list of all item IDs/sequence numbers that failed processing * **All records failed to be processed**. We will raise `BatchProcessingError` exception with a list of all exceptions raised when processing -???+ warning - You will not have access to the **processed messages** within the Lambda Handler; use context manager for that. - - All processing logic will and should be performed by the `record_handler` function. - ### Processing messages asynchronously !!! tip "New to AsyncIO? Read this [comprehensive guide first](https://realpython.com/async-io-python/){target="_blank"}." -You can use `AsyncBatchProcessor` class and `async_batch_processor` decorator to process messages concurrently. +You can use `AsyncBatchProcessor` class and `async_process_partial_response` function to process messages concurrently. ???+ question "When is this useful?" Your use case might be able to process multiple records at the same time without conflicting with one another. @@ -666,8 +674,8 @@ You can use `AsyncBatchProcessor` class and `async_batch_processor` decorator to The reason this is not the default behaviour is that not all use cases can handle concurrency safely (e.g., loyalty points must be updated in order). -```python hl_lines="4 6 11 14 23" title="High-concurrency with AsyncBatchProcessor" ---8<-- "examples/batch_processing/src/getting_started_async_batch_processor.py" +```python hl_lines="3 11 14 24" title="High-concurrency with AsyncBatchProcessor" +--8<-- "examples/batch_processing/src/getting_started_async.py" ``` ???+ warning "Using tracer?" @@ -685,13 +693,14 @@ Inheritance is importance because we need to access message IDs and sequence num === "SQS" - ```python hl_lines="5 9-10 12-19 21 27" + ```python hl_lines="5 13 22 28" import json from aws_lambda_powertools import Logger, Tracer - from aws_lambda_powertools.utilities.batch import BatchProcessor, EventType, batch_processor + from aws_lambda_powertools.utilities.batch import BatchProcessor, EventType, process_partial_response from aws_lambda_powertools.utilities.parser.models import SqsRecordModel from aws_lambda_powertools.utilities.typing import LambdaContext + from aws_lambda_powertools.utilities.parser import validator, BaseModel class Order(BaseModel): @@ -717,25 +726,26 @@ Inheritance is importance because we need to access message IDs and sequence num @logger.inject_lambda_context @tracer.capture_lambda_handler - @batch_processor(record_handler=record_handler, processor=processor) def lambda_handler(event, context: LambdaContext): - return processor.response() + return process_partial_response(event=event, record_handler=record_handler, processor=processor, context=context) ``` === "Kinesis Data Streams" - ```python hl_lines="5 9-10 12-20 22-23 26 32" + ```python hl_lines="5 14 25 29 35" import json from aws_lambda_powertools import Logger, Tracer - from aws_lambda_powertools.utilities.batch import BatchProcessor, EventType, batch_processor - from aws_lambda_powertools.utilities.parser.models import KinesisDataStreamRecord + from aws_lambda_powertools.utilities.batch import BatchProcessor, EventType, process_partial_response + from aws_lambda_powertools.utilities.parser.models import KinesisDataStreamRecordPayload, KinesisDataStreamRecord + from aws_lambda_powertools.utilities.parser import BaseModel, validator from aws_lambda_powertools.utilities.typing import LambdaContext class Order(BaseModel): item: dict + class OrderKinesisPayloadRecord(KinesisDataStreamRecordPayload): data: Order @@ -743,10 +753,11 @@ Inheritance is importance because we need to access message IDs and sequence num # so Pydantic can auto-initialize nested Order model @validator("data", pre=True) def transform_message_to_dict(cls, value: str): - # Powertools KinesisDataStreamRecordModel already decodes b64 to str here + # Powertools KinesisDataStreamRecord already decodes b64 to str here return json.loads(value) - class OrderKinesisRecord(KinesisDataStreamRecordModel): + + class OrderKinesisRecord(KinesisDataStreamRecord): kinesis: OrderKinesisPayloadRecord @@ -762,27 +773,28 @@ Inheritance is importance because we need to access message IDs and sequence num @logger.inject_lambda_context @tracer.capture_lambda_handler - @batch_processor(record_handler=record_handler, processor=processor) def lambda_handler(event, context: LambdaContext): - return processor.response() + return process_partial_response(event=event, record_handler=record_handler, processor=processor, context=context) ``` === "DynamoDB Streams" - ```python hl_lines="7 11-12 14-21 23-25 27-28 31 37" + ```python hl_lines="7 16 26 31 35 41" import json - from typing import Dict, Literal + from typing import Dict, Literal, Optional from aws_lambda_powertools import Logger, Tracer - from aws_lambda_powertools.utilities.batch import BatchProcessor, EventType, batch_processor - from aws_lambda_powertools.utilities.parser.models import DynamoDBStreamRecordModel + from aws_lambda_powertools.utilities.batch import BatchProcessor, EventType, process_partial_response + from aws_lambda_powertools.utilities.parser.models import DynamoDBStreamChangedRecordModel, DynamoDBStreamRecordModel from aws_lambda_powertools.utilities.typing import LambdaContext + from aws_lambda_powertools.utilities.parser import BaseModel, validator class Order(BaseModel): item: dict + class OrderDynamoDB(BaseModel): Message: Order @@ -792,15 +804,17 @@ Inheritance is importance because we need to access message IDs and sequence num def transform_message_to_dict(cls, value: Dict[Literal["S"], str]): return json.loads(value["S"]) + class OrderDynamoDBChangeRecord(DynamoDBStreamChangedRecordModel): NewImage: Optional[OrderDynamoDB] OldImage: Optional[OrderDynamoDB] + class OrderDynamoDBRecord(DynamoDBStreamRecordModel): dynamodb: OrderDynamoDBChangeRecord - processor = BatchProcessor(event_type=EventType.DynamoDBStreams, model=OrderKinesisRecord) + processor = BatchProcessor(event_type=EventType.DynamoDBStreams, model=OrderDynamoDBRecord) tracer = Tracer() logger = Logger() @@ -812,9 +826,8 @@ Inheritance is importance because we need to access message IDs and sequence num @logger.inject_lambda_context @tracer.capture_lambda_handler - @batch_processor(record_handler=record_handler, processor=processor) def lambda_handler(event, context: LambdaContext): - return processor.response() + return process_partial_response(event=event, record_handler=record_handler, processor=processor, context=context) ``` ### Accessing processed messages @@ -824,7 +837,7 @@ Use the context manager to access a list of all returned values from your `recor * **When successful**. We will include a tuple with `success`, the result of `record_handler`, and the batch record * **When failed**. We will include a tuple with `fail`, exception as a string, and the batch record -```python hl_lines="31-38" title="Accessing processed messages via context manager" +```python hl_lines="30-36" title="Accessing processed messages via context manager" import json from typing import Any, List, Literal, Union @@ -833,8 +846,7 @@ from aws_lambda_powertools import Logger, Tracer from aws_lambda_powertools.utilities.batch import (BatchProcessor, EventType, FailureResponse, - SuccessResponse, - batch_processor) + SuccessResponse) from aws_lambda_powertools.utilities.data_classes.sqs_event import SQSRecord from aws_lambda_powertools.utilities.typing import LambdaContext @@ -873,7 +885,13 @@ Within your `record_handler` function, you might need access to the Lambda conte We can automatically inject the [Lambda context](https://docs.aws.amazon.com/lambda/latest/dg/python-context.html){target="_blank"} into your `record_handler` if your function signature has a parameter named `lambda_context`. When using a context manager, you also need to pass the Lambda context object like in the example below. -=== "As a decorator" +=== "Recommended" + + ```python hl_lines="19" + --8<-- "examples/batch_processing/src/advanced_accessing_lambda_context.py" + ``` + +=== "As a decorator (legacy)" ```python hl_lines="15" from typing import Optional @@ -952,7 +970,7 @@ from typing import Tuple from aws_lambda_powertools import Metrics from aws_lambda_powertools.metrics import MetricUnit -from aws_lambda_powertools.utilities.batch import batch_processor, BatchProcessor, ExceptionInfo, EventType, FailureResponse +from aws_lambda_powertools.utilities.batch import BatchProcessor, ExceptionInfo, EventType, FailureResponse, process_partial_response from aws_lambda_powertools.utilities.data_classes.sqs_event import SQSRecord @@ -973,9 +991,8 @@ def record_handler(record: SQSRecord): ... @metrics.log_metrics(capture_cold_start_metric=True) -@batch_processor(record_handler=record_handler, processor=processor) def lambda_handler(event, context: LambdaContext): - return processor.response() + return process_partial_response(event=event, record_handler=record_handler, processor=processor, context=context) ``` ### Create your own partial processor @@ -1157,7 +1174,7 @@ Given a SQS batch where the first batch record succeeds and the second fails pro import json from aws_lambda_powertools import Logger, Tracer - from aws_lambda_powertools.utilities.batch import BatchProcessor, EventType, batch_processor + from aws_lambda_powertools.utilities.batch import BatchProcessor, EventType, process_partial_response from aws_lambda_powertools.utilities.data_classes.sqs_event import SQSRecord from aws_lambda_powertools.utilities.typing import LambdaContext @@ -1176,9 +1193,8 @@ Given a SQS batch where the first batch record succeeds and the second fails pro @logger.inject_lambda_context @tracer.capture_lambda_handler - @batch_processor(record_handler=record_handler, processor=processor) def lambda_handler(event, context: LambdaContext): - return processor.response() + return process_partial_response(event=event, record_handler=record_handler, processor=processor, context=context) ``` === "Sample SQS event" @@ -1228,6 +1244,12 @@ Given a SQS batch where the first batch record succeeds and the second fails pro Use context manager when you want access to the processed messages or handle `BatchProcessingError` exception when all records within the batch fail to be processed. +### What's the difference between the decorator and process_partial_response functions? + +`batch_processor` and `async_batch_processor` decorators are now considered legacy. Historically, they were kept due to backwards compatibility and to minimize code changes between V1 and V2. + +As 2.12.0, `process_partial_response` and `async_process_partial_response` are the recommended instead. It reduces boilerplate, smaller memory/CPU cycles, and it makes it less error prone - e.g., decorators required an additional return. + ### Integrating exception handling with Sentry.io When using Sentry.io for error monitoring, you can override `failure_handler` to capture each processing exception with Sentry SDK: diff --git a/examples/batch_processing/src/advanced_accessing_lambda_context.py b/examples/batch_processing/src/advanced_accessing_lambda_context.py new file mode 100644 index 00000000000..96d95ca5445 --- /dev/null +++ b/examples/batch_processing/src/advanced_accessing_lambda_context.py @@ -0,0 +1,30 @@ +import json +from typing import Optional + +from aws_lambda_powertools import Logger, Tracer +from aws_lambda_powertools.utilities.batch import ( + BatchProcessor, + EventType, + process_partial_response, +) +from aws_lambda_powertools.utilities.data_classes.sqs_event import SQSRecord +from aws_lambda_powertools.utilities.typing import LambdaContext + +processor = BatchProcessor(event_type=EventType.SQS) +tracer = Tracer() +logger = Logger() + + +@tracer.capture_method +def record_handler(record: SQSRecord, lambda_context: Optional[LambdaContext] = None): + payload: str = record.body + if payload: + item: dict = json.loads(payload) + logger.info(item) + ... + + +@logger.inject_lambda_context +@tracer.capture_lambda_handler +def lambda_handler(event, context: LambdaContext): + return process_partial_response(event=event, record_handler=record_handler, processor=processor, context=context) diff --git a/examples/batch_processing/src/getting_started_async_batch_processor.py b/examples/batch_processing/src/getting_started_async.py similarity index 79% rename from examples/batch_processing/src/getting_started_async_batch_processor.py rename to examples/batch_processing/src/getting_started_async.py index 594be0540f3..304c01795bb 100644 --- a/examples/batch_processing/src/getting_started_async_batch_processor.py +++ b/examples/batch_processing/src/getting_started_async.py @@ -3,7 +3,7 @@ from aws_lambda_powertools.utilities.batch import ( AsyncBatchProcessor, EventType, - async_batch_processor, + async_process_partial_response, ) from aws_lambda_powertools.utilities.data_classes.sqs_event import SQSRecord from aws_lambda_powertools.utilities.typing import LambdaContext @@ -20,6 +20,7 @@ async def async_record_handler(record: SQSRecord): return ret.status_code -@async_batch_processor(record_handler=async_record_handler, processor=processor) def lambda_handler(event, context: LambdaContext): - return processor.response() + return async_process_partial_response( + event=event, record_handler=async_record_handler, processor=processor, context=context + ) diff --git a/examples/batch_processing/src/getting_started_dynamodb.py b/examples/batch_processing/src/getting_started_dynamodb.py new file mode 100644 index 00000000000..60d8ed89f0e --- /dev/null +++ b/examples/batch_processing/src/getting_started_dynamodb.py @@ -0,0 +1,30 @@ +import json + +from aws_lambda_powertools import Logger, Tracer +from aws_lambda_powertools.utilities.batch import ( + BatchProcessor, + EventType, + process_partial_response, +) +from aws_lambda_powertools.utilities.data_classes.dynamo_db_stream_event import ( + DynamoDBRecord, +) +from aws_lambda_powertools.utilities.typing import LambdaContext + +processor = BatchProcessor(event_type=EventType.DynamoDBStreams) +tracer = Tracer() +logger = Logger() + + +@tracer.capture_method +def record_handler(record: DynamoDBRecord): + logger.info(record.dynamodb.new_image) # type: ignore[union-attr] + payload: dict = json.loads(record.dynamodb.new_image.get("Message")) # type: ignore[union-attr,arg-type] + logger.info(payload) + ... + + +@logger.inject_lambda_context +@tracer.capture_lambda_handler +def lambda_handler(event, context: LambdaContext): + return process_partial_response(event=event, record_handler=record_handler, processor=processor, context=context) diff --git a/examples/batch_processing/src/getting_started_kinesis.py b/examples/batch_processing/src/getting_started_kinesis.py new file mode 100644 index 00000000000..e58222733e1 --- /dev/null +++ b/examples/batch_processing/src/getting_started_kinesis.py @@ -0,0 +1,28 @@ +from aws_lambda_powertools import Logger, Tracer +from aws_lambda_powertools.utilities.batch import ( + BatchProcessor, + EventType, + process_partial_response, +) +from aws_lambda_powertools.utilities.data_classes.kinesis_stream_event import ( + KinesisStreamRecord, +) +from aws_lambda_powertools.utilities.typing import LambdaContext + +processor = BatchProcessor(event_type=EventType.KinesisDataStreams) +tracer = Tracer() +logger = Logger() + + +@tracer.capture_method +def record_handler(record: KinesisStreamRecord): + logger.info(record.kinesis.data_as_text) + payload: dict = record.kinesis.data_as_json() + logger.info(payload) + ... + + +@logger.inject_lambda_context +@tracer.capture_lambda_handler +def lambda_handler(event, context: LambdaContext): + return process_partial_response(event=event, record_handler=record_handler, processor=processor, context=context) diff --git a/examples/batch_processing/src/getting_started_sqs.py b/examples/batch_processing/src/getting_started_sqs.py new file mode 100644 index 00000000000..15f8701f297 --- /dev/null +++ b/examples/batch_processing/src/getting_started_sqs.py @@ -0,0 +1,29 @@ +import json + +from aws_lambda_powertools import Logger, Tracer +from aws_lambda_powertools.utilities.batch import ( + BatchProcessor, + EventType, + process_partial_response, +) +from aws_lambda_powertools.utilities.data_classes.sqs_event import SQSRecord +from aws_lambda_powertools.utilities.typing import LambdaContext + +processor = BatchProcessor(event_type=EventType.SQS) +tracer = Tracer() +logger = Logger() + + +@tracer.capture_method +def record_handler(record: SQSRecord): + payload: str = record.body + if payload: + item: dict = json.loads(payload) + logger.info(item) + ... + + +@logger.inject_lambda_context +@tracer.capture_lambda_handler +def lambda_handler(event, context: LambdaContext): + return process_partial_response(event=event, record_handler=record_handler, processor=processor, context=context) diff --git a/examples/batch_processing/src/getting_started_sqs_fifo.py b/examples/batch_processing/src/getting_started_sqs_fifo.py new file mode 100644 index 00000000000..d39f8ba63f1 --- /dev/null +++ b/examples/batch_processing/src/getting_started_sqs_fifo.py @@ -0,0 +1,22 @@ +from aws_lambda_powertools import Logger, Tracer +from aws_lambda_powertools.utilities.batch import ( + SqsFifoPartialProcessor, + process_partial_response, +) +from aws_lambda_powertools.utilities.data_classes.sqs_event import SQSRecord +from aws_lambda_powertools.utilities.typing import LambdaContext + +processor = SqsFifoPartialProcessor() +tracer = Tracer() +logger = Logger() + + +@tracer.capture_method +def record_handler(record: SQSRecord): + ... + + +@logger.inject_lambda_context +@tracer.capture_lambda_handler +def lambda_handler(event, context: LambdaContext): + return process_partial_response(event=event, record_handler=record_handler, processor=processor, context=context) diff --git a/examples/batch_processing/src/sqs_fifo_batch_processor_context_manager.py b/examples/batch_processing/src/getting_started_sqs_fifo_context_manager.py similarity index 100% rename from examples/batch_processing/src/sqs_fifo_batch_processor_context_manager.py rename to examples/batch_processing/src/getting_started_sqs_fifo_context_manager.py diff --git a/examples/batch_processing/src/sqs_fifo_batch_processor.py b/examples/batch_processing/src/getting_started_sqs_fifo_decorator.py similarity index 100% rename from examples/batch_processing/src/sqs_fifo_batch_processor.py rename to examples/batch_processing/src/getting_started_sqs_fifo_decorator.py diff --git a/tests/functional/test_utilities_batch.py b/tests/functional/test_utilities_batch.py index 2205d47660c..4ad33b290bd 100644 --- a/tests/functional/test_utilities_batch.py +++ b/tests/functional/test_utilities_batch.py @@ -12,7 +12,9 @@ EventType, SqsFifoPartialProcessor, async_batch_processor, + async_process_partial_response, batch_processor, + process_partial_response, ) from aws_lambda_powertools.utilities.batch.exceptions import BatchProcessingError from aws_lambda_powertools.utilities.data_classes.dynamo_db_stream_event import ( @@ -775,6 +777,70 @@ def test_async_batch_processor_context_with_failure(sqs_event_factory, async_rec } +def test_process_partial_response(sqs_event_factory, record_handler): + # GIVEN + records = [sqs_event_factory("success"), sqs_event_factory("success")] + batch = {"Records": records} + processor = BatchProcessor(event_type=EventType.SQS) + + # WHEN + ret = process_partial_response(batch, record_handler, processor) + + # THEN + assert ret == {"batchItemFailures": []} + + +@pytest.mark.parametrize( + "batch", + [ + pytest.param(123456789, id="num"), + pytest.param([], id="list"), + pytest.param(False, id="bool"), + pytest.param(object, id="object"), + pytest.param(lambda x: x, id="callable"), + ], +) +def test_process_partial_response_invalid_input(record_handler: Callable, batch: Any): + # GIVEN + processor = BatchProcessor(event_type=EventType.SQS) + + # WHEN/THEN + with pytest.raises(ValueError): + process_partial_response(batch, record_handler, processor) + + +def test_async_process_partial_response(sqs_event_factory, async_record_handler): + # GIVEN + records = [sqs_event_factory("success"), sqs_event_factory("success")] + batch = {"Records": records} + processor = AsyncBatchProcessor(event_type=EventType.SQS) + + # WHEN + ret = async_process_partial_response(batch, async_record_handler, processor) + + # THEN + assert ret == {"batchItemFailures": []} + + +@pytest.mark.parametrize( + "batch", + [ + pytest.param(123456789, id="num"), + pytest.param([], id="list"), + pytest.param(False, id="bool"), + pytest.param(object, id="object"), + pytest.param(lambda x: x, id="callable"), + ], +) +def test_async_process_partial_response_invalid_input(async_record_handler: Callable, batch: Any): + # GIVEN + processor = AsyncBatchProcessor(event_type=EventType.SQS) + + # WHEN/THEN + with pytest.raises(ValueError): + async_process_partial_response(batch, record_handler, processor) + + def test_batch_processor_model_with_partial_validation_error( record_handler_model: Callable, sqs_event_factory, order_event_factory ): From f2da40f911d945ed6c83abb408ff619cb3f94230 Mon Sep 17 00:00:00 2001 From: Release bot Date: Fri, 7 Apr 2023 13:37:17 +0000 Subject: [PATCH 30/34] update changelog with latest changes --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 74ee0d15b13..08af8ea5b65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ # Unreleased +## Bug Fixes + +* **batch:** handle early validation errors for pydantic models (poison pill) [#2091](https://github.com/awslabs/aws-lambda-powertools-python/issues/2091) ([#2099](https://github.com/awslabs/aws-lambda-powertools-python/issues/2099)) + ## Documentation * **homepage:** remove banner for end-of-support v1 ([#2098](https://github.com/awslabs/aws-lambda-powertools-python/issues/2098)) @@ -13,6 +17,7 @@ ## Features +* **batch:** reduce boilerplate with process_partial_response ([#2090](https://github.com/awslabs/aws-lambda-powertools-python/issues/2090)) * **idempotency:** allow custom sdk clients in DynamoDBPersistenceLayer ([#2087](https://github.com/awslabs/aws-lambda-powertools-python/issues/2087)) ## Maintenance From 1977faf6718a46725fc58918dfb4e9ba22578d41 Mon Sep 17 00:00:00 2001 From: Heitor Lessa Date: Fri, 7 Apr 2023 16:14:54 +0200 Subject: [PATCH 31/34] docs(batch): use newly supported Json model (#2100) --- docs/utilities/batch.md | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/docs/utilities/batch.md b/docs/utilities/batch.md index 47cc2147978..296ce4f02ac 100644 --- a/docs/utilities/batch.md +++ b/docs/utilities/batch.md @@ -693,27 +693,22 @@ Inheritance is importance because we need to access message IDs and sequence num === "SQS" - ```python hl_lines="5 13 22 28" + ```python hl_lines="5 14 23 29" import json from aws_lambda_powertools import Logger, Tracer from aws_lambda_powertools.utilities.batch import BatchProcessor, EventType, process_partial_response from aws_lambda_powertools.utilities.parser.models import SqsRecordModel from aws_lambda_powertools.utilities.typing import LambdaContext - from aws_lambda_powertools.utilities.parser import validator, BaseModel + from aws_lambda_powertools.utilities.parser import BaseModel + from aws_lambda_powertools.utilities.parser.types import Json class Order(BaseModel): item: dict class OrderSqsRecord(SqsRecordModel): - body: Order - - # auto transform json string - # so Pydantic can auto-initialize nested Order model - @validator("body", pre=True) - def transform_body_to_dict(cls, value: str): - return json.loads(value) + body: Json[Order] # deserialize order data from JSON string processor = BatchProcessor(event_type=EventType.SQS, model=OrderSqsRecord) tracer = Tracer() @@ -732,13 +727,14 @@ Inheritance is importance because we need to access message IDs and sequence num === "Kinesis Data Streams" - ```python hl_lines="5 14 25 29 35" + ```python hl_lines="5 15 19 23 29 36" import json from aws_lambda_powertools import Logger, Tracer from aws_lambda_powertools.utilities.batch import BatchProcessor, EventType, process_partial_response from aws_lambda_powertools.utilities.parser.models import KinesisDataStreamRecordPayload, KinesisDataStreamRecord from aws_lambda_powertools.utilities.parser import BaseModel, validator + from aws_lambda_powertools.utilities.parser.types import Json from aws_lambda_powertools.utilities.typing import LambdaContext @@ -747,14 +743,7 @@ Inheritance is importance because we need to access message IDs and sequence num class OrderKinesisPayloadRecord(KinesisDataStreamRecordPayload): - data: Order - - # auto transform json string - # so Pydantic can auto-initialize nested Order model - @validator("data", pre=True) - def transform_message_to_dict(cls, value: str): - # Powertools KinesisDataStreamRecord already decodes b64 to str here - return json.loads(value) + data: Json[Order] class OrderKinesisRecord(KinesisDataStreamRecord): From cbd66943276f8b34544b48992ca982b84fac3402 Mon Sep 17 00:00:00 2001 From: Release bot Date: Fri, 7 Apr 2023 14:15:17 +0000 Subject: [PATCH 32/34] update changelog with latest changes --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08af8ea5b65..158e0868ef8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ ## Documentation +* **batch:** use newly supported Json model ([#2100](https://github.com/awslabs/aws-lambda-powertools-python/issues/2100)) * **homepage:** remove banner for end-of-support v1 ([#2098](https://github.com/awslabs/aws-lambda-powertools-python/issues/2098)) * **idempotency:** fixes to testing your code section ([#2073](https://github.com/awslabs/aws-lambda-powertools-python/issues/2073)) * **idempotency:** new sequence diagrams, fix idempotency record vs DynamoDB TTL confusion ([#2074](https://github.com/awslabs/aws-lambda-powertools-python/issues/2074)) From 3c2e182e20c9d1026dc672ab88b0b486bce9db22 Mon Sep 17 00:00:00 2001 From: Release bot Date: Fri, 7 Apr 2023 14:32:07 +0000 Subject: [PATCH 33/34] bump version to 2.12.0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 148e7f994e5..c8bdfabaf13 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "aws_lambda_powertools" -version = "2.11.0" +version = "2.12.0" description = "AWS Lambda Powertools is a developer toolkit to implement Serverless best practices and increase developer velocity." authors = ["Amazon Web Services"] include = ["aws_lambda_powertools/py.typed", "THIRD-PARTY-LICENSES"] From 985c1aeea0a491a0d72f332e5b57b2ef70c9bce3 Mon Sep 17 00:00:00 2001 From: Release bot Date: Fri, 7 Apr 2023 14:57:20 +0000 Subject: [PATCH 34/34] chore: update v2 layer ARN on documentation --- docs/index.md | 118 +++++++++++++++++++++++++------------------------- 1 file changed, 59 insertions(+), 59 deletions(-) diff --git a/docs/index.md b/docs/index.md index 82795db6b4e..55d44ced65d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -26,8 +26,8 @@ Powertools is a developer toolkit to implement Serverless best practices and inc You can install Powertools using one of the following options: -* **Lambda Layer (x86_64)**: [**arn:aws:lambda:{region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:25**](#){: .copyMe}:clipboard: -* **Lambda Layer (arm64)**: [**arn:aws:lambda:{region}:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:25**](#){: .copyMe}:clipboard: +* **Lambda Layer (x86_64)**: [**arn:aws:lambda:{region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:26**](#){: .copyMe}:clipboard: +* **Lambda Layer (arm64)**: [**arn:aws:lambda:{region}:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:26**](#){: .copyMe}:clipboard: * **Pip**: **[`pip install "aws-lambda-powertools"`](#){: .copyMe}:clipboard:** ??? question "Using Pip? You might need to install additional dependencies." @@ -78,55 +78,55 @@ You can include Powertools Lambda Layer using [AWS Lambda Console](https://docs. | Region | Layer ARN | | ---------------- | ---------------------------------------------------------------------------------------------------------- | - | `af-south-1` | [arn:aws:lambda:af-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:25](#){: .copyMe}:clipboard: | - | `ap-east-1` | [arn:aws:lambda:ap-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:25](#){: .copyMe}:clipboard: | - | `ap-northeast-1` | [arn:aws:lambda:ap-northeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:25](#){: .copyMe}:clipboard: | - | `ap-northeast-2` | [arn:aws:lambda:ap-northeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:25](#){: .copyMe}:clipboard: | - | `ap-northeast-3` | [arn:aws:lambda:ap-northeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV2:25](#){: .copyMe}:clipboard: | - | `ap-south-1` | [arn:aws:lambda:ap-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:25](#){: .copyMe}:clipboard: | - | `ap-southeast-1` | [arn:aws:lambda:ap-southeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:25](#){: .copyMe}:clipboard: | - | `ap-southeast-2` | [arn:aws:lambda:ap-southeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:25](#){: .copyMe}:clipboard: | - | `ap-southeast-3` | [arn:aws:lambda:ap-southeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV2:25](#){: .copyMe}:clipboard: | - | `ca-central-1` | [arn:aws:lambda:ca-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:25](#){: .copyMe}:clipboard: | - | `eu-central-1` | [arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:25](#){: .copyMe}:clipboard: | - | `eu-north-1` | [arn:aws:lambda:eu-north-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:25](#){: .copyMe}:clipboard: | - | `eu-south-1` | [arn:aws:lambda:eu-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:25](#){: .copyMe}:clipboard: | - | `eu-west-1` | [arn:aws:lambda:eu-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:25](#){: .copyMe}:clipboard: | - | `eu-west-2` | [arn:aws:lambda:eu-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:25](#){: .copyMe}:clipboard: | - | `eu-west-3` | [arn:aws:lambda:eu-west-3:017000801446:layer:AWSLambdaPowertoolsPythonV2:25](#){: .copyMe}:clipboard: | - | `me-south-1` | [arn:aws:lambda:me-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:25](#){: .copyMe}:clipboard: | - | `sa-east-1` | [arn:aws:lambda:sa-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:25](#){: .copyMe}:clipboard: | - | `us-east-1` | [arn:aws:lambda:us-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:25](#){: .copyMe}:clipboard: | - | `us-east-2` | [arn:aws:lambda:us-east-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:25](#){: .copyMe}:clipboard: | - | `us-west-1` | [arn:aws:lambda:us-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:25](#){: .copyMe}:clipboard: | - | `us-west-2` | [arn:aws:lambda:us-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:25](#){: .copyMe}:clipboard: | + | `af-south-1` | [arn:aws:lambda:af-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:26](#){: .copyMe}:clipboard: | + | `ap-east-1` | [arn:aws:lambda:ap-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:26](#){: .copyMe}:clipboard: | + | `ap-northeast-1` | [arn:aws:lambda:ap-northeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:26](#){: .copyMe}:clipboard: | + | `ap-northeast-2` | [arn:aws:lambda:ap-northeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:26](#){: .copyMe}:clipboard: | + | `ap-northeast-3` | [arn:aws:lambda:ap-northeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV2:26](#){: .copyMe}:clipboard: | + | `ap-south-1` | [arn:aws:lambda:ap-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:26](#){: .copyMe}:clipboard: | + | `ap-southeast-1` | [arn:aws:lambda:ap-southeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:26](#){: .copyMe}:clipboard: | + | `ap-southeast-2` | [arn:aws:lambda:ap-southeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:26](#){: .copyMe}:clipboard: | + | `ap-southeast-3` | [arn:aws:lambda:ap-southeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV2:26](#){: .copyMe}:clipboard: | + | `ca-central-1` | [arn:aws:lambda:ca-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:26](#){: .copyMe}:clipboard: | + | `eu-central-1` | [arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:26](#){: .copyMe}:clipboard: | + | `eu-north-1` | [arn:aws:lambda:eu-north-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:26](#){: .copyMe}:clipboard: | + | `eu-south-1` | [arn:aws:lambda:eu-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:26](#){: .copyMe}:clipboard: | + | `eu-west-1` | [arn:aws:lambda:eu-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:26](#){: .copyMe}:clipboard: | + | `eu-west-2` | [arn:aws:lambda:eu-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:26](#){: .copyMe}:clipboard: | + | `eu-west-3` | [arn:aws:lambda:eu-west-3:017000801446:layer:AWSLambdaPowertoolsPythonV2:26](#){: .copyMe}:clipboard: | + | `me-south-1` | [arn:aws:lambda:me-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:26](#){: .copyMe}:clipboard: | + | `sa-east-1` | [arn:aws:lambda:sa-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:26](#){: .copyMe}:clipboard: | + | `us-east-1` | [arn:aws:lambda:us-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:26](#){: .copyMe}:clipboard: | + | `us-east-2` | [arn:aws:lambda:us-east-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:26](#){: .copyMe}:clipboard: | + | `us-west-1` | [arn:aws:lambda:us-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:26](#){: .copyMe}:clipboard: | + | `us-west-2` | [arn:aws:lambda:us-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV2:26](#){: .copyMe}:clipboard: | === "arm64" | Region | Layer ARN | | ---------------- | ---------------------------------------------------------------------------------------------------------------- | - | `af-south-1` | [arn:aws:lambda:af-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:25](#){: .copyMe}:clipboard: | - | `ap-east-1` | [arn:aws:lambda:ap-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:25](#){: .copyMe}:clipboard: | - | `ap-northeast-1` | [arn:aws:lambda:ap-northeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:25](#){: .copyMe}:clipboard: | - | `ap-northeast-2` | [arn:aws:lambda:ap-northeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:25](#){: .copyMe}:clipboard: | - | `ap-northeast-3` | [arn:aws:lambda:ap-northeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:25](#){: .copyMe}:clipboard: | - | `ap-south-1` | [arn:aws:lambda:ap-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:25](#){: .copyMe}:clipboard: | - | `ap-southeast-1` | [arn:aws:lambda:ap-southeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:25](#){: .copyMe}:clipboard: | - | `ap-southeast-2` | [arn:aws:lambda:ap-southeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:25](#){: .copyMe}:clipboard: | - | `ap-southeast-3` | [arn:aws:lambda:ap-southeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:25](#){: .copyMe}:clipboard: | - | `ca-central-1` | [arn:aws:lambda:ca-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:25](#){: .copyMe}:clipboard: | - | `eu-central-1` | [arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:25](#){: .copyMe}:clipboard: | - | `eu-north-1` | [arn:aws:lambda:eu-north-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:25](#){: .copyMe}:clipboard: | - | `eu-south-1` | [arn:aws:lambda:eu-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:25](#){: .copyMe}:clipboard: | - | `eu-west-1` | [arn:aws:lambda:eu-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:25](#){: .copyMe}:clipboard: | - | `eu-west-2` | [arn:aws:lambda:eu-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:25](#){: .copyMe}:clipboard: | - | `eu-west-3` | [arn:aws:lambda:eu-west-3:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:25](#){: .copyMe}:clipboard: | - | `me-south-1` | [arn:aws:lambda:me-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:25](#){: .copyMe}:clipboard: | - | `sa-east-1` | [arn:aws:lambda:sa-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:25](#){: .copyMe}:clipboard: | - | `us-east-1` | [arn:aws:lambda:us-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:25](#){: .copyMe}:clipboard: | - | `us-east-2` | [arn:aws:lambda:us-east-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:25](#){: .copyMe}:clipboard: | - | `us-west-1` | [arn:aws:lambda:us-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:25](#){: .copyMe}:clipboard: | - | `us-west-2` | [arn:aws:lambda:us-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:25](#){: .copyMe}:clipboard: | + | `af-south-1` | [arn:aws:lambda:af-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:26](#){: .copyMe}:clipboard: | + | `ap-east-1` | [arn:aws:lambda:ap-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:26](#){: .copyMe}:clipboard: | + | `ap-northeast-1` | [arn:aws:lambda:ap-northeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:26](#){: .copyMe}:clipboard: | + | `ap-northeast-2` | [arn:aws:lambda:ap-northeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:26](#){: .copyMe}:clipboard: | + | `ap-northeast-3` | [arn:aws:lambda:ap-northeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:26](#){: .copyMe}:clipboard: | + | `ap-south-1` | [arn:aws:lambda:ap-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:26](#){: .copyMe}:clipboard: | + | `ap-southeast-1` | [arn:aws:lambda:ap-southeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:26](#){: .copyMe}:clipboard: | + | `ap-southeast-2` | [arn:aws:lambda:ap-southeast-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:26](#){: .copyMe}:clipboard: | + | `ap-southeast-3` | [arn:aws:lambda:ap-southeast-3:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:26](#){: .copyMe}:clipboard: | + | `ca-central-1` | [arn:aws:lambda:ca-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:26](#){: .copyMe}:clipboard: | + | `eu-central-1` | [arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:26](#){: .copyMe}:clipboard: | + | `eu-north-1` | [arn:aws:lambda:eu-north-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:26](#){: .copyMe}:clipboard: | + | `eu-south-1` | [arn:aws:lambda:eu-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:26](#){: .copyMe}:clipboard: | + | `eu-west-1` | [arn:aws:lambda:eu-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:26](#){: .copyMe}:clipboard: | + | `eu-west-2` | [arn:aws:lambda:eu-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:26](#){: .copyMe}:clipboard: | + | `eu-west-3` | [arn:aws:lambda:eu-west-3:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:26](#){: .copyMe}:clipboard: | + | `me-south-1` | [arn:aws:lambda:me-south-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:26](#){: .copyMe}:clipboard: | + | `sa-east-1` | [arn:aws:lambda:sa-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:26](#){: .copyMe}:clipboard: | + | `us-east-1` | [arn:aws:lambda:us-east-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:26](#){: .copyMe}:clipboard: | + | `us-east-2` | [arn:aws:lambda:us-east-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:26](#){: .copyMe}:clipboard: | + | `us-west-1` | [arn:aws:lambda:us-west-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:26](#){: .copyMe}:clipboard: | + | `us-west-2` | [arn:aws:lambda:us-west-2:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:26](#){: .copyMe}:clipboard: | ??? note "Note: Click to expand and copy code snippets for popular frameworks" @@ -139,7 +139,7 @@ You can include Powertools Lambda Layer using [AWS Lambda Console](https://docs. Type: AWS::Serverless::Function Properties: Layers: - - !Sub arn:aws:lambda:${AWS::Region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:25 + - !Sub arn:aws:lambda:${AWS::Region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:26 ``` === "Serverless framework" @@ -149,7 +149,7 @@ You can include Powertools Lambda Layer using [AWS Lambda Console](https://docs. hello: handler: lambda_function.lambda_handler layers: - - arn:aws:lambda:${aws:region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:25 + - arn:aws:lambda:${aws:region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:26 ``` === "CDK" @@ -165,7 +165,7 @@ You can include Powertools Lambda Layer using [AWS Lambda Console](https://docs. powertools_layer = aws_lambda.LayerVersion.from_layer_version_arn( self, id="lambda-powertools", - layer_version_arn=f"arn:aws:lambda:{env.region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:25" + layer_version_arn=f"arn:aws:lambda:{env.region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:26" ) aws_lambda.Function(self, 'sample-app-lambda', @@ -214,7 +214,7 @@ You can include Powertools Lambda Layer using [AWS Lambda Console](https://docs. role = aws_iam_role.iam_for_lambda.arn handler = "index.test" runtime = "python3.9" - layers = ["arn:aws:lambda:{region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:25"] + layers = ["arn:aws:lambda:{region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:26"] source_code_hash = filebase64sha256("lambda_function_payload.zip") } @@ -267,7 +267,7 @@ You can include Powertools Lambda Layer using [AWS Lambda Console](https://docs. ? Do you want to configure advanced settings? Yes ... ? Do you want to enable Lambda layers for this function? Yes - ? Enter up to 5 existing Lambda layer ARNs (comma-separated): arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:25 + ? Enter up to 5 existing Lambda layer ARNs (comma-separated): arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:26 ❯ amplify push -y @@ -278,7 +278,7 @@ You can include Powertools Lambda Layer using [AWS Lambda Console](https://docs. - Name: ? Which setting do you want to update? Lambda layers configuration ? Do you want to enable Lambda layers for this function? Yes - ? Enter up to 5 existing Lambda layer ARNs (comma-separated): arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:25 + ? Enter up to 5 existing Lambda layer ARNs (comma-separated): arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:26 ? Do you want to edit the local lambda function now? No ``` @@ -292,7 +292,7 @@ You can include Powertools Lambda Layer using [AWS Lambda Console](https://docs. Properties: Architectures: [arm64] Layers: - - !Sub arn:aws:lambda:${AWS::Region}:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:25 + - !Sub arn:aws:lambda:${AWS::Region}:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:26 ``` === "Serverless framework" @@ -303,7 +303,7 @@ You can include Powertools Lambda Layer using [AWS Lambda Console](https://docs. handler: lambda_function.lambda_handler architecture: arm64 layers: - - arn:aws:lambda:${aws:region}:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:25 + - arn:aws:lambda:${aws:region}:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:26 ``` === "CDK" @@ -319,7 +319,7 @@ You can include Powertools Lambda Layer using [AWS Lambda Console](https://docs. powertools_layer = aws_lambda.LayerVersion.from_layer_version_arn( self, id="lambda-powertools", - layer_version_arn=f"arn:aws:lambda:{env.region}:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:25" + layer_version_arn=f"arn:aws:lambda:{env.region}:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:26" ) aws_lambda.Function(self, 'sample-app-lambda', @@ -369,7 +369,7 @@ You can include Powertools Lambda Layer using [AWS Lambda Console](https://docs. role = aws_iam_role.iam_for_lambda.arn handler = "index.test" runtime = "python3.9" - layers = ["arn:aws:lambda:{region}:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:25"] + layers = ["arn:aws:lambda:{region}:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:26"] architectures = ["arm64"] source_code_hash = filebase64sha256("lambda_function_payload.zip") @@ -425,7 +425,7 @@ You can include Powertools Lambda Layer using [AWS Lambda Console](https://docs. ? Do you want to configure advanced settings? Yes ... ? Do you want to enable Lambda layers for this function? Yes - ? Enter up to 5 existing Lambda layer ARNs (comma-separated): arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:25 + ? Enter up to 5 existing Lambda layer ARNs (comma-separated): arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:26 ❯ amplify push -y @@ -436,7 +436,7 @@ You can include Powertools Lambda Layer using [AWS Lambda Console](https://docs. - Name: ? Which setting do you want to update? Lambda layers configuration ? Do you want to enable Lambda layers for this function? Yes - ? Enter up to 5 existing Lambda layer ARNs (comma-separated): arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:25 + ? Enter up to 5 existing Lambda layer ARNs (comma-separated): arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPythonV2-Arm64:26 ? Do you want to edit the local lambda function now? No ``` @@ -444,7 +444,7 @@ You can include Powertools Lambda Layer using [AWS Lambda Console](https://docs. Change {region} to your AWS region, e.g. `eu-west-1` ```bash title="AWS CLI" - aws lambda get-layer-version-by-arn --arn arn:aws:lambda:{region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:25 --region {region} + aws lambda get-layer-version-by-arn --arn arn:aws:lambda:{region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:26 --region {region} ``` The pre-signed URL to download this Lambda Layer will be within `Location` key.